diff --git a/README.md b/README.md index e98673ca..30a60d23 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,5 @@ To achieve these goals, the project will: # Credits + +[Reshot](https://www.reshot.com/free-svg-icons/) - for toolbar icons. \ No newline at end of file diff --git a/androidVariant/src/main/java/org/comixedproject/variant/android/view/HomeView.kt b/androidVariant/src/main/java/org/comixedproject/variant/android/view/HomeView.kt index ed04815d..183f7096 100644 --- a/androidVariant/src/main/java/org/comixedproject/variant/android/view/HomeView.kt +++ b/androidVariant/src/main/java/org/comixedproject/variant/android/view/HomeView.kt @@ -36,14 +36,20 @@ import org.comixedproject.variant.android.view.comics.ComicBookView import org.comixedproject.variant.android.view.reading.ReadingView import org.comixedproject.variant.android.view.server.ServerView import org.comixedproject.variant.android.view.settings.SettingsView +import org.comixedproject.variant.platform.Log import org.comixedproject.variant.viewmodel.VariantViewModel import org.koin.androidx.compose.koinViewModel +private const val TAG = "HomeView" + @Composable fun HomeView() { val variantViewModel: VariantViewModel = koinViewModel() var currentDestination by remember { mutableStateOf(AppDestination.COMICS) } val coroutineScope = rememberCoroutineScope() + val comicBookList by variantViewModel.comicBookList.collectAsState() + val selectionMode by variantViewModel.selectionMode.collectAsState() + val selectionList by variantViewModel.selectionList.collectAsState() val comicBook by variantViewModel.comicBook.collectAsState() Scaffold( @@ -72,8 +78,29 @@ fun HomeView() { ) } else { ComicBookView( - onReadComicBook = { comicBook -> - variantViewModel.readComicBook(comicBook) + comicBookList, + selectionMode, + selectionList, + onSetSelectionMode = { + Log.info(TAG, "Setting selection mode: ${it}") + variantViewModel.setSelectMode(it) + }, + onComicBookClicked = { comicBook -> + if (selectionMode) { + Log.info( + TAG, + "Toggling comic book selection: ${comicBook.path}" + ) + variantViewModel.updateSelectionList(comicBook.path) + } else { + Log.info(TAG, "Reading comic book: ${comicBook.filename}") + variantViewModel.readComicBook(comicBook) + } + }, + onDeleteComics = { + coroutineScope.launch(Dispatchers.IO) { + variantViewModel.deleteSelections() + } }, modifier = Modifier.padding(padding) ) diff --git a/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookListItemView.kt b/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookListItemView.kt index e60648ce..0fab71c9 100644 --- a/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookListItemView.kt +++ b/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookListItemView.kt @@ -1,8 +1,10 @@ package org.comixedproject.variant.android.view.comics import android.graphics.BitmapFactory +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable +import androidx.compose.foundation.border +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.CardDefaults @@ -17,10 +19,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.comixedproject.variant.adaptor.ArchiveAPI @@ -33,23 +37,34 @@ import org.comixedproject.variant.platform.Log private val TAG = "ComicBookListItemView" +@OptIn(ExperimentalFoundationApi::class) @Composable fun ComicBookListItemView( comicBook: ComicBook, + selected: Boolean, onClick: (ComicBook) -> Unit, modifier: Modifier = Modifier ) { var coverContent by remember { mutableStateOf(null) } val coroutineScope = rememberCoroutineScope() + val borderWidth = when (selected) { + true -> 5.dp + false -> 0.dp + } ElevatedCard( colors = CardDefaults.cardColors(containerColor = colorScheme.surface), modifier = modifier .fillMaxWidth() + .border(borderWidth, Color.Red) ) { val title = MetadataAPI.displayableTitle(comicBook) - Column(modifier = Modifier.clickable(onClick = { onClick(comicBook) })) { + Column( + modifier = Modifier.combinedClickable( + onClick = { onClick(comicBook) } + ) + ) { comicBook.pages.firstOrNull()?.let { cover -> if (coverContent == null) { Image( @@ -91,5 +106,21 @@ fun ComicBookListItemView( @Composable @Preview fun ComicBookListItemViewPreview() { - VariantTheme { ComicBookListItemView(comicBook = COMIC_BOOK_LIST.get(0), onClick = {}) } + VariantTheme { + ComicBookListItemView( + comicBook = COMIC_BOOK_LIST.get(0), + false, + onClick = {}) + } +} + +@Composable +@Preview +fun ComicBookListItemViewSelectedPreview() { + VariantTheme { + ComicBookListItemView( + comicBook = COMIC_BOOK_LIST.get(0), + true, + onClick = {}) + } } \ No newline at end of file diff --git a/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookListView.kt b/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookListView.kt index 9aa73583..143a1825 100644 --- a/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookListView.kt +++ b/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookListView.kt @@ -41,6 +41,7 @@ private val TAG = "ComicBookListView" @Composable fun ComicBookListView( comicBookList: List, + selectionList: List, onClick: (ComicBook) -> Unit, modifier: Modifier = Modifier ) { @@ -61,11 +62,13 @@ fun ComicBookListView( items(comicBookList) { comicBook -> ComicBookListItemView( comicBook, - onClick = { onClick(it) }, - modifier = Modifier.padding(padding) + selectionList.contains(comicBook.path), + onClick = { onClick(it) } ) } - }) + }, + modifier = modifier.padding(padding) + ) } }, modifier = modifier.padding(8.dp) ) @@ -75,6 +78,6 @@ fun ComicBookListView( @Preview fun ComicBookListViewPreview() { VariantTheme { - ComicBookListView(COMIC_BOOK_LIST, onClick = {}) + ComicBookListView(COMIC_BOOK_LIST, emptyList(), onClick = {}) } } \ No newline at end of file diff --git a/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookView.kt b/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookView.kt index 6b722b00..1ca9aa1f 100644 --- a/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookView.kt +++ b/androidVariant/src/main/java/org/comixedproject/variant/android/view/comics/ComicBookView.kt @@ -18,28 +18,105 @@ package org.comixedproject.variant.android.view.comics +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import org.comixedproject.variant.android.COMIC_BOOK_LIST +import org.comixedproject.variant.android.R import org.comixedproject.variant.android.VariantTheme import org.comixedproject.variant.model.library.ComicBook -import org.comixedproject.variant.viewmodel.VariantViewModel -import org.koin.androidx.compose.koinViewModel +import org.comixedproject.variant.platform.Log private val TAG = "ComicBookView" @Composable -fun ComicBookView(onReadComicBook: (ComicBook) -> Unit, modifier: Modifier = Modifier) { - val variantViewModel: VariantViewModel = koinViewModel() - val comicBookList by variantViewModel.comicBookList.collectAsState() +fun ComicBookView( + comicBookList: List, + selectionMode: Boolean, + selectionList: List, + onSetSelectionMode: (Boolean) -> Unit, + onComicBookClicked: (ComicBook) -> Unit, + onDeleteComics: () -> Unit, + modifier: Modifier = Modifier +) { + Scaffold( + content = { padding -> + ComicBookListView( + comicBookList, + selectionList, + onClick = { onComicBookClicked(it) }, + modifier = modifier.padding(padding) + ) + }, + bottomBar = { + BottomAppBar( + actions = { + if (selectionMode) { + IconButton(onClick = { onSetSelectionMode(false) }) { + Icon( + painterResource(id = R.drawable.ic_selection_mode_on), + contentDescription = stringResource(R.string.markReadLabel) + ) + } + } else { + IconButton(onClick = { onSetSelectionMode(true) }) { + Icon( + painterResource(id = R.drawable.ic_selection_mode_off), + contentDescription = stringResource(R.string.markReadLabel) + ) + } + } + if (!selectionList.isEmpty()) { + IconButton(enabled = !selectionList.isEmpty(), onClick = { + Log.info(TAG, "Deleting ${selectionList.size} comic book(s)") + onDeleteComics() + }) { + Icon( + Icons.Filled.Delete, + contentDescription = stringResource(R.string.deleteSelectionsLabel) + ) + } + } - ComicBookListView(comicBookList, onClick = { onReadComicBook(it) }, modifier = modifier) + } + ) + } + ) } @Composable @Preview fun ComicBookViewPreview() { - VariantTheme { ComicBookView(onReadComicBook = { }) } + VariantTheme { + ComicBookView( + COMIC_BOOK_LIST, + false, + emptyList(), + onSetSelectionMode = { _ -> }, + onComicBookClicked = { _ -> }, + onDeleteComics = { }) + } +} + +@Composable +@Preview +fun ComicBookViewWithSelectionsPreview() { + VariantTheme { + ComicBookView( + COMIC_BOOK_LIST, + true, + listOf(COMIC_BOOK_LIST.get(0).path), + onSetSelectionMode = { _ -> }, + onComicBookClicked = { _ -> }, + onDeleteComics = { }) + } } \ No newline at end of file diff --git a/androidVariant/src/main/res/drawable-anydpi/ic_selection_mode_off.xml b/androidVariant/src/main/res/drawable-anydpi/ic_selection_mode_off.xml new file mode 100644 index 00000000..0d86f6e1 --- /dev/null +++ b/androidVariant/src/main/res/drawable-anydpi/ic_selection_mode_off.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/androidVariant/src/main/res/drawable-anydpi/ic_selection_mode_on.xml b/androidVariant/src/main/res/drawable-anydpi/ic_selection_mode_on.xml new file mode 100644 index 00000000..04b64462 --- /dev/null +++ b/androidVariant/src/main/res/drawable-anydpi/ic_selection_mode_on.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/androidVariant/src/main/res/drawable-hdpi/ic_selection_mode_off.png b/androidVariant/src/main/res/drawable-hdpi/ic_selection_mode_off.png new file mode 100644 index 00000000..6bb3eee8 Binary files /dev/null and b/androidVariant/src/main/res/drawable-hdpi/ic_selection_mode_off.png differ diff --git a/androidVariant/src/main/res/drawable-hdpi/ic_selection_mode_on.png b/androidVariant/src/main/res/drawable-hdpi/ic_selection_mode_on.png new file mode 100644 index 00000000..65b4b01b Binary files /dev/null and b/androidVariant/src/main/res/drawable-hdpi/ic_selection_mode_on.png differ diff --git a/androidVariant/src/main/res/drawable-mdpi/ic_selection_mode_off.png b/androidVariant/src/main/res/drawable-mdpi/ic_selection_mode_off.png new file mode 100644 index 00000000..25b2f710 Binary files /dev/null and b/androidVariant/src/main/res/drawable-mdpi/ic_selection_mode_off.png differ diff --git a/androidVariant/src/main/res/drawable-mdpi/ic_selection_mode_on.png b/androidVariant/src/main/res/drawable-mdpi/ic_selection_mode_on.png new file mode 100644 index 00000000..6047839d Binary files /dev/null and b/androidVariant/src/main/res/drawable-mdpi/ic_selection_mode_on.png differ diff --git a/androidVariant/src/main/res/drawable-xhdpi/ic_selection_mode_off.png b/androidVariant/src/main/res/drawable-xhdpi/ic_selection_mode_off.png new file mode 100644 index 00000000..2334861c Binary files /dev/null and b/androidVariant/src/main/res/drawable-xhdpi/ic_selection_mode_off.png differ diff --git a/androidVariant/src/main/res/drawable-xhdpi/ic_selection_mode_on.png b/androidVariant/src/main/res/drawable-xhdpi/ic_selection_mode_on.png new file mode 100644 index 00000000..ad86dc8b Binary files /dev/null and b/androidVariant/src/main/res/drawable-xhdpi/ic_selection_mode_on.png differ diff --git a/androidVariant/src/main/res/drawable-xxhdpi/ic_selection_mode_off.png b/androidVariant/src/main/res/drawable-xxhdpi/ic_selection_mode_off.png new file mode 100644 index 00000000..1bdfa500 Binary files /dev/null and b/androidVariant/src/main/res/drawable-xxhdpi/ic_selection_mode_off.png differ diff --git a/androidVariant/src/main/res/drawable-xxhdpi/ic_selection_mode_on.png b/androidVariant/src/main/res/drawable-xxhdpi/ic_selection_mode_on.png new file mode 100644 index 00000000..62d18089 Binary files /dev/null and b/androidVariant/src/main/res/drawable-xxhdpi/ic_selection_mode_on.png differ diff --git a/androidVariant/src/main/res/values/strings.xml b/androidVariant/src/main/res/values/strings.xml index fbb5f56a..74f2adf6 100644 --- a/androidVariant/src/main/res/values/strings.xml +++ b/androidVariant/src/main/res/values/strings.xml @@ -16,4 +16,7 @@ Go to the previous page. Go to the next page. Stop reading comic book. + Mark selected comics as read. + Mark selected comics as unread. + Delete selected comics from device. diff --git a/iosVariant/iosVariant/Assets.xcassets/Contents.json b/iosVariant/iosVariant/Assets.xcassets/Contents.json index 4aa7c535..73c00596 100644 --- a/iosVariant/iosVariant/Assets.xcassets/Contents.json +++ b/iosVariant/iosVariant/Assets.xcassets/Contents.json @@ -3,4 +3,4 @@ "author" : "xcode", "version" : 1 } -} \ No newline at end of file +} diff --git a/iosVariant/iosVariant/Assets.xcassets/selection_mode_off.imageset/Contents.json b/iosVariant/iosVariant/Assets.xcassets/selection_mode_off.imageset/Contents.json new file mode 100644 index 00000000..065f5aeb --- /dev/null +++ b/iosVariant/iosVariant/Assets.xcassets/selection_mode_off.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "reshot-icon-off-switch-TQVBLRMESW-1772e.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosVariant/iosVariant/Assets.xcassets/selection_mode_off.imageset/reshot-icon-off-switch-TQVBLRMESW-1772e.svg b/iosVariant/iosVariant/Assets.xcassets/selection_mode_off.imageset/reshot-icon-off-switch-TQVBLRMESW-1772e.svg new file mode 100644 index 00000000..78a9ca7f --- /dev/null +++ b/iosVariant/iosVariant/Assets.xcassets/selection_mode_off.imageset/reshot-icon-off-switch-TQVBLRMESW-1772e.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/iosVariant/iosVariant/Assets.xcassets/selection_mode_on.imageset/Contents.json b/iosVariant/iosVariant/Assets.xcassets/selection_mode_on.imageset/Contents.json new file mode 100644 index 00000000..c95ac2e8 --- /dev/null +++ b/iosVariant/iosVariant/Assets.xcassets/selection_mode_on.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "reshot-icon-on-switch-S253U8PFKM-f8513.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosVariant/iosVariant/Assets.xcassets/selection_mode_on.imageset/reshot-icon-on-switch-S253U8PFKM-f8513.svg b/iosVariant/iosVariant/Assets.xcassets/selection_mode_on.imageset/reshot-icon-on-switch-S253U8PFKM-f8513.svg new file mode 100644 index 00000000..640b5431 --- /dev/null +++ b/iosVariant/iosVariant/Assets.xcassets/selection_mode_on.imageset/reshot-icon-on-switch-S253U8PFKM-f8513.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/iosVariant/iosVariant/HomeView.swift b/iosVariant/iosVariant/HomeView.swift index 332737e3..31e84202 100644 --- a/iosVariant/iosVariant/HomeView.swift +++ b/iosVariant/iosVariant/HomeView.swift @@ -28,10 +28,43 @@ struct HomeView: View { var body: some View { TabView(selection: $currentDestination) { - ComicBooksView(onReadComicBook: { comicBook in - variantViewModel.readComicBook(comicBook: comicBook) - currentDestination = .comics - }) + ComicBooksView( + comicBookList: variantViewModel.comicBookList, + selectionMode: variantViewModel.selectionMode, + selectionList: variantViewModel.selectionList, + onSetSelectionMode: { enable in + Log().info( + tag: TAG, + message: "Setting selection mode: \(enable)" + ) + variantViewModel.setSelectMode(enable: enable) + }, + onComicClicked: { comicBook in + if variantViewModel.selectionMode { + Log().info( + tag: TAG, + message: + "Toggling comic book select: \(comicBook.path)" + ) + variantViewModel.updateSelectionList( + filename: comicBook.path + ) + } else { + Log().info( + tag: TAG, + message: "Reading comic book: \(comicBook.filename)" + ) + variantViewModel.readComicBook(comicBook: comicBook) + currentDestination = .comics + } + }, + onDeleteComics: { + Log().info(tag: TAG, message: "Deleting \(variantViewModel.selectionList.count) comic book(s)") + Task { + try await variantViewModel.deleteSelections() + } + } + ) .tag(AppDestination.comics) .tabItem { Label("Comics", systemImage: "book.fill") } @@ -58,9 +91,3 @@ struct HomeView: View { } } } - -struct HomeViewPreviews: PreviewProvider { - static var previews: some View { - HomeView() - } -} diff --git a/iosVariant/iosVariant/Views/Comics/ComicBookListItemView.swift b/iosVariant/iosVariant/Views/Comics/ComicBookListItemView.swift index 08d6c456..893ce4eb 100644 --- a/iosVariant/iosVariant/Views/Comics/ComicBookListItemView.swift +++ b/iosVariant/iosVariant/Views/Comics/ComicBookListItemView.swift @@ -26,21 +26,32 @@ struct ComicBookListItemView: View { @ObservedObject var imageLoader: ImageLoader let comicBook: ComicBook + let selected: Bool - var onComicBookClicked: (ComicBook) -> Void + var onClick: (ComicBook) -> Void init( comicBook: ComicBook, - onComicBookClicked: @escaping (ComicBook) -> Void + selected: Bool, + onClick: @escaping (ComicBook) -> Void ) { self.comicBook = comicBook - self.onComicBookClicked = onComicBookClicked + self.selected = selected + self.onClick = onClick self.imageLoader = ImageLoader(comicBook: comicBook) } + var borderWidth: CGFloat { + if selected { + return 5 + } else { + return 0 + } + } + var body: some View { VStack(alignment: .leading) { - if (imageLoader.image != nil) { + if imageLoader.image != nil { Image(uiImage: imageLoader.image!) .resizable() .scaledToFit() @@ -59,14 +70,25 @@ struct ComicBookListItemView: View { } .onTapGesture { Log().debug(tag: TAG, message: "Comic book item tapped") - onComicBookClicked(self.comicBook) + onClick(self.comicBook) } + .padding() + .border(.red, width: borderWidth) } } #Preview { ComicBookListItemView( comicBook: COMIC_BOOK_LIST[0], - onComicBookClicked: { _ in } + selected: false, + onClick: { _ in } + ) +} + +#Preview("selected") { + ComicBookListItemView( + comicBook: COMIC_BOOK_LIST[0], + selected: true, + onClick: { _ in } ) } diff --git a/iosVariant/iosVariant/Views/Comics/ComicBookListView.swift b/iosVariant/iosVariant/Views/Comics/ComicBookListView.swift index 1fc42461..d4e307f0 100644 --- a/iosVariant/iosVariant/Views/Comics/ComicBookListView.swift +++ b/iosVariant/iosVariant/Views/Comics/ComicBookListView.swift @@ -24,10 +24,11 @@ private let TAG = "ComicBookListView" struct ComicBookListView: View { let comicBookList: [ComicBook] + let selectionList: [String] let columns = [GridItem(.adaptive(minimum: 128))] - var onComicBookClicked: (ComicBook) -> Void + var onClick: (ComicBook) -> Void var body: some View { NavigationStack { @@ -36,8 +37,9 @@ struct ComicBookListView: View { ForEach(comicBookList, id: \.path) { comicBook in ComicBookListItemView( comicBook: comicBook, - onComicBookClicked: { comicBook in - onComicBookClicked(comicBook) + selected: selectionList.contains(comicBook.path), + onClick: { comicBook in + onClick(comicBook) } ) } @@ -52,6 +54,7 @@ struct ComicBookListView: View { #Preview { ComicBookListView( comicBookList: COMIC_BOOK_LIST, - onComicBookClicked: { _ in } + selectionList: [COMIC_BOOK_LIST[0].path], + onClick: { _ in } ) } diff --git a/iosVariant/iosVariant/Views/Comics/ComicBooksView.swift b/iosVariant/iosVariant/Views/Comics/ComicBooksView.swift index 94a7f265..90eb6f92 100644 --- a/iosVariant/iosVariant/Views/Comics/ComicBooksView.swift +++ b/iosVariant/iosVariant/Views/Comics/ComicBooksView.swift @@ -23,25 +23,80 @@ import shared private let TAG = "ComicBooksView" struct ComicBooksView: View { - @EnvironmentViewModel var variantViewModel: VariantViewModel + let comicBookList: [ComicBook] + let selectionMode: Bool + let selectionList: [String] - var onReadComicBook: (ComicBook) -> Void + var onSetSelectionMode: (Bool) -> Void + var onComicClicked: (ComicBook) -> Void + var onDeleteComics: () -> Void var body: some View { - if (variantViewModel.comicBook != nil) { - ReadingView(comicBook: self.variantViewModel.comicBook!, onStopReading: { - Log().debug(tag: TAG, message: "Stop reading comic book") - variantViewModel.readComicBook(comicBook: nil) - }) - } else { + NavigationStack { ComicBookListView( - comicBookList: self.variantViewModel.comicBookList, - onComicBookClicked: { comicBook in onReadComicBook(comicBook) } + comicBookList: comicBookList, + selectionList: selectionList, + onClick: { comicBook in onComicClicked(comicBook) } ) + .toolbar { + ToolbarItemGroup(placement: .bottomBar) { + if selectionMode { + Button { + onSetSelectionMode(false) + } label: { + Image("selection_mode_on") + } + + Button { + onDeleteComics() + } label: { + Image(systemName: "trash.fill") + } + .disabled(selectionList.isEmpty) + } else { + Button { + onSetSelectionMode(true) + } label: { + Image("selection_mode_off") + } + } + + Spacer() + } + } } } } #Preview { - ComicBooksView(onReadComicBook: { _ in }) + ComicBooksView( + comicBookList: COMIC_BOOK_LIST, + selectionMode: false, + selectionList: [], + onSetSelectionMode: { _ in }, + onComicClicked: { _ in }, + onDeleteComics: { } + ) +} + +#Preview("selection mode on") { + ComicBooksView( + comicBookList: COMIC_BOOK_LIST, + selectionMode: true, + selectionList: [COMIC_BOOK_LIST[0].path], + onSetSelectionMode: { _ in }, + onComicClicked: { _ in }, + onDeleteComics: { } + ) +} + +#Preview("selection mode on no selections") { + ComicBooksView( + comicBookList: COMIC_BOOK_LIST, + selectionMode: true, + selectionList: [], + onSetSelectionMode: { _ in }, + onComicClicked: { _ in }, + onDeleteComics: { } + ) } diff --git a/shared/src/commonMain/kotlin/org/comixedproject/variant/viewmodel/VariantViewModel.kt b/shared/src/commonMain/kotlin/org/comixedproject/variant/viewmodel/VariantViewModel.kt index 30c84172..affa7977 100644 --- a/shared/src/commonMain/kotlin/org/comixedproject/variant/viewmodel/VariantViewModel.kt +++ b/shared/src/commonMain/kotlin/org/comixedproject/variant/viewmodel/VariantViewModel.kt @@ -137,6 +137,16 @@ open class VariantViewModel( @NativeCoroutinesState val comicBookList: StateFlow> = _comicBookList.asStateFlow() + private val _selectionMode = MutableStateFlow(viewModelScope, false) + + @NativeCoroutinesState + val selectionMode: StateFlow = _selectionMode.asStateFlow() + + private val _selectionList = MutableStateFlow>(viewModelScope, listOf()) + + @NativeCoroutinesState + val selectionList: StateFlow> = _selectionList.asStateFlow() + fun loadDirectory(path: String, reload: Boolean) { viewModelScope.launch(Dispatchers.Main) { Log.debug(TAG, "Loading directory: ${path}") @@ -255,6 +265,43 @@ open class VariantViewModel( } } + fun setSelectMode(enable: Boolean) { + viewModelScope.launch(Dispatchers.Main) { + Log.debug(TAG, "Setting selection mode: ${enable}") + _selectionMode.emit(enable) + if (!enable) { + Log.debug(TAG, "Clearing selections") + _selectionList.emit(emptyList()) + } + } + } + + fun updateSelectionList(filename: String) { + viewModelScope.launch(Dispatchers.Main) { + val selections = mutableListOf() + if (_selectionList.value.contains(filename)) { + Log.debug(TAG, "Removing selection: ${filename}") + selections.addAll(_selectionList.value.filter { !it.equals(filename) }.toList()) + } else { + Log.debug(TAG, "Adding selection: ${filename}") + selections.addAll(_selectionList.value) + selections.add(filename) + } + _selectionList.emit(selections) + } + } + + suspend fun deleteSelections() { + _selectionList.value.forEach { + val file = File(it) + Log.info(TAG, "Deleting file: ${it}") + file.delete() + } + + _selectionList.emit(emptyList()) + _selectionMode.emit(false) + } + private suspend fun loadLibraryContents() { Log.debug(TAG, "Loading library contents: ${_libraryDirectory}")