diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index d6ae80c65..be4582d86 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -7,11 +7,11 @@ - + - + \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index ea1345004..c9e7c67c8 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -2,7 +2,7 @@ object ProjectConfig { const val minSdk = 23 const val targetSdk = 31 const val compileSdk = 31 - const val versionName = "0.1.15" - const val versionCode = 16 + const val versionName = "0.1.16" + const val versionCode = 17 const val applicationId = "ir.kazemcodes.infinityreader" } \ No newline at end of file diff --git a/data/src/main/java/org/ireader/data/local/dao/LibraryBookDao.kt b/data/src/main/java/org/ireader/data/local/dao/LibraryBookDao.kt index b79d0af9f..6ec509250 100644 --- a/data/src/main/java/org/ireader/data/local/dao/LibraryBookDao.kt +++ b/data/src/main/java/org/ireader/data/local/dao/LibraryBookDao.kt @@ -165,7 +165,10 @@ interface LibraryBookDao { @Query("SELECT sourceId FROM library GROUP BY sourceId ORDER BY COUNT(sourceId) DESC") suspend fun findFavoriteSourceIds(): List - @Query("DELETE FROM library WHERE favorite = 0") + @Query(""" + DELETE FROM library + WHERE favorite = 0 + """) suspend fun deleteExploredBooks() @Query("UPDATE library SET tableId = 0 WHERE tableId != 0 AND favorite = 1") diff --git a/domain/src/main/java/org/ireader/domain/feature_services/downloaderService/DownloaderService.kt b/domain/src/main/java/org/ireader/domain/feature_services/downloaderService/DownloaderService.kt index 6e36dc9f2..767a11a60 100644 --- a/domain/src/main/java/org/ireader/domain/feature_services/downloaderService/DownloaderService.kt +++ b/domain/src/main/java/org/ireader/domain/feature_services/downloaderService/DownloaderService.kt @@ -21,6 +21,7 @@ import org.ireader.domain.feature_services.notification.Notifications.CHANNEL_DO import org.ireader.domain.feature_services.notification.Notifications.ID_DOWNLOAD_CHAPTER_COMPLETE import org.ireader.domain.feature_services.notification.Notifications.ID_DOWNLOAD_CHAPTER_ERROR import org.ireader.domain.feature_services.notification.Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS +import org.ireader.domain.models.entities.Chapter import org.ireader.domain.models.entities.SavedDownload import org.ireader.domain.repository.LocalBookRepository import org.ireader.domain.repository.LocalChapterRepository @@ -46,6 +47,8 @@ class DownloadService @AssistedInject constructor( const val DOWNLOADER_SERVICE_NAME = "DOWNLOAD_SERVICE" const val DOWNLOADER_BOOK_ID = "book_id" const val DOWNLOADER_SOURCE_ID = "sourceId" + const val DOWNLOADER_Chapters_IDS = "chapterIds" + const val DOWNLOADER_BOOKS_IDS = "booksIds" } @@ -55,6 +58,8 @@ class DownloadService @AssistedInject constructor( val bookId = inputData.getLong("book_id", 0) val sourceId = inputData.getLong("sourceId", 0) + val downloadIds = inputData.getLongArray(DOWNLOADER_Chapters_IDS)?.distinct() + val booksIds = inputData.getLongArray(DOWNLOADER_BOOKS_IDS)?.distinct() val bookResource = bookRepo.subscribeBookById(bookId).first() ?: throw IllegalArgumentException( "Invalid bookId as argument: $bookId" @@ -75,7 +80,22 @@ class DownloadService @AssistedInject constructor( val source = extensions.get(sourceId)?.source - val chapters = chapterRepo.subscribeChaptersByBookId(bookId).first() + val chapters = mutableListOf() + + if (booksIds?.isNotEmpty() == true) { + booksIds.forEach { + chapters.addAll(chapterRepo.findChaptersByBookId(it)) + } + } else { + chapters.addAll(chapterRepo.findChaptersByBookId(bookId).filter { + if (downloadIds != null) { + it.id in downloadIds + } else { + true + } + }) + } + val cancelDownloadIntent = WorkManager.getInstance(applicationContext) .createCancelPendingIntent(id) @@ -140,6 +160,7 @@ class DownloadService @AssistedInject constructor( delay(1000) } } + } catch (e: Exception) { Timber.e("getNotifications: Failed to download ${bookResource.title}") notify( diff --git a/presentation/src/main/java/org/ireader/presentation/feature_detail/presentation/chapter_detail/ChapterDetailScreen.kt b/presentation/src/main/java/org/ireader/presentation/feature_detail/presentation/chapter_detail/ChapterDetailScreen.kt index 6afdf4ff2..de53e425b 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_detail/presentation/chapter_detail/ChapterDetailScreen.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_detail/presentation/chapter_detail/ChapterDetailScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -13,10 +14,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.BookmarkBorder -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Done -import androidx.compose.material.icons.filled.GetApp +import androidx.compose.material.icons.filled.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -25,6 +23,7 @@ import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -53,6 +52,7 @@ fun ChapterDetailScreen( navController: NavController = rememberNavController(), ) { val book = vm.book + val context = LocalContext.current val scrollState = rememberLazyListState() val focusManager = LocalFocusManager.current // LaunchedEffect(key1 = true) { @@ -179,26 +179,55 @@ fun ChapterDetailScreen( } when { vm.hasSelection -> { - Row(modifier = Modifier - .fillMaxWidth() - .height(60.dp) - .align(Alignment.BottomCenter) - .padding(8.dp) - .background(MaterialTheme.colors.background) - .border(width = 1.dp, color = MaterialTheme.colors.onBackground.copy(.4f)), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + Box( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + .align(Alignment.BottomCenter) + .padding(8.dp) + .background(MaterialTheme.colors.background) + .border(width = 1.dp, + color = MaterialTheme.colors.onBackground.copy(.1f)) + .clickable(enabled = false) {}, ) { - AppIconButton(imageVector = Icons.Default.GetApp, - title = "Download", - onClick = { /*TODO*/ }) - AppIconButton(imageVector = Icons.Default.BookmarkBorder, - title = "Bookmark", - onClick = { /*TODO*/ }) - AppIconButton(imageVector = Icons.Default.Done, - title = "Mark as read", - onClick = { /*TODO*/ }) + Row(modifier = Modifier + .fillMaxSize(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + AppIconButton(imageVector = Icons.Default.GetApp, + title = "Download", + onClick = { + vm.downloadChapters(context = context) + vm.selection.clear() + }) + AppIconButton(imageVector = Icons.Default.BookmarkBorder, + title = "Bookmark", + onClick = { + vm.insertChapters(vm.chapters.filter { it.id in vm.selection } + .map { it.copy(bookmark = !it.bookmark) }) + vm.selection.clear() + }) + + AppIconButton(imageVector = if (vm.chapters.filter { it.read } + .map { it.id } + .containsAll(vm.selection)) Icons.Default.DoneOutline else Icons.Default.Done, + title = "Mark as read", + onClick = { + vm.insertChapters(vm.chapters.filter { it.id in vm.selection } + .map { it.copy(read = !it.read) }) + vm.selection.clear() + }) + AppIconButton(imageVector = Icons.Default.PlaylistAddCheck, + title = "Mark Previous as read", + onClick = { + vm.insertChapters(vm.chapters.filter { it.id <= vm.selection.maxOrNull() ?: 0 } + .map { it.copy(read = true) }) + vm.selection.clear() + }) + } } + } } } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_detail/presentation/chapter_detail/viewmodel/ChapterDetailViewModel.kt b/presentation/src/main/java/org/ireader/presentation/feature_detail/presentation/chapter_detail/viewmodel/ChapterDetailViewModel.kt index fc4dcbd4c..9508375e5 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_detail/presentation/chapter_detail/viewmodel/ChapterDetailViewModel.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_detail/presentation/chapter_detail/viewmodel/ChapterDetailViewModel.kt @@ -1,8 +1,10 @@ package org.ireader.presentation.feature_detail.presentation.chapter_detail.viewmodel +import android.content.Context import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.work.* import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -13,7 +15,10 @@ import kotlinx.coroutines.launch import org.ireader.core.R import org.ireader.core.utils.UiEvent import org.ireader.core.utils.UiText +import org.ireader.domain.feature_services.downloaderService.DownloadService +import org.ireader.domain.feature_services.downloaderService.DownloadService.Companion.DOWNLOADER_Chapters_IDS import org.ireader.domain.models.entities.Book +import org.ireader.domain.models.entities.Chapter import org.ireader.domain.ui.NavigationArgs import org.ireader.domain.use_cases.local.DeleteUseCase import org.ireader.domain.use_cases.local.LocalGetChapterUseCase @@ -127,6 +132,40 @@ class ChapterDetailViewModel @Inject constructor( } } + fun insertChapters(chapters: List) { + viewModelScope.launch(Dispatchers.IO) { + insertUseCases.insertChapters(chapters) + } + } + + lateinit var work: OneTimeWorkRequest + fun downloadChapters(context: Context) { + + book?.let { book -> + work = + OneTimeWorkRequestBuilder().apply { + setInputData( + Data.Builder().apply { + putLong(DownloadService.DOWNLOADER_BOOK_ID, + book.id) + putLong(DownloadService.DOWNLOADER_SOURCE_ID, + book.sourceId) + putLongArray(DOWNLOADER_Chapters_IDS, + this@ChapterDetailViewModel.selection.toLongArray()) + }.build() + ) + addTag(DownloadService.DOWNLOADER_SERVICE_NAME) + }.build() + WorkManager.getInstance(context).enqueueUniqueWork( + DownloadService.DOWNLOADER_SERVICE_NAME.plus( + book.id + book.sourceId), + ExistingWorkPolicy.REPLACE, + work + ) + } + + } + suspend fun showSnackBar(message: UiText?) { _eventFlow.emit( UiEvent.ShowSnackbar( diff --git a/presentation/src/main/java/org/ireader/presentation/feature_explore/presentation/browse/ExploreScreen.kt b/presentation/src/main/java/org/ireader/presentation/feature_explore/presentation/browse/ExploreScreen.kt index 15e079e9c..519cf066f 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_explore/presentation/browse/ExploreScreen.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_explore/presentation/browse/ExploreScreen.kt @@ -192,7 +192,7 @@ fun ExploreScreen( navController = navController, isLocal = false, gridState = gridState, - onBookTap = { book -> + onClick = { book -> navController.navigate( route = BookDetailScreenSpec.buildRoute(sourceId = book.sourceId, bookId = book.id) diff --git a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/LibraryScreen.kt b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/LibraryScreen.kt index c8c2516dc..fd882c013 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/LibraryScreen.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/LibraryScreen.kt @@ -3,14 +3,23 @@ package org.ireader.presentation.feature_library.presentation import androidx.compose.animation.Crossfade import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Done +import androidx.compose.material.icons.filled.DoneOutline import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController @@ -24,6 +33,7 @@ import org.ireader.core_ui.ui.LoadingScreen import org.ireader.presentation.feature_library.presentation.components.BottomTabComposable import org.ireader.presentation.feature_library.presentation.components.LayoutComposable import org.ireader.presentation.feature_library.presentation.viewmodel.LibraryViewModel +import org.ireader.presentation.presentation.reusable_composable.AppIconButton import org.ireader.presentation.ui.BookDetailScreenSpec import org.ireader.presentation.ui.ReaderScreenSpec @@ -48,6 +58,7 @@ fun LibraryScreen( val gridState = rememberLazyGridState() val lazyListState = rememberLazyListState() + val context = LocalContext.current LaunchedEffect(key1 = true) { vm.getLibraryBooks() @@ -82,7 +93,7 @@ fun LibraryScreen( Crossfade(targetState = Pair(vm.isLoading, vm.isEmpty)) { (isLoading, isEmpty) -> when { isLoading -> LoadingScreen() - isEmpty -> EmptyScreen(UiText.DynamicString("There is no book is Library, you can add books in the Explore screen.")) + isEmpty && vm.filters.isEmpty() -> EmptyScreen(UiText.DynamicString("There is no book is Library, you can add books in the Explore screen.")) else -> LayoutComposable( books = vm.books, layout = vm.layout, @@ -90,6 +101,7 @@ fun LibraryScreen( isLocal = true, gridState = gridState, scrollState = lazyListState, + selection = vm.selection, goToLatestChapter = { book -> navController.navigate( ReaderScreenSpec.buildRoute( @@ -99,17 +111,70 @@ fun LibraryScreen( ) ) }, - onBookTap = { book -> - navController.navigate( - route = BookDetailScreenSpec.buildRoute( - sourceId = book.sourceId, - bookId = book.id) - ) + onClick = { book -> + if (vm.hasSelection) { + if (book.id in vm.selection) { + vm.selection.remove(book.id) + } else { + vm.selection.add(book.id) + } + + } else { + navController.navigate( + route = BookDetailScreenSpec.buildRoute( + sourceId = book.sourceId, + bookId = book.id) + ) + } + + }, + onLongClick = { + vm.selection.add(it.id) }, histories = vm.histories ) } } + when { + vm.hasSelection -> { + Box( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + .align(Alignment.BottomCenter) + .padding(8.dp) + .background(MaterialTheme.colors.background) + .border(width = 1.dp, + color = MaterialTheme.colors.onBackground.copy(.1f)) + .clickable(enabled = false) {}, + ) { + Row(modifier = Modifier + .fillMaxSize(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + AppIconButton(imageVector = Icons.Default.Done, + title = "Mark as read", + onClick = { + vm.markAsRead() + // vm.selection.clear() + }) + AppIconButton(imageVector = Icons.Default.DoneOutline, + title = "Mark as Not read", + onClick = { + vm.markAsNotRead() + }) + AppIconButton(imageVector = Icons.Default.Delete, + title = "Mark Previous as read", + onClick = { + vm.deleteBooks() + }) + } + } + + } + } + } } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/LibraryScreenTopBar.kt b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/LibraryScreenTopBar.kt index e57108676..f3186c2bf 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/LibraryScreenTopBar.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/LibraryScreenTopBar.kt @@ -62,13 +62,7 @@ fun LibraryScreenTopBar( }, ) } - AppIconButton( - imageVector = Icons.Default.Refresh, - title = "Refresh", - onClick = { - vm.refreshUpdate(context = context) - }, - ) + AppIconButton( imageVector = Icons.Default.Sort, title = "Filter", @@ -89,7 +83,13 @@ fun LibraryScreenTopBar( vm.onEvent(LibraryEvents.ToggleSearchMode(true)) }, ) - + AppIconButton( + imageVector = Icons.Default.Refresh, + title = "Refresh", + onClick = { + vm.refreshUpdate(context = context) + }, + ) }, navigationIcon = if (vm.inSearchMode) { diff --git a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/DisplayScreen.kt b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/DisplayScreen.kt index a3f650cf1..11b6eb8ad 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/DisplayScreen.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/DisplayScreen.kt @@ -2,13 +2,16 @@ package org.ireader.presentation.feature_library.presentation.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import org.ireader.domain.models.layouts import org.ireader.presentation.feature_library.presentation.viewmodel.LibraryEvents import org.ireader.presentation.feature_library.presentation.viewmodel.LibraryViewModel +import org.ireader.presentation.feature_sources.presentation.extension.composables.TextSection @Composable @@ -18,6 +21,11 @@ fun DisplayScreen(viewModel: LibraryViewModel) { .fillMaxSize() .background(MaterialTheme.colors.background) ) { + TextSection( + text = "DISPLAY MODE", + padding = PaddingValues(vertical = 8.dp, horizontal = 16.dp), + style = MaterialTheme.typography.subtitle1, + ) layouts.forEach { layout -> RadioButtonWithTitleComposable( text = layout.title, @@ -27,5 +35,10 @@ fun DisplayScreen(viewModel: LibraryViewModel) { } ) } +// Spacer(modifier = Modifier.height(8.dp)) +// TextSection(text = "BADGES") +// Spacer(modifier = Modifier.height(8.dp)) +// TextSection(text = "TABS") +// Spacer(modifier = Modifier.height(8.dp)) } } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/LayoutComposables.kt b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/LayoutComposables.kt index 7866efa9e..5a00a1e9c 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/LayoutComposables.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/LayoutComposables.kt @@ -21,7 +21,9 @@ fun LayoutComposable( books: List = emptyList(), lazyBook: LazyPagingItems? = null, histories: List = emptyList(), - onBookTap: (book: Book) -> Unit, + onClick: (book: Book) -> Unit, + onLongClick: (Book) -> Unit = {}, + selection: List = emptyList(), layout: LayoutType, scrollState: LazyListState, gridState: androidx.compose.foundation.lazy.grid.LazyGridState, @@ -37,16 +39,21 @@ fun LayoutComposable( books = books, lazyBooks = lazyBook, onClick = { book -> - onBookTap(book) - }, scrollState = gridState, + onClick(book) + }, + selection = selection, + onLongClick = { onLongClick(it) }, + scrollState = gridState, isLocal = isLocal, goToLatestChapter = { goToLatestChapter(it) }, histories = histories) } is LayoutType.ListLayout -> { LinearListDisplay(books = books, onClick = { book -> - onBookTap(book) + onClick(book) }, scrollState = scrollState, isLocal = isLocal, + selection = selection, + onLongClick = { onLongClick(it) }, lazyBooks = lazyBook, goToLatestChapter = { goToLatestChapter(it) }) } @@ -54,9 +61,11 @@ fun LayoutComposable( CompactGridLayoutComposable( books = books, onClick = { book -> - onBookTap(book) + onClick(book) }, scrollState = gridState, isLocal = isLocal, + selection = selection, + onLongClick = { onLongClick(it) }, lazyBooks = lazyBook, goToLatestChapter = { goToLatestChapter(it) }, histories = histories) diff --git a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/TwoTextinRow.kt b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/TwoTextinRow.kt index 6389ff49c..faca8493b 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/TwoTextinRow.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/components/TwoTextinRow.kt @@ -17,7 +17,7 @@ fun RadioButtonWithTitleComposable( ) { Row(modifier = modifier .fillMaxWidth() - .padding(vertical = 8.dp), + .padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) { RadioButton(selected = selected, onClick = { onClick() }) diff --git a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/viewmodel/LibraryScreenState.kt b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/viewmodel/LibraryScreenState.kt index 5cea52496..bc63a8762 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/viewmodel/LibraryScreenState.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/viewmodel/LibraryScreenState.kt @@ -27,6 +27,8 @@ interface LibraryState { var filters: SnapshotStateList var currentScrollState: Int var histories: List + var selection: SnapshotStateList + val hasSelection: Boolean } open class LibraryStateImpl @Inject constructor() : LibraryState { @@ -43,6 +45,8 @@ open class LibraryStateImpl @Inject constructor() : LibraryState { override var filters: SnapshotStateList = mutableStateListOf() override var currentScrollState by mutableStateOf(0) override var histories by mutableStateOf>(emptyList()) + override var selection: SnapshotStateList = mutableStateListOf() + override val hasSelection: Boolean by derivedStateOf { selection.isNotEmpty() } } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/viewmodel/LibraryViewModel.kt b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/viewmodel/LibraryViewModel.kt index b7f31288c..957950d85 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/viewmodel/LibraryViewModel.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_library/presentation/viewmodel/LibraryViewModel.kt @@ -8,6 +8,7 @@ import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -16,8 +17,12 @@ import org.ireader.domain.feature_services.LibraryUpdatesService import org.ireader.domain.models.DisplayMode import org.ireader.domain.models.FilterType import org.ireader.domain.models.SortType +import org.ireader.domain.models.entities.Book import org.ireader.domain.use_cases.history.HistoryUseCase +import org.ireader.domain.use_cases.local.DeleteUseCase import org.ireader.domain.use_cases.local.LocalGetBookUseCases +import org.ireader.domain.use_cases.local.LocalGetChapterUseCase +import org.ireader.domain.use_cases.local.LocalInsertUseCases import org.ireader.domain.use_cases.preferences.reader_preferences.LibraryLayoutTypeUseCase import org.ireader.domain.use_cases.preferences.reader_preferences.SortersUseCase import javax.inject.Inject @@ -26,6 +31,9 @@ import javax.inject.Inject @HiltViewModel class LibraryViewModel @Inject constructor( private val localGetBookUseCases: LocalGetBookUseCases, + private val insertUseCases: LocalInsertUseCases, + private val deleteUseCase: DeleteUseCase, + private val localGetChapterUseCase: LocalGetChapterUseCase, private val libraryLayoutUseCase: LibraryLayoutTypeUseCase, private val sortersUseCase: SortersUseCase, private val historyUseCase: HistoryUseCase, @@ -71,6 +79,10 @@ class LibraryViewModel @Inject constructor( } + fun addBooksToSelection(book: Book) { + selection.add(book.id) + } + private var getBooksJob: Job? = null fun getLibraryBooks() { @@ -101,6 +113,63 @@ class LibraryViewModel @Inject constructor( this.layout = layoutType.layout } +// lateinit var downloadWork: OneTimeWorkRequest +// fun downloadChapters(book: List,context: Context) { +// downloadWork = +// OneTimeWorkRequestBuilder().apply { +// setInputData( +// Data.Builder().apply { +// putLong(DownloadService.DOWNLOADER_BOOK_ID, +// book.id) +// putLong(DownloadService.DOWNLOADER_SOURCE_ID, +// book.sourceId) +// }.build() +// ) +// addTag(DownloadService.DOWNLOADER_SERVICE_NAME) +// }.build() +// WorkManager.getInstance(context).enqueueUniqueWork( +// DownloadService.DOWNLOADER_SERVICE_NAME.plus( +// book.id + book.sourceId), +// ExistingWorkPolicy.REPLACE, +// downloadWork +// ) +// +// +// } + + fun markAsRead() { + viewModelScope.launch(Dispatchers.IO) { + selection.forEach { bookId -> + val chapters = localGetChapterUseCase.findChaptersByBookId(bookId) + insertUseCases.insertChapters(chapters.map { it.copy(read = true) }) + + } + selection.clear() + } + + } + + fun markAsNotRead() { + viewModelScope.launch(Dispatchers.IO) { + selection.forEach { bookId -> + val chapters = localGetChapterUseCase.findChaptersByBookId(bookId) + insertUseCases.insertChapters(chapters.map { it.copy(read = false) }) + } + selection.clear() + } + + } + + fun deleteBooks() { + viewModelScope.launch(Dispatchers.IO) { + selection.forEach { bookId -> + deleteUseCase.deleteBookById(bookId) + } + selection.clear() + } + + + } private fun readLayoutTypeAndFilterTypeAndSortType() { val sortType = sortersUseCase.read() diff --git a/presentation/src/main/java/org/ireader/presentation/feature_sources/presentation/extension/composables/TextSection.kt b/presentation/src/main/java/org/ireader/presentation/feature_sources/presentation/extension/composables/TextSection.kt index 4a9db8af2..da5767a49 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_sources/presentation/extension/composables/TextSection.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_sources/presentation/extension/composables/TextSection.kt @@ -1,5 +1,6 @@ package org.ireader.presentation.feature_sources.presentation.extension.composables +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -10,22 +11,25 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp @Composable fun TextSection( text: String, toUpper: Boolean = true, + padding: PaddingValues = PaddingValues(16.dp), + style: TextStyle = MaterialTheme.typography.subtitle2, ) { Row( modifier = Modifier .fillMaxWidth() - .padding(16.dp), + .padding(padding), verticalAlignment = Alignment.CenterVertically ) { Text( if (toUpper) text.uppercase() else text, - style = MaterialTheme.typography.subtitle2, + style = style, color = LocalContentColor.current.copy(alpha = ContentAlpha.medium) ) } diff --git a/presentation/src/main/java/org/ireader/presentation/presentation/components/ChapterItemListComposable.kt b/presentation/src/main/java/org/ireader/presentation/presentation/components/ChapterItemListComposable.kt index 965c6b97f..12c012b93 100644 --- a/presentation/src/main/java/org/ireader/presentation/presentation/components/ChapterItemListComposable.kt +++ b/presentation/src/main/java/org/ireader/presentation/presentation/components/ChapterItemListComposable.kt @@ -3,6 +3,7 @@ package org.ireader.presentation.presentation.components import androidx.compose.foundation.combinedClickable import androidx.compose.material.* import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Bookmark import androidx.compose.material.icons.filled.PublishedWithChanges import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -30,6 +31,15 @@ fun ChapterListItemComposable( onLongClick = { onLongClick() } ) .selectedBackground(isSelected), + icon = if (chapter.bookmark) { + { + Icon( + imageVector = Icons.Default.Bookmark, + contentDescription = "Bookmarked", + tint = MaterialTheme.colors.primary, + ) + } + } else null, text = { Text( text = chapter.title, diff --git a/presentation/src/main/java/org/ireader/presentation/presentation/layouts/BookImage.kt b/presentation/src/main/java/org/ireader/presentation/presentation/layouts/BookImage.kt index 4e11f3477..e59f446e2 100644 --- a/presentation/src/main/java/org/ireader/presentation/presentation/layouts/BookImage.kt +++ b/presentation/src/main/java/org/ireader/presentation/presentation/layouts/BookImage.kt @@ -1,8 +1,9 @@ package org.ireader.presentation.presentation.layouts +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme @@ -13,7 +14,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -22,27 +22,35 @@ import org.ireader.domain.feature_services.io.BookCover import org.ireader.domain.models.entities.Book import org.ireader.presentation.presentation.components.BookImageComposable +@OptIn(ExperimentalFoundationApi::class) @Composable fun BookImage( modifier: Modifier = Modifier, - onClick: (Book) -> Unit, + onClick: (Book) -> Unit = {}, + onLongClick: (Book) -> Unit = {}, book: Book, ratio: Float = 3f / 4f, + selected: Boolean = false, badge: @Composable BoxScope.() -> Unit, ) { Box( modifier = modifier .fillMaxSize() .padding(8.dp) - .clickable(role = Role.Button) { onClick(book) }, + .combinedClickable( + onClick = { onClick(book) }, + onLongClick = { onLongClick(book) } + ) + .border(3.dp, + if (selected) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground.copy( + alpha = .1f)), ) { BookImageComposable( modifier = Modifier .aspectRatio(ratio) .fillMaxWidth() .clip(RoundedCornerShape(4.dp)) - .border(2.dp, - MaterialTheme.colors.onBackground.copy(alpha = .1f)) + .align(Alignment.Center), image = BookCover.from(book), ) diff --git a/presentation/src/main/java/org/ireader/presentation/presentation/layouts/CompactGrid.kt b/presentation/src/main/java/org/ireader/presentation/presentation/layouts/CompactGrid.kt index adf0fcac9..8603c1477 100644 --- a/presentation/src/main/java/org/ireader/presentation/presentation/layouts/CompactGrid.kt +++ b/presentation/src/main/java/org/ireader/presentation/presentation/layouts/CompactGrid.kt @@ -23,7 +23,9 @@ fun CompactGridLayoutComposable( lazyBooks: LazyPagingItems?, books: List, histories: List, + selection: List = emptyList(), onClick: (book: Book) -> Unit, + onLongClick: (book: Book) -> Unit = {}, scrollState: LazyGridState = rememberLazyGridState(), isLocal: Boolean, goToLatestChapter: (book: Book) -> Unit, @@ -38,7 +40,9 @@ fun CompactGridLayoutComposable( items(lazyBooks) { book -> if (book != null) { BookImage( - onClick = { onClick(book) }, book = book, ratio = 6f / 9f + onClick = { onClick(book) }, book = book, ratio = 6f / 9f, + selected = book.id in selection, + onLongClick = { onLongClick(book) }, ) { if (isLocal && histories.find { it.bookId == book.id }?.readAt != 0L) { GoToLastReadComposable(onClick = { goToLatestChapter(book) }) @@ -52,7 +56,9 @@ fun CompactGridLayoutComposable( items(count = books.size) { index -> BookImage( - onClick = { onClick(books[index]) }, book = books[index], ratio = 6f / 9f + onClick = { onClick(books[index]) }, book = books[index], ratio = 6f / 9f, + selected = books[index].id in selection, + onLongClick = { onLongClick(books[index]) }, ) { if (isLocal && histories.find { it.bookId == books[index].id }?.readAt != 0L) { GoToLastReadComposable(onClick = { goToLatestChapter(books[index]) }) diff --git a/presentation/src/main/java/org/ireader/presentation/presentation/layouts/GridLayoutComposable.kt b/presentation/src/main/java/org/ireader/presentation/presentation/layouts/GridLayoutComposable.kt index 50bbb5433..abf4418bb 100644 --- a/presentation/src/main/java/org/ireader/presentation/presentation/layouts/GridLayoutComposable.kt +++ b/presentation/src/main/java/org/ireader/presentation/presentation/layouts/GridLayoutComposable.kt @@ -19,7 +19,9 @@ fun GridLayoutComposable( lazyBooks: LazyPagingItems?, books: List, histories: List, + selection: List = emptyList(), onClick: (book: Book) -> Unit, + onLongClick: (book: Book) -> Unit = {}, scrollState: androidx.compose.foundation.lazy.grid.LazyGridState, isLocal: Boolean, goToLatestChapter: (book: Book) -> Unit, @@ -33,7 +35,10 @@ fun GridLayoutComposable( items(lazyBooks) { book -> if (book != null) { BookImage( - onClick = { onClick(book) }, book = book, ratio = 6f / 10f + onClick = { onClick(book) }, + onLongClick = { onLongClick(book) }, + book = book, ratio = 6f / 10f, + selected = book.id in selection ) { if (book.lastUpdated > 1 && isLocal && histories.find { it.bookId == book.id }?.readAt != 0L) { GoToLastReadComposable(onClick = { goToLatestChapter(book) }) @@ -44,7 +49,9 @@ fun GridLayoutComposable( } else { items(count = books.size) { index -> BookImage( - onClick = { onClick(books[index]) }, book = books[index], ratio = 6f / 10f + onClick = { onClick(books[index]) }, book = books[index], ratio = 6f / 10f, + selected = books[index].id in selection, + onLongClick = { onLongClick(books[index]) }, ) { if (books[index].lastUpdated > 1 && isLocal && histories.find { it.bookId == books[index].id }?.readAt != 0L) { GoToLastReadComposable(onClick = { goToLatestChapter(books[index]) }) diff --git a/presentation/src/main/java/org/ireader/presentation/presentation/layouts/LineatListView.kt b/presentation/src/main/java/org/ireader/presentation/presentation/layouts/LineatListView.kt index 0d767928f..c2bc80aaa 100644 --- a/presentation/src/main/java/org/ireader/presentation/presentation/layouts/LineatListView.kt +++ b/presentation/src/main/java/org/ireader/presentation/presentation/layouts/LineatListView.kt @@ -1,7 +1,8 @@ package org.ireader.presentation.presentation.layouts +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.border -import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -26,6 +27,7 @@ import org.ireader.presentation.presentation.components.BookImageComposable fun LinearBookItem( modifier: Modifier = Modifier, title: String, + selected: Boolean = false, book: Book, ) { @@ -42,7 +44,9 @@ fun LinearBookItem( modifier = modifier .aspectRatio(3f / 4f) .clip(RoundedCornerShape(4.dp)) - .border(.2.dp, MaterialTheme.colors.onBackground.copy(alpha = .1f))) + .border(.2.dp, + if (selected) MaterialTheme.colors.primary.copy(alpha = .5f) else MaterialTheme.colors.onBackground.copy( + alpha = .1f))) Spacer(modifier = Modifier.width(15.dp)) Text( text = title, @@ -55,11 +59,14 @@ fun LinearBookItem( } +@OptIn(ExperimentalFoundationApi::class) @Composable fun LinearListDisplay( lazyBooks: LazyPagingItems?, books: List, + selection: List = emptyList(), onClick: (book: Book) -> Unit, + onLongClick: (book: Book) -> Unit = {}, scrollState: LazyListState = rememberLazyListState(), isLocal: Boolean, goToLatestChapter: (book: Book) -> Unit, @@ -71,9 +78,11 @@ fun LinearListDisplay( LinearBookItem( title = book.title, book = book, - modifier = Modifier.clickable { - onClick(book) - } + modifier = Modifier.combinedClickable( + onClick = { onClick(book) }, + onLongClick = { onLongClick(book) }, + ), + selected = book.id in selection ) } @@ -83,9 +92,11 @@ fun LinearListDisplay( LinearBookItem( title = books[index].title, book = books[index], - modifier = Modifier.clickable { - onClick(books[index]) - } + modifier = Modifier.combinedClickable( + onClick = { onClick(books[index]) }, + onLongClick = { onClick(books[index]) }, + ), + selected = books[index].id in selection ) } } diff --git a/presentation/src/main/java/org/ireader/presentation/presentation/reusable_composable/TopAppBarReusableComposables.kt b/presentation/src/main/java/org/ireader/presentation/presentation/reusable_composable/TopAppBarReusableComposables.kt index f57d0514f..8c95aaf54 100644 --- a/presentation/src/main/java/org/ireader/presentation/presentation/reusable_composable/TopAppBarReusableComposables.kt +++ b/presentation/src/main/java/org/ireader/presentation/presentation/reusable_composable/TopAppBarReusableComposables.kt @@ -1,5 +1,6 @@ package org.ireader.presentation.presentation.reusable_composable +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -140,6 +141,7 @@ fun CaptionTextComposable( ) } +@OptIn(ExperimentalFoundationApi::class) @Composable fun AppIconButton( modifier: Modifier = Modifier,