diff --git a/app/src/main/java/com/starry/myne/epub/EpubXMLFileParser.kt b/app/src/main/java/com/starry/myne/epub/EpubXMLFileParser.kt
index 7304222e..85b24411 100644
--- a/app/src/main/java/com/starry/myne/epub/EpubXMLFileParser.kt
+++ b/app/src/main/java/com/starry/myne/epub/EpubXMLFileParser.kt
@@ -83,7 +83,7 @@ class EpubXMLFileParser(
"EpubXMLFileParser",
"Fragment ID: $fragmentId doesn't represent a
tag. Using the fragment and next fragment logic."
)
- // If the fragment ID doesn't represent a tag, use the fragment and next fragment logic
+ // If the fragment ID doesn't represent a
tag, use the fragment and next fragment logic
val fragmentElement = document.selectFirst("#$fragmentId")
title = fragmentElement?.selectFirst("h1, h2, h3, h4, h5, h6")?.text() ?: ""
val bodyBuilder = StringBuilder()
diff --git a/app/src/main/java/com/starry/myne/ui/screens/home/composables/HomeScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/home/composables/HomeScreen.kt
index fb2938a7..23cb448f 100644
--- a/app/src/main/java/com/starry/myne/ui/screens/home/composables/HomeScreen.kt
+++ b/app/src/main/java/com/starry/myne/ui/screens/home/composables/HomeScreen.kt
@@ -58,6 +58,7 @@ import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -82,6 +83,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
+import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import coil.annotation.ExperimentalCoilApi
import com.starry.myne.R
@@ -92,8 +94,11 @@ import com.starry.myne.ui.common.BookItemCard
import com.starry.myne.ui.common.BookLanguageButton
import com.starry.myne.ui.common.NetworkError
import com.starry.myne.ui.common.ProgressDots
+import com.starry.myne.ui.navigation.BottomBarScreen
import com.starry.myne.ui.navigation.Screens
+import com.starry.myne.ui.screens.home.viewmodels.AllBooksState
import com.starry.myne.ui.screens.home.viewmodels.HomeViewModel
+import com.starry.myne.ui.screens.home.viewmodels.SearchBarState
import com.starry.myne.ui.screens.home.viewmodels.UserAction
import com.starry.myne.ui.theme.figeronaFont
import com.starry.myne.ui.theme.pacificoFont
@@ -117,8 +122,8 @@ fun HomeScreen(navController: NavController, networkStatus: NetworkObserver.Stat
*/
val sysBackButtonState = remember { mutableStateOf(false) }
BackHandler(enabled = sysBackButtonState.value) {
- if (viewModel.topBarState.isSearchBarVisible) {
- if (viewModel.topBarState.searchText.isNotEmpty()) {
+ if (viewModel.searchBarState.isSearchBarVisible) {
+ if (viewModel.searchBarState.searchText.isNotEmpty()) {
viewModel.onAction(UserAction.TextFieldInput("", networkStatus))
} else {
viewModel.onAction(UserAction.CloseIconClicked)
@@ -131,6 +136,18 @@ fun HomeScreen(navController: NavController, networkStatus: NetworkObserver.Stat
val modalBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden
)
+
+ // Close search bar when navigating to other screens.
+ val navBackStackEntry by navController.currentBackStackEntryAsState()
+ val currentDestination = navBackStackEntry?.destination
+ LaunchedEffect(currentDestination) {
+ if (currentDestination?.route != BottomBarScreen.Home.route) {
+ viewModel.onAction(UserAction.TextFieldInput("", networkStatus))
+ viewModel.onAction(UserAction.CloseIconClicked)
+ }
+ }
+
+
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
@@ -206,7 +223,7 @@ private fun HomeScreenScaffold(
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
- val topBarState = viewModel.topBarState
+ val topBarState = viewModel.searchBarState
Scaffold(
modifier = Modifier
@@ -275,7 +292,7 @@ fun HomeScreenContents(
navController: NavController,
paddingValues: PaddingValues
) {
- val topBarState = viewModel.topBarState
+ val topBarState = viewModel.searchBarState
val allBooksState = viewModel.allBooksState
@@ -288,119 +305,15 @@ fun HomeScreenContents(
// If search text is empty show list of all books.
if (topBarState.searchText.isBlank()) {
- // show fullscreen progress indicator when loading the first page.
- if (allBooksState.page == 1L && allBooksState.isLoading) {
- Box(
- modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
- ) {
- CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
- }
- } else if (!allBooksState.isLoading && allBooksState.error != null) {
- NetworkError(onRetryClicked = { viewModel.reloadItems() })
- } else {
- LazyVerticalGrid(
- modifier = Modifier
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.background)
- .padding(start = 8.dp, end = 8.dp),
- columns = GridCells.Adaptive(295.dp)
- ) {
- items(allBooksState.items.size) { i ->
- val item = allBooksState.items[i]
- if (networkStatus == NetworkObserver.Status.Available
- && i >= allBooksState.items.size - 1
- && !allBooksState.endReached
- && !allBooksState.isLoading
- ) {
- viewModel.loadNextItems()
- }
- Box(
- modifier = Modifier
- .padding(4.dp)
- .fillMaxWidth(),
- contentAlignment = Alignment.Center
- ) {
- BookItemCard(
- title = item.title,
- author = BookUtils.getAuthorsAsString(item.authors),
- language = BookUtils.getLanguagesAsString(item.languages),
- subjects = BookUtils.getSubjectsAsString(
- item.subjects, 3
- ),
- coverImageUrl = item.formats.imagejpeg
- ) {
- navController.navigate(
- Screens.BookDetailScreen.withBookId(
- item.id.toString()
- )
- )
- }
- }
-
- }
- item {
- if (allBooksState.isLoading) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(8.dp),
- horizontalArrangement = Arrangement.Center
- ) {
- ProgressDots()
- }
- }
- }
- }
- }
-
- // Else show the search results.
+ AllBooksList(
+ allBooksState = allBooksState,
+ networkStatus = networkStatus,
+ navController = navController,
+ onRetryClicked = { viewModel.reloadItems() },
+ onLoadNextItems = { viewModel.loadNextItems() }
+ )
} else {
- LazyVerticalGrid(
- modifier = Modifier
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.background)
- .padding(start = 8.dp, end = 8.dp),
- columns = GridCells.Adaptive(295.dp)
- ) {
- if (topBarState.isSearching) {
- item {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(8.dp),
- horizontalArrangement = Arrangement.Center
- ) {
- ProgressDots()
- }
- }
- }
-
- items(topBarState.searchResults.size) { i ->
- val item = topBarState.searchResults[i]
- Box(
- modifier = Modifier
- .padding(4.dp)
- .fillMaxWidth(),
- contentAlignment = Alignment.Center
- ) {
- BookItemCard(
- title = item.title,
- author = BookUtils.getAuthorsAsString(item.authors),
- language = BookUtils.getLanguagesAsString(item.languages),
- subjects = BookUtils.getSubjectsAsString(
- item.subjects, 3
- ),
- coverImageUrl = item.formats.imagejpeg
- ) {
- navController.navigate(
- Screens.BookDetailScreen.withBookId(
- item.id.toString()
- )
- )
- }
- }
- }
- }
+ SearchBookList(searchBarState = topBarState, navController = navController)
}
}
@@ -446,6 +359,130 @@ private fun HomeTopAppBar(
}
}
+@Composable
+private fun AllBooksList(
+ allBooksState: AllBooksState,
+ networkStatus: NetworkObserver.Status,
+ navController: NavController,
+ onRetryClicked: () -> Unit,
+ onLoadNextItems: () -> Unit
+) {
+ // show fullscreen progress indicator when loading the first page.
+ if (allBooksState.page == 1L && allBooksState.isLoading) {
+ Box(
+ modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
+ }
+ } else if (!allBooksState.isLoading && allBooksState.error != null) {
+ NetworkError(onRetryClicked = { onRetryClicked() })
+ } else {
+ LazyVerticalGrid(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.background)
+ .padding(start = 8.dp, end = 8.dp),
+ columns = GridCells.Adaptive(295.dp)
+ ) {
+ items(allBooksState.items.size) { i ->
+ val item = allBooksState.items[i]
+ if (networkStatus == NetworkObserver.Status.Available
+ && i >= allBooksState.items.size - 1
+ && !allBooksState.endReached
+ && !allBooksState.isLoading
+ ) {
+ onLoadNextItems()
+ }
+ Box(
+ modifier = Modifier
+ .padding(4.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ BookItemCard(
+ title = item.title,
+ author = BookUtils.getAuthorsAsString(item.authors),
+ language = BookUtils.getLanguagesAsString(item.languages),
+ subjects = BookUtils.getSubjectsAsString(
+ item.subjects, 3
+ ),
+ coverImageUrl = item.formats.imagejpeg
+ ) {
+ navController.navigate(
+ Screens.BookDetailScreen.withBookId(
+ item.id.toString()
+ )
+ )
+ }
+ }
+
+ }
+ item {
+ if (allBooksState.isLoading) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ ProgressDots()
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun SearchBookList(searchBarState: SearchBarState, navController: NavController) {
+ LazyVerticalGrid(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.background)
+ .padding(start = 8.dp, end = 8.dp),
+ columns = GridCells.Adaptive(295.dp)
+ ) {
+ if (searchBarState.isSearching) {
+ item {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ ProgressDots()
+ }
+ }
+ }
+
+ items(searchBarState.searchResults.size) { i ->
+ val item = searchBarState.searchResults[i]
+ Box(
+ modifier = Modifier
+ .padding(4.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ BookItemCard(
+ title = item.title,
+ author = BookUtils.getAuthorsAsString(item.authors),
+ language = BookUtils.getLanguagesAsString(item.languages),
+ subjects = BookUtils.getSubjectsAsString(
+ item.subjects, 3
+ ),
+ coverImageUrl = item.formats.imagejpeg
+ ) {
+ navController.navigate(
+ Screens.BookDetailScreen.withBookId(
+ item.id.toString()
+ )
+ )
+ }
+ }
+ }
+ }
+}
+
@ExperimentalMaterial3Api
@Composable
private fun SearchAppBar(
diff --git a/app/src/main/java/com/starry/myne/ui/screens/home/viewmodels/HomeViewModel.kt b/app/src/main/java/com/starry/myne/ui/screens/home/viewmodels/HomeViewModel.kt
index ca868748..035026a3 100644
--- a/app/src/main/java/com/starry/myne/ui/screens/home/viewmodels/HomeViewModel.kt
+++ b/app/src/main/java/com/starry/myne/ui/screens/home/viewmodels/HomeViewModel.kt
@@ -45,7 +45,7 @@ data class AllBooksState(
val page: Long = 1L
)
-data class TopBarState(
+data class SearchBarState(
val searchText: String = "",
val isSearchBarVisible: Boolean = false,
val isSortMenuVisible: Boolean = false,
@@ -70,7 +70,7 @@ class HomeViewModel @Inject constructor(
private val preferenceUtil: PreferenceUtil
) : ViewModel() {
var allBooksState by mutableStateOf(AllBooksState())
- var topBarState by mutableStateOf(TopBarState())
+ var searchBarState by mutableStateOf(SearchBarState())
private val _language: MutableState = mutableStateOf(getPreferredLanguage())
val language: State = _language
@@ -129,20 +129,20 @@ class HomeViewModel @Inject constructor(
fun onAction(userAction: UserAction) {
when (userAction) {
UserAction.CloseIconClicked -> {
- topBarState = topBarState.copy(isSearchBarVisible = false)
+ searchBarState = searchBarState.copy(isSearchBarVisible = false)
}
UserAction.SearchIconClicked -> {
- topBarState = topBarState.copy(isSearchBarVisible = true)
+ searchBarState = searchBarState.copy(isSearchBarVisible = true)
}
is UserAction.TextFieldInput -> {
- topBarState = topBarState.copy(searchText = userAction.text)
+ searchBarState = searchBarState.copy(searchText = userAction.text)
if (userAction.networkStatus == NetworkObserver.Status.Available) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
if (userAction.text.isNotBlank()) {
- topBarState = topBarState.copy(isSearching = true)
+ searchBarState = searchBarState.copy(isSearching = true)
}
delay(500L)
searchBooks(userAction.text)
@@ -159,7 +159,7 @@ class HomeViewModel @Inject constructor(
private suspend fun searchBooks(query: String) {
val bookSet = bookAPI.searchBooks(query)
val books = bookSet.getOrNull()!!.books.filter { it.formats.applicationepubzip != null }
- topBarState = topBarState.copy(searchResults = books, isSearching = false)
+ searchBarState = searchBarState.copy(searchResults = books, isSearching = false)
}
private fun changeLanguage(language: BookLanguage) {
diff --git a/app/src/main/java/com/starry/myne/ui/screens/main/MainScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/main/MainScreen.kt
index a77788fb..b092c30b 100644
--- a/app/src/main/java/com/starry/myne/ui/screens/main/MainScreen.kt
+++ b/app/src/main/java/com/starry/myne/ui/screens/main/MainScreen.kt
@@ -138,6 +138,7 @@ private fun BottomBar(
) {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id)
+ launchSingleTop = true
}
}
}
diff --git a/app/src/main/java/com/starry/myne/ui/screens/reader/activities/ReaderActivity.kt b/app/src/main/java/com/starry/myne/ui/screens/reader/activities/ReaderActivity.kt
index 919c6591..f63dbbab 100644
--- a/app/src/main/java/com/starry/myne/ui/screens/reader/activities/ReaderActivity.kt
+++ b/app/src/main/java/com/starry/myne/ui/screens/reader/activities/ReaderActivity.kt
@@ -52,8 +52,15 @@ object ReaderConstants {
}
+/**
+ * Data class to hold intent information for ReaderActivity.
+ *
+ * @param libraryItemId Library item id.
+ * @param chapterIndex Chapter index.
+ * @param isExternalFile Is book opened from external file.
+ */
data class IntentData(
- val libraryItemId: Int?, val chapterIndex: Int?, val isExternalBook: Boolean
+ val libraryItemId: Int?, val chapterIndex: Int?, val isExternalFile: Boolean
)
@AndroidEntryPoint
@@ -119,7 +126,7 @@ class ReaderActivity : AppCompatActivity() {
// If book was not opened from external epub file, update the
// reading progress into the database.
- if (!intentData.isExternalBook) {
+ if (!intentData.isExternalFile) {
viewModel.updateReaderProgress(
// Book ID is not null here since we are not opening
// an external book.
@@ -165,12 +172,11 @@ fun handleIntent(
val chapterIndex = intent.extras?.getInt(
ReaderConstants.EXTRA_CHAPTER_IDX, ReaderConstants.DEFAULT_NONE
)
- val isExternalBook = intent.type == "application/epub+zip"
+ val isExternalFile = intent.type == "application/epub+zip"
// Internal book
if (libraryItemId != null && libraryItemId != ReaderConstants.DEFAULT_NONE) {
- // Load epub book from given id and set chapters as items in
- // reader's recycler view adapter.
+ // Load epub book from library.
viewModel.loadEpubBook(libraryItemId = libraryItemId, onLoaded = {
// if there is saved progress for this book, then scroll to
// last page at exact position were used had left.
@@ -184,18 +190,19 @@ fun handleIntent(
scrollToPosition(chapterIndex, 0)
}
- // External book.
- } else if (isExternalBook) {
+
+ } else if (isExternalFile) {
+ // External book from file.
intent.data?.let {
contentResolver.openInputStream(it)?.let { ips ->
viewModel.loadEpubBookExternal(ips as FileInputStream)
}
}
} else {
- onError() // If no book id is provided, then show error.
+ onError() // Invalid intent.
}
- return IntentData(libraryItemId, chapterIndex, isExternalBook)
+ return IntentData(libraryItemId, chapterIndex, isExternalFile)
}
/**