* add a UI trigger for demo annotations * Implement internal link navigation * Simplify bottom padding logic in `EpubReaderScreen` for vertical scroll mode * Add support for Calibre metadata * Implement advanced library management with Room-backed collections, tags, and smart rules. * Display book series information in book details and remove tags from home screen * Implement zoom reset functionality in PDF viewer * Add a "Pages" thumbnail view to the PDF navigation drawer * Implement jump-back navigation in PDF viewer * MainViewModel: disable FolderSyncWorker, add log export, and enhance PDF position logging * Replace Gson with Kotlin Serialization in SmartCollectionEngine * Implement ONNX-based speech bubble detection * Implement speech bubble detection in PDF viewer * Implement "Smart Comic Zoom" feature for PDF manga/comic reading * Implement reader session persistence and restoration in `MainViewModel` * Add tap-to-turn page feature to PDF viewer * Improve book cache management and recovery * Refactor top overlay padding logic in `PdfViewerScreen` * Implement stylus eraser support in PDF reader * Refactor ReaderTextFormatPanel to use ModalBottomSheet and add new formatting controls * Introduce a customizable horizontal margin setting for the EPUB reader * Refine folder sync and metadata handling for better conflict resolution and stability. * Introduce user-adjustable image scaling for the EPUB reader * Use maxWidthPx as default width fallback for blocks * Improve cross-page text selection and header styling in the paginated reader * Refactor speech bubble detection and UI to support segmentation masks and interactive scaling * Improve speech bubble detection masking and rendering quality * Update ONNX speech bubble detector to use `.ort` model and optimize inference * Improve pagination accuracy * Implement hierarchical folder navigation for the library. * Refactor library item layout for improved space efficiency * Add support for browsing and downloading Google Fonts * Persist library landing state and add shelf search functionality * Fix base tts sample. * Move SpeechBubbleDetector to main source set and update ONNX dependency * Implement a "locate" feature and improve synchronization for TTS (Text-to-Speech) playback across EPUB and PDF readers. * Refine TTS synchronization and voice management in the EPUB reader * Adjust OCR checks for OSS flavor and optimize external dictionary intent flags * Add "Locate" button to PDF drawer's pages tab and move the page number to bottom right of thumbnail * fix various crashes * Refactor SpeechBubbleDetector into product flavors * Implement speech bubble detection caching and background prefetching * Implement on-demand download for Bubble Zoom ML model * Add smooth animation for PDF speech bubble expansion * fixes #252 * hide Google Fonts option, in FontsScreen, for offline variant * Bump version to 1.0.46(46)
145 lines
5.7 KiB
Kotlin
145 lines
5.7 KiB
Kotlin
/*
|
|
* Episteme Reader - A native Android document reader.
|
|
* Copyright (C) 2026 Episteme
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* mail: epistemereader@gmail.com
|
|
*/
|
|
// MainScreen.kt
|
|
package com.aryan.reader
|
|
|
|
import androidx.activity.ComponentActivity
|
|
import androidx.activity.enableEdgeToEdge
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.pager.HorizontalPager
|
|
import androidx.compose.foundation.pager.rememberPagerState
|
|
import androidx.compose.material3.Icon
|
|
import androidx.compose.material3.NavigationBar
|
|
import androidx.compose.material3.NavigationBarItem
|
|
import androidx.compose.material3.Scaffold
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.SideEffect
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.res.painterResource
|
|
import androidx.compose.ui.res.stringResource
|
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
import androidx.navigation.NavHostController
|
|
import kotlinx.coroutines.launch
|
|
|
|
sealed class BottomBarScreen(val route: String, val stringResId: Int, val iconResId: Int) {
|
|
object Home : BottomBarScreen("home", R.string.nav_home, R.drawable.home)
|
|
object Library : BottomBarScreen("library", R.string.nav_library, R.drawable.library_books)
|
|
}
|
|
|
|
private val bottomBarItems = listOf(
|
|
BottomBarScreen.Home,
|
|
BottomBarScreen.Library,
|
|
)
|
|
|
|
@Composable
|
|
fun MainScreen(
|
|
viewModel: MainViewModel,
|
|
windowSizeClass: WindowSizeClass,
|
|
navController: NavHostController
|
|
) {
|
|
val context = LocalContext.current
|
|
|
|
SideEffect {
|
|
val activity = context as? ComponentActivity
|
|
activity?.enableEdgeToEdge()
|
|
}
|
|
|
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
|
val viewingShelfName = uiState.viewingShelfId
|
|
|
|
androidx.compose.foundation.layout.Box(modifier = Modifier.fillMaxSize()) {
|
|
if (viewingShelfName != null) {
|
|
ShelfScreen(viewModel = viewModel)
|
|
} else {
|
|
val pagerState = rememberPagerState(
|
|
initialPage = uiState.mainScreenStartPage,
|
|
pageCount = { bottomBarItems.size }
|
|
)
|
|
val scope = rememberCoroutineScope()
|
|
|
|
LaunchedEffect(uiState.mainScreenStartPage) {
|
|
if (pagerState.currentPage != uiState.mainScreenStartPage) {
|
|
pagerState.animateScrollToPage(uiState.mainScreenStartPage)
|
|
}
|
|
}
|
|
|
|
LaunchedEffect(pagerState.currentPage) {
|
|
viewModel.setMainScreenPage(pagerState.currentPage)
|
|
}
|
|
|
|
Scaffold(
|
|
contentWindowInsets = androidx.compose.foundation.layout.WindowInsets(0, 0, 0, 0),
|
|
bottomBar = {
|
|
NavigationBar {
|
|
bottomBarItems.forEachIndexed { index, screen ->
|
|
NavigationBarItem(
|
|
icon = { Icon(painterResource(id = screen.iconResId), contentDescription = stringResource(screen.stringResId)) },
|
|
label = { Text(stringResource(screen.stringResId)) },
|
|
selected = pagerState.currentPage == index,
|
|
onClick = { scope.launch { pagerState.animateScrollToPage(index) } }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
) { innerPadding ->
|
|
HorizontalPager(
|
|
state = pagerState,
|
|
modifier = Modifier
|
|
.fillMaxSize()
|
|
.padding(innerPadding),
|
|
key = { bottomBarItems[it].route },
|
|
beyondViewportPageCount = 1,
|
|
userScrollEnabled = false
|
|
) { page ->
|
|
when (page) {
|
|
0 -> HomeScreen(
|
|
viewModel = viewModel,
|
|
windowSizeClass = windowSizeClass,
|
|
navController = navController
|
|
)
|
|
1 -> LibraryScreen(viewModel = viewModel)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uiState.showTagSelectionDialogFor.isNotEmpty()) {
|
|
TagSelectionBottomSheet(
|
|
allTags = uiState.allTags,
|
|
selectedBookIds = uiState.showTagSelectionDialogFor,
|
|
booksWithTags = uiState.rawLibraryFiles,
|
|
onCreateAndAssign = { name ->
|
|
viewModel.createAndAssignTag(name, uiState.showTagSelectionDialogFor)
|
|
},
|
|
onToggleTag = { tagId, assign ->
|
|
viewModel.toggleTagForBooks(tagId, uiState.showTagSelectionDialogFor, assign)
|
|
},
|
|
onDismiss = viewModel::closeTagSelection
|
|
)
|
|
}
|
|
}
|
|
}
|