0.9.8 - StartScreen + Check for updates + Bug fixes + Lot more...
This commit is contained in:
parent
513b286e07
commit
b5c2399034
129 changed files with 3944 additions and 1535 deletions
9
.idea/inspectionProfiles/Project_Default.xml
generated
9
.idea/inspectionProfiles/Project_Default.xml
generated
|
|
@ -3,30 +3,39 @@
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
|
|
@ -4,6 +4,7 @@ plugins {
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
id("com.google.dagger.hilt.android")
|
id("com.google.dagger.hilt.android")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
|
id("com.mikepenz.aboutlibraries.plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -15,7 +16,7 @@ android {
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "0.9.7"
|
versionName = "0.9.8"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
@ -61,16 +62,15 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// Default
|
// Default
|
||||||
implementation("androidx.core:core-ktx:1.12.0")
|
implementation("androidx.core:core-ktx:1.13.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
||||||
implementation("androidx.activity:activity-compose:1.8.2")
|
implementation("androidx.activity:activity-compose:1.9.0")
|
||||||
|
|
||||||
// Compose BOM libraries
|
// Compose BOM libraries
|
||||||
implementation(platform("androidx.compose:compose-bom:2024.04.00"))
|
implementation(platform("androidx.compose:compose-bom:2024.04.01"))
|
||||||
implementation("androidx.compose.foundation:foundation")
|
implementation("androidx.compose.foundation:foundation")
|
||||||
implementation("androidx.compose.animation:animation")
|
implementation("androidx.compose.animation:animation")
|
||||||
implementation("androidx.compose.animation:animation-core")
|
implementation("androidx.compose.animation:animation-android")
|
||||||
implementation("androidx.compose.animation:animation-graphics")
|
|
||||||
implementation("androidx.compose.foundation:foundation-layout")
|
implementation("androidx.compose.foundation:foundation-layout")
|
||||||
implementation("androidx.compose.ui:ui")
|
implementation("androidx.compose.ui:ui")
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
implementation("androidx.compose.ui:ui-graphics")
|
||||||
|
|
@ -81,35 +81,13 @@ dependencies {
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
implementation("androidx.compose.material:material")
|
implementation("androidx.compose.material:material")
|
||||||
|
|
||||||
// Unit tests
|
|
||||||
testImplementation("androidx.test:core:latest.release")
|
|
||||||
testImplementation("junit:junit:latest.release")
|
|
||||||
testImplementation("androidx.arch.core:core-testing:latest.release")
|
|
||||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:latest.release")
|
|
||||||
testImplementation("com.google.truth:truth:1.4.2")
|
|
||||||
testImplementation("androidx.test.ext:truth:1.5.0")
|
|
||||||
testImplementation("com.squareup.okhttp3:mockwebserver:latest.release")
|
|
||||||
testImplementation("io.mockk:mockk:latest.release")
|
|
||||||
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.5")
|
|
||||||
|
|
||||||
// Instrumentation tests
|
|
||||||
androidTestImplementation("com.google.dagger:hilt-android-testing:latest.release")
|
|
||||||
androidTestImplementation("junit:junit:latest.release")
|
|
||||||
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:latest.release")
|
|
||||||
androidTestImplementation("androidx.arch.core:core-testing:latest.release")
|
|
||||||
androidTestImplementation("com.google.truth:truth:latest.release")
|
|
||||||
androidTestImplementation("androidx.test.ext:junit:latest.release")
|
|
||||||
androidTestImplementation("androidx.test:core-ktx:latest.release")
|
|
||||||
androidTestImplementation("com.squareup.okhttp3:mockwebserver:latest.release")
|
|
||||||
androidTestImplementation("io.mockk:mockk-android:latest.release")
|
|
||||||
androidTestImplementation("androidx.test:runner:latest.release")
|
|
||||||
|
|
||||||
// All dependencies
|
// All dependencies
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
|
||||||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
|
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
|
||||||
|
|
||||||
// Dagger - Hilt
|
// Dagger - Hilt
|
||||||
implementation("com.google.dagger:hilt-android:2.51.1")
|
implementation("com.google.dagger:hilt-android:2.51.1")
|
||||||
|
debugImplementation("androidx.compose.ui:ui-tooling:1.6.6")
|
||||||
ksp("com.google.dagger:hilt-android-compiler:2.51")
|
ksp("com.google.dagger:hilt-android-compiler:2.51")
|
||||||
implementation("com.google.dagger:hilt-compiler:latest.release")
|
implementation("com.google.dagger:hilt-compiler:latest.release")
|
||||||
ksp("androidx.hilt:hilt-compiler:latest.release")
|
ksp("androidx.hilt:hilt-compiler:latest.release")
|
||||||
|
|
@ -148,4 +126,16 @@ dependencies {
|
||||||
|
|
||||||
// Coil for loading bitmaps
|
// Coil for loading bitmaps
|
||||||
implementation("io.coil-kt:coil-compose:2.6.0")
|
implementation("io.coil-kt:coil-compose:2.6.0")
|
||||||
|
|
||||||
|
// Retrofit
|
||||||
|
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||||
|
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
|
||||||
|
implementation("com.squareup.moshi:moshi:1.15.1")
|
||||||
|
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
||||||
|
|
||||||
|
// About open source libraries
|
||||||
|
implementation("com.mikepenz:aboutlibraries-core:latest.release")
|
||||||
|
implementation("com.mikepenz:aboutlibraries-compose:latest.release")
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,16 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<!-- Check device for books -->
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
|
<!-- Send notification about new app version -->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
|
|
||||||
<!-- Translator -->
|
<!-- Translator -->
|
||||||
|
|
|
||||||
|
|
@ -7,30 +7,15 @@ import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.ime
|
import androidx.compose.foundation.layout.ime
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
|
||||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
|
||||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
@ -39,22 +24,27 @@ import ua.acclorite.book_story.presentation.components.custom_navigation_rail.Cu
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
import ua.acclorite.book_story.presentation.data.NavigationHost
|
import ua.acclorite.book_story.presentation.data.NavigationHost
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.about.AboutScreen
|
import ua.acclorite.book_story.presentation.screens.about.AboutScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.BookInfoScreen
|
import ua.acclorite.book_story.presentation.screens.about.data.AboutViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.book_info.BookInfoScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.BrowseScreen
|
import ua.acclorite.book_story.presentation.screens.browse.BrowseScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.help.HelpScreen
|
import ua.acclorite.book_story.presentation.screens.help.HelpScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.history.HistoryScreen
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.history.HistoryScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.library.LibraryScreen
|
import ua.acclorite.book_story.presentation.screens.library.LibraryScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.ReaderScreen
|
import ua.acclorite.book_story.presentation.screens.reader.ReaderScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.SettingsScreen
|
import ua.acclorite.book_story.presentation.screens.settings.SettingsScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettings
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettings
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettingsRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettings
|
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettingsRoot
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettingsRoot
|
||||||
|
import ua.acclorite.book_story.presentation.screens.start.StartScreenRoot
|
||||||
|
import ua.acclorite.book_story.presentation.screens.start.data.StartViewModel
|
||||||
import ua.acclorite.book_story.presentation.ui.BookStoryTheme
|
import ua.acclorite.book_story.presentation.ui.BookStoryTheme
|
||||||
import ua.acclorite.book_story.presentation.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
import ua.acclorite.book_story.presentation.ui.isDark
|
import ua.acclorite.book_story.presentation.ui.isDark
|
||||||
|
|
@ -62,16 +52,21 @@ import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
@SuppressLint("DiscouragedPrivateApi")
|
@SuppressLint("DiscouragedPrivateApi")
|
||||||
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class Activity : AppCompatActivity() {
|
class Activity : AppCompatActivity() {
|
||||||
|
// Initializing all viewModels on app startup
|
||||||
private val mainViewModel: MainViewModel by viewModels()
|
private val mainViewModel: MainViewModel by viewModels()
|
||||||
private val libraryViewModel: LibraryViewModel by viewModels()
|
private val libraryViewModel: LibraryViewModel by viewModels()
|
||||||
private val historyViewModel: HistoryViewModel by viewModels()
|
private val historyViewModel: HistoryViewModel by viewModels()
|
||||||
private val browseViewModel: BrowseViewModel by viewModels()
|
private val browseViewModel: BrowseViewModel by viewModels()
|
||||||
private val bookInfoViewModel: BookInfoViewModel by viewModels()
|
private val bookInfoViewModel: BookInfoViewModel by viewModels()
|
||||||
private val readerViewModel: ReaderViewModel by viewModels()
|
private val readerViewModel: ReaderViewModel by viewModels()
|
||||||
|
private val helpViewModel: HelpViewModel by viewModels()
|
||||||
|
private val aboutViewModel: AboutViewModel by viewModels()
|
||||||
|
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||||
|
private val startViewModel: StartViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -103,19 +98,6 @@ class Activity : AppCompatActivity() {
|
||||||
val isLoaded by mainViewModel.isReady.collectAsState()
|
val isLoaded by mainViewModel.isReady.collectAsState()
|
||||||
val state by mainViewModel.state.collectAsState()
|
val state by mainViewModel.state.collectAsState()
|
||||||
|
|
||||||
val windowClass = calculateWindowSizeClass(activity = this)
|
|
||||||
val tabletUI = remember(windowClass) {
|
|
||||||
windowClass.widthSizeClass != WindowWidthSizeClass.Compact
|
|
||||||
}
|
|
||||||
val localLayoutDirection = LocalLayoutDirection.current
|
|
||||||
val layoutDirection = remember(localLayoutDirection) {
|
|
||||||
if (localLayoutDirection == LayoutDirection.Ltr) {
|
|
||||||
LayoutDirection.Ltr
|
|
||||||
} else {
|
|
||||||
LayoutDirection.Rtl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val imeInsets = WindowInsets.ime
|
val imeInsets = WindowInsets.ime
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
@ -124,6 +106,7 @@ class Activity : AppCompatActivity() {
|
||||||
imeInsets.getBottom(density) > 0
|
imeInsets.getBottom(density) > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(isKeyboardVisible) {
|
LaunchedEffect(isKeyboardVisible) {
|
||||||
if (!isKeyboardVisible) {
|
if (!isKeyboardVisible) {
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
|
|
@ -137,175 +120,90 @@ class Activity : AppCompatActivity() {
|
||||||
isPureDark = state.pureDark!!.isPureDark(this),
|
isPureDark = state.pureDark!!.isPureDark(this),
|
||||||
themeContrast = state.themeContrast!!
|
themeContrast = state.themeContrast!!
|
||||||
) {
|
) {
|
||||||
NavigationHost(startScreen = Screen.LIBRARY) {
|
NavigationHost(
|
||||||
val currentScreen by this.getCurrentScreen().collectAsState()
|
startScreen = if (state.showStartScreen!!) Screen.START
|
||||||
|
else Screen.LIBRARY
|
||||||
AnimatedVisibility(
|
|
||||||
visible = currentScreen == Screen.LIBRARY ||
|
|
||||||
currentScreen == Screen.HISTORY ||
|
|
||||||
currentScreen == Screen.BROWSE,
|
|
||||||
enter = Transitions.BackSlidingTransitionIn,
|
|
||||||
exit = Transitions.SlidingTransitionOut
|
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
navigation(
|
||||||
|
screens = arrayOf(Screen.LIBRARY, Screen.HISTORY, Screen.BROWSE),
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
if (!tabletUI) {
|
BottomNavigationBar()
|
||||||
BottomNavigationBar(navigator = this@NavigationHost)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
navigationRail = {
|
||||||
|
CustomNavigationRail()
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Box(
|
// Library
|
||||||
modifier = Modifier
|
composable(
|
||||||
.fillMaxSize()
|
screen = Screen.LIBRARY,
|
||||||
|
enterAnim = Transitions.FadeTransitionIn,
|
||||||
|
exitAnim = Transitions.FadeTransitionOut
|
||||||
) {
|
) {
|
||||||
Box(
|
LibraryScreenRoot()
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(
|
|
||||||
end = it.calculateEndPadding(
|
|
||||||
layoutDirection
|
|
||||||
),
|
|
||||||
bottom = it.calculateBottomPadding(),
|
|
||||||
start = if (tabletUI) {
|
|
||||||
80.dp + it.calculateStartPadding(
|
|
||||||
layoutDirection
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
it.calculateStartPadding(
|
|
||||||
layoutDirection
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
composable(screen = Screen.LIBRARY) {
|
|
||||||
LibraryScreen(
|
|
||||||
viewModel = libraryViewModel,
|
|
||||||
historyViewModel = historyViewModel,
|
|
||||||
browseViewModel = browseViewModel,
|
|
||||||
navigator = this@NavigationHost
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(screen = Screen.HISTORY) {
|
// History
|
||||||
HistoryScreen(
|
composable(
|
||||||
viewModel = historyViewModel,
|
screen = Screen.HISTORY,
|
||||||
libraryViewModel = libraryViewModel,
|
enterAnim = Transitions.FadeTransitionIn,
|
||||||
navigator = this@NavigationHost
|
exitAnim = Transitions.FadeTransitionOut
|
||||||
)
|
) {
|
||||||
|
HistoryScreenRoot()
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(screen = Screen.BROWSE) {
|
// Browse
|
||||||
BrowseScreen(
|
composable(
|
||||||
viewModel = browseViewModel,
|
screen = Screen.BROWSE,
|
||||||
libraryViewModel = libraryViewModel,
|
enterAnim = Transitions.FadeTransitionIn,
|
||||||
navigator = this@NavigationHost
|
exitAnim = Transitions.FadeTransitionOut
|
||||||
)
|
) {
|
||||||
}
|
BrowseScreenRoot()
|
||||||
}
|
|
||||||
|
|
||||||
if (tabletUI) {
|
|
||||||
CustomNavigationRail(
|
|
||||||
navigator = this@NavigationHost,
|
|
||||||
layoutDirection = layoutDirection
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Book Info
|
// Book Info
|
||||||
composable(
|
composable(screen = Screen.BOOK_INFO) {
|
||||||
screen = Screen.BOOK_INFO,
|
BookInfoScreenRoot()
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
|
||||||
) {
|
|
||||||
BookInfoScreen(
|
|
||||||
viewModel = bookInfoViewModel,
|
|
||||||
libraryViewModel = libraryViewModel,
|
|
||||||
browseViewModel = browseViewModel,
|
|
||||||
historyViewModel = historyViewModel,
|
|
||||||
navigator = this@NavigationHost
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
composable(
|
|
||||||
screen = Screen.READER,
|
// Reader
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
composable(screen = Screen.READER) {
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
ReaderScreenRoot()
|
||||||
) {
|
|
||||||
ReaderScreen(
|
|
||||||
viewModel = readerViewModel,
|
|
||||||
mainViewModel = mainViewModel,
|
|
||||||
libraryViewModel = libraryViewModel,
|
|
||||||
historyViewModel = historyViewModel,
|
|
||||||
navigator = this@NavigationHost
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
composable(
|
composable(screen = Screen.SETTINGS) {
|
||||||
screen = Screen.SETTINGS,
|
SettingsScreenRoot()
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
|
||||||
) {
|
|
||||||
SettingsScreen(
|
|
||||||
navigator = this@NavigationHost
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nested categories
|
// Nested settings categories
|
||||||
composable(
|
composable(screen = Screen.GENERAL_SETTINGS) {
|
||||||
screen = Screen.GENERAL_SETTINGS,
|
GeneralSettingsRoot()
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
|
||||||
) {
|
|
||||||
GeneralSettings(
|
|
||||||
mainViewModel = mainViewModel,
|
|
||||||
navigator = this@NavigationHost
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
composable(
|
composable(screen = Screen.APPEARANCE_SETTINGS) {
|
||||||
screen = Screen.APPEARANCE_SETTINGS,
|
AppearanceSettingsRoot()
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
|
||||||
) {
|
|
||||||
AppearanceSettings(
|
|
||||||
mainViewModel = mainViewModel,
|
|
||||||
navigator = this@NavigationHost
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
composable(
|
composable(screen = Screen.READER_SETTINGS) {
|
||||||
screen = Screen.READER_SETTINGS,
|
ReaderSettingsRoot()
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
|
||||||
) {
|
|
||||||
ReaderSettings(
|
|
||||||
mainViewModel = mainViewModel,
|
|
||||||
navigator = this@NavigationHost
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// About screen
|
// About screen
|
||||||
composable(
|
composable(screen = Screen.ABOUT) {
|
||||||
screen = Screen.ABOUT,
|
AboutScreenRoot()
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
|
||||||
) {
|
|
||||||
AboutScreen(navigator = this@NavigationHost)
|
|
||||||
}
|
|
||||||
// Help screen
|
|
||||||
composable(
|
|
||||||
screen = Screen.HELP,
|
|
||||||
enterAnim = Transitions.SlidingTransitionIn,
|
|
||||||
exitAnim = Transitions.SlidingTransitionOut
|
|
||||||
) {
|
|
||||||
HelpScreen(navigator = this@NavigationHost)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start screen (later)
|
// Help screen
|
||||||
// composable(screen = Screen.START) {
|
composable(screen = Screen.HELP) {
|
||||||
// StartScreen()
|
HelpScreenRoot()
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
// Start screen
|
||||||
|
composable(
|
||||||
|
screen = Screen.START,
|
||||||
|
enterAnim = Transitions.FadeTransitionIn
|
||||||
|
) {
|
||||||
|
StartScreenRoot()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,36 @@
|
||||||
package ua.acclorite.book_story
|
package ua.acclorite.book_story
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
import ua.acclorite.book_story.data.local.notification.UpdatesNotificationService
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class Application : Application()
|
class Application : Application() {
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
createNotificationChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationChannel() {
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
UpdatesNotificationService.CHANNEL_ID,
|
||||||
|
getString(R.string.updates_notification_channel),
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
|
|
||||||
|
val notificationManager = getSystemService(
|
||||||
|
Context.NOTIFICATION_SERVICE
|
||||||
|
) as NotificationManager
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,35 @@ package ua.acclorite.book_story.data.di
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
import ua.acclorite.book_story.data.local.room.BookDao
|
import ua.acclorite.book_story.data.local.room.BookDao
|
||||||
import ua.acclorite.book_story.data.local.room.BookDatabase
|
import ua.acclorite.book_story.data.local.room.BookDatabase
|
||||||
|
import ua.acclorite.book_story.data.remote.GithubAPI
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object AppModule {
|
object AppModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGithubApi(): GithubAPI {
|
||||||
|
val moshi = Moshi.Builder()
|
||||||
|
.add(KotlinJsonAdapterFactory())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.baseUrl(GithubAPI.BASE_URL)
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
|
.build()
|
||||||
|
.create(GithubAPI::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import ua.acclorite.book_story.data.local.data_store.DataStore
|
import ua.acclorite.book_story.data.local.data_store.DataStore
|
||||||
import ua.acclorite.book_story.data.local.data_store.DataStoreImpl
|
import ua.acclorite.book_story.data.local.data_store.DataStoreImpl
|
||||||
|
import ua.acclorite.book_story.data.local.notification.UpdatesNotificationService
|
||||||
|
import ua.acclorite.book_story.data.local.notification.UpdatesNotificationServiceImpl
|
||||||
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
||||||
import ua.acclorite.book_story.data.mapper.book.BookMapperImpl
|
import ua.acclorite.book_story.data.mapper.book.BookMapperImpl
|
||||||
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
||||||
|
|
@ -56,4 +58,10 @@ abstract class RepositoryModule {
|
||||||
abstract fun bindTextParser(
|
abstract fun bindTextParser(
|
||||||
textParserImpl: TextParserImpl
|
textParserImpl: TextParserImpl
|
||||||
): TextParser
|
): TextParser
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindNotificationService(
|
||||||
|
updatesNotificationServiceImpl: UpdatesNotificationServiceImpl
|
||||||
|
): UpdatesNotificationService
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.data.local.notification
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.data.remote.dto.ReleaseResponse
|
||||||
|
|
||||||
|
interface UpdatesNotificationService {
|
||||||
|
|
||||||
|
fun postNotification(releaseInfo: ReleaseResponse)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CHANNEL_ID = "updates_channel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package ua.acclorite.book_story.data.local.notification
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.data.remote.dto.ReleaseResponse
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdatesNotificationServiceImpl @Inject constructor(
|
||||||
|
private val application: Application
|
||||||
|
) : UpdatesNotificationService {
|
||||||
|
|
||||||
|
private val notificationManager = application.getSystemService(Context.NOTIFICATION_SERVICE)
|
||||||
|
as NotificationManager
|
||||||
|
|
||||||
|
override fun postNotification(releaseInfo: ReleaseResponse) {
|
||||||
|
val downloadLatestVersionIntent = Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(application.getString(R.string.download_latest_release_page))
|
||||||
|
)
|
||||||
|
val latestReleaseIntent = Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(application.getString(R.string.releases_page))
|
||||||
|
)
|
||||||
|
|
||||||
|
val downloadLatestVersionPendingIntent = PendingIntent.getActivity(
|
||||||
|
application,
|
||||||
|
1,
|
||||||
|
downloadLatestVersionIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
val latestReleasePendingIntent = PendingIntent.getActivity(
|
||||||
|
application,
|
||||||
|
1,
|
||||||
|
latestReleaseIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val notification = Notification.Builder(
|
||||||
|
application,
|
||||||
|
UpdatesNotificationService.CHANNEL_ID
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.notification_icon)
|
||||||
|
.setContentTitle(application.getString(R.string.updates_notification_title))
|
||||||
|
.setContentText(
|
||||||
|
application.getString(
|
||||||
|
R.string.updates_notification_desc,
|
||||||
|
releaseInfo.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setContentIntent(downloadLatestVersionPendingIntent)
|
||||||
|
.addAction(
|
||||||
|
R.drawable.app_icon_monochrome,
|
||||||
|
application.getString(R.string.updates_notification_action),
|
||||||
|
latestReleasePendingIntent
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(
|
||||||
|
0,
|
||||||
|
notification
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package ua.acclorite.book_story.data.remote
|
||||||
|
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import ua.acclorite.book_story.data.remote.dto.ReleaseResponse
|
||||||
|
|
||||||
|
interface GithubAPI {
|
||||||
|
@GET("repos/Acclorite/book-story/releases/latest")
|
||||||
|
suspend fun getLatestRelease(): ReleaseResponse
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val BASE_URL = "https://api.github.com/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ua.acclorite.book_story.data.remote.dto
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Suppress("PropertyName")
|
||||||
|
@Immutable
|
||||||
|
data class ReleaseResponse(
|
||||||
|
val name: String,
|
||||||
|
val tag_name: String
|
||||||
|
)
|
||||||
|
|
@ -17,11 +17,14 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.local.data_store.DataStore
|
import ua.acclorite.book_story.data.local.data_store.DataStore
|
||||||
|
import ua.acclorite.book_story.data.local.notification.UpdatesNotificationService
|
||||||
import ua.acclorite.book_story.data.local.room.BookDao
|
import ua.acclorite.book_story.data.local.room.BookDao
|
||||||
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
||||||
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.data.parser.TextParser
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
|
import ua.acclorite.book_story.data.remote.GithubAPI
|
||||||
|
import ua.acclorite.book_story.data.remote.dto.ReleaseResponse
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
|
|
@ -31,7 +34,7 @@ import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.domain.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
import ua.acclorite.book_story.presentation.data.MainSettingsState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -44,6 +47,9 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
private val database: BookDao,
|
private val database: BookDao,
|
||||||
private val dataStore: DataStore,
|
private val dataStore: DataStore,
|
||||||
|
|
||||||
|
private val githubAPI: GithubAPI,
|
||||||
|
private val updatesNotificationService: UpdatesNotificationService,
|
||||||
|
|
||||||
private val bookMapper: BookMapper,
|
private val bookMapper: BookMapper,
|
||||||
private val historyMapper: HistoryMapper,
|
private val historyMapper: HistoryMapper,
|
||||||
|
|
||||||
|
|
@ -385,8 +391,8 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
dataStore.putData(key, value)
|
dataStore.putData(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAllSettings(scope: CoroutineScope): MainState {
|
override suspend fun getAllSettings(scope: CoroutineScope): MainSettingsState {
|
||||||
val result = CompletableDeferred<MainState>()
|
val result = CompletableDeferred<MainSettingsState>()
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val keys = dataStore.getAllData()
|
val keys = dataStore.getAllData()
|
||||||
|
|
@ -405,7 +411,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
jobs?.awaitAll()
|
jobs?.awaitAll()
|
||||||
|
|
||||||
result.complete(MainState.initialize(data))
|
result.complete(MainSettingsState.initialize(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.await()
|
return result.await()
|
||||||
|
|
@ -601,6 +607,28 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
override suspend fun deleteHistory(history: List<History>) {
|
override suspend fun deleteHistory(history: List<History>) {
|
||||||
database.deleteHistory(history.map { historyMapper.toHistoryEntity(it) })
|
database.deleteHistory(history.map { historyMapper.toHistoryEntity(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun checkForUpdates(postNotification: Boolean): ReleaseResponse? {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val result = githubAPI.getLatestRelease()
|
||||||
|
|
||||||
|
val version = result.tag_name.substringAfterLast("v")
|
||||||
|
val currentVersion = application.getString(R.string.app_version)
|
||||||
|
|
||||||
|
if (version != currentVersion && postNotification) {
|
||||||
|
updatesNotificationService.postNotification(
|
||||||
|
result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ package ua.acclorite.book_story.domain.repository
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import ua.acclorite.book_story.data.remote.dto.ReleaseResponse
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
import ua.acclorite.book_story.domain.model.StringWithId
|
import ua.acclorite.book_story.domain.model.StringWithId
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.domain.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
import ua.acclorite.book_story.presentation.data.MainSettingsState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface BookRepository {
|
interface BookRepository {
|
||||||
|
|
@ -57,7 +58,7 @@ interface BookRepository {
|
||||||
value: T
|
value: T
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun getAllSettings(scope: CoroutineScope): MainState
|
suspend fun getAllSettings(scope: CoroutineScope): MainSettingsState
|
||||||
|
|
||||||
suspend fun getFilesFromDevice(query: String = ""): Flow<Resource<List<File>>>
|
suspend fun getFilesFromDevice(query: String = ""): Flow<Resource<List<File>>>
|
||||||
|
|
||||||
|
|
@ -76,4 +77,6 @@ interface BookRepository {
|
||||||
suspend fun deleteHistory(
|
suspend fun deleteHistory(
|
||||||
history: List<History>
|
history: List<History>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun checkForUpdates(postNotification: Boolean): ReleaseResponse?
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.data.remote.dto.ReleaseResponse
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CheckForUpdates @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(postNotification: Boolean): ReleaseResponse? {
|
||||||
|
return repository.checkForUpdates(postNotification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,14 +2,14 @@ package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
import ua.acclorite.book_story.presentation.data.MainSettingsState
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetAllSettings @Inject constructor(
|
class GetAllSettings @Inject constructor(
|
||||||
private val repository: BookRepository
|
private val repository: BookRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(scope: CoroutineScope): MainState {
|
suspend fun execute(scope: CoroutineScope): MainSettingsState {
|
||||||
return repository.getAllSettings(scope)
|
return repository.getAllSettings(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,4 +20,5 @@ object DataStoreConstants {
|
||||||
val LINE_HEIGHT = intPreferencesKey("line_height")
|
val LINE_HEIGHT = intPreferencesKey("line_height")
|
||||||
val PARAGRAPH_HEIGHT = intPreferencesKey("paragraph_height")
|
val PARAGRAPH_HEIGHT = intPreferencesKey("paragraph_height")
|
||||||
val PARAGRAPH_INDENTATION = booleanPreferencesKey("paragraph_indentation")
|
val PARAGRAPH_INDENTATION = booleanPreferencesKey("paragraph_indentation")
|
||||||
|
val CHECK_FOR_UPDATES = booleanPreferencesKey("check_for_updates")
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package ua.acclorite.book_story.presentation.components
|
package ua.acclorite.book_story.presentation.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.Spring
|
import androidx.compose.animation.core.Spring
|
||||||
import androidx.compose.animation.core.spring
|
import androidx.compose.animation.core.spring
|
||||||
|
|
@ -95,7 +94,7 @@ fun AnimatedTopAppBar(
|
||||||
label = stringResource(id = R.string.top_app_bar_anim_content_desc)
|
label = stringResource(id = R.string.top_app_bar_anim_content_desc)
|
||||||
)
|
)
|
||||||
|
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = content1Visibility ?: true,
|
visible = content1Visibility ?: true,
|
||||||
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
|
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
|
||||||
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
||||||
|
|
@ -115,7 +114,7 @@ fun AnimatedTopAppBar(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content2Visibility != null) {
|
if (content2Visibility != null) {
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = content2Visibility,
|
visible = content2Visibility,
|
||||||
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
|
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
|
||||||
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
||||||
|
|
@ -136,7 +135,7 @@ fun AnimatedTopAppBar(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content3Visibility != null) {
|
if (content3Visibility != null) {
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = content3Visibility,
|
visible = content3Visibility,
|
||||||
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
|
enter = fadeIn(spring(stiffness = Spring.StiffnessMediumLow)),
|
||||||
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,12 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapsible Scroll Behavior with Lazy List State.
|
||||||
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState(
|
fun TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState(
|
||||||
|
|
||||||
): Pair<TopAppBarScrollBehavior, LazyListState> {
|
): Pair<TopAppBarScrollBehavior, LazyListState> {
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package ua.acclorite.book_story.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.EnterTransition
|
||||||
|
import androidx.compose.animation.ExitTransition
|
||||||
|
import androidx.compose.animation.core.MutableTransitionState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom [AnimatedVisibility]. Prevents bug that animation freezes halfway.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CustomAnimatedVisibility(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
visible: Boolean,
|
||||||
|
enter: EnterTransition,
|
||||||
|
exit: ExitTransition,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val visibleState = remember {
|
||||||
|
MutableTransitionState(visible)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(visible) {
|
||||||
|
if (visible == visibleState.targetState) {
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibleState.isIdle) {
|
||||||
|
visibleState.targetState = visible
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotFlow {
|
||||||
|
visibleState.isIdle
|
||||||
|
}.first {
|
||||||
|
if (it) {
|
||||||
|
visibleState.targetState = visible
|
||||||
|
}
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visibleState = visibleState,
|
||||||
|
modifier = modifier,
|
||||||
|
enter = enter,
|
||||||
|
exit = exit,
|
||||||
|
label = ""
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,9 @@ import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Cover Image. Has smooth appearing.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomCoverImage(
|
fun CustomCoverImage(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package ua.acclorite.book_story.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Search Text Field. Used in main screens for searching.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CustomSearchTextField(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
query: String,
|
||||||
|
onQueryChange: (String) -> Unit,
|
||||||
|
onSearch: () -> Unit,
|
||||||
|
placeholder: String
|
||||||
|
) {
|
||||||
|
val keyboardManager = LocalSoftwareKeyboardController.current
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
value = query,
|
||||||
|
singleLine = true,
|
||||||
|
textStyle = TextStyle(
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||||
|
lineHeight = MaterialTheme.typography.titleLarge.lineHeight,
|
||||||
|
fontFamily = MaterialTheme.typography.titleLarge.fontFamily
|
||||||
|
),
|
||||||
|
modifier = modifier,
|
||||||
|
onValueChange = onQueryChange,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
KeyboardCapitalization.Words,
|
||||||
|
imeAction = ImeAction.Search
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onSearch = {
|
||||||
|
onSearch()
|
||||||
|
keyboardManager?.hide()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
) { innerText ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
placeholder,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
innerText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package ua.acclorite.book_story.presentation.components
|
package ua.acclorite.book_story.presentation.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
|
@ -82,7 +81,7 @@ fun CustomSnackbar(modifier: Modifier = Modifier, snackbarState: SnackbarHostSta
|
||||||
showSnackbar = true
|
showSnackbar = true
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = showSnackbar,
|
visible = showSnackbar,
|
||||||
enter = slideInVertically(tween(350)) { it } + fadeIn(tween(350)),
|
enter = slideInVertically(tween(350)) { it } + fadeIn(tween(350)),
|
||||||
exit = slideOutVertically(tween(200)) { it } + fadeOut(tween(100))
|
exit = slideOutVertically(tween(200)) { it } + fadeOut(tween(100))
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Tooltip.
|
||||||
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomTooltip(text: String, padding: Dp = 14.dp, content: @Composable () -> Unit) {
|
fun CustomTooltip(text: String, padding: Dp = 14.dp, content: @Composable () -> Unit) {
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,15 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* More drop down. Navigates to Settings, About and Help screens.
|
* More drop down. Navigates to Settings, About and Help screens.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MoreDropDown(navigator: Navigator) {
|
fun MoreDropDown() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
var showDropDown by remember { mutableStateOf(false) }
|
var showDropDown by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val startPadding = remember { 12.dp }
|
val startPadding = remember { 12.dp }
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,17 @@ import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.NavigationItem
|
import ua.acclorite.book_story.domain.model.NavigationItem
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bottom navigation bar, uses default [NavigationBar].
|
* Bottom navigation bar, uses default [NavigationBar].
|
||||||
*
|
*
|
||||||
* @param navigator Navigator.
|
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomNavigationBar(
|
fun BottomNavigationBar() {
|
||||||
navigator: Navigator
|
|
||||||
) {
|
|
||||||
var currentScreen: Screen? by remember { mutableStateOf(null) }
|
var currentScreen: Screen? by remember { mutableStateOf(null) }
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
navigator.getCurrentScreen().collect {
|
navigator.getCurrentScreen().collect {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@ import ua.acclorite.book_story.R
|
||||||
* @param title The title of the dialog.
|
* @param title The title of the dialog.
|
||||||
* @param description The description of the dialog.
|
* @param description The description of the dialog.
|
||||||
* @param actionText The action text of the dialog.
|
* @param actionText The action text of the dialog.
|
||||||
* @param isActionEnabled Whether action is enabled or not,
|
* @param disableOnClick Whether should all actions be disabled after clicking it.
|
||||||
|
* @param isActionEnabled Whether action is enabled or not.
|
||||||
* @param onDismiss Code to execute when the dialog is dismissed.
|
* @param onDismiss Code to execute when the dialog is dismissed.
|
||||||
* @param onAction Code to execute when action is clicked.
|
* @param onAction Code to execute when action is clicked.
|
||||||
* @param withDivider Should divider be shown or not.
|
* @param withDivider Should divider be shown or not.
|
||||||
|
|
@ -63,6 +64,7 @@ fun CustomDialogWithContent(
|
||||||
title: String,
|
title: String,
|
||||||
description: String?,
|
description: String?,
|
||||||
actionText: String?,
|
actionText: String?,
|
||||||
|
disableOnClick: Boolean = true,
|
||||||
isActionEnabled: Boolean?,
|
isActionEnabled: Boolean?,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onAction: () -> Unit,
|
onAction: () -> Unit,
|
||||||
|
|
@ -158,7 +160,9 @@ fun CustomDialogWithContent(
|
||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
if (disableOnClick) {
|
||||||
actionClicked = true
|
actionClicked = true
|
||||||
|
}
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
},
|
||||||
enabled = !actionClicked
|
enabled = !actionClicked
|
||||||
|
|
@ -173,7 +177,9 @@ fun CustomDialogWithContent(
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
if (disableOnClick) {
|
||||||
actionClicked = true
|
actionClicked = true
|
||||||
|
}
|
||||||
onAction()
|
onAction()
|
||||||
},
|
},
|
||||||
enabled = isActionEnabled == true && !actionClicked
|
enabled = isActionEnabled == true && !actionClicked
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,8 @@ import ua.acclorite.book_story.R
|
||||||
* @param title The title of the dialog.
|
* @param title The title of the dialog.
|
||||||
* @param description The description of the dialog.
|
* @param description The description of the dialog.
|
||||||
* @param actionText The action text of the dialog.
|
* @param actionText The action text of the dialog.
|
||||||
* @param isActionEnabled Whether action is enabled or not,
|
* @param disableOnClick Whether should all actions be disabled after clicking it.
|
||||||
|
* @param isActionEnabled Whether action is enabled or not.
|
||||||
* @param onDismiss Code to execute when the dialog is dismissed.
|
* @param onDismiss Code to execute when the dialog is dismissed.
|
||||||
* @param onAction Code to execute when action is clicked.
|
* @param onAction Code to execute when action is clicked.
|
||||||
* @param withDivider Should divider be shown or not.
|
* @param withDivider Should divider be shown or not.
|
||||||
|
|
@ -65,6 +66,7 @@ fun CustomDialogWithLazyColumn(
|
||||||
title: String,
|
title: String,
|
||||||
description: String?,
|
description: String?,
|
||||||
actionText: String?,
|
actionText: String?,
|
||||||
|
disableOnClick: Boolean = true,
|
||||||
isActionEnabled: Boolean?,
|
isActionEnabled: Boolean?,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onAction: () -> Unit,
|
onAction: () -> Unit,
|
||||||
|
|
@ -162,7 +164,9 @@ fun CustomDialogWithLazyColumn(
|
||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
if (disableOnClick) {
|
||||||
actionClicked = true
|
actionClicked = true
|
||||||
|
}
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
},
|
||||||
enabled = !actionClicked
|
enabled = !actionClicked
|
||||||
|
|
@ -177,7 +181,9 @@ fun CustomDialogWithLazyColumn(
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
if (disableOnClick) {
|
||||||
actionClicked = true
|
actionClicked = true
|
||||||
|
}
|
||||||
onAction()
|
onAction()
|
||||||
},
|
},
|
||||||
enabled = isActionEnabled == true && !actionClicked
|
enabled = isActionEnabled == true && !actionClicked
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,24 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selectable dialog item. Perfectly combines with [CustomDialogWithLazyColumn].
|
* Selectable dialog item. Perfectly combines with [CustomDialogWithLazyColumn].
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectableDialogItem(selected: Boolean, title: String, onClick: () -> Unit) {
|
fun SelectableDialogItem(
|
||||||
|
selected: Boolean,
|
||||||
|
title: String,
|
||||||
|
horizontalPadding: Dp = 24.dp,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(enabled = !selected) { onClick() }
|
.clickable(enabled = !selected) { onClick() }
|
||||||
.padding(horizontal = 24.dp, vertical = 12.dp),
|
.padding(horizontal = horizontalPadding, vertical = 12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
|
|
|
||||||
|
|
@ -21,24 +21,23 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.NavigationItem
|
import ua.acclorite.book_story.domain.model.NavigationItem
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Navigation Rail. It is used to be shown on Tablets.
|
* Custom Navigation Rail. It is used to be shown on Tablets.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BoxScope.CustomNavigationRail(
|
fun BoxScope.CustomNavigationRail() {
|
||||||
navigator: Navigator,
|
|
||||||
layoutDirection: LayoutDirection
|
|
||||||
) {
|
|
||||||
var currentScreen: Screen? by remember { mutableStateOf(null) }
|
var currentScreen: Screen? by remember { mutableStateOf(null) }
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
navigator.getCurrentScreen().collect {
|
navigator.getCurrentScreen().collect {
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,5 @@ sealed class MainEvent {
|
||||||
data class OnChangeBackgroundColor(val color: Long) : MainEvent()
|
data class OnChangeBackgroundColor(val color: Long) : MainEvent()
|
||||||
data class OnChangeFontColor(val color: Long) : MainEvent()
|
data class OnChangeFontColor(val color: Long) : MainEvent()
|
||||||
data class OnChangeShowStartScreen(val bool: Boolean) : MainEvent()
|
data class OnChangeShowStartScreen(val bool: Boolean) : MainEvent()
|
||||||
|
data class OnChangeCheckForUpdates(val bool: Boolean) : MainEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ import java.util.Locale
|
||||||
* Main State. All app's settings are here. Wrapped in SavedStateHandle, so it won't reset.
|
* Main State. All app's settings are here. Wrapped in SavedStateHandle, so it won't reset.
|
||||||
*/
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
data class MainState(
|
data class MainSettingsState(
|
||||||
val language: String? = null,
|
val language: String? = null,
|
||||||
val theme: Theme? = null,
|
val theme: Theme? = null,
|
||||||
val darkTheme: DarkTheme? = null,
|
val darkTheme: DarkTheme? = null,
|
||||||
|
|
@ -36,6 +36,7 @@ data class MainState(
|
||||||
val backgroundColor: Long? = null,
|
val backgroundColor: Long? = null,
|
||||||
val fontColor: Long? = null,
|
val fontColor: Long? = null,
|
||||||
val showStartScreen: Boolean? = null,
|
val showStartScreen: Boolean? = null,
|
||||||
|
val checkForUpdates: Boolean? = null,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
constructor(parcel: Parcel) : this(
|
constructor(parcel: Parcel) : this(
|
||||||
// String
|
// String
|
||||||
|
|
@ -68,6 +69,9 @@ data class MainState(
|
||||||
// Boolean
|
// Boolean
|
||||||
showStartScreen = if (parcel.readByte().toInt() != 0) parcel.readByte()
|
showStartScreen = if (parcel.readByte().toInt() != 0) parcel.readByte()
|
||||||
.toInt() == 1 else null,
|
.toInt() == 1 else null,
|
||||||
|
// Boolean
|
||||||
|
checkForUpdates = if (parcel.readByte().toInt() != 0) parcel.readByte()
|
||||||
|
.toInt() == 1 else null,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
|
@ -107,25 +111,28 @@ data class MainState(
|
||||||
// Show Start Screen
|
// Show Start Screen
|
||||||
parcel.writeByte((if (showStartScreen != null) 1 else 0).toByte())
|
parcel.writeByte((if (showStartScreen != null) 1 else 0).toByte())
|
||||||
showStartScreen?.let { parcel.writeByte(if (it) 1 else 0) }
|
showStartScreen?.let { parcel.writeByte(if (it) 1 else 0) }
|
||||||
|
// Check For Updates
|
||||||
|
parcel.writeByte((if (checkForUpdates != null) 1 else 0).toByte())
|
||||||
|
checkForUpdates?.let { parcel.writeByte(if (it) 1 else 0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun describeContents(): Int {
|
override fun describeContents(): Int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object CREATOR : Parcelable.Creator<MainState> {
|
companion object CREATOR : Parcelable.Creator<MainSettingsState> {
|
||||||
override fun createFromParcel(parcel: Parcel): MainState {
|
override fun createFromParcel(parcel: Parcel): MainSettingsState {
|
||||||
return MainState(parcel)
|
return MainSettingsState(parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun newArray(size: Int): Array<MainState?> {
|
override fun newArray(size: Int): Array<MainSettingsState?> {
|
||||||
return arrayOfNulls(size)
|
return arrayOfNulls(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes [MainState] by given [Map].
|
* Initializes [MainSettingsState] by given [Map].
|
||||||
*/
|
*/
|
||||||
fun initialize(data: Map<String, Any>): MainState {
|
fun initialize(data: Map<String, Any>): MainSettingsState {
|
||||||
DataStoreConstants.apply {
|
DataStoreConstants.apply {
|
||||||
|
|
||||||
val language: String = data[LANGUAGE.name] as? String ?: if (
|
val language: String = data[LANGUAGE.name] as? String ?: if (
|
||||||
|
|
@ -176,8 +183,10 @@ data class MainState(
|
||||||
val paragraphIndentation: Boolean = data[PARAGRAPH_INDENTATION.name] as? Boolean
|
val paragraphIndentation: Boolean = data[PARAGRAPH_INDENTATION.name] as? Boolean
|
||||||
?: false
|
?: false
|
||||||
|
|
||||||
|
val checkForUpdates: Boolean = data[CHECK_FOR_UPDATES.name] as? Boolean
|
||||||
|
?: false
|
||||||
|
|
||||||
return MainState(
|
return MainSettingsState(
|
||||||
language = language,
|
language = language,
|
||||||
theme = theme.toTheme(),
|
theme = theme.toTheme(),
|
||||||
darkTheme = darkTheme.toDarkTheme(),
|
darkTheme = darkTheme.toDarkTheme(),
|
||||||
|
|
@ -191,7 +200,8 @@ data class MainState(
|
||||||
fontSize = fontSize,
|
fontSize = fontSize,
|
||||||
lineHeight = lineHeight,
|
lineHeight = lineHeight,
|
||||||
paragraphHeight = paragraphHeight,
|
paragraphHeight = paragraphHeight,
|
||||||
paragraphIndentation = paragraphIndentation
|
paragraphIndentation = paragraphIndentation,
|
||||||
|
checkForUpdates = checkForUpdates
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ua.acclorite.book_story.domain.use_case.ChangeLanguage
|
import ua.acclorite.book_story.domain.use_case.ChangeLanguage
|
||||||
|
import ua.acclorite.book_story.domain.use_case.CheckForUpdates
|
||||||
import ua.acclorite.book_story.domain.use_case.GetAllSettings
|
import ua.acclorite.book_story.domain.use_case.GetAllSettings
|
||||||
import ua.acclorite.book_story.domain.use_case.SetDatastore
|
import ua.acclorite.book_story.domain.use_case.SetDatastore
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
|
|
@ -33,6 +34,7 @@ class MainViewModel @Inject constructor(
|
||||||
|
|
||||||
private val setDatastore: SetDatastore,
|
private val setDatastore: SetDatastore,
|
||||||
private val changeLanguage: ChangeLanguage,
|
private val changeLanguage: ChangeLanguage,
|
||||||
|
private val checkForUpdates: CheckForUpdates,
|
||||||
private val getAllSettings: GetAllSettings
|
private val getAllSettings: GetAllSettings
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
|
@ -42,8 +44,8 @@ class MainViewModel @Inject constructor(
|
||||||
private val isSettingsReady = MutableStateFlow(false)
|
private val isSettingsReady = MutableStateFlow(false)
|
||||||
private val isViewModelReady = MutableStateFlow(false)
|
private val isViewModelReady = MutableStateFlow(false)
|
||||||
|
|
||||||
private val _state: MutableStateFlow<MainState> = MutableStateFlow(
|
private val _state: MutableStateFlow<MainSettingsState> = MutableStateFlow(
|
||||||
stateHandle[Constants.MAIN_STATE] ?: MainState()
|
stateHandle[Constants.MAIN_STATE] ?: MainSettingsState()
|
||||||
)
|
)
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
|
@ -203,11 +205,22 @@ class MainViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeCheckForUpdates -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.CHECK_FOR_UPDATES, event.bool)
|
||||||
|
updateStateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
checkForUpdates = event.bool
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(
|
fun init(
|
||||||
libraryViewModel: LibraryViewModel,
|
libraryViewModel: LibraryViewModel
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
val settings = getAllSettings.execute(viewModelScope)
|
val settings = getAllSettings.execute(viewModelScope)
|
||||||
|
|
@ -215,6 +228,14 @@ class MainViewModel @Inject constructor(
|
||||||
// All additional execution
|
// All additional execution
|
||||||
changeLanguage.execute(settings.language!!)
|
changeLanguage.execute(settings.language!!)
|
||||||
|
|
||||||
|
if (settings.checkForUpdates == true) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
checkForUpdates.execute(
|
||||||
|
postNotification = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateStateWithSavedHandle {
|
updateStateWithSavedHandle {
|
||||||
settings
|
settings
|
||||||
}
|
}
|
||||||
|
|
@ -249,10 +270,10 @@ class MainViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates [MainState] along with [SavedStateHandle].
|
* Updates [MainSettingsState] along with [SavedStateHandle].
|
||||||
*/
|
*/
|
||||||
private fun updateStateWithSavedHandle(
|
private fun updateStateWithSavedHandle(
|
||||||
function: (MainState) -> MainState
|
function: (MainSettingsState) -> MainSettingsState
|
||||||
) {
|
) {
|
||||||
_state.update {
|
_state.update {
|
||||||
stateHandle[Constants.MAIN_STATE] = function(it)
|
stateHandle[Constants.MAIN_STATE] = function(it)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
package ua.acclorite.book_story.presentation.data
|
package ua.acclorite.book_story.presentation.data
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
|
@ -5,19 +7,33 @@ import android.os.Parcelable
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.EnterTransition
|
import androidx.compose.animation.EnterTransition
|
||||||
import androidx.compose.animation.ExitTransition
|
import androidx.compose.animation.ExitTransition
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||||
|
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||||
|
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
|
@ -30,6 +46,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
|
|
@ -39,6 +56,10 @@ private const val BACKSTACK = "back_stack"
|
||||||
private const val USE_BACK_ANIM = "use_back_animation"
|
private const val USE_BACK_ANIM = "use_back_animation"
|
||||||
private const val ARGUMENTS = "arguments"
|
private const val ARGUMENTS = "arguments"
|
||||||
|
|
||||||
|
val LocalNavigator = compositionLocalOf<Navigator> {
|
||||||
|
error("Cannot initialize Navigator.")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All screens are listed here, later each screen will be passed as a param for [Navigator.composable] function.
|
* All screens are listed here, later each screen will be passed as a param for [Navigator.composable] function.
|
||||||
*/
|
*/
|
||||||
|
|
@ -71,7 +92,7 @@ data class Argument<out T : Serializable>(
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigator. Using for navigation between screens.
|
* Navigator. Using to navigate between screens.
|
||||||
*
|
*
|
||||||
* [Navigator.currentScreen] param represents current [Screen].
|
* [Navigator.currentScreen] param represents current [Screen].
|
||||||
* [Navigator.navigate] navigates to [Screen] passed as param.
|
* [Navigator.navigate] navigates to [Screen] passed as param.
|
||||||
|
|
@ -134,7 +155,7 @@ class Navigator @AssistedInject constructor(
|
||||||
useBackAnimation: Boolean,
|
useBackAnimation: Boolean,
|
||||||
vararg args: Argument<Serializable>
|
vararg args: Argument<Serializable>
|
||||||
) = viewModelScope.launch(Dispatchers.Default) {
|
) = viewModelScope.launch(Dispatchers.Default) {
|
||||||
if (backStack.value.last() == screen) {
|
if (backStack.value.lastOrNull() == screen) {
|
||||||
backStack.value.removeLast()
|
backStack.value.removeLast()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,6 +176,10 @@ class Navigator @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearBackStack() {
|
||||||
|
backStack.value.clear()
|
||||||
|
}
|
||||||
|
|
||||||
fun canGoBack(): Boolean {
|
fun canGoBack(): Boolean {
|
||||||
return backStack.value.isNotEmpty()
|
return backStack.value.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +188,10 @@ class Navigator @AssistedInject constructor(
|
||||||
return currentScreen
|
return currentScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUseBackAnim(): StateFlow<Boolean> {
|
||||||
|
return useBackAnimation
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Animated Screen. Used in [NavigationHost]. Be sure to not use the same [screen] parameter twice, it'll override the highest one in your code.
|
* Animated Screen. Used in [NavigationHost]. Be sure to not use the same [screen] parameter twice, it'll override the highest one in your code.
|
||||||
*
|
*
|
||||||
|
|
@ -177,22 +206,94 @@ class Navigator @AssistedInject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
fun composable(
|
fun composable(
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
enterAnim: EnterTransition = Transitions.FadeTransitionIn,
|
enterAnim: EnterTransition = Transitions.SlidingTransitionIn,
|
||||||
backEnterAnim: EnterTransition = Transitions.BackSlidingTransitionIn,
|
backEnterAnim: EnterTransition = Transitions.BackSlidingTransitionIn,
|
||||||
exitAnim: ExitTransition = Transitions.FadeTransitionOut,
|
exitAnim: ExitTransition = Transitions.SlidingTransitionOut,
|
||||||
backExitAnim: ExitTransition = Transitions.BackSlidingTransitionOut,
|
backExitAnim: ExitTransition = Transitions.BackSlidingTransitionOut,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = getCurrentScreen().collectAsState().value == screen,
|
visible = getCurrentScreen().collectAsState().value == screen,
|
||||||
enter = if (!useBackAnimation.collectAsState().value) enterAnim else backEnterAnim,
|
enter = if (!useBackAnimation.collectAsState().value) enterAnim else backEnterAnim,
|
||||||
exit = if (!useBackAnimation.collectAsState().value) exitAnim else backExitAnim,
|
exit = if (!useBackAnimation.collectAsState().value) exitAnim else backExitAnim
|
||||||
label = ""
|
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
||||||
|
@SuppressLint("ComposableNaming")
|
||||||
|
@Composable
|
||||||
|
fun navigation(
|
||||||
|
vararg screens: Screen,
|
||||||
|
bottomBar: @Composable () -> Unit,
|
||||||
|
navigationRail: @Composable BoxScope.() -> Unit,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val activity = LocalContext.current as ComponentActivity
|
||||||
|
val currentScreen by getCurrentScreen().collectAsState()
|
||||||
|
val useBackAnimation by getUseBackAnim().collectAsState()
|
||||||
|
val shouldShow by remember(currentScreen) {
|
||||||
|
derivedStateOf {
|
||||||
|
screens.any { it == currentScreen }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val windowClass = calculateWindowSizeClass(activity = activity)
|
||||||
|
val tabletUI = remember(windowClass) {
|
||||||
|
windowClass.widthSizeClass != WindowWidthSizeClass.Compact
|
||||||
|
}
|
||||||
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
|
|
||||||
|
CustomAnimatedVisibility(
|
||||||
|
visible = shouldShow,
|
||||||
|
enter = if (useBackAnimation) Transitions.BackSlidingTransitionIn
|
||||||
|
else Transitions.SlidingTransitionIn,
|
||||||
|
exit = if (useBackAnimation) Transitions.BackSlidingTransitionOut
|
||||||
|
else Transitions.SlidingTransitionOut
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
if (!tabletUI) {
|
||||||
|
bottomBar()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(
|
||||||
|
end = it.calculateEndPadding(
|
||||||
|
layoutDirection
|
||||||
|
),
|
||||||
|
bottom = it.calculateBottomPadding(),
|
||||||
|
start = if (tabletUI) {
|
||||||
|
80.dp + it.calculateStartPadding(
|
||||||
|
layoutDirection
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it.calculateStartPadding(
|
||||||
|
layoutDirection
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabletUI) {
|
||||||
|
navigationRail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(startScreen: Screen): Navigator
|
fun create(startScreen: Screen): Navigator
|
||||||
|
|
@ -214,7 +315,6 @@ fun NavigationHost(
|
||||||
content: @Composable Navigator.() -> Unit
|
content: @Composable Navigator.() -> Unit
|
||||||
) {
|
) {
|
||||||
val activity = LocalContext.current as ComponentActivity
|
val activity = LocalContext.current as ComponentActivity
|
||||||
|
|
||||||
val navigator by activity.viewModels<Navigator>(
|
val navigator by activity.viewModels<Navigator>(
|
||||||
extrasProducer = {
|
extrasProducer = {
|
||||||
activity
|
activity
|
||||||
|
|
@ -234,10 +334,12 @@ fun NavigationHost(
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(colorBetweenAnimations)
|
.background(colorBetweenAnimations)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalNavigator provides navigator) {
|
||||||
content(navigator)
|
content(navigator)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,30 +21,58 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.screens.about.components.AboutItem
|
import ua.acclorite.book_story.presentation.screens.about.components.AboutItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.about.components.AboutUpdateDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
|
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.about.data.AboutState
|
||||||
import ua.acclorite.book_story.presentation.screens.about.data.AboutViewModel
|
import ua.acclorite.book_story.presentation.screens.about.data.AboutViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AboutScreenRoot() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val aboutViewModel: AboutViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
val state = aboutViewModel.state.collectAsState()
|
||||||
|
|
||||||
|
AboutScreen(
|
||||||
|
state = state,
|
||||||
|
navigator = navigator,
|
||||||
|
onEvent = aboutViewModel::onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AboutScreen(
|
private fun AboutScreen(
|
||||||
viewModel: AboutViewModel = hiltViewModel(),
|
state: State<AboutState>,
|
||||||
navigator: Navigator
|
navigator: Navigator,
|
||||||
|
onEvent: (AboutEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
|
||||||
|
|
||||||
|
if (state.value.showUpdateDialog) {
|
||||||
|
AboutUpdateDialog(
|
||||||
|
state = state,
|
||||||
|
onEvent = onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -104,36 +132,32 @@ fun AboutScreen(
|
||||||
item {
|
item {
|
||||||
AboutItem(
|
AboutItem(
|
||||||
title = stringResource(id = R.string.app_version_option),
|
title = stringResource(id = R.string.app_version_option),
|
||||||
description = stringResource(
|
description = buildAnnotatedString {
|
||||||
id = R.string.app_version_option_desc,
|
append(
|
||||||
stringResource(id = R.string.app_version)
|
stringResource(
|
||||||
),
|
id = R.string.app_version_option_desc_1,
|
||||||
isOnClickEnabled = false
|
context.getString(R.string.app_version)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
append("\n")
|
||||||
|
append(stringResource(id = R.string.app_version_option_desc_2))
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
|
||||||
AboutItem(
|
|
||||||
title = stringResource(id = R.string.all_releases_option),
|
|
||||||
description = stringResource(
|
|
||||||
id = R.string.all_releases_option_desc
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
|
onEvent(
|
||||||
|
AboutEvent.OnCheckForUpdates(
|
||||||
|
context = context,
|
||||||
|
noUpdatesFound = {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
"${context.getString(R.string.app_version_option)}:" +
|
context.getString(R.string.no_updates),
|
||||||
" ${context.getString(R.string.app_version)}",
|
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
viewModel.onEvent(
|
},
|
||||||
AboutEvent.OnNavigateToBrowserPage(
|
error = {
|
||||||
context.getString(R.string.releases_page),
|
|
||||||
context,
|
|
||||||
noAppsFound = {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.error_no_browser),
|
context.getString(R.string.error_check_internet),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -143,12 +167,10 @@ fun AboutScreen(
|
||||||
|
|
||||||
item {
|
item {
|
||||||
AboutItem(
|
AboutItem(
|
||||||
title = stringResource(id = R.string.issues_option),
|
title = stringResource(id = R.string.report_bug_option),
|
||||||
description = stringResource(
|
description = null
|
||||||
id = R.string.issues_option_desc
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
AboutEvent.OnNavigateToBrowserPage(
|
AboutEvent.OnNavigateToBrowserPage(
|
||||||
context.getString(R.string.issues_page),
|
context.getString(R.string.issues_page),
|
||||||
context,
|
context,
|
||||||
|
|
@ -167,11 +189,9 @@ fun AboutScreen(
|
||||||
item {
|
item {
|
||||||
AboutItem(
|
AboutItem(
|
||||||
title = stringResource(id = R.string.project_page_option),
|
title = stringResource(id = R.string.project_page_option),
|
||||||
description = stringResource(
|
description = null
|
||||||
id = R.string.project_page_option_desc
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
AboutEvent.OnNavigateToBrowserPage(
|
AboutEvent.OnNavigateToBrowserPage(
|
||||||
context.getString(R.string.project_page),
|
context.getString(R.string.project_page),
|
||||||
context,
|
context,
|
||||||
|
|
@ -187,37 +207,12 @@ fun AboutScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
|
||||||
AboutItem(
|
|
||||||
title = stringResource(id = R.string.my_page_option),
|
|
||||||
description = stringResource(
|
|
||||||
id = R.string.my_page_option_desc
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
viewModel.onEvent(
|
|
||||||
AboutEvent.OnNavigateToBrowserPage(
|
|
||||||
context.getString(R.string.my_page),
|
|
||||||
context,
|
|
||||||
noAppsFound = {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.error_no_browser),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
item {
|
||||||
AboutItem(
|
AboutItem(
|
||||||
title = stringResource(id = R.string.support_option),
|
title = stringResource(id = R.string.support_option),
|
||||||
description = stringResource(
|
description = null
|
||||||
id = R.string.support_option_desc
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
AboutEvent.OnNavigateToBrowserPage(
|
AboutEvent.OnNavigateToBrowserPage(
|
||||||
context.getString(R.string.support_page),
|
context.getString(R.string.support_page),
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,13 @@ package ua.acclorite.book_story.presentation.screens.about.components
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -24,7 +22,7 @@ import androidx.compose.ui.unit.sp
|
||||||
fun AboutItem(
|
fun AboutItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
description: String?,
|
description: AnnotatedString?,
|
||||||
verticalPadding: Dp = 12.dp,
|
verticalPadding: Dp = 12.dp,
|
||||||
isOnClickEnabled: Boolean = true,
|
isOnClickEnabled: Boolean = true,
|
||||||
onClick: () -> Unit = {}
|
onClick: () -> Unit = {}
|
||||||
|
|
@ -36,21 +34,19 @@ fun AboutItem(
|
||||||
onClick()
|
onClick()
|
||||||
}
|
}
|
||||||
.padding(horizontal = 18.dp, vertical = verticalPadding),
|
.padding(horizontal = 18.dp, vertical = verticalPadding),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center
|
||||||
horizontalAlignment = Alignment.Start
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
|
||||||
description?.let {
|
description?.let {
|
||||||
Text(
|
Text(
|
||||||
it,
|
it,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.about.components
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Update
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.about.data.AboutState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update dialog. If there is app update transfers to the download page.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun AboutUpdateDialog(
|
||||||
|
state: State<AboutState>,
|
||||||
|
onEvent: (AboutEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val update = remember(state.value.showUpdateDialog) {
|
||||||
|
state.value.updateInfo!!
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomDialogWithContent(
|
||||||
|
title = stringResource(id = R.string.update_query, update.tag_name),
|
||||||
|
imageVectorIcon = Icons.Default.Update,
|
||||||
|
description = stringResource(
|
||||||
|
id = R.string.update_app_description
|
||||||
|
),
|
||||||
|
actionText = stringResource(id = R.string.download),
|
||||||
|
isActionEnabled = true,
|
||||||
|
disableOnClick = false,
|
||||||
|
onDismiss = { onEvent(AboutEvent.OnDismissUpdateDialog) },
|
||||||
|
onAction = {
|
||||||
|
onEvent(
|
||||||
|
AboutEvent.OnNavigateToBrowserPage(
|
||||||
|
page = context.getString(R.string.download_latest_release_page),
|
||||||
|
context = context,
|
||||||
|
noAppsFound = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_no_browser),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
onEvent(AboutEvent.OnDismissUpdateDialog)
|
||||||
|
},
|
||||||
|
withDivider = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,21 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.about.data
|
package ua.acclorite.book_story.presentation.screens.about.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed class AboutEvent {
|
sealed class AboutEvent {
|
||||||
data class OnNavigateToBrowserPage(
|
data class OnNavigateToBrowserPage(
|
||||||
val page: String,
|
val page: String,
|
||||||
val context: Context,
|
val context: Context,
|
||||||
val noAppsFound: () -> Unit
|
val noAppsFound: () -> Unit
|
||||||
) : AboutEvent()
|
) : AboutEvent()
|
||||||
|
|
||||||
|
data class OnCheckForUpdates(
|
||||||
|
val context: Context,
|
||||||
|
val noUpdatesFound: () -> Unit,
|
||||||
|
val error: () -> Unit
|
||||||
|
) : AboutEvent()
|
||||||
|
|
||||||
|
data object OnDismissUpdateDialog : AboutEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.about.data
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import ua.acclorite.book_story.data.remote.dto.ReleaseResponse
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class AboutState(
|
||||||
|
val showUpdateDialog: Boolean = false,
|
||||||
|
val updateInfo: ReleaseResponse? = null
|
||||||
|
)
|
||||||
|
|
@ -5,14 +5,22 @@ import android.net.Uri
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.domain.use_case.CheckForUpdates
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AboutViewModel @Inject constructor(
|
class AboutViewModel @Inject constructor(
|
||||||
|
private val checkForUpdates: CheckForUpdates,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _state = MutableStateFlow(AboutState())
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
fun onEvent(event: AboutEvent) {
|
fun onEvent(event: AboutEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is AboutEvent.OnNavigateToBrowserPage -> {
|
is AboutEvent.OnNavigateToBrowserPage -> {
|
||||||
|
|
@ -30,6 +38,40 @@ class AboutViewModel @Inject constructor(
|
||||||
event.noAppsFound()
|
event.noAppsFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is AboutEvent.OnCheckForUpdates -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val result = checkForUpdates.execute(false)
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
event.error()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val version = result.tag_name.substringAfterLast("v")
|
||||||
|
val currentVersion = event.context.getString(R.string.app_version)
|
||||||
|
|
||||||
|
if (version == currentVersion) {
|
||||||
|
event.noUpdatesFound()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
showUpdateDialog = true,
|
||||||
|
updateInfo = result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is AboutEvent.OnDismissUpdateDialog -> {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
showUpdateDialog = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ package ua.acclorite.book_story.presentation.screens.book_info
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.expandHorizontally
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkHorizontally
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|
@ -22,6 +24,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Done
|
import androidx.compose.material.icons.filled.Done
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
|
|
@ -39,6 +42,7 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -51,11 +55,14 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
import ua.acclorite.book_story.presentation.components.CustomSnackbar
|
import ua.acclorite.book_story.presentation.components.CustomSnackbar
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoBackground
|
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoBackground
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDescriptionSection
|
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDescriptionSection
|
||||||
|
|
@ -68,7 +75,9 @@ import ua.acclorite.book_story.presentation.screens.book_info.components.details
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoDeleteDialog
|
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoDeleteDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoMoveDialog
|
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoMoveDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
||||||
|
|
@ -77,25 +86,51 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode
|
||||||
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.presentation.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BookInfoScreenRoot() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
|
val viewModel: BookInfoViewModel = hiltViewModel()
|
||||||
|
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
||||||
|
val historyViewModel: HistoryViewModel = hiltViewModel()
|
||||||
|
val browseViewModel: BrowseViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
val state = viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.init(
|
||||||
|
navigator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BookInfoScreen(
|
||||||
|
state = state,
|
||||||
|
navigator = navigator,
|
||||||
|
onEvent = viewModel::onEvent,
|
||||||
|
onLibraryEvent = libraryViewModel::onEvent,
|
||||||
|
onBrowseEvent = browseViewModel::onEvent,
|
||||||
|
onHistoryEvent = historyViewModel::onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoScreen(
|
private fun BookInfoScreen(
|
||||||
viewModel: BookInfoViewModel,
|
state: State<BookInfoState>,
|
||||||
libraryViewModel: LibraryViewModel,
|
navigator: Navigator,
|
||||||
browseViewModel: BrowseViewModel,
|
onEvent: (BookInfoEvent) -> Unit,
|
||||||
historyViewModel: HistoryViewModel,
|
onLibraryEvent: (LibraryEvent) -> Unit,
|
||||||
navigator: Navigator
|
onBrowseEvent: (BrowseEvent) -> Unit,
|
||||||
|
onHistoryEvent: (HistoryEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val snackbarState = remember { SnackbarHostState() }
|
val snackbarState = remember { SnackbarHostState() }
|
||||||
val refreshState = rememberPullRefreshState(
|
val refreshState = rememberPullRefreshState(
|
||||||
refreshing = state.isRefreshing || state.isLoadingUpdate,
|
refreshing = state.value.isRefreshing || state.value.isLoadingUpdate,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnLoadUpdate(
|
||||||
snackbarState,
|
snackbarState,
|
||||||
context
|
context
|
||||||
|
|
@ -105,45 +140,43 @@ fun BookInfoScreen(
|
||||||
)
|
)
|
||||||
val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } }
|
val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
if (state.value.showChangeCoverBottomSheet) {
|
||||||
viewModel.init(
|
|
||||||
navigator
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.showChangeCoverBottomSheet) {
|
|
||||||
BookInfoChangeCoverBottomSheet(
|
BookInfoChangeCoverBottomSheet(
|
||||||
libraryViewModel = libraryViewModel,
|
state = state,
|
||||||
historyViewModel = historyViewModel,
|
onEvent = onEvent,
|
||||||
viewModel = viewModel
|
onLibraryUpdateEvent = onLibraryEvent,
|
||||||
|
onHistoryUpdateEvent = onHistoryEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.showDetailsBottomSheet) {
|
if (state.value.showDetailsBottomSheet) {
|
||||||
BookInfoDetailsBottomSheet(viewModel = viewModel)
|
BookInfoDetailsBottomSheet(
|
||||||
|
state = state,
|
||||||
|
onEvent = onEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (state.showDeleteDialog) {
|
if (state.value.showDeleteDialog) {
|
||||||
BookInfoDeleteDialog(
|
BookInfoDeleteDialog(
|
||||||
libraryViewModel = libraryViewModel,
|
onEvent = onEvent,
|
||||||
browseViewModel = browseViewModel,
|
onLibraryLoadEvent = onLibraryEvent,
|
||||||
historyViewModel = historyViewModel,
|
onHistoryLoadEvent = onHistoryEvent,
|
||||||
viewModel = viewModel,
|
onBrowseLoadEvent = onBrowseEvent
|
||||||
navigator = navigator
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.showMoveDialog) {
|
if (state.value.showMoveDialog) {
|
||||||
BookInfoMoveDialog(
|
BookInfoMoveDialog(
|
||||||
libraryViewModel = libraryViewModel,
|
state = state,
|
||||||
historyViewModel = historyViewModel,
|
onEvent = onEvent,
|
||||||
viewModel = viewModel,
|
onLibraryEvent = onLibraryEvent,
|
||||||
navigator = navigator
|
onHistoryUpdateEvent = onHistoryEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.showConfirmUpdateDialog) {
|
if (state.value.showConfirmUpdateDialog) {
|
||||||
BookInfoConfirmUpdateDialog(
|
BookInfoConfirmUpdateDialog(
|
||||||
libraryViewModel = libraryViewModel,
|
state = state,
|
||||||
historyViewModel = historyViewModel,
|
snackbarHostState = snackbarState,
|
||||||
viewModel = viewModel,
|
onEvent = onEvent,
|
||||||
snackbarHostState = snackbarState
|
onLibraryUpdateEvent = onLibraryEvent,
|
||||||
|
onHistoryUpdateEvent = onHistoryEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,14 +194,24 @@ fun BookInfoScreen(
|
||||||
isTopBarScrolled = null,
|
isTopBarScrolled = null,
|
||||||
|
|
||||||
content1NavigationIcon = {
|
content1NavigationIcon = {
|
||||||
GoBackButton(navigator = navigator, enabled = !state.isRefreshing) {
|
if (state.value.editTitle) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnCancelUpdate)
|
CustomIconButton(
|
||||||
|
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = R.string.hide_edit_title_content_desc,
|
||||||
|
disableOnClick = true
|
||||||
|
) {
|
||||||
|
onEvent(BookInfoEvent.OnShowHideEditTitle)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GoBackButton(navigator = navigator, enabled = !state.value.isRefreshing) {
|
||||||
|
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
DefaultTransition(visible = firstVisibleItemIndex > 0) {
|
DefaultTransition(visible = firstVisibleItemIndex > 0) {
|
||||||
Text(
|
Text(
|
||||||
state.book.title,
|
state.value.book.title,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
|
@ -181,9 +224,9 @@ fun BookInfoScreen(
|
||||||
icon = Icons.Default.Refresh,
|
icon = Icons.Default.Refresh,
|
||||||
contentDescription = R.string.refresh_book_content_desc,
|
contentDescription = R.string.refresh_book_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !state.isRefreshing && !state.isLoadingUpdate
|
enabled = !state.value.isRefreshing && !state.value.isLoadingUpdate
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnLoadUpdate(
|
||||||
snackbarState,
|
snackbarState,
|
||||||
context
|
context
|
||||||
|
|
@ -193,15 +236,16 @@ fun BookInfoScreen(
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
DefaultTransition(
|
DefaultTransition(
|
||||||
visible = !state.editTitle
|
visible = !state.value.editTitle
|
||||||
) {
|
) {
|
||||||
BookInfoMoreDropDown(
|
BookInfoMoreDropDown(
|
||||||
viewModel = viewModel,
|
state = state,
|
||||||
|
onEvent = onEvent,
|
||||||
snackbarState = snackbarState
|
snackbarState = snackbarState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = state.editTitle,
|
visible = state.value.editTitle,
|
||||||
enter = Transitions.DefaultTransitionIn,
|
enter = Transitions.DefaultTransitionIn,
|
||||||
exit = fadeOut(tween(200))
|
exit = fadeOut(tween(200))
|
||||||
) {
|
) {
|
||||||
|
|
@ -209,19 +253,20 @@ fun BookInfoScreen(
|
||||||
icon = Icons.Default.Done,
|
icon = Icons.Default.Done,
|
||||||
contentDescription = R.string.apply_changes_content_desc,
|
contentDescription = R.string.apply_changes_content_desc,
|
||||||
disableOnClick = true,
|
disableOnClick = true,
|
||||||
enabled = !state.isRefreshing &&
|
enabled = !state.value.isRefreshing &&
|
||||||
state.titleValue.isNotBlank() &&
|
state.value.titleValue.isNotBlank() &&
|
||||||
state.titleValue.trim() !=
|
state.value.titleValue.trim() !=
|
||||||
state.book.title.trim(),
|
state.value.book.title.trim(),
|
||||||
color = if (state.titleValue.isNotBlank()
|
color = if (state.value.titleValue.isNotBlank()
|
||||||
&& state.titleValue != state.book.title
|
&& state.value.titleValue != state.value.book.title
|
||||||
) MaterialTheme.colorScheme.onSurface
|
) MaterialTheme.colorScheme.onSurface
|
||||||
else MaterialTheme.colorScheme.onSurfaceVariant
|
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnUpdateTitle(
|
onEvent(
|
||||||
|
BookInfoEvent.OnUpdateTitle(
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
@ -253,17 +298,17 @@ fun BookInfoScreen(
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Box(modifier = Modifier.fillMaxWidth()) {
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
if (state.book.coverImage != null) {
|
if (state.value.book.coverImage != null) {
|
||||||
BookInfoBackground(
|
BookInfoBackground(
|
||||||
height = paddingValues.calculateTopPadding() + 12.dp + 195.dp,
|
height = paddingValues.calculateTopPadding() + 12.dp + 195.dp,
|
||||||
image = state.book.coverImage!!
|
image = state.value.book.coverImage!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding() + 12.dp))
|
Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding() + 12.dp))
|
||||||
// Info
|
// Info
|
||||||
BookInfoInfoSection(viewModel = viewModel, book = state.book)
|
BookInfoInfoSection(state = state, onEvent = onEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -271,20 +316,20 @@ fun BookInfoScreen(
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
// Statistic
|
// Statistic
|
||||||
BookInfoStatisticSection(book = state.book)
|
BookInfoStatisticSection(state = state)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
// Description
|
// Description
|
||||||
BookInfoDescriptionSection(book = state.book)
|
BookInfoDescriptionSection(state = state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!state.isRefreshing) {
|
if (!state.value.isRefreshing) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnNavigateToReaderScreen(navigator))
|
onEvent(BookInfoEvent.OnNavigateToReaderScreen(navigator))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shape = MaterialTheme.shapes.large,
|
shape = MaterialTheme.shapes.large,
|
||||||
|
|
@ -302,12 +347,14 @@ fun BookInfoScreen(
|
||||||
contentDescription = stringResource(id = R.string.continue_reading_content_desc),
|
contentDescription = stringResource(id = R.string.continue_reading_content_desc),
|
||||||
Modifier.size(24.dp)
|
Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = !listState.canScrollBackward
|
visible = !listState.canScrollBackward,
|
||||||
|
enter = fadeIn() + expandHorizontally(),
|
||||||
|
exit = fadeOut() + shrinkHorizontally()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
if (state.book.progress == 0f) R.string.start
|
if (state.value.book.progress == 0f) R.string.start
|
||||||
else R.string.continue_read
|
else R.string.continue_read
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
|
@ -321,7 +368,7 @@ fun BookInfoScreen(
|
||||||
)
|
)
|
||||||
|
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
state.isRefreshing || state.isLoadingUpdate,
|
state.value.isRefreshing || state.value.isLoadingUpdate,
|
||||||
refreshState,
|
refreshState,
|
||||||
Modifier
|
Modifier
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
|
|
@ -333,8 +380,13 @@ fun BookInfoScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
if (!state.isRefreshing) {
|
if (state.value.editTitle) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnCancelUpdate)
|
onEvent(BookInfoEvent.OnShowHideEditTitle)
|
||||||
|
return@BackHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.value.isRefreshing) {
|
||||||
|
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
navigator.navigateBack()
|
navigator.navigateBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,22 @@ import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description section.
|
* Description section.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoDescriptionSection(book: Book) {
|
fun BookInfoDescriptionSection(state: State<BookInfoState>) {
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
Text(
|
Text(
|
||||||
if (book.description?.isNotBlank() == true) book.description
|
if (state.value.book.description?.isNotBlank() == true) state.value.book.description!!
|
||||||
else stringResource(id = R.string.error_no_description),
|
else stringResource(id = R.string.error_no_description),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,7 @@ import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -35,23 +34,23 @@ import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
|
||||||
import ua.acclorite.book_story.presentation.components.CustomCoverImage
|
import ua.acclorite.book_story.presentation.components.CustomCoverImage
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Info section.
|
* Info section.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
fun BookInfoInfoSection(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<BookInfoState>,
|
||||||
|
onEvent: (BookInfoEvent) -> Unit
|
||||||
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -70,13 +69,13 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
indication = null,
|
indication = null,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideChangeCoverBottomSheet)
|
onEvent(BookInfoEvent.OnShowHideChangeCoverBottomSheet)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (book.coverImage != null) {
|
if (state.value.book.coverImage != null) {
|
||||||
CustomCoverImage(
|
CustomCoverImage(
|
||||||
uri = book.coverImage,
|
uri = state.value.book.coverImage!!,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
|
@ -97,9 +96,9 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
Column(verticalArrangement = Arrangement.Center) {
|
Column(verticalArrangement = Arrangement.Center) {
|
||||||
if (!state.editTitle) {
|
if (!state.value.editTitle) {
|
||||||
Text(
|
Text(
|
||||||
book.title,
|
state.value.book.title,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -109,7 +108,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
indication = null,
|
indication = null,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideEditTitle)
|
onEvent(BookInfoEvent.OnShowHideEditTitle)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
maxLines = 4,
|
maxLines = 4,
|
||||||
|
|
@ -118,22 +117,19 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
} else {
|
} else {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
maxLines = 4,
|
maxLines = 4,
|
||||||
value = state.titleValue,
|
value = state.value.titleValue,
|
||||||
textStyle = TextStyle(
|
textStyle = MaterialTheme.typography.headlineSmall.copy(
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
fontSize = MaterialTheme.typography.headlineSmall.fontSize,
|
|
||||||
lineHeight = MaterialTheme.typography.headlineSmall.lineHeight,
|
|
||||||
fontFamily = MaterialTheme.typography.headlineSmall.fontFamily
|
|
||||||
),
|
),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (it.length < 100 || it.length < state.titleValue.length) {
|
if (it.length < 100 || it.length < state.value.titleValue.length) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnTitleValueChange(it))
|
onEvent(BookInfoEvent.OnTitleValueChange(it))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
viewModel.onEvent(BookInfoEvent.OnRequestFocus(focusRequester))
|
onEvent(BookInfoEvent.OnRequestFocus(focusRequester))
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words),
|
keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words),
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
|
@ -149,7 +145,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
Text(
|
Text(
|
||||||
book.author.asString(),
|
state.value.book.author.asString(),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -21,14 +21,17 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.CustomDropDownMenuItem
|
import ua.acclorite.book_story.presentation.components.CustomDropDownMenuItem
|
||||||
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Book info more dropdown. Has three options: Delete, Move and Details.
|
* Book info more dropdown. Has three options: Delete, Move and Details.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoMoreDropDown(viewModel: BookInfoViewModel, snackbarState: SnackbarHostState) {
|
fun BookInfoMoreDropDown(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<BookInfoState>,
|
||||||
|
onEvent: (BookInfoEvent) -> Unit,
|
||||||
|
snackbarState: SnackbarHostState
|
||||||
|
) {
|
||||||
var showDropDown by remember { mutableStateOf(false) }
|
var showDropDown by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
|
|
@ -37,7 +40,7 @@ fun BookInfoMoreDropDown(viewModel: BookInfoViewModel, snackbarState: SnackbarHo
|
||||||
contentDescription = R.string.show_dropdown_content_desc,
|
contentDescription = R.string.show_dropdown_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !showDropDown &&
|
enabled = !showDropDown &&
|
||||||
!state.isRefreshing
|
!state.value.isRefreshing
|
||||||
) {
|
) {
|
||||||
showDropDown = true
|
showDropDown = true
|
||||||
}
|
}
|
||||||
|
|
@ -53,14 +56,14 @@ fun BookInfoMoreDropDown(viewModel: BookInfoViewModel, snackbarState: SnackbarHo
|
||||||
leadingIcon = Icons.AutoMirrored.Outlined.DriveFileMove,
|
leadingIcon = Icons.AutoMirrored.Outlined.DriveFileMove,
|
||||||
text = stringResource(id = R.string.move_this_book)
|
text = stringResource(id = R.string.move_this_book)
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideMoveDialog)
|
onEvent(BookInfoEvent.OnShowHideMoveDialog)
|
||||||
showDropDown = false
|
showDropDown = false
|
||||||
}
|
}
|
||||||
CustomDropDownMenuItem(
|
CustomDropDownMenuItem(
|
||||||
leadingIcon = Icons.Outlined.Delete,
|
leadingIcon = Icons.Outlined.Delete,
|
||||||
text = stringResource(id = R.string.delete_this_book)
|
text = stringResource(id = R.string.delete_this_book)
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideDeleteDialog)
|
onEvent(BookInfoEvent.OnShowHideDeleteDialog)
|
||||||
showDropDown = false
|
showDropDown = false
|
||||||
}
|
}
|
||||||
CustomDropDownMenuItem(
|
CustomDropDownMenuItem(
|
||||||
|
|
@ -68,7 +71,7 @@ fun BookInfoMoreDropDown(viewModel: BookInfoViewModel, snackbarState: SnackbarHo
|
||||||
text = stringResource(id = R.string.details_book)
|
text = stringResource(id = R.string.details_book)
|
||||||
) {
|
) {
|
||||||
snackbarState.currentSnackbarData?.dismiss()
|
snackbarState.currentSnackbarData?.dismiss()
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideDetailsBottomSheet)
|
onEvent(BookInfoEvent.OnShowHideDetailsBottomSheet)
|
||||||
showDropDown = false
|
showDropDown = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
|
@ -16,26 +17,26 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
|
||||||
import ua.acclorite.book_story.presentation.data.removeDigits
|
import ua.acclorite.book_story.presentation.data.removeDigits
|
||||||
import ua.acclorite.book_story.presentation.data.removeTrailingZero
|
import ua.acclorite.book_story.presentation.data.removeTrailingZero
|
||||||
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Statistic section.
|
* Statistic section.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoStatisticSection(book: Book) {
|
fun BookInfoStatisticSection(state: State<BookInfoState>) {
|
||||||
val progress = remember(book) {
|
val progress = remember(state.value.book) {
|
||||||
"${
|
"${
|
||||||
(book.progress * 100)
|
(state.value.book.progress * 100)
|
||||||
.toDouble()
|
.toDouble()
|
||||||
.removeDigits(1)
|
.removeDigits(1)
|
||||||
.removeTrailingZero()
|
.removeTrailingZero()
|
||||||
}%"
|
}%"
|
||||||
}
|
}
|
||||||
val description = stringResource(
|
val description = stringResource(
|
||||||
if (book.progress == 1f) R.string.read_done
|
if (state.value.book.progress == 1f) R.string.read_done
|
||||||
else if (book.progress > 0.2f) R.string.read_keep
|
else if (state.value.book.progress > 0.2f) R.string.read_keep
|
||||||
else R.string.read_more
|
else R.string.read_more
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -53,7 +54,7 @@ fun BookInfoStatisticSection(book: Book) {
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = { book.progress.coerceAtLeast(0.01f) },
|
progress = { state.value.book.progress.coerceAtLeast(0.01f) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,16 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change cover bottom sheet. Lets user select photo from the gallery and replaces old one.
|
* Change cover bottom sheet. Lets user select photo from the gallery and replaces old one.
|
||||||
|
|
@ -38,26 +35,25 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoChangeCoverBottomSheet(
|
fun BookInfoChangeCoverBottomSheet(
|
||||||
libraryViewModel: LibraryViewModel,
|
state: State<BookInfoState>,
|
||||||
historyViewModel: HistoryViewModel,
|
onEvent: (BookInfoEvent) -> Unit,
|
||||||
viewModel: BookInfoViewModel
|
onLibraryUpdateEvent: (LibraryEvent.OnUpdateBook) -> Unit,
|
||||||
|
onHistoryUpdateEvent: (HistoryEvent.OnUpdateBook) -> Unit
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigationBarPadding =
|
val navigationBarPadding =
|
||||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||||
|
|
||||||
val photoPicker = rememberLauncherForActivityResult(
|
val photoPicker = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.PickVisualMedia(),
|
contract = ActivityResultContracts.PickVisualMedia(),
|
||||||
onResult = { uri ->
|
onResult = { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnChangeCover(
|
BookInfoEvent.OnChangeCover(
|
||||||
uri,
|
uri,
|
||||||
context,
|
context,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryUpdateEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -73,7 +69,7 @@ fun BookInfoChangeCoverBottomSheet(
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideChangeCoverBottomSheet)
|
onEvent(BookInfoEvent.OnShowHideChangeCoverBottomSheet)
|
||||||
},
|
},
|
||||||
sheetState = rememberModalBottomSheetState(true),
|
sheetState = rememberModalBottomSheetState(true),
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
|
|
@ -90,17 +86,17 @@ fun BookInfoChangeCoverBottomSheet(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.book.coverImage != null) {
|
if (state.value.book.coverImage != null) {
|
||||||
BookInfoChangeCoverBottomSheetItem(
|
BookInfoChangeCoverBottomSheetItem(
|
||||||
icon = Icons.Default.HideImage,
|
icon = Icons.Default.HideImage,
|
||||||
text = stringResource(id = R.string.delete_cover),
|
text = stringResource(id = R.string.delete_cover),
|
||||||
description = stringResource(id = R.string.delete_cover_desc)
|
description = stringResource(id = R.string.delete_cover_desc)
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnDeleteCover(
|
BookInfoEvent.OnDeleteCover(
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryUpdateEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
|
@ -58,7 +57,6 @@ fun BookInfoChangeCoverBottomSheetItem(
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
|
||||||
Text(
|
Text(
|
||||||
description,
|
description,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
|
|
||||||
|
|
@ -4,28 +4,28 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Update
|
import androidx.compose.material.icons.filled.Update
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm update dialog. Updates book if action clicked.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoConfirmUpdateDialog(
|
fun BookInfoConfirmUpdateDialog(
|
||||||
libraryViewModel: LibraryViewModel,
|
state: State<BookInfoState>,
|
||||||
historyViewModel: HistoryViewModel,
|
snackbarHostState: SnackbarHostState,
|
||||||
viewModel: BookInfoViewModel,
|
onEvent: (BookInfoEvent) -> Unit,
|
||||||
snackbarHostState: SnackbarHostState
|
onLibraryUpdateEvent: (LibraryEvent.OnUpdateBook) -> Unit,
|
||||||
|
onHistoryUpdateEvent: (HistoryEvent.OnUpdateBook) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
|
|
||||||
CustomDialogWithLazyColumn(
|
CustomDialogWithLazyColumn(
|
||||||
title = stringResource(id = R.string.confirm_update),
|
title = stringResource(id = R.string.confirm_update),
|
||||||
|
|
@ -35,32 +35,32 @@ fun BookInfoConfirmUpdateDialog(
|
||||||
),
|
),
|
||||||
actionText = stringResource(id = R.string.confirm),
|
actionText = stringResource(id = R.string.confirm),
|
||||||
isActionEnabled = true,
|
isActionEnabled = true,
|
||||||
onDismiss = { viewModel.onEvent(BookInfoEvent.OnDismissConfirmUpdateDialog) },
|
onDismiss = { onEvent(BookInfoEvent.OnDismissConfirmUpdateDialog) },
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnConfirmUpdate(
|
BookInfoEvent.OnConfirmUpdate(
|
||||||
snackbarState = snackbarHostState,
|
snackbarState = snackbarHostState,
|
||||||
context = context,
|
context = context,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryUpdateEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
withDivider = false,
|
withDivider = false,
|
||||||
items = {
|
items = {
|
||||||
if (state.authorChanged) {
|
if (state.value.authorChanged) {
|
||||||
item {
|
item {
|
||||||
BookInfoConfirmUpdateDialogItem(title = stringResource(id = R.string.author))
|
BookInfoConfirmUpdateDialogItem(title = stringResource(id = R.string.author))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.descriptionChanged) {
|
if (state.value.descriptionChanged) {
|
||||||
item {
|
item {
|
||||||
BookInfoConfirmUpdateDialogItem(title = stringResource(id = R.string.description))
|
BookInfoConfirmUpdateDialogItem(title = stringResource(id = R.string.description))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.textChanged) {
|
if (state.value.textChanged) {
|
||||||
item {
|
item {
|
||||||
BookInfoConfirmUpdateDialogItem(title = stringResource(id = R.string.text))
|
BookInfoConfirmUpdateDialogItem(title = stringResource(id = R.string.text))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm update dialog item.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoConfirmUpdateDialogItem(title: String) {
|
fun BookInfoConfirmUpdateDialogItem(title: String) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -21,7 +20,7 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
@ -32,9 +31,9 @@ import java.util.Locale
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoDetailsBottomSheet(
|
fun BookInfoDetailsBottomSheet(
|
||||||
viewModel: BookInfoViewModel
|
state: State<BookInfoState>,
|
||||||
|
onEvent: (BookInfoEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigationBarPadding =
|
val navigationBarPadding =
|
||||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||||
|
|
@ -43,11 +42,11 @@ fun BookInfoDetailsBottomSheet(
|
||||||
SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault())
|
SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault())
|
||||||
}
|
}
|
||||||
val lastOpened = remember {
|
val lastOpened = remember {
|
||||||
pattern.format(Date(state.book.lastOpened ?: 0))
|
pattern.format(Date(state.value.book.lastOpened ?: 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
val sizeBytes = remember {
|
val sizeBytes = remember {
|
||||||
state.book.file?.length() ?: 0
|
state.value.book.file?.length() ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileSizeKB = remember {
|
val fileSizeKB = remember {
|
||||||
|
|
@ -66,7 +65,7 @@ fun BookInfoDetailsBottomSheet(
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
viewModel.onEvent(BookInfoEvent.OnShowHideDetailsBottomSheet)
|
onEvent(BookInfoEvent.OnShowHideDetailsBottomSheet)
|
||||||
},
|
},
|
||||||
sheetState = rememberModalBottomSheetState(true),
|
sheetState = rememberModalBottomSheetState(true),
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
|
|
@ -74,11 +73,12 @@ fun BookInfoDetailsBottomSheet(
|
||||||
) {
|
) {
|
||||||
BookInfoDetailsBottomSheetItem(
|
BookInfoDetailsBottomSheetItem(
|
||||||
title = stringResource(id = R.string.file_name),
|
title = stringResource(id = R.string.file_name),
|
||||||
description = state.book.filePath.substringAfterLast("/").trim()
|
description = state.value.book.filePath.substringAfterLast("/").trim()
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnCopyToClipboard(
|
onEvent(
|
||||||
|
BookInfoEvent.OnCopyToClipboard(
|
||||||
context,
|
context,
|
||||||
state.book.filePath.substringAfterLast("/").trim(),
|
state.value.book.filePath.substringAfterLast("/").trim(),
|
||||||
success = {
|
success = {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
|
|
@ -90,11 +90,12 @@ fun BookInfoDetailsBottomSheet(
|
||||||
}
|
}
|
||||||
BookInfoDetailsBottomSheetItem(
|
BookInfoDetailsBottomSheetItem(
|
||||||
title = stringResource(id = R.string.file_path),
|
title = stringResource(id = R.string.file_path),
|
||||||
description = state.book.filePath.trim()
|
description = state.value.book.filePath.trim()
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnCopyToClipboard(
|
onEvent(
|
||||||
|
BookInfoEvent.OnCopyToClipboard(
|
||||||
context,
|
context,
|
||||||
state.book.filePath.trim(),
|
state.value.book.filePath.trim(),
|
||||||
success = {
|
success = {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
|
|
@ -106,11 +107,12 @@ fun BookInfoDetailsBottomSheet(
|
||||||
}
|
}
|
||||||
BookInfoDetailsBottomSheetItem(
|
BookInfoDetailsBottomSheetItem(
|
||||||
title = stringResource(id = R.string.file_last_opened),
|
title = stringResource(id = R.string.file_last_opened),
|
||||||
description = if (state.book.lastOpened != null) lastOpened
|
description = if (state.value.book.lastOpened != null) lastOpened
|
||||||
else stringResource(id = R.string.never)
|
else stringResource(id = R.string.never)
|
||||||
) {
|
) {
|
||||||
if (state.book.lastOpened != null) {
|
if (state.value.book.lastOpened != null) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnCopyToClipboard(
|
onEvent(
|
||||||
|
BookInfoEvent.OnCopyToClipboard(
|
||||||
context,
|
context,
|
||||||
lastOpened,
|
lastOpened,
|
||||||
success = {
|
success = {
|
||||||
|
|
@ -128,7 +130,8 @@ fun BookInfoDetailsBottomSheet(
|
||||||
description = fileSize.ifBlank { stringResource(id = R.string.unknown) }
|
description = fileSize.ifBlank { stringResource(id = R.string.unknown) }
|
||||||
) {
|
) {
|
||||||
if (fileSize.isNotBlank()) {
|
if (fileSize.isNotBlank()) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnCopyToClipboard(
|
onEvent(
|
||||||
|
BookInfoEvent.OnCopyToClipboard(
|
||||||
context,
|
context,
|
||||||
fileSize,
|
fileSize,
|
||||||
success = {
|
success = {
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,23 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete dialog. Deletes current book.
|
* Delete dialog. Deletes current book.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoDeleteDialog(
|
fun BookInfoDeleteDialog(
|
||||||
libraryViewModel: LibraryViewModel,
|
onEvent: (BookInfoEvent) -> Unit,
|
||||||
browseViewModel: BrowseViewModel,
|
onLibraryLoadEvent: (LibraryEvent.OnLoadList) -> Unit,
|
||||||
historyViewModel: HistoryViewModel,
|
onHistoryLoadEvent: (HistoryEvent.OnLoadList) -> Unit,
|
||||||
viewModel: BookInfoViewModel,
|
onBrowseLoadEvent: (BrowseEvent.OnLoadList) -> Unit
|
||||||
navigator: Navigator
|
|
||||||
) {
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
CustomDialogWithContent(
|
CustomDialogWithContent(
|
||||||
|
|
@ -38,17 +34,17 @@ fun BookInfoDeleteDialog(
|
||||||
id = R.string.delete_book_description
|
id = R.string.delete_book_description
|
||||||
),
|
),
|
||||||
actionText = stringResource(id = R.string.delete),
|
actionText = stringResource(id = R.string.delete),
|
||||||
onDismiss = { viewModel.onEvent(BookInfoEvent.OnShowHideDeleteDialog) },
|
onDismiss = { onEvent(BookInfoEvent.OnShowHideDeleteDialog) },
|
||||||
withDivider = false,
|
withDivider = false,
|
||||||
isActionEnabled = true,
|
isActionEnabled = true,
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnDeleteBook(
|
BookInfoEvent.OnDeleteBook(
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
onLibraryLoadEvent(LibraryEvent.OnLoadList)
|
||||||
browseViewModel.onEvent(BrowseEvent.OnLoadList)
|
onBrowseLoadEvent(BrowseEvent.OnLoadList)
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
onHistoryLoadEvent(HistoryEvent.OnLoadList)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.DriveFileMove
|
import androidx.compose.material.icons.automirrored.outlined.DriveFileMove
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -15,33 +14,31 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.SelectableDialogItem
|
import ua.acclorite.book_story.presentation.components.custom_dialog.SelectableDialogItem
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move dialog. Moves current book to the selected category.
|
* Move dialog. Moves current book to the selected category.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoMoveDialog(
|
fun BookInfoMoveDialog(
|
||||||
libraryViewModel: LibraryViewModel,
|
state: State<BookInfoState>,
|
||||||
historyViewModel: HistoryViewModel,
|
onEvent: (BookInfoEvent) -> Unit,
|
||||||
viewModel: BookInfoViewModel,
|
onLibraryEvent: (LibraryEvent) -> Unit,
|
||||||
navigator: Navigator
|
onHistoryUpdateEvent: (HistoryEvent.OnUpdateBook) -> Unit
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val navigator = LocalNavigator.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val categories = remember {
|
val categories = remember {
|
||||||
Category.entries.filter { state.book.category != it }
|
Category.entries.filter { state.value.book.category != it }
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnSelectCategory(categories[0]))
|
onEvent(BookInfoEvent.OnSelectCategory(categories[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomDialogWithLazyColumn(
|
CustomDialogWithLazyColumn(
|
||||||
|
|
@ -52,16 +49,16 @@ fun BookInfoMoveDialog(
|
||||||
),
|
),
|
||||||
actionText = stringResource(id = R.string.move),
|
actionText = stringResource(id = R.string.move),
|
||||||
isActionEnabled = true,
|
isActionEnabled = true,
|
||||||
onDismiss = { viewModel.onEvent(BookInfoEvent.OnShowHideMoveDialog) },
|
onDismiss = { onEvent(BookInfoEvent.OnShowHideMoveDialog) },
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnMoveBook(
|
BookInfoEvent.OnMoveBook(
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
updatePage = {
|
updatePage = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
onLibraryEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
||||||
},
|
},
|
||||||
navigator = navigator
|
navigator = navigator
|
||||||
)
|
)
|
||||||
|
|
@ -82,8 +79,11 @@ fun BookInfoMoveDialog(
|
||||||
Category.DROPPED -> stringResource(id = R.string.dropped_tab)
|
Category.DROPPED -> stringResource(id = R.string.dropped_tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectableDialogItem(selected = it == state.selectedCategory, title = category) {
|
SelectableDialogItem(
|
||||||
viewModel.onEvent(BookInfoEvent.OnSelectCategory(it))
|
selected = it == state.value.selectedCategory,
|
||||||
|
title = category
|
||||||
|
) {
|
||||||
|
onEvent(BookInfoEvent.OnSelectCategory(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ package ua.acclorite.book_story.presentation.screens.book_info.data
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed class BookInfoEvent {
|
sealed class BookInfoEvent {
|
||||||
data object OnShowHideChangeCoverBottomSheet : BookInfoEvent()
|
data object OnShowHideChangeCoverBottomSheet : BookInfoEvent()
|
||||||
data object OnShowHideDetailsBottomSheet : BookInfoEvent()
|
data object OnShowHideDetailsBottomSheet : BookInfoEvent()
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,14 @@ package ua.acclorite.book_story.presentation.screens.browse
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
|
@ -30,6 +26,7 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -39,62 +36,81 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomSearchTextField
|
||||||
import ua.acclorite.book_story.presentation.components.MoreDropDown
|
import ua.acclorite.book_story.presentation.components.MoreDropDown
|
||||||
import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty
|
import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty
|
||||||
import ua.acclorite.book_story.presentation.components.is_messages.IsError
|
import ua.acclorite.book_story.presentation.components.is_messages.IsError
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.components.BrowseFileItem
|
import ua.acclorite.book_story.presentation.screens.browse.components.BrowseFileItem
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.components.BrowseStoragePermissionDialog
|
import ua.acclorite.book_story.presentation.screens.browse.components.BrowseStoragePermissionDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.components.adding_dialog.BrowseAddingDialog
|
import ua.acclorite.book_story.presentation.screens.browse.components.adding_dialog.BrowseAddingDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseState
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
||||||
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.presentation.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BrowseScreenRoot() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val viewModel: BrowseViewModel = hiltViewModel()
|
||||||
|
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
val state = viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
BrowseScreen(
|
||||||
|
state = state,
|
||||||
|
navigator = navigator,
|
||||||
|
onEvent = viewModel::onEvent,
|
||||||
|
onLibraryEvent = libraryViewModel::onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class,
|
ExperimentalMaterial3Api::class,
|
||||||
ExperimentalPermissionsApi::class,
|
ExperimentalPermissionsApi::class,
|
||||||
ExperimentalMaterialApi::class
|
ExperimentalMaterialApi::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseScreen(
|
private fun BrowseScreen(
|
||||||
viewModel: BrowseViewModel,
|
state: State<BrowseState>,
|
||||||
libraryViewModel: LibraryViewModel,
|
navigator: Navigator,
|
||||||
navigator: Navigator
|
onEvent: (BrowseEvent) -> Unit,
|
||||||
|
onLibraryEvent: (LibraryEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val permissionState = rememberPermissionState(
|
val permissionState = rememberPermissionState(
|
||||||
permission = Manifest.permission.READ_EXTERNAL_STORAGE
|
permission = Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
)
|
)
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val refreshState = rememberPullRefreshState(
|
val refreshState = rememberPullRefreshState(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.value.isRefreshing,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
viewModel.onEvent(BrowseEvent.OnRefreshList)
|
onEvent(BrowseEvent.OnRefreshList)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
var showErrorMessage by remember { mutableStateOf(false) }
|
var showErrorMessage by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.onEvent(BrowseEvent.OnUpdateScrollOffset)
|
onEvent(BrowseEvent.OnUpdateScrollOffset)
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BrowseEvent.OnPermissionCheck(
|
BrowseEvent.OnPermissionCheck(
|
||||||
permissionState,
|
permissionState,
|
||||||
hideErrorMessage = { showErrorMessage = false }
|
hideErrorMessage = { showErrorMessage = false }
|
||||||
|
|
@ -102,16 +118,16 @@ fun BrowseScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.requestPermissionDialog) {
|
if (state.value.requestPermissionDialog) {
|
||||||
BrowseStoragePermissionDialog(viewModel, permissionState) {
|
BrowseStoragePermissionDialog(onEvent, permissionState) {
|
||||||
showErrorMessage = it
|
showErrorMessage = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.showAddingDialog) {
|
if (state.value.showAddingDialog) {
|
||||||
BrowseAddingDialog(
|
BrowseAddingDialog(
|
||||||
viewModel = viewModel,
|
state = state,
|
||||||
navigator = navigator,
|
onEvent = onEvent,
|
||||||
libraryViewModel = libraryViewModel
|
onLibraryEvent = onLibraryEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,9 +139,9 @@ fun BrowseScreen(
|
||||||
topBar = {
|
topBar = {
|
||||||
AnimatedTopAppBar(
|
AnimatedTopAppBar(
|
||||||
scrollBehavior = null,
|
scrollBehavior = null,
|
||||||
isTopBarScrolled = state.hasSelectedItems || state.listState.canScrollBackward,
|
isTopBarScrolled = state.value.hasSelectedItems || state.value.listState.canScrollBackward,
|
||||||
|
|
||||||
content1Visibility = !state.hasSelectedItems && !state.showSearch,
|
content1Visibility = !state.value.hasSelectedItems && !state.value.showSearch,
|
||||||
content1NavigationIcon = {},
|
content1NavigationIcon = {},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
Text(stringResource(id = R.string.browse_screen))
|
Text(stringResource(id = R.string.browse_screen))
|
||||||
|
|
@ -136,26 +152,26 @@ fun BrowseScreen(
|
||||||
contentDescription = R.string.search_content_desc,
|
contentDescription = R.string.search_content_desc,
|
||||||
disableOnClick = true
|
disableOnClick = true
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BrowseEvent.OnSearchShowHide)
|
onEvent(BrowseEvent.OnSearchShowHide)
|
||||||
}
|
}
|
||||||
MoreDropDown(navigator = navigator)
|
MoreDropDown()
|
||||||
},
|
},
|
||||||
|
|
||||||
content2Visibility = state.hasSelectedItems,
|
content2Visibility = state.value.hasSelectedItems,
|
||||||
content2NavigationIcon = {
|
content2NavigationIcon = {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.Default.Clear,
|
icon = Icons.Default.Clear,
|
||||||
contentDescription = R.string.clear_selected_items_content_desc,
|
contentDescription = R.string.clear_selected_items_content_desc,
|
||||||
disableOnClick = true
|
disableOnClick = true
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BrowseEvent.OnClearSelectedFiles)
|
onEvent(BrowseEvent.OnClearSelectedFiles)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content2Title = {
|
content2Title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(
|
stringResource(
|
||||||
id = R.string.selected_items_count_query,
|
id = R.string.selected_items_count_query,
|
||||||
state.selectedItemsCount.coerceAtLeast(1)
|
state.value.selectedItemsCount.coerceAtLeast(1)
|
||||||
),
|
),
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
|
|
@ -166,65 +182,44 @@ fun BrowseScreen(
|
||||||
icon = Icons.Default.Check,
|
icon = Icons.Default.Check,
|
||||||
contentDescription = R.string.add_files_content_desc,
|
contentDescription = R.string.add_files_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !state.showAddingDialog
|
enabled = !state.value.showAddingDialog
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BrowseEvent.OnAddingDialogRequest)
|
onEvent(BrowseEvent.OnAddingDialogRequest)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
content3Visibility = state.showSearch && !state.hasSelectedItems,
|
content3Visibility = state.value.showSearch && !state.value.hasSelectedItems,
|
||||||
content3NavigationIcon = {
|
content3NavigationIcon = {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.AutoMirrored.Default.ArrowBack,
|
icon = Icons.AutoMirrored.Default.ArrowBack,
|
||||||
contentDescription = R.string.exit_search_content_desc,
|
contentDescription = R.string.exit_search_content_desc,
|
||||||
disableOnClick = true
|
disableOnClick = true
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BrowseEvent.OnSearchShowHide)
|
onEvent(BrowseEvent.OnSearchShowHide)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content3Title = {
|
content3Title = {
|
||||||
BasicTextField(
|
CustomSearchTextField(
|
||||||
value = state.searchQuery,
|
|
||||||
singleLine = true,
|
|
||||||
textStyle = TextStyle(
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
|
||||||
lineHeight = MaterialTheme.typography.titleLarge.lineHeight,
|
|
||||||
fontFamily = MaterialTheme.typography.titleLarge.fontFamily
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
viewModel.onEvent(BrowseEvent.OnRequestFocus(focusRequester))
|
onEvent(BrowseEvent.OnRequestFocus(focusRequester))
|
||||||
},
|
},
|
||||||
onValueChange = {
|
query = state.value.searchQuery,
|
||||||
viewModel.onEvent(BrowseEvent.OnSearchQueryChange(it))
|
onQueryChange = {
|
||||||
|
onEvent(BrowseEvent.OnSearchQueryChange(it))
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words),
|
onSearch = {
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
|
onEvent(BrowseEvent.OnSearch)
|
||||||
) { innerText ->
|
},
|
||||||
Box(
|
placeholder = stringResource(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
contentAlignment = Alignment.CenterStart
|
|
||||||
) {
|
|
||||||
if (state.searchQuery.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
id = R.string.search_query,
|
id = R.string.search_query,
|
||||||
stringResource(id = R.string.files)
|
stringResource(id = R.string.files)
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
innerText()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
content3Actions = {
|
content3Actions = {
|
||||||
MoreDropDown(navigator = navigator)
|
MoreDropDown()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -234,21 +229,21 @@ fun BrowseScreen(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = padding.calculateTopPadding())
|
.padding(top = padding.calculateTopPadding())
|
||||||
) {
|
) {
|
||||||
DefaultTransition(visible = !state.isLoading) {
|
DefaultTransition(visible = !state.value.isLoading) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = state.listState,
|
state = state.value.listState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(vertical = 8.dp)
|
contentPadding = PaddingValues(vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
state.selectableFiles,
|
state.value.selectableFiles,
|
||||||
key = { it.first.path }
|
key = { it.first.path }
|
||||||
) { selectableFile ->
|
) { selectableFile ->
|
||||||
BrowseFileItem(
|
BrowseFileItem(
|
||||||
file = selectableFile,
|
file = selectableFile,
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
hasSelectedFiles = state.selectableFiles.any { it.second },
|
hasSelectedFiles = state.value.selectableFiles.any { it.second },
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
|
|
@ -260,14 +255,14 @@ fun BrowseScreen(
|
||||||
).show()
|
).show()
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onEvent(BrowseEvent.OnSelectFile(selectableFile))
|
onEvent(BrowseEvent.OnSelectFile(selectableFile))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = showErrorMessage,
|
visible = showErrorMessage,
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
enter = Transitions.DefaultTransitionIn,
|
enter = Transitions.DefaultTransitionIn,
|
||||||
|
|
@ -279,7 +274,7 @@ fun BrowseScreen(
|
||||||
icon = painterResource(id = R.drawable.error),
|
icon = painterResource(id = R.drawable.error),
|
||||||
actionTitle = stringResource(id = R.string.grant_permission)
|
actionTitle = stringResource(id = R.string.grant_permission)
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BrowseEvent.OnPermissionCheck(
|
BrowseEvent.OnPermissionCheck(
|
||||||
permissionState,
|
permissionState,
|
||||||
hideErrorMessage = { showErrorMessage = false }
|
hideErrorMessage = { showErrorMessage = false }
|
||||||
|
|
@ -288,10 +283,10 @@ fun BrowseScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = !state.isLoading && state.selectableFiles.isEmpty()
|
visible = !state.value.isLoading && state.value.selectableFiles.isEmpty()
|
||||||
&& !showErrorMessage && !state.requestPermissionDialog
|
&& !showErrorMessage && !state.value.requestPermissionDialog
|
||||||
&& !state.isRefreshing,
|
&& !state.value.isRefreshing,
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
enter = Transitions.DefaultTransitionIn,
|
enter = Transitions.DefaultTransitionIn,
|
||||||
exit = fadeOut(tween(0))
|
exit = fadeOut(tween(0))
|
||||||
|
|
@ -305,7 +300,7 @@ fun BrowseScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
state.isRefreshing,
|
state.value.isRefreshing,
|
||||||
refreshState,
|
refreshState,
|
||||||
Modifier.align(Alignment.TopCenter),
|
Modifier.align(Alignment.TopCenter),
|
||||||
backgroundColor = MaterialTheme.colorScheme.inverseSurface,
|
backgroundColor = MaterialTheme.colorScheme.inverseSurface,
|
||||||
|
|
@ -315,13 +310,13 @@ fun BrowseScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
if (state.hasSelectedItems) {
|
if (state.value.hasSelectedItems) {
|
||||||
viewModel.onEvent(BrowseEvent.OnClearSelectedFiles)
|
onEvent(BrowseEvent.OnClearSelectedFiles)
|
||||||
return@BackHandler
|
return@BackHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.showSearch) {
|
if (state.value.showSearch) {
|
||||||
viewModel.onEvent(BrowseEvent.OnSearchShowHide)
|
onEvent(BrowseEvent.OnSearchShowHide)
|
||||||
return@BackHandler
|
return@BackHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.browse.components
|
package ua.acclorite.book_story.presentation.screens.browse.components
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.SdStorage
|
import androidx.compose.material.icons.filled.SdStorage
|
||||||
|
|
@ -12,7 +11,6 @@ import com.google.accompanist.permissions.PermissionState
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Storage permission dialog.
|
* Storage permission dialog.
|
||||||
|
|
@ -20,7 +18,7 @@ import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseStoragePermissionDialog(
|
fun BrowseStoragePermissionDialog(
|
||||||
viewModel: BrowseViewModel,
|
onEvent: (BrowseEvent) -> Unit,
|
||||||
permissionState: PermissionState,
|
permissionState: PermissionState,
|
||||||
showErrorMessage: (Boolean) -> Unit
|
showErrorMessage: (Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -32,7 +30,7 @@ fun BrowseStoragePermissionDialog(
|
||||||
actionText = stringResource(id = R.string.grant),
|
actionText = stringResource(id = R.string.grant),
|
||||||
imageVectorIcon = Icons.Default.SdStorage,
|
imageVectorIcon = Icons.Default.SdStorage,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BrowseEvent.OnStoragePermissionDismiss(
|
BrowseEvent.OnStoragePermissionDismiss(
|
||||||
permissionState,
|
permissionState,
|
||||||
showErrorMessage = { showErrorMessage(true) }
|
showErrorMessage = { showErrorMessage(true) }
|
||||||
|
|
@ -41,19 +39,14 @@ fun BrowseStoragePermissionDialog(
|
||||||
},
|
},
|
||||||
isActionEnabled = true,
|
isActionEnabled = true,
|
||||||
withDivider = false,
|
withDivider = false,
|
||||||
|
disableOnClick = false,
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
BrowseEvent.OnStoragePermissionRequest(
|
BrowseEvent.OnStoragePermissionRequest(
|
||||||
activity,
|
activity,
|
||||||
|
storagePermissionState = permissionState,
|
||||||
hideErrorMessage = { showErrorMessage(false) }
|
hideErrorMessage = { showErrorMessage(false) }
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
BrowseEvent.OnLegacyStoragePermissionRequest(
|
|
||||||
permissionState,
|
|
||||||
hideErrorMessage = { showErrorMessage(false) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ import androidx.compose.material.icons.filled.AddChart
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
|
@ -22,39 +21,38 @@ import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseState
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adding dialog. Adds all selected books to the Library.
|
* Adding dialog. Adds all selected books to the Library.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseAddingDialog(
|
fun BrowseAddingDialog(
|
||||||
libraryViewModel: LibraryViewModel,
|
state: State<BrowseState>,
|
||||||
viewModel: BrowseViewModel,
|
onEvent: (BrowseEvent) -> Unit,
|
||||||
navigator: Navigator
|
onLibraryEvent: (LibraryEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
CustomDialogWithLazyColumn(
|
CustomDialogWithLazyColumn(
|
||||||
title = stringResource(id = R.string.add_books),
|
title = stringResource(id = R.string.add_books),
|
||||||
imageVectorIcon = Icons.Default.AddChart,
|
imageVectorIcon = Icons.Default.AddChart,
|
||||||
description = stringResource(id = R.string.add_books_description),
|
description = stringResource(id = R.string.add_books_description),
|
||||||
actionText = stringResource(id = R.string.add),
|
actionText = stringResource(id = R.string.add),
|
||||||
isActionEnabled = !state.isBooksLoading &&
|
isActionEnabled = !state.value.isBooksLoading &&
|
||||||
state.selectedBooks.any { it.first is NullableBook.NotNull },
|
state.value.selectedBooks.any { it.first is NullableBook.NotNull },
|
||||||
onDismiss = { viewModel.onEvent(BrowseEvent.OnAddingDialogDismiss) },
|
onDismiss = { onEvent(BrowseEvent.OnAddingDialogDismiss) },
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
BrowseEvent.OnAddBooks(
|
BrowseEvent.OnAddBooks(
|
||||||
navigator,
|
navigator,
|
||||||
resetScroll = {
|
resetScroll = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(0))
|
onLibraryEvent(LibraryEvent.OnUpdateCurrentPage(0))
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
onLibraryEvent(LibraryEvent.OnLoadList)
|
||||||
},
|
},
|
||||||
onFailed = {
|
onFailed = {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
@ -75,7 +73,7 @@ fun BrowseAddingDialog(
|
||||||
},
|
},
|
||||||
withDivider = true,
|
withDivider = true,
|
||||||
items = {
|
items = {
|
||||||
if (state.isBooksLoading) {
|
if (state.value.isBooksLoading) {
|
||||||
item {
|
item {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -92,12 +90,12 @@ fun BrowseAddingDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
items(state.selectedBooks) { book ->
|
items(state.value.selectedBooks) { book ->
|
||||||
BrowseAddingDialogItem(
|
BrowseAddingDialogItem(
|
||||||
result = book
|
result = book
|
||||||
) {
|
) {
|
||||||
if (it) {
|
if (it) {
|
||||||
viewModel.onEvent(BrowseEvent.OnSelectBook(book))
|
onEvent(BrowseEvent.OnSelectBook(book))
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.browse.data
|
package ua.acclorite.book_story.presentation.screens.browse.data
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.PermissionState
|
import com.google.accompanist.permissions.PermissionState
|
||||||
|
|
@ -11,13 +12,12 @@ import ua.acclorite.book_story.domain.util.Selected
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed class BrowseEvent {
|
sealed class BrowseEvent {
|
||||||
data class OnStoragePermissionRequest(
|
data class OnStoragePermissionRequest(
|
||||||
val activity: ComponentActivity, val hideErrorMessage: () -> Unit
|
val activity: ComponentActivity,
|
||||||
) : BrowseEvent()
|
val storagePermissionState: PermissionState,
|
||||||
|
val hideErrorMessage: () -> Unit
|
||||||
data class OnLegacyStoragePermissionRequest(
|
|
||||||
val permissionState: PermissionState, val hideErrorMessage: () -> Unit
|
|
||||||
) : BrowseEvent()
|
) : BrowseEvent()
|
||||||
|
|
||||||
data class OnStoragePermissionDismiss(
|
data class OnStoragePermissionDismiss(
|
||||||
|
|
@ -36,6 +36,7 @@ sealed class BrowseEvent {
|
||||||
data class OnRequestFocus(val focusRequester: FocusRequester) : BrowseEvent()
|
data class OnRequestFocus(val focusRequester: FocusRequester) : BrowseEvent()
|
||||||
data object OnClearSelectedFiles : BrowseEvent()
|
data object OnClearSelectedFiles : BrowseEvent()
|
||||||
data class OnSearchQueryChange(val query: String) : BrowseEvent()
|
data class OnSearchQueryChange(val query: String) : BrowseEvent()
|
||||||
|
data object OnSearch : BrowseEvent()
|
||||||
data object OnAddingDialogRequest : BrowseEvent()
|
data object OnAddingDialogRequest : BrowseEvent()
|
||||||
data object OnAddingDialogDismiss : BrowseEvent()
|
data object OnAddingDialogDismiss : BrowseEvent()
|
||||||
data object OnGetBooksFromFiles : BrowseEvent()
|
data object OnGetBooksFromFiles : BrowseEvent()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -41,6 +42,7 @@ class BrowseViewModel @Inject constructor(
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
private var job2: Job? = null
|
private var job2: Job? = null
|
||||||
private var job3: Job? = null
|
private var job3: Job? = null
|
||||||
|
private var storagePermissionJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|
@ -55,15 +57,16 @@ class BrowseViewModel @Inject constructor(
|
||||||
|
|
||||||
fun onEvent(event: BrowseEvent) {
|
fun onEvent(event: BrowseEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is BrowseEvent.OnLegacyStoragePermissionRequest -> {
|
is BrowseEvent.OnStoragePermissionRequest -> {
|
||||||
event.permissionState.launchPermissionRequest()
|
val legacyStoragePermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||||
|
|
||||||
viewModelScope.launch {
|
val isPermissionGranted = if (legacyStoragePermission) {
|
||||||
for (i in 0 until 100) {
|
event.storagePermissionState.status.isGranted
|
||||||
if (!event.permissionState.status.isGranted) {
|
} else {
|
||||||
delay(100)
|
Environment.isExternalStorageManager()
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPermissionGranted) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
requestPermissionDialog = false
|
requestPermissionDialog = false
|
||||||
|
|
@ -71,27 +74,55 @@ class BrowseViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
event.hideErrorMessage()
|
event.hideErrorMessage()
|
||||||
onEvent(BrowseEvent.OnRefreshList)
|
onEvent(BrowseEvent.OnRefreshList)
|
||||||
break
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legacyStoragePermission) {
|
||||||
|
if (event.storagePermissionState.status.shouldShowRationale) {
|
||||||
|
event.storagePermissionState.launchPermissionRequest()
|
||||||
|
} else {
|
||||||
|
val uri = Uri.parse("package:${event.activity.packageName}")
|
||||||
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri)
|
||||||
|
|
||||||
|
if (intent.resolveActivity(event.activity.packageManager) != null) {
|
||||||
|
event.activity.startActivity(intent)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is BrowseEvent.OnStoragePermissionRequest -> {
|
if (!legacyStoragePermission) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
|
|
||||||
val uri = Uri.parse("package:${event.activity.packageName}")
|
val uri = Uri.parse("package:${event.activity.packageName}")
|
||||||
val intent = Intent(
|
val intent = Intent(
|
||||||
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
||||||
uri
|
uri
|
||||||
)
|
)
|
||||||
event.activity.startActivity(intent)
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
if (intent.resolveActivity(event.activity.packageManager) != null) {
|
||||||
for (i in 0 until 20) {
|
event.activity.startActivity(intent)
|
||||||
if (!Environment.isExternalStorageManager()) {
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storagePermissionJob?.cancel()
|
||||||
|
storagePermissionJob = viewModelScope.launch {
|
||||||
|
while (true) {
|
||||||
|
val granted = if (legacyStoragePermission) {
|
||||||
|
event.storagePermissionState.status.isGranted
|
||||||
|
} else {
|
||||||
|
Environment.isExternalStorageManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!granted) {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
|
yield()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
requestPermissionDialog = false
|
requestPermissionDialog = false
|
||||||
|
|
@ -103,7 +134,6 @@ class BrowseViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is BrowseEvent.OnStoragePermissionDismiss -> {
|
is BrowseEvent.OnStoragePermissionDismiss -> {
|
||||||
val legacyPermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
val legacyPermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||||
|
|
@ -111,6 +141,7 @@ class BrowseViewModel @Inject constructor(
|
||||||
if (!legacyPermission) Environment.isExternalStorageManager()
|
if (!legacyPermission) Environment.isExternalStorageManager()
|
||||||
else event.permissionState.status.isGranted
|
else event.permissionState.status.isGranted
|
||||||
|
|
||||||
|
storagePermissionJob?.cancel()
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
requestPermissionDialog = false
|
requestPermissionDialog = false
|
||||||
|
|
@ -270,6 +301,12 @@ class BrowseViewModel @Inject constructor(
|
||||||
job = viewModelScope.launch(Dispatchers.IO) {
|
job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
delay(500)
|
delay(500)
|
||||||
yield()
|
yield()
|
||||||
|
onEvent(BrowseEvent.OnSearch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is BrowseEvent.OnSearch -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getFilesFromDownloads()
|
getFilesFromDownloads()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,22 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.help
|
package ua.acclorite.book_story.presentation.screens.help
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.RestartAlt
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.LargeTopAppBar
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -16,35 +24,73 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpAddBooksItem
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpAddBooksItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpClickMeNoteItem
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpClickMeNoteItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpCustomizeApp
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpCustomizeApp
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpCustomizeReader
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpCustomizeReader
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpEditBook
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpEditBook
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpFindBooksItem
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpFindBooksItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpManageHistory
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpMoveOrDeleteBooks
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpMoveOrDeleteBooks
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpReadBook
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpReadBook
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpUpdateBook
|
import ua.acclorite.book_story.presentation.screens.help.components.items.HelpUpdateBook
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.start.data.StartEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.start.data.StartViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HelpScreenRoot() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val helpViewModel: HelpViewModel = hiltViewModel()
|
||||||
|
val mainViewModel: MainViewModel = hiltViewModel()
|
||||||
|
val startViewModel: StartViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
val state = helpViewModel.state.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
helpViewModel.init(navigator)
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpScreen(
|
||||||
|
state = state,
|
||||||
|
navigator = navigator,
|
||||||
|
onEvent = helpViewModel::onEvent,
|
||||||
|
onMainEvent = mainViewModel::onEvent,
|
||||||
|
onStartEvent = startViewModel::onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class
|
ExperimentalMaterial3Api::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun HelpScreen(
|
private fun HelpScreen(
|
||||||
viewModel: HelpViewModel = hiltViewModel(),
|
state: State<HelpState>,
|
||||||
navigator: Navigator
|
navigator: Navigator,
|
||||||
|
onEvent: (HelpEvent) -> Unit,
|
||||||
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
|
onStartEvent: (StartEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -58,7 +104,23 @@ fun HelpScreen(
|
||||||
Text(stringResource(id = R.string.help_screen))
|
Text(stringResource(id = R.string.help_screen))
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
|
if (!state.value.fromStart) {
|
||||||
GoBackButton(navigator = navigator)
|
GoBackButton(navigator = navigator)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
if (!state.value.fromStart) {
|
||||||
|
CustomIconButton(
|
||||||
|
icon = Icons.Default.RestartAlt,
|
||||||
|
contentDescription = R.string.reset_start_content_desc,
|
||||||
|
disableOnClick = false
|
||||||
|
) {
|
||||||
|
onStartEvent(StartEvent.OnResetStartScreen)
|
||||||
|
onMainEvent(MainEvent.OnChangeShowStartScreen(true))
|
||||||
|
navigator.navigateWithoutBackStack(Screen.START, false)
|
||||||
|
navigator.clearBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollState.first,
|
scrollBehavior = scrollState.first,
|
||||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||||
|
|
@ -66,14 +128,37 @@ fun HelpScreen(
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
if (state.value.fromStart) {
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
.padding(horizontal = 18.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(100),
|
||||||
|
onClick = {
|
||||||
|
navigator.clearArgument("from_start")
|
||||||
|
onMainEvent(MainEvent.OnChangeShowStartScreen(false))
|
||||||
|
navigator.navigateWithoutBackStack(Screen.BROWSE, false)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.done))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding(), start = 18.dp, end = 18.dp)
|
.padding(paddingValues)
|
||||||
.imePadding(),
|
.imePadding(),
|
||||||
state = scrollState.second
|
state = scrollState.second,
|
||||||
|
contentPadding = PaddingValues(horizontal = 18.dp)
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Spacer(
|
Spacer(
|
||||||
|
|
@ -84,7 +169,10 @@ fun HelpScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpClickMeNoteItem(viewModel = viewModel)
|
HelpClickMeNoteItem(
|
||||||
|
state = state,
|
||||||
|
onEvent = onEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
|
|
@ -95,54 +183,66 @@ fun HelpScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpFindBooksItem(viewModel = viewModel)
|
HelpFindBooksItem(
|
||||||
|
state = state,
|
||||||
|
onEvent = onEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpAddBooksItem(navigator = navigator, viewModel = viewModel)
|
HelpAddBooksItem(
|
||||||
|
state = state,
|
||||||
|
onEvent = onEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpCustomizeApp(
|
HelpCustomizeApp(
|
||||||
navigator = navigator,
|
state = state,
|
||||||
viewModel = viewModel
|
onEvent = onEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpMoveOrDeleteBooks(
|
HelpMoveOrDeleteBooks(
|
||||||
navigator = navigator,
|
state = state,
|
||||||
viewModel = viewModel
|
onEvent = onEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpEditBook(
|
HelpEditBook(
|
||||||
navigator = navigator,
|
state = state,
|
||||||
viewModel = viewModel
|
onEvent = onEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpReadBook(
|
HelpReadBook(
|
||||||
navigator = navigator,
|
state = state,
|
||||||
viewModel = viewModel
|
onEvent = onEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpCustomizeReader(
|
HelpCustomizeReader(
|
||||||
navigator = navigator,
|
state = state,
|
||||||
viewModel = viewModel
|
onEvent = onEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
HelpUpdateBook(
|
HelpUpdateBook(
|
||||||
navigator = navigator,
|
state = state,
|
||||||
viewModel = viewModel
|
onEvent = onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HelpManageHistory(
|
||||||
|
state = state,
|
||||||
|
onEvent = onEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Help clickable annotation. Mainly used for highlighting screens.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AnnotatedString.Builder.HelpAnnotation(
|
fun AnnotatedString.Builder.HelpAnnotation(
|
||||||
tag: String,
|
tag: String,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Help Item. Can be Expanded or Collapsed.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpItem(
|
fun LazyItemScope.HelpItem(
|
||||||
title: String,
|
title: String,
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,23 @@ package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpAddBooksItem(navigator: Navigator, viewModel: HelpViewModel) {
|
fun LazyItemScope.HelpAddBooksItem(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
HelpItem(
|
HelpItem(
|
||||||
title = stringResource(id = R.string.help_title_how_to_add_books),
|
title = stringResource(id = R.string.help_title_how_to_add_books),
|
||||||
|
|
@ -35,13 +38,15 @@ fun LazyItemScope.HelpAddBooksItem(navigator: Navigator, viewModel: HelpViewMode
|
||||||
append(".")
|
append(".")
|
||||||
},
|
},
|
||||||
tags = listOf("browse", "library"),
|
tags = listOf("browse", "library"),
|
||||||
shouldShowDescription = state.showHelpItem2,
|
shouldShowDescription = state.value.showHelpItem2,
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showHelpItem2 = !it.showHelpItem2
|
showHelpItem2 = !it.showHelpItem2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTagClick = { tag ->
|
onTagClick = { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,21 @@ package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpClickableNote
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpClickableNote
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpClickMeNoteItem(viewModel: HelpViewModel) {
|
fun LazyItemScope.HelpClickMeNoteItem(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
HelpClickableNote(
|
HelpClickableNote(
|
||||||
|
|
@ -29,13 +31,15 @@ fun LazyItemScope.HelpClickMeNoteItem(viewModel: HelpViewModel) {
|
||||||
append(" " + stringResource(id = R.string.help_clickable_note_3))
|
append(" " + stringResource(id = R.string.help_clickable_note_3))
|
||||||
},
|
},
|
||||||
tags = listOf("note"),
|
tags = listOf("note"),
|
||||||
isNoteFullyShown = state.showNote1,
|
isNoteFullyShown = state.value.showNote1,
|
||||||
onIconClick = {
|
onIconClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showNote1 = !it.showNote1
|
showNote1 = !it.showNote1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTagClick = { tag ->
|
onTagClick = { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,23 @@ package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpCustomizeApp(viewModel: HelpViewModel, navigator: Navigator) {
|
fun LazyItemScope.HelpCustomizeApp(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
HelpItem(
|
HelpItem(
|
||||||
title = stringResource(id = R.string.help_title_how_to_customize_app),
|
title = stringResource(id = R.string.help_title_how_to_customize_app),
|
||||||
|
|
@ -30,13 +33,15 @@ fun LazyItemScope.HelpCustomizeApp(viewModel: HelpViewModel, navigator: Navigato
|
||||||
append(stringResource(id = R.string.help_desc_how_to_customize_app_3))
|
append(stringResource(id = R.string.help_desc_how_to_customize_app_3))
|
||||||
},
|
},
|
||||||
tags = listOf("settings"),
|
tags = listOf("settings"),
|
||||||
shouldShowDescription = state.showHelpItem3,
|
shouldShowDescription = state.value.showHelpItem3,
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showHelpItem3 = !it.showHelpItem3
|
showHelpItem3 = !it.showHelpItem3
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTagClick = { tag ->
|
onTagClick = { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,23 @@ package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpCustomizeReader(viewModel: HelpViewModel, navigator: Navigator) {
|
fun LazyItemScope.HelpCustomizeReader(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
HelpItem(
|
HelpItem(
|
||||||
title = stringResource(id = R.string.help_title_how_to_customize_reader),
|
title = stringResource(id = R.string.help_title_how_to_customize_reader),
|
||||||
|
|
@ -30,13 +33,15 @@ fun LazyItemScope.HelpCustomizeReader(viewModel: HelpViewModel, navigator: Navig
|
||||||
append(stringResource(id = R.string.help_desc_how_to_customize_reader_3))
|
append(stringResource(id = R.string.help_desc_how_to_customize_reader_3))
|
||||||
},
|
},
|
||||||
tags = listOf("settings"),
|
tags = listOf("settings"),
|
||||||
shouldShowDescription = state.showHelpItem7,
|
shouldShowDescription = state.value.showHelpItem7,
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showHelpItem7 = !it.showHelpItem7
|
showHelpItem7 = !it.showHelpItem7
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTagClick = { tag ->
|
onTagClick = { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,23 @@ package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpEditBook(viewModel: HelpViewModel, navigator: Navigator) {
|
fun LazyItemScope.HelpEditBook(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
HelpItem(
|
HelpItem(
|
||||||
title = stringResource(id = R.string.help_title_how_to_edit_book),
|
title = stringResource(id = R.string.help_title_how_to_edit_book),
|
||||||
|
|
@ -30,13 +33,15 @@ fun LazyItemScope.HelpEditBook(viewModel: HelpViewModel, navigator: Navigator) {
|
||||||
append(stringResource(id = R.string.help_desc_how_to_edit_book_3))
|
append(stringResource(id = R.string.help_desc_how_to_edit_book_3))
|
||||||
},
|
},
|
||||||
tags = listOf("library"),
|
tags = listOf("library"),
|
||||||
shouldShowDescription = state.showHelpItem5,
|
shouldShowDescription = state.value.showHelpItem5,
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showHelpItem5 = !it.showHelpItem5
|
showHelpItem5 = !it.showHelpItem5
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTagClick = { tag ->
|
onTagClick = { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringArrayResource
|
import androidx.compose.ui.res.stringArrayResource
|
||||||
|
|
@ -31,12 +30,14 @@ import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
import ua.acclorite.book_story.presentation.ui.SlidingTransition
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpFindBooksItem(viewModel: HelpViewModel) {
|
fun LazyItemScope.HelpFindBooksItem(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val sites = stringArrayResource(id = R.array.book_sites)
|
val sites = stringArrayResource(id = R.array.book_sites)
|
||||||
|
|
@ -60,34 +61,38 @@ fun LazyItemScope.HelpFindBooksItem(viewModel: HelpViewModel) {
|
||||||
append(stringResource(id = R.string.help_desc_how_to_find_books))
|
append(stringResource(id = R.string.help_desc_how_to_find_books))
|
||||||
},
|
},
|
||||||
tags = emptyList(),
|
tags = emptyList(),
|
||||||
shouldShowDescription = state.showHelpItem1,
|
shouldShowDescription = state.value.showHelpItem1,
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showHelpItem1 = !it.showHelpItem1
|
showHelpItem1 = !it.showHelpItem1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
customContent = {
|
customContent = {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.textFieldValue,
|
value = state.value.textFieldValue,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
onValueChange = { value ->
|
onValueChange = { value ->
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
textFieldValue = value,
|
textFieldValue = value,
|
||||||
showError = false
|
showError = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(id = R.string.help_field_placeholder))
|
Text(text = stringResource(id = R.string.help_field_placeholder))
|
||||||
},
|
},
|
||||||
keyboardActions = KeyboardActions(
|
keyboardActions = KeyboardActions(
|
||||||
onSearch = {
|
onSearch = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
HelpEvent.OnSearchInWeb(
|
HelpEvent.OnSearchInWeb(
|
||||||
context = context,
|
context = context,
|
||||||
noAppsFound = {
|
noAppsFound = {
|
||||||
|
|
@ -110,14 +115,14 @@ fun LazyItemScope.HelpFindBooksItem(viewModel: HelpViewModel) {
|
||||||
icon = Icons.Default.Search,
|
icon = Icons.Default.Search,
|
||||||
contentDescription = R.string.search_content_desc,
|
contentDescription = R.string.search_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !state.showError,
|
enabled = !state.value.showError,
|
||||||
color = if (state.showError) {
|
color = if (state.value.showError) {
|
||||||
MaterialTheme.colorScheme.outline
|
MaterialTheme.colorScheme.outline
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
HelpEvent.OnSearchInWeb(
|
HelpEvent.OnSearchInWeb(
|
||||||
context = context,
|
context = context,
|
||||||
noAppsFound = {
|
noAppsFound = {
|
||||||
|
|
@ -135,14 +140,14 @@ fun LazyItemScope.HelpFindBooksItem(viewModel: HelpViewModel) {
|
||||||
Column {
|
Column {
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
Box {
|
Box {
|
||||||
SlidingTransition(visible = state.showError) {
|
SlidingTransition(visible = state.value.showError) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.help_field_error),
|
text = stringResource(id = R.string.help_field_error),
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SlidingTransition(visible = !state.showError) {
|
SlidingTransition(visible = !state.value.showError) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = text,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
|
|
@ -155,7 +160,7 @@ fun LazyItemScope.HelpFindBooksItem(viewModel: HelpViewModel) {
|
||||||
start = offset,
|
start = offset,
|
||||||
end = offset
|
end = offset
|
||||||
).firstOrNull()?.let {
|
).firstOrNull()?.let {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
HelpEvent.OnNavigateToBrowserPage(
|
HelpEvent.OnNavigateToBrowserPage(
|
||||||
page = tag.substringAfterLast("|"),
|
page = tag.substringAfterLast("|"),
|
||||||
context = context,
|
context = context,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.HelpManageHistory(
|
||||||
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
|
HelpItem(
|
||||||
|
title = stringResource(id = R.string.help_title_how_to_manage_history),
|
||||||
|
description = buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_manage_history_1) + " ")
|
||||||
|
|
||||||
|
HelpAnnotation(tag = "history") {
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_manage_history_2))
|
||||||
|
}
|
||||||
|
append(". ")
|
||||||
|
|
||||||
|
append(stringResource(id = R.string.help_desc_how_to_manage_history_3))
|
||||||
|
},
|
||||||
|
tags = listOf("history"),
|
||||||
|
shouldShowDescription = state.value.showHelpItem9,
|
||||||
|
onTitleClick = {
|
||||||
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
|
it.copy(
|
||||||
|
showHelpItem9 = !it.showHelpItem9
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onTagClick = { tag ->
|
||||||
|
when (tag) {
|
||||||
|
"history" -> {
|
||||||
|
navigator.navigate(Screen.HISTORY, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -2,20 +2,23 @@ package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpMoveOrDeleteBooks(viewModel: HelpViewModel, navigator: Navigator) {
|
fun LazyItemScope.HelpMoveOrDeleteBooks(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
HelpItem(
|
HelpItem(
|
||||||
title = stringResource(id = R.string.help_title_how_to_move_or_delete_books),
|
title = stringResource(id = R.string.help_title_how_to_move_or_delete_books),
|
||||||
|
|
@ -30,13 +33,15 @@ fun LazyItemScope.HelpMoveOrDeleteBooks(viewModel: HelpViewModel, navigator: Nav
|
||||||
append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_3))
|
append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_3))
|
||||||
},
|
},
|
||||||
tags = listOf("library"),
|
tags = listOf("library"),
|
||||||
shouldShowDescription = state.showHelpItem4,
|
shouldShowDescription = state.value.showHelpItem4,
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showHelpItem4 = !it.showHelpItem4
|
showHelpItem4 = !it.showHelpItem4
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTagClick = { tag ->
|
onTagClick = { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,23 @@ package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpReadBook(viewModel: HelpViewModel, navigator: Navigator) {
|
fun LazyItemScope.HelpReadBook(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
HelpItem(
|
HelpItem(
|
||||||
title = stringResource(id = R.string.help_title_how_to_read_book),
|
title = stringResource(id = R.string.help_title_how_to_read_book),
|
||||||
|
|
@ -30,13 +33,15 @@ fun LazyItemScope.HelpReadBook(viewModel: HelpViewModel, navigator: Navigator) {
|
||||||
append(stringResource(id = R.string.help_desc_how_to_read_book_3))
|
append(stringResource(id = R.string.help_desc_how_to_read_book_3))
|
||||||
},
|
},
|
||||||
tags = listOf("library"),
|
tags = listOf("library"),
|
||||||
shouldShowDescription = state.showHelpItem6,
|
shouldShowDescription = state.value.showHelpItem6,
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showHelpItem6 = !it.showHelpItem6
|
showHelpItem6 = !it.showHelpItem6
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTagClick = { tag ->
|
onTagClick = { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,23 @@ package ua.acclorite.book_story.presentation.screens.help.components.items
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpAnnotation
|
||||||
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
import ua.acclorite.book_story.presentation.screens.help.components.HelpItem
|
||||||
import ua.acclorite.book_story.presentation.screens.help.data.HelpViewModel
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.help.data.HelpState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.HelpUpdateBook(viewModel: HelpViewModel, navigator: Navigator) {
|
fun LazyItemScope.HelpUpdateBook(
|
||||||
val state by viewModel.state.collectAsState()
|
state: State<HelpState>,
|
||||||
|
onEvent: (HelpEvent) -> Unit
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
HelpItem(
|
HelpItem(
|
||||||
title = stringResource(id = R.string.help_title_how_to_update_book),
|
title = stringResource(id = R.string.help_title_how_to_update_book),
|
||||||
|
|
@ -30,13 +33,15 @@ fun LazyItemScope.HelpUpdateBook(viewModel: HelpViewModel, navigator: Navigator)
|
||||||
append(stringResource(id = R.string.help_desc_how_to_update_book_3))
|
append(stringResource(id = R.string.help_desc_how_to_update_book_3))
|
||||||
},
|
},
|
||||||
tags = listOf("library"),
|
tags = listOf("library"),
|
||||||
shouldShowDescription = state.showHelpItem8,
|
shouldShowDescription = state.value.showHelpItem8,
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onUpdate {
|
onEvent(
|
||||||
|
HelpEvent.OnUpdateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
showHelpItem8 = !it.showHelpItem8
|
showHelpItem8 = !it.showHelpItem8
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTagClick = { tag ->
|
onTagClick = { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.help.data
|
package ua.acclorite.book_story.presentation.screens.help.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed class HelpEvent {
|
sealed class HelpEvent {
|
||||||
|
|
||||||
|
data class OnUpdateState(val block: (HelpState) -> HelpState) : HelpEvent()
|
||||||
|
|
||||||
data class OnSearchInWeb(
|
data class OnSearchInWeb(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
val noAppsFound: () -> Unit
|
val noAppsFound: () -> Unit
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.help.data
|
package ua.acclorite.book_story.presentation.screens.help.data
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class HelpState(
|
data class HelpState(
|
||||||
|
val fromStart: Boolean = false,
|
||||||
|
|
||||||
val showNote1: Boolean = true,
|
val showNote1: Boolean = true,
|
||||||
|
|
||||||
val showHelpItem1: Boolean = false,
|
val showHelpItem1: Boolean = false,
|
||||||
|
|
@ -14,4 +19,5 @@ data class HelpState(
|
||||||
val showHelpItem6: Boolean = false,
|
val showHelpItem6: Boolean = false,
|
||||||
val showHelpItem7: Boolean = false,
|
val showHelpItem7: Boolean = false,
|
||||||
val showHelpItem8: Boolean = false,
|
val showHelpItem8: Boolean = false,
|
||||||
|
val showHelpItem9: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
|
|
@ -65,12 +66,24 @@ class HelpViewModel @Inject constructor(
|
||||||
event.noAppsFound()
|
event.noAppsFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onUpdate(calculation: (HelpState) -> HelpState) {
|
is HelpEvent.OnUpdateState -> {
|
||||||
_state.update {
|
_state.update {
|
||||||
calculation(it)
|
event.block(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(navigator: Navigator) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val isFromStart = navigator.retrieveArgument("from_start") as? Boolean ?: false
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
fromStart = isFromStart
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,16 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.history
|
package ua.acclorite.book_story.presentation.screens.history
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
|
@ -30,68 +26,88 @@ import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomSearchTextField
|
||||||
import ua.acclorite.book_story.presentation.components.CustomSnackbar
|
import ua.acclorite.book_story.presentation.components.CustomSnackbar
|
||||||
import ua.acclorite.book_story.presentation.components.MoreDropDown
|
import ua.acclorite.book_story.presentation.components.MoreDropDown
|
||||||
import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty
|
import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty
|
||||||
import ua.acclorite.book_story.presentation.data.Argument
|
import ua.acclorite.book_story.presentation.data.Argument
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.history.components.HistoryDeleteWholeHistoryDialog
|
import ua.acclorite.book_story.presentation.screens.history.components.HistoryDeleteWholeHistoryDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.history.components.HistoryItem
|
import ua.acclorite.book_story.presentation.screens.history.components.HistoryItem
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryState
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
||||||
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.presentation.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HistoryScreenRoot() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val viewModel: HistoryViewModel = hiltViewModel()
|
||||||
|
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
val state = viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
HistoryScreen(
|
||||||
|
state = state,
|
||||||
|
navigator = navigator,
|
||||||
|
onEvent = viewModel::onEvent,
|
||||||
|
onLibraryEvent = libraryViewModel::onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class,
|
ExperimentalMaterial3Api::class,
|
||||||
ExperimentalMaterialApi::class
|
ExperimentalMaterialApi::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryScreen(
|
private fun HistoryScreen(
|
||||||
viewModel: HistoryViewModel,
|
state: State<HistoryState>,
|
||||||
libraryViewModel: LibraryViewModel,
|
navigator: Navigator,
|
||||||
navigator: Navigator
|
onEvent: (HistoryEvent) -> Unit,
|
||||||
|
onLibraryEvent: (LibraryEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val refreshState = rememberPullRefreshState(
|
val refreshState = rememberPullRefreshState(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.value.isRefreshing,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
viewModel.onEvent(HistoryEvent.OnRefreshList)
|
onEvent(HistoryEvent.OnRefreshList)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val snackbarState = remember { SnackbarHostState() }
|
val snackbarState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.onEvent(HistoryEvent.OnUpdateScrollOffset)
|
onEvent(HistoryEvent.OnUpdateScrollOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.showDeleteWholeHistoryDialog) {
|
if (state.value.showDeleteWholeHistoryDialog) {
|
||||||
HistoryDeleteWholeHistoryDialog(viewModel = viewModel, libraryViewModel = libraryViewModel)
|
HistoryDeleteWholeHistoryDialog(
|
||||||
|
onEvent = onEvent,
|
||||||
|
onLibraryLoadEvent = onLibraryEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -102,9 +118,9 @@ fun HistoryScreen(
|
||||||
topBar = {
|
topBar = {
|
||||||
AnimatedTopAppBar(
|
AnimatedTopAppBar(
|
||||||
scrollBehavior = null,
|
scrollBehavior = null,
|
||||||
isTopBarScrolled = state.listState.canScrollBackward,
|
isTopBarScrolled = state.value.listState.canScrollBackward,
|
||||||
|
|
||||||
content1Visibility = !state.showSearch,
|
content1Visibility = !state.value.showSearch,
|
||||||
content1NavigationIcon = {},
|
content1NavigationIcon = {},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -117,75 +133,54 @@ fun HistoryScreen(
|
||||||
contentDescription = R.string.search_content_desc,
|
contentDescription = R.string.search_content_desc,
|
||||||
disableOnClick = true,
|
disableOnClick = true,
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(HistoryEvent.OnSearchShowHide)
|
onEvent(HistoryEvent.OnSearchShowHide)
|
||||||
}
|
}
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.Outlined.DeleteSweep,
|
icon = Icons.Outlined.DeleteSweep,
|
||||||
contentDescription = R.string.delete_whole_history_content_desc,
|
contentDescription = R.string.delete_whole_history_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !state.isLoading
|
enabled = !state.value.isLoading
|
||||||
&& !state.isRefreshing
|
&& !state.value.isRefreshing
|
||||||
&& state.history.isNotEmpty()
|
&& state.value.history.isNotEmpty()
|
||||||
&& !state.showDeleteWholeHistoryDialog
|
&& !state.value.showDeleteWholeHistoryDialog
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog)
|
onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog)
|
||||||
}
|
}
|
||||||
MoreDropDown(navigator = navigator)
|
MoreDropDown()
|
||||||
},
|
},
|
||||||
|
|
||||||
content2Visibility = state.showSearch,
|
content2Visibility = state.value.showSearch,
|
||||||
content2NavigationIcon = {
|
content2NavigationIcon = {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.AutoMirrored.Default.ArrowBack,
|
icon = Icons.AutoMirrored.Default.ArrowBack,
|
||||||
contentDescription = R.string.exit_search_content_desc,
|
contentDescription = R.string.exit_search_content_desc,
|
||||||
disableOnClick = true
|
disableOnClick = true
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(HistoryEvent.OnSearchShowHide)
|
onEvent(HistoryEvent.OnSearchShowHide)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content2Title = {
|
content2Title = {
|
||||||
BasicTextField(
|
CustomSearchTextField(
|
||||||
value = state.searchQuery,
|
|
||||||
singleLine = true,
|
|
||||||
textStyle = TextStyle(
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
|
||||||
lineHeight = MaterialTheme.typography.titleLarge.lineHeight,
|
|
||||||
fontFamily = MaterialTheme.typography.titleLarge.fontFamily
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
viewModel.onEvent(HistoryEvent.OnRequestFocus(focusRequester))
|
onEvent(HistoryEvent.OnRequestFocus(focusRequester))
|
||||||
},
|
},
|
||||||
onValueChange = {
|
query = state.value.searchQuery,
|
||||||
viewModel.onEvent(HistoryEvent.OnSearchQueryChange(it))
|
onQueryChange = {
|
||||||
|
onEvent(HistoryEvent.OnSearchQueryChange(it))
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words),
|
onSearch = {
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
|
onEvent(HistoryEvent.OnSearch)
|
||||||
) { innerText ->
|
},
|
||||||
Box(
|
placeholder = stringResource(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
contentAlignment = Alignment.CenterStart
|
|
||||||
) {
|
|
||||||
if (state.searchQuery.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
id = R.string.search_query,
|
id = R.string.search_query,
|
||||||
stringResource(id = R.string.history)
|
stringResource(id = R.string.history)
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
innerText()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
content2Actions = {
|
content2Actions = {
|
||||||
MoreDropDown(navigator = navigator)
|
MoreDropDown()
|
||||||
},
|
},
|
||||||
content3Visibility = false
|
content3Visibility = false
|
||||||
)
|
)
|
||||||
|
|
@ -199,14 +194,14 @@ fun HistoryScreen(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
.padding(top = paddingValues.calculateTopPadding())
|
||||||
) {
|
) {
|
||||||
DefaultTransition(visible = !state.isLoading) {
|
DefaultTransition(visible = !state.value.isLoading) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
state = state.listState,
|
state = state.value.listState,
|
||||||
contentPadding = PaddingValues(vertical = 12.dp)
|
contentPadding = PaddingValues(vertical = 12.dp)
|
||||||
) {
|
) {
|
||||||
state.history.forEachIndexed { index, groupedHistory ->
|
state.value.history.forEachIndexed { index, groupedHistory ->
|
||||||
item(key = groupedHistory.title) {
|
item(key = groupedHistory.title) {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
@ -231,7 +226,7 @@ fun HistoryScreen(
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
history = it,
|
history = it,
|
||||||
isOnClickEnabled = !state.isRefreshing,
|
isOnClickEnabled = !state.value.isRefreshing,
|
||||||
onBodyClick = {
|
onBodyClick = {
|
||||||
navigator.navigate(
|
navigator.navigate(
|
||||||
Screen.BOOK_INFO,
|
Screen.BOOK_INFO,
|
||||||
|
|
@ -240,22 +235,22 @@ fun HistoryScreen(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnNavigateToReaderScreen(
|
HistoryEvent.OnNavigateToReaderScreen(
|
||||||
navigator,
|
navigator,
|
||||||
it.book!!
|
it.book!!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
isDeleteEnabled = !state.isRefreshing,
|
isDeleteEnabled = !state.value.isRefreshing,
|
||||||
onDeleteClick = {
|
onDeleteClick = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnDeleteHistoryElement(
|
HistoryEvent.OnDeleteHistoryElement(
|
||||||
historyToDelete = it,
|
historyToDelete = it,
|
||||||
snackbarState = snackbarState,
|
snackbarState = snackbarState,
|
||||||
context = context,
|
context = context,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(
|
onLibraryEvent(
|
||||||
LibraryEvent.OnLoadList
|
LibraryEvent.OnLoadList
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -268,10 +263,10 @@ fun HistoryScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = !state.isLoading
|
visible = !state.value.isLoading
|
||||||
&& state.history.isEmpty()
|
&& state.value.history.isEmpty()
|
||||||
&& !state.isRefreshing,
|
&& !state.value.isRefreshing,
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
enter = Transitions.DefaultTransitionIn,
|
enter = Transitions.DefaultTransitionIn,
|
||||||
exit = fadeOut(tween(0))
|
exit = fadeOut(tween(0))
|
||||||
|
|
@ -284,7 +279,7 @@ fun HistoryScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
state.isRefreshing,
|
state.value.isRefreshing,
|
||||||
refreshState,
|
refreshState,
|
||||||
Modifier.align(Alignment.TopCenter),
|
Modifier.align(Alignment.TopCenter),
|
||||||
backgroundColor = MaterialTheme.colorScheme.inverseSurface,
|
backgroundColor = MaterialTheme.colorScheme.inverseSurface,
|
||||||
|
|
@ -294,8 +289,8 @@ fun HistoryScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
if (state.showSearch) {
|
if (state.value.showSearch) {
|
||||||
viewModel.onEvent(HistoryEvent.OnSearchShowHide)
|
onEvent(HistoryEvent.OnSearchShowHide)
|
||||||
return@BackHandler
|
return@BackHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,15 @@ import androidx.compose.ui.res.stringResource
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete whole history dialog.
|
* Delete whole history dialog.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryDeleteWholeHistoryDialog(
|
fun HistoryDeleteWholeHistoryDialog(
|
||||||
viewModel: HistoryViewModel,
|
onEvent: (HistoryEvent) -> Unit,
|
||||||
libraryViewModel: LibraryViewModel
|
onLibraryLoadEvent: (LibraryEvent.OnLoadList) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
|
@ -30,13 +28,13 @@ fun HistoryDeleteWholeHistoryDialog(
|
||||||
id = R.string.delete_history_description
|
id = R.string.delete_history_description
|
||||||
),
|
),
|
||||||
actionText = stringResource(id = R.string.delete),
|
actionText = stringResource(id = R.string.delete),
|
||||||
onDismiss = { viewModel.onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog) },
|
onDismiss = { onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog) },
|
||||||
withDivider = false,
|
withDivider = false,
|
||||||
isActionEnabled = true,
|
isActionEnabled = true,
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnDeleteWholeHistory {
|
HistoryEvent.OnDeleteWholeHistory {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
onLibraryLoadEvent(LibraryEvent.OnLoadList)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ package ua.acclorite.book_story.presentation.screens.history.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed class HistoryEvent {
|
sealed class HistoryEvent {
|
||||||
data object OnRefreshList : HistoryEvent()
|
data object OnRefreshList : HistoryEvent()
|
||||||
data object OnLoadList : HistoryEvent()
|
data object OnLoadList : HistoryEvent()
|
||||||
|
|
@ -20,6 +22,7 @@ sealed class HistoryEvent {
|
||||||
) : HistoryEvent()
|
) : HistoryEvent()
|
||||||
|
|
||||||
data class OnSearchQueryChange(val query: String) : HistoryEvent()
|
data class OnSearchQueryChange(val query: String) : HistoryEvent()
|
||||||
|
data object OnSearch : HistoryEvent()
|
||||||
data object OnSearchShowHide : HistoryEvent()
|
data object OnSearchShowHide : HistoryEvent()
|
||||||
data class OnRequestFocus(val focusRequester: FocusRequester) : HistoryEvent()
|
data class OnRequestFocus(val focusRequester: FocusRequester) : HistoryEvent()
|
||||||
data class OnUpdateBook(val book: Book) : HistoryEvent()
|
data class OnUpdateBook(val book: Book) : HistoryEvent()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.history.data
|
package ua.acclorite.book_story.presentation.screens.history.data
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import ua.acclorite.book_story.domain.model.GroupedHistory
|
import ua.acclorite.book_story.domain.model.GroupedHistory
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class HistoryState(
|
data class HistoryState(
|
||||||
val history: List<GroupedHistory> = emptyList(),
|
val history: List<GroupedHistory> = emptyList(),
|
||||||
val listState: LazyListState = LazyListState(0, 0),
|
val listState: LazyListState = LazyListState(0, 0),
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,12 @@ class HistoryViewModel @Inject constructor(
|
||||||
job = viewModelScope.launch(Dispatchers.IO) {
|
job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
delay(500)
|
delay(500)
|
||||||
yield()
|
yield()
|
||||||
|
onEvent(HistoryEvent.OnSearch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is HistoryEvent.OnSearch -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getHistoryFromDatabase()
|
getHistoryFromDatabase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,14 @@ package ua.acclorite.book_story.presentation.screens.library
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -25,8 +22,6 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
|
@ -43,6 +38,7 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -54,16 +50,14 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
|
|
@ -71,69 +65,95 @@ import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.domain.util.Selected
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomSearchTextField
|
||||||
import ua.acclorite.book_story.presentation.components.MoreDropDown
|
import ua.acclorite.book_story.presentation.components.MoreDropDown
|
||||||
import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty
|
import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty
|
||||||
import ua.acclorite.book_story.presentation.data.Argument
|
import ua.acclorite.book_story.presentation.data.Argument
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.library.components.LibraryBookItem
|
import ua.acclorite.book_story.presentation.screens.library.components.LibraryBookItem
|
||||||
import ua.acclorite.book_story.presentation.screens.library.components.LibraryTabRow
|
import ua.acclorite.book_story.presentation.screens.library.components.LibraryTabRow
|
||||||
import ua.acclorite.book_story.presentation.screens.library.components.dialog.LibraryDeleteDialog
|
import ua.acclorite.book_story.presentation.screens.library.components.dialog.LibraryDeleteDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.library.components.dialog.LibraryMoveDialog
|
import ua.acclorite.book_story.presentation.screens.library.components.dialog.LibraryMoveDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryState
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
||||||
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.presentation.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LibraryScreenRoot() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val viewModel: LibraryViewModel = hiltViewModel()
|
||||||
|
val browseViewModel: BrowseViewModel = hiltViewModel()
|
||||||
|
val historyViewModel: HistoryViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
val state = viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
LibraryScreen(
|
||||||
|
state = state,
|
||||||
|
navigator = navigator,
|
||||||
|
onEvent = viewModel::onEvent,
|
||||||
|
onBrowseEvent = browseViewModel::onEvent,
|
||||||
|
onHistoryEvent = historyViewModel::onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class,
|
ExperimentalMaterial3Api::class,
|
||||||
ExperimentalMaterialApi::class
|
ExperimentalMaterialApi::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryScreen(
|
private fun LibraryScreen(
|
||||||
viewModel: LibraryViewModel,
|
state: State<LibraryState>,
|
||||||
browseViewModel: BrowseViewModel,
|
navigator: Navigator,
|
||||||
historyViewModel: HistoryViewModel,
|
onEvent: (LibraryEvent) -> Unit,
|
||||||
navigator: Navigator
|
onHistoryEvent: (HistoryEvent) -> Unit,
|
||||||
|
onBrowseEvent: (BrowseEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val pagerState = rememberPagerState(
|
val pagerState = rememberPagerState(
|
||||||
initialPage = state.currentPage,
|
initialPage = state.value.currentPage,
|
||||||
pageCount = { Category.entries.size }
|
pageCount = { Category.entries.size }
|
||||||
)
|
)
|
||||||
var isScrollInProgress by remember { mutableStateOf(false) }
|
var isScrollInProgress by remember { mutableStateOf(false) }
|
||||||
val refreshState = rememberPullRefreshState(
|
val refreshState = rememberPullRefreshState(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.value.isRefreshing,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
viewModel.onEvent(LibraryEvent.OnRefreshList)
|
onEvent(LibraryEvent.OnRefreshList)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.onEvent(LibraryEvent.OnScrollToPage(state.currentPage, pagerState))
|
onEvent(LibraryEvent.OnScrollToPage(state.value.currentPage, pagerState))
|
||||||
}
|
}
|
||||||
LaunchedEffect(pagerState.currentPage) {
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
if (pagerState.currentPage != state.currentPage) {
|
if (pagerState.currentPage != state.value.currentPage) {
|
||||||
viewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(pagerState.currentPage))
|
onEvent(LibraryEvent.OnUpdateCurrentPage(pagerState.currentPage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.showMoveDialog) {
|
if (state.value.showMoveDialog) {
|
||||||
LibraryMoveDialog(
|
LibraryMoveDialog(
|
||||||
viewModel = viewModel,
|
state = state,
|
||||||
historyViewModel = historyViewModel,
|
onEvent = onEvent,
|
||||||
|
onHistoryLoadEvent = onHistoryEvent,
|
||||||
pagerState = pagerState
|
pagerState = pagerState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (state.showDeleteDialog) {
|
if (state.value.showDeleteDialog) {
|
||||||
LibraryDeleteDialog(
|
LibraryDeleteDialog(
|
||||||
viewModel = viewModel,
|
state = state,
|
||||||
browseViewModel = browseViewModel,
|
onEvent = onEvent,
|
||||||
historyViewModel = historyViewModel
|
onBrowseLoadEvent = onBrowseEvent,
|
||||||
|
onHistoryLoadEvent = onHistoryEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,16 +166,16 @@ fun LibraryScreen(
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
AnimatedTopAppBar(
|
AnimatedTopAppBar(
|
||||||
scrollBehavior = null,
|
scrollBehavior = null,
|
||||||
isTopBarScrolled = state.hasSelectedItems,
|
isTopBarScrolled = state.value.hasSelectedItems,
|
||||||
|
|
||||||
content1Visibility = !state.hasSelectedItems && !state.showSearch,
|
content1Visibility = !state.value.hasSelectedItems && !state.value.showSearch,
|
||||||
content1NavigationIcon = {},
|
content1NavigationIcon = {},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(stringResource(id = R.string.library_screen))
|
Text(stringResource(id = R.string.library_screen))
|
||||||
Spacer(modifier = Modifier.width(6.dp))
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
Text(
|
Text(
|
||||||
text = state.books.size.toString(),
|
text = state.value.books.size.toString(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(14.dp))
|
.clip(RoundedCornerShape(14.dp))
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
|
|
@ -171,26 +191,26 @@ fun LibraryScreen(
|
||||||
contentDescription = R.string.search_content_desc,
|
contentDescription = R.string.search_content_desc,
|
||||||
disableOnClick = true,
|
disableOnClick = true,
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(LibraryEvent.OnSearchShowHide)
|
onEvent(LibraryEvent.OnSearchShowHide)
|
||||||
}
|
}
|
||||||
MoreDropDown(navigator = navigator)
|
MoreDropDown()
|
||||||
},
|
},
|
||||||
|
|
||||||
content2Visibility = state.hasSelectedItems,
|
content2Visibility = state.value.hasSelectedItems,
|
||||||
content2NavigationIcon = {
|
content2NavigationIcon = {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.Default.Clear,
|
icon = Icons.Default.Clear,
|
||||||
contentDescription = R.string.clear_selected_items_content_desc,
|
contentDescription = R.string.clear_selected_items_content_desc,
|
||||||
disableOnClick = true
|
disableOnClick = true
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(LibraryEvent.OnClearSelectedBooks)
|
onEvent(LibraryEvent.OnClearSelectedBooks)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content2Title = {
|
content2Title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(
|
stringResource(
|
||||||
id = R.string.selected_items_count_query,
|
id = R.string.selected_items_count_query,
|
||||||
state.selectedItemsCount.coerceAtLeast(1)
|
state.value.selectedItemsCount.coerceAtLeast(1)
|
||||||
),
|
),
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
|
|
@ -200,86 +220,65 @@ fun LibraryScreen(
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.AutoMirrored.Outlined.DriveFileMove,
|
icon = Icons.AutoMirrored.Outlined.DriveFileMove,
|
||||||
contentDescription = R.string.move_books_content_desc,
|
contentDescription = R.string.move_books_content_desc,
|
||||||
enabled = !state.isLoading
|
enabled = !state.value.isLoading
|
||||||
&& !state.isRefreshing
|
&& !state.value.isRefreshing
|
||||||
&& !state.showMoveDialog,
|
&& !state.value.showMoveDialog,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(LibraryEvent.OnShowHideMoveDialog)
|
onEvent(LibraryEvent.OnShowHideMoveDialog)
|
||||||
}
|
}
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.Outlined.Delete,
|
icon = Icons.Outlined.Delete,
|
||||||
contentDescription = R.string.delete_books_content_desc,
|
contentDescription = R.string.delete_books_content_desc,
|
||||||
enabled = !state.isLoading
|
enabled = !state.value.isLoading
|
||||||
&& !state.isRefreshing
|
&& !state.value.isRefreshing
|
||||||
&& !state.showDeleteDialog,
|
&& !state.value.showDeleteDialog,
|
||||||
disableOnClick = false
|
disableOnClick = false
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(LibraryEvent.OnShowHideDeleteDialog)
|
onEvent(LibraryEvent.OnShowHideDeleteDialog)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
content3Visibility = state.showSearch && !state.hasSelectedItems,
|
content3Visibility = state.value.showSearch && !state.value.hasSelectedItems,
|
||||||
content3NavigationIcon = {
|
content3NavigationIcon = {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.AutoMirrored.Default.ArrowBack,
|
icon = Icons.AutoMirrored.Default.ArrowBack,
|
||||||
contentDescription = R.string.exit_search_content_desc,
|
contentDescription = R.string.exit_search_content_desc,
|
||||||
disableOnClick = true
|
disableOnClick = true
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
LibraryEvent.OnSearchShowHide
|
LibraryEvent.OnSearchShowHide
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content3Title = {
|
content3Title = {
|
||||||
BasicTextField(
|
CustomSearchTextField(
|
||||||
value = state.searchQuery,
|
|
||||||
singleLine = true,
|
|
||||||
textStyle = TextStyle(
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
|
||||||
lineHeight = MaterialTheme.typography.titleLarge.lineHeight,
|
|
||||||
fontFamily = MaterialTheme.typography.titleLarge.fontFamily
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
viewModel.onEvent(LibraryEvent.OnRequestFocus(focusRequester))
|
onEvent(LibraryEvent.OnRequestFocus(focusRequester))
|
||||||
},
|
},
|
||||||
onValueChange = {
|
query = state.value.searchQuery,
|
||||||
viewModel.onEvent(LibraryEvent.OnSearchQueryChange(it))
|
onQueryChange = {
|
||||||
|
onEvent(LibraryEvent.OnSearchQueryChange(it))
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(KeyboardCapitalization.Words),
|
onSearch = {
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
|
onEvent(LibraryEvent.OnSearch)
|
||||||
) { innerText ->
|
},
|
||||||
Box(
|
placeholder = stringResource(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
contentAlignment = Alignment.CenterStart
|
|
||||||
) {
|
|
||||||
if (state.searchQuery.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
id = R.string.search_query,
|
id = R.string.search_query,
|
||||||
stringResource(id = R.string.books)
|
stringResource(id = R.string.books)
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
innerText()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
content3Actions = {
|
content3Actions = {
|
||||||
MoreDropDown(navigator = navigator)
|
MoreDropDown()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
LibraryTabRow(
|
LibraryTabRow(
|
||||||
viewModel = viewModel,
|
onEvent = onEvent,
|
||||||
books = state.categorizedBooks,
|
books = state.value.categorizedBooks,
|
||||||
pagerState = pagerState
|
pagerState = pagerState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -295,8 +294,8 @@ fun LibraryScreen(
|
||||||
userScrollEnabled = !isScrollInProgress
|
userScrollEnabled = !isScrollInProgress
|
||||||
) { index ->
|
) { index ->
|
||||||
val category = remember { Category.entries[index] }
|
val category = remember { Category.entries[index] }
|
||||||
val books = remember(state.categorizedBooks) {
|
val books = remember(state.value.categorizedBooks) {
|
||||||
state.categorizedBooks.find {
|
state.value.categorizedBooks.find {
|
||||||
it.category == category
|
it.category == category
|
||||||
}?.books?.sortedWith(
|
}?.books?.sortedWith(
|
||||||
compareByDescending<Pair<Book, Selected>> { it.first.lastOpened }
|
compareByDescending<Pair<Book, Selected>> { it.first.lastOpened }
|
||||||
|
|
@ -307,14 +306,14 @@ fun LibraryScreen(
|
||||||
|
|
||||||
LaunchedEffect(listState.isScrollInProgress, pagerState.currentPage) {
|
LaunchedEffect(listState.isScrollInProgress, pagerState.currentPage) {
|
||||||
if (listState.isScrollInProgress != isScrollInProgress
|
if (listState.isScrollInProgress != isScrollInProgress
|
||||||
&& pagerState.currentPage == state.currentPage
|
&& pagerState.currentPage == state.value.currentPage
|
||||||
) {
|
) {
|
||||||
isScrollInProgress = listState.isScrollInProgress
|
isScrollInProgress = listState.isScrollInProgress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
DefaultTransition(visible = !state.isLoading) {
|
DefaultTransition(visible = !state.value.isLoading) {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Adaptive(120.dp),
|
columns = GridCells.Adaptive(120.dp),
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|
@ -327,10 +326,13 @@ fun LibraryScreen(
|
||||||
) {
|
) {
|
||||||
LibraryBookItem(
|
LibraryBookItem(
|
||||||
book = it,
|
book = it,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(
|
||||||
|
fadeInSpec = null,
|
||||||
|
fadeOutSpec = null
|
||||||
|
),
|
||||||
onCoverImageClick = {
|
onCoverImageClick = {
|
||||||
if (state.hasSelectedItems) {
|
if (state.value.hasSelectedItems) {
|
||||||
viewModel.onEvent(LibraryEvent.OnSelectBook(it))
|
onEvent(LibraryEvent.OnSelectBook(it))
|
||||||
} else {
|
} else {
|
||||||
navigator.navigate(
|
navigator.navigate(
|
||||||
Screen.BOOK_INFO,
|
Screen.BOOK_INFO,
|
||||||
|
|
@ -341,11 +343,11 @@ fun LibraryScreen(
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
if (!it.second) {
|
if (!it.second) {
|
||||||
viewModel.onEvent(LibraryEvent.OnSelectBook(it, true))
|
onEvent(LibraryEvent.OnSelectBook(it, true))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onButtonClick = {
|
onButtonClick = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
LibraryEvent.OnNavigateToReaderScreen(
|
LibraryEvent.OnNavigateToReaderScreen(
|
||||||
navigator,
|
navigator,
|
||||||
it.first
|
it.first
|
||||||
|
|
@ -357,9 +359,9 @@ fun LibraryScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = !state.isLoading
|
visible = !state.value.isLoading
|
||||||
&& !state.isRefreshing
|
&& !state.value.isRefreshing
|
||||||
&& books.isEmpty(),
|
&& books.isEmpty(),
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
enter = Transitions.DefaultTransitionIn,
|
enter = Transitions.DefaultTransitionIn,
|
||||||
|
|
@ -378,7 +380,7 @@ fun LibraryScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
state.isRefreshing,
|
state.value.isRefreshing,
|
||||||
refreshState,
|
refreshState,
|
||||||
Modifier.align(Alignment.TopCenter),
|
Modifier.align(Alignment.TopCenter),
|
||||||
backgroundColor = MaterialTheme.colorScheme.inverseSurface,
|
backgroundColor = MaterialTheme.colorScheme.inverseSurface,
|
||||||
|
|
@ -391,18 +393,18 @@ fun LibraryScreen(
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var shouldExit = remember { false }
|
var shouldExit = remember { false }
|
||||||
BackHandler {
|
BackHandler {
|
||||||
if (state.hasSelectedItems) {
|
if (state.value.hasSelectedItems) {
|
||||||
viewModel.onEvent(LibraryEvent.OnClearSelectedBooks)
|
onEvent(LibraryEvent.OnClearSelectedBooks)
|
||||||
return@BackHandler
|
return@BackHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.showSearch) {
|
if (state.value.showSearch) {
|
||||||
viewModel.onEvent(LibraryEvent.OnSearchShowHide)
|
onEvent(LibraryEvent.OnSearchShowHide)
|
||||||
return@BackHandler
|
return@BackHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.currentPage > 0) {
|
if (state.value.currentPage > 0) {
|
||||||
viewModel.onEvent(LibraryEvent.OnScrollToPage(0, pagerState))
|
onEvent(LibraryEvent.OnScrollToPage(0, pagerState))
|
||||||
return@BackHandler
|
return@BackHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.CategorizedBooks
|
import ua.acclorite.book_story.domain.model.CategorizedBooks
|
||||||
import ua.acclorite.book_story.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tab row, either scrollable or static, depends on screen width.
|
* Tab row, either scrollable or static, depends on screen width.
|
||||||
|
|
@ -38,7 +37,7 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryTabRow(
|
fun LibraryTabRow(
|
||||||
viewModel: LibraryViewModel,
|
onEvent: (LibraryEvent) -> Unit,
|
||||||
books: List<CategorizedBooks>,
|
books: List<CategorizedBooks>,
|
||||||
pagerState: PagerState
|
pagerState: PagerState
|
||||||
) {
|
) {
|
||||||
|
|
@ -99,7 +98,7 @@ fun LibraryTabRow(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
selected = pagerState.currentPage == index,
|
selected = pagerState.currentPage == index,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onEvent(LibraryEvent.OnScrollToPage(index, pagerState))
|
onEvent(LibraryEvent.OnScrollToPage(index, pagerState))
|
||||||
},
|
},
|
||||||
selectedContentColor = MaterialTheme.colorScheme.primary,
|
selectedContentColor = MaterialTheme.colorScheme.primary,
|
||||||
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
|
|
||||||
|
|
@ -4,29 +4,26 @@ import android.widget.Toast
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.DeleteOutline
|
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete dialog. Deletes all selected books.
|
* Delete dialog. Deletes all selected books.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryDeleteDialog(
|
fun LibraryDeleteDialog(
|
||||||
viewModel: LibraryViewModel,
|
state: State<LibraryState>,
|
||||||
browseViewModel: BrowseViewModel,
|
onEvent: (LibraryEvent) -> Unit,
|
||||||
historyViewModel: HistoryViewModel
|
onBrowseLoadEvent: (BrowseEvent.OnLoadList) -> Unit,
|
||||||
|
onHistoryLoadEvent: (HistoryEvent.OnLoadList) -> Unit
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
CustomDialogWithContent(
|
CustomDialogWithContent(
|
||||||
|
|
@ -34,17 +31,17 @@ fun LibraryDeleteDialog(
|
||||||
imageVectorIcon = Icons.Outlined.DeleteOutline,
|
imageVectorIcon = Icons.Outlined.DeleteOutline,
|
||||||
description = stringResource(
|
description = stringResource(
|
||||||
id = R.string.delete_books_description,
|
id = R.string.delete_books_description,
|
||||||
state.books.filter { it.second }.size
|
state.value.books.filter { it.second }.size
|
||||||
),
|
),
|
||||||
actionText = stringResource(id = R.string.delete),
|
actionText = stringResource(id = R.string.delete),
|
||||||
onDismiss = { viewModel.onEvent(LibraryEvent.OnShowHideDeleteDialog) },
|
onDismiss = { onEvent(LibraryEvent.OnShowHideDeleteDialog) },
|
||||||
withDivider = false,
|
withDivider = false,
|
||||||
isActionEnabled = true,
|
isActionEnabled = true,
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
LibraryEvent.OnDeleteBooks {
|
LibraryEvent.OnDeleteBooks {
|
||||||
browseViewModel.onEvent(BrowseEvent.OnLoadList)
|
onBrowseLoadEvent(BrowseEvent.OnLoadList)
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
onHistoryLoadEvent(HistoryEvent.OnLoadList)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.DriveFileMove
|
import androidx.compose.material.icons.automirrored.outlined.DriveFileMove
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
|
|
@ -15,20 +14,19 @@ import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn
|
import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn
|
||||||
import ua.acclorite.book_story.presentation.components.custom_dialog.SelectableDialogItem
|
import ua.acclorite.book_story.presentation.components.custom_dialog.SelectableDialogItem
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move dialog. Moves all selected books to the selected category.
|
* Move dialog. Moves all selected books to the selected category.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryMoveDialog(
|
fun LibraryMoveDialog(
|
||||||
viewModel: LibraryViewModel,
|
state: State<LibraryState>,
|
||||||
historyViewModel: HistoryViewModel,
|
onEvent: (LibraryEvent) -> Unit,
|
||||||
|
onHistoryLoadEvent: (HistoryEvent.OnLoadList) -> Unit,
|
||||||
pagerState: PagerState
|
pagerState: PagerState
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
CustomDialogWithLazyColumn(
|
CustomDialogWithLazyColumn(
|
||||||
|
|
@ -36,17 +34,17 @@ fun LibraryMoveDialog(
|
||||||
imageVectorIcon = Icons.AutoMirrored.Outlined.DriveFileMove,
|
imageVectorIcon = Icons.AutoMirrored.Outlined.DriveFileMove,
|
||||||
description = stringResource(
|
description = stringResource(
|
||||||
id = R.string.move_books_description,
|
id = R.string.move_books_description,
|
||||||
state.books.filter { it.second }.size
|
state.value.books.filter { it.second }.size
|
||||||
),
|
),
|
||||||
actionText = stringResource(id = R.string.move),
|
actionText = stringResource(id = R.string.move),
|
||||||
isActionEnabled = true,
|
isActionEnabled = true,
|
||||||
onDismiss = { viewModel.onEvent(LibraryEvent.OnShowHideMoveDialog) },
|
onDismiss = { onEvent(LibraryEvent.OnShowHideMoveDialog) },
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
LibraryEvent.OnMoveBooks(
|
LibraryEvent.OnMoveBooks(
|
||||||
pagerState,
|
pagerState,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
onHistoryLoadEvent(HistoryEvent.OnLoadList)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -58,7 +56,7 @@ fun LibraryMoveDialog(
|
||||||
},
|
},
|
||||||
withDivider = false,
|
withDivider = false,
|
||||||
items = {
|
items = {
|
||||||
items(state.categories) {
|
items(state.value.categories) {
|
||||||
val category = when (it) {
|
val category = when (it) {
|
||||||
Category.READING -> stringResource(id = R.string.reading_tab)
|
Category.READING -> stringResource(id = R.string.reading_tab)
|
||||||
Category.ALREADY_READ -> stringResource(id = R.string.already_read_tab)
|
Category.ALREADY_READ -> stringResource(id = R.string.already_read_tab)
|
||||||
|
|
@ -66,8 +64,11 @@ fun LibraryMoveDialog(
|
||||||
Category.DROPPED -> stringResource(id = R.string.dropped_tab)
|
Category.DROPPED -> stringResource(id = R.string.dropped_tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectableDialogItem(selected = it == state.selectedCategory, title = category) {
|
SelectableDialogItem(
|
||||||
viewModel.onEvent(LibraryEvent.OnSelectCategory(it))
|
selected = it == state.value.selectedCategory,
|
||||||
|
title = category
|
||||||
|
) {
|
||||||
|
onEvent(LibraryEvent.OnSelectCategory(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.library.data
|
package ua.acclorite.book_story.presentation.screens.library.data
|
||||||
|
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.domain.util.Selected
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed class LibraryEvent {
|
sealed class LibraryEvent {
|
||||||
data object OnRefreshList : LibraryEvent()
|
data object OnRefreshList : LibraryEvent()
|
||||||
data object OnLoadList : LibraryEvent()
|
data object OnLoadList : LibraryEvent()
|
||||||
|
|
@ -16,6 +18,7 @@ sealed class LibraryEvent {
|
||||||
data object OnSearchShowHide : LibraryEvent()
|
data object OnSearchShowHide : LibraryEvent()
|
||||||
data class OnRequestFocus(val focusRequester: FocusRequester) : LibraryEvent()
|
data class OnRequestFocus(val focusRequester: FocusRequester) : LibraryEvent()
|
||||||
data class OnSearchQueryChange(val query: String) : LibraryEvent()
|
data class OnSearchQueryChange(val query: String) : LibraryEvent()
|
||||||
|
data object OnSearch : LibraryEvent()
|
||||||
data class OnSelectBook(val book: Pair<Book, Selected>, val select: Boolean? = null) :
|
data class OnSelectBook(val book: Pair<Book, Selected>, val select: Boolean? = null) :
|
||||||
LibraryEvent()
|
LibraryEvent()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,12 @@ class LibraryViewModel @Inject constructor(
|
||||||
job = viewModelScope.launch(Dispatchers.IO) {
|
job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
delay(500)
|
delay(500)
|
||||||
yield()
|
yield()
|
||||||
|
onEvent(LibraryEvent.OnSearch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is LibraryEvent.OnSearch -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getBooksFromDatabase()
|
getBooksFromDatabase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +243,20 @@ class LibraryViewModel @Inject constructor(
|
||||||
}.map { it.first.copy(category = _state.value.selectedCategory) }
|
}.map { it.first.copy(category = _state.value.selectedCategory) }
|
||||||
updateBooks.execute(books)
|
updateBooks.execute(books)
|
||||||
|
|
||||||
getBooksFromDatabase()
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
books = it.books.map { book ->
|
||||||
|
if (book.second) {
|
||||||
|
book.copy(
|
||||||
|
first = book.first.copy(category = _state.value.selectedCategory)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
book
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onEvent(LibraryEvent.OnRecalculateCategories(_state.value.books))
|
||||||
event.refreshList()
|
event.refreshList()
|
||||||
|
|
||||||
onEvent(LibraryEvent.OnClearSelectedBooks)
|
onEvent(LibraryEvent.OnClearSelectedBooks)
|
||||||
|
|
@ -264,8 +283,7 @@ class LibraryViewModel @Inject constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
showDeleteDialog = false,
|
showDeleteDialog = false
|
||||||
isLoading = true
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,7 +292,12 @@ class LibraryViewModel @Inject constructor(
|
||||||
}.map { it.first }
|
}.map { it.first }
|
||||||
deleteBooks.execute(books)
|
deleteBooks.execute(books)
|
||||||
|
|
||||||
getBooksFromDatabase()
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
books = it.books.filter { book -> !book.second }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onEvent(LibraryEvent.OnRecalculateCategories(_state.value.books))
|
||||||
event.refreshList()
|
event.refreshList()
|
||||||
|
|
||||||
onEvent(LibraryEvent.OnClearSelectedBooks)
|
onEvent(LibraryEvent.OnClearSelectedBooks)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
|
@ -18,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.text.selection.DisableSelection
|
import androidx.compose.foundation.text.selection.DisableSelection
|
||||||
|
|
@ -27,12 +27,12 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -51,13 +51,18 @@ import androidx.compose.ui.text.style.LineBreak
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.components.CustomSelectionContainer
|
import ua.acclorite.book_story.presentation.components.CustomSelectionContainer
|
||||||
import ua.acclorite.book_story.presentation.components.is_messages.IsError
|
import ua.acclorite.book_story.presentation.components.is_messages.IsError
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainSettingsState
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
|
|
@ -70,54 +75,24 @@ import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.Re
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderTopBar
|
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderTopBar
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.settings_bottom_sheet.ReaderSettingsBottomSheet
|
import ua.acclorite.book_story.presentation.screens.reader.components.settings_bottom_sheet.ReaderSettingsBottomSheet
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderScreen(
|
fun ReaderScreenRoot() {
|
||||||
viewModel: ReaderViewModel,
|
val navigator = LocalNavigator.current
|
||||||
mainViewModel: MainViewModel,
|
|
||||||
libraryViewModel: LibraryViewModel,
|
|
||||||
historyViewModel: HistoryViewModel,
|
|
||||||
navigator: Navigator
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current as ComponentActivity
|
val context = LocalContext.current as ComponentActivity
|
||||||
val systemBarsColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(0.85f)
|
|
||||||
|
|
||||||
var loading by remember { mutableStateOf(true) }
|
val viewModel: ReaderViewModel = hiltViewModel()
|
||||||
|
val mainViewModel: MainViewModel = hiltViewModel()
|
||||||
|
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
||||||
|
val historyViewModel: HistoryViewModel = hiltViewModel()
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
val loading = remember { mutableStateOf(true) }
|
||||||
|
|
||||||
val state by viewModel.state.collectAsState()
|
val state = viewModel.state.collectAsState()
|
||||||
val canScroll by remember {
|
val mainState = mainViewModel.state.collectAsState()
|
||||||
derivedStateOf {
|
|
||||||
listState.canScrollBackward && listState.canScrollForward
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val nestedScrollConnection = remember {
|
|
||||||
object : NestedScrollConnection {
|
|
||||||
override fun onPostScroll(
|
|
||||||
consumed: Offset,
|
|
||||||
available: Offset,
|
|
||||||
source: NestedScrollSource
|
|
||||||
): Offset {
|
|
||||||
val velocity = consumed.y
|
|
||||||
|
|
||||||
if ((velocity > 70 || velocity < -70) && state.showMenu && !state.lockMenu) {
|
|
||||||
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onPostScroll(consumed, available, source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val mainState by mainViewModel.state.collectAsState()
|
|
||||||
val fontFamily = remember(mainState.fontFamily) {
|
|
||||||
Constants.FONTS.find {
|
|
||||||
it.id == mainState.fontFamily
|
|
||||||
} ?: Constants.FONTS[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.init(
|
viewModel.init(
|
||||||
|
|
@ -129,13 +104,73 @@ fun ReaderScreen(
|
||||||
},
|
},
|
||||||
listState = listState,
|
listState = listState,
|
||||||
onLoaded = {
|
onLoaded = {
|
||||||
loading = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReaderScreen(
|
||||||
|
state = state,
|
||||||
|
mainState = mainState,
|
||||||
|
listState = listState,
|
||||||
|
loading = loading,
|
||||||
|
navigator = navigator,
|
||||||
|
onEvent = viewModel::onEvent,
|
||||||
|
onMainEvent = mainViewModel::onEvent,
|
||||||
|
onLibraryEvent = libraryViewModel::onEvent,
|
||||||
|
onHistoryEvent = historyViewModel::onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
|
@Composable
|
||||||
|
private fun ReaderScreen(
|
||||||
|
state: State<ReaderState>,
|
||||||
|
mainState: State<MainSettingsState>,
|
||||||
|
listState: LazyListState,
|
||||||
|
loading: State<Boolean>,
|
||||||
|
navigator: Navigator,
|
||||||
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
|
onLibraryEvent: (LibraryEvent) -> Unit,
|
||||||
|
onHistoryEvent: (HistoryEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current as ComponentActivity
|
||||||
|
val systemBarsColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(0.85f)
|
||||||
|
|
||||||
|
val canScroll by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
listState.canScrollBackward && listState.canScrollForward
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val nestedScrollConnection = remember(state) {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override fun onPostScroll(
|
||||||
|
consumed: Offset,
|
||||||
|
available: Offset,
|
||||||
|
source: NestedScrollSource
|
||||||
|
): Offset {
|
||||||
|
val velocity = consumed.y
|
||||||
|
|
||||||
|
if ((velocity > 70 || velocity < -70) && state.value.showMenu && !state.value.lockMenu) {
|
||||||
|
onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onPostScroll(consumed, available, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fontFamily = remember(mainState.value.fontFamily) {
|
||||||
|
Constants.FONTS.find {
|
||||||
|
it.id == mainState.value.fontFamily
|
||||||
|
} ?: Constants.FONTS[0]
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(canScroll) {
|
LaunchedEffect(canScroll) {
|
||||||
if (!canScroll && !state.showMenu && !loading) {
|
if (!canScroll && !state.value.showMenu && !loading.value) {
|
||||||
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,28 +178,28 @@ fun ReaderScreen(
|
||||||
snapshotFlow {
|
snapshotFlow {
|
||||||
listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset
|
listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset
|
||||||
}.debounce(500).collectLatest { items ->
|
}.debounce(500).collectLatest { items ->
|
||||||
if (!loading && state.book.text.isNotEmpty() && listState.layoutInfo.totalItemsCount > 0) {
|
if (!loading.value && state.value.book.text.isNotEmpty() && listState.layoutInfo.totalItemsCount > 0) {
|
||||||
val lastVisibleItemIndex = listState.layoutInfo.visibleItemsInfo.last().index
|
val lastVisibleItemIndex = listState.layoutInfo.visibleItemsInfo.last().index
|
||||||
|
|
||||||
val progress = if (items.first > 0) {
|
val progress = if (items.first > 0) {
|
||||||
if (lastVisibleItemIndex >= (listState.layoutInfo.totalItemsCount - 1)) {
|
if (lastVisibleItemIndex >= (listState.layoutInfo.totalItemsCount - 1)) {
|
||||||
1f
|
1f
|
||||||
} else {
|
} else {
|
||||||
(items.first.toFloat() / (state.book.text.lastIndex).toFloat())
|
(items.first.toFloat() / (state.value.book.text.lastIndex).toFloat())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
0f
|
0f
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnChangeProgress(
|
ReaderEvent.OnChangeProgress(
|
||||||
progress = progress,
|
progress = progress,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
firstVisibleItemIndex = items.first,
|
firstVisibleItemIndex = items.first,
|
||||||
firstVisibleItemOffset = items.second,
|
firstVisibleItemOffset = items.second,
|
||||||
refreshList = { book ->
|
refreshList = { book ->
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(book))
|
onLibraryEvent(LibraryEvent.OnUpdateBook(book))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(book))
|
onHistoryEvent(HistoryEvent.OnUpdateBook(book))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -172,8 +207,12 @@ fun ReaderScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.showSettingsBottomSheet) {
|
if (state.value.showSettingsBottomSheet) {
|
||||||
ReaderSettingsBottomSheet(viewModel = viewModel, mainViewModel = mainViewModel)
|
ReaderSettingsBottomSheet(
|
||||||
|
mainState = mainState,
|
||||||
|
onEvent = onEvent,
|
||||||
|
onMainEvent = onMainEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -182,34 +221,34 @@ fun ReaderScreen(
|
||||||
.nestedScroll(nestedScrollConnection),
|
.nestedScroll(nestedScrollConnection),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = state.showMenu,
|
visible = state.value.showMenu,
|
||||||
enter = slideInVertically { -it },
|
enter = slideInVertically { -it },
|
||||||
exit = slideOutVertically { -it }
|
exit = slideOutVertically { -it }
|
||||||
) {
|
) {
|
||||||
ReaderTopBar(
|
ReaderTopBar(
|
||||||
viewModel = viewModel,
|
state = state,
|
||||||
libraryViewModel = libraryViewModel,
|
onEvent = onEvent,
|
||||||
historyViewModel = historyViewModel,
|
onLibraryUpdateEvent = onLibraryEvent,
|
||||||
navigator = navigator,
|
onHistoryUpdateEvent = onHistoryEvent,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
containerColor = systemBarsColor
|
containerColor = systemBarsColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = state.showMenu,
|
visible = state.value.showMenu,
|
||||||
enter = slideInVertically { it },
|
enter = slideInVertically { it },
|
||||||
exit = slideOutVertically { it }
|
exit = slideOutVertically { it }
|
||||||
) {
|
) {
|
||||||
ReaderBottomBar(
|
ReaderBottomBar(
|
||||||
viewModel = viewModel,
|
state = state,
|
||||||
libraryViewModel = libraryViewModel,
|
onEvent = onEvent,
|
||||||
historyViewModel = historyViewModel,
|
onLibraryUpdateEvent = onLibraryEvent,
|
||||||
|
onHistoryUpdateEvent = onHistoryEvent,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
navigator = navigator,
|
containerColor = systemBarsColor
|
||||||
systemBarsColor = systemBarsColor
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,7 +264,7 @@ fun ReaderScreen(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTranslateRequested = { textToTranslate ->
|
onTranslateRequested = { textToTranslate ->
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnTranslateText(
|
ReaderEvent.OnTranslateText(
|
||||||
textToTranslate,
|
textToTranslate,
|
||||||
context,
|
context,
|
||||||
|
|
@ -240,7 +279,7 @@ fun ReaderScreen(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onDictionaryRequested = { textToDefine ->
|
onDictionaryRequested = { textToDefine ->
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnOpenDictionary(
|
ReaderEvent.OnOpenDictionary(
|
||||||
textToDefine,
|
textToDefine,
|
||||||
context,
|
context,
|
||||||
|
|
@ -260,13 +299,13 @@ fun ReaderScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.then(
|
.then(
|
||||||
if (!loading && toolbarShowed) {
|
if (!loading.value && toolbarShowed) {
|
||||||
Modifier
|
Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
indication = null,
|
indication = null,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnShowHideMenu(context = context)
|
ReaderEvent.OnShowHideMenu(context = context)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -276,28 +315,29 @@ fun ReaderScreen(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (state.book.text.isNotEmpty()) {
|
if (state.value.book.text.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
DisableSelection {
|
DisableSelection {
|
||||||
ReaderStartItem(viewModel = viewModel)
|
ReaderStartItem(state = state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
state.book.text, key = { _, key -> key.id }
|
state.value.book.text, key = { _, key -> key.id }
|
||||||
) { index, line ->
|
) { index, line ->
|
||||||
val text = remember(mainState.paragraphIndentation) {
|
val text = remember(mainState.value.paragraphIndentation) {
|
||||||
"${if (mainState.paragraphIndentation!!) " " else ""}${line.line}"
|
"${if (mainState.value.paragraphIndentation!!) " " else ""}${line.line}"
|
||||||
}
|
}
|
||||||
val fontColor = remember(mainState.fontColor) {
|
val fontColor = remember(mainState.value.fontColor) {
|
||||||
Color(mainState.fontColor!!.toULong())
|
Color(mainState.value.fontColor!!.toULong())
|
||||||
}
|
}
|
||||||
val backgroundColor = remember(mainState.backgroundColor) {
|
val backgroundColor = remember(mainState.value.backgroundColor) {
|
||||||
Color(mainState.backgroundColor!!.toULong())
|
Color(mainState.value.backgroundColor!!.toULong())
|
||||||
}
|
}
|
||||||
val lineHeight = remember(mainState.fontSize, mainState.lineHeight) {
|
val lineHeight =
|
||||||
(mainState.fontSize!! + mainState.lineHeight!!).sp
|
remember(mainState.value.fontSize, mainState.value.lineHeight) {
|
||||||
|
(mainState.value.fontSize!! + mainState.value.lineHeight!!).sp
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -308,8 +348,8 @@ fun ReaderScreen(
|
||||||
top = if (index == 0) 18.dp else 0.dp,
|
top = if (index == 0) 18.dp else 0.dp,
|
||||||
start = 18.dp,
|
start = 18.dp,
|
||||||
end = 18.dp,
|
end = 18.dp,
|
||||||
bottom = if (index == state.book.text.lastIndex) 18.dp
|
bottom = if (index == state.value.book.text.lastIndex) 18.dp
|
||||||
else (mainState.paragraphHeight!! * 3).dp
|
else (mainState.value.paragraphHeight!! * 3).dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -319,25 +359,25 @@ fun ReaderScreen(
|
||||||
lineBreak = LineBreak.Paragraph
|
lineBreak = LineBreak.Paragraph
|
||||||
),
|
),
|
||||||
fontFamily = fontFamily.font,
|
fontFamily = fontFamily.font,
|
||||||
fontStyle = when (mainState.isItalic!!) {
|
fontStyle = when (mainState.value.isItalic!!) {
|
||||||
true -> FontStyle.Italic
|
true -> FontStyle.Italic
|
||||||
false -> FontStyle.Normal
|
false -> FontStyle.Normal
|
||||||
},
|
},
|
||||||
fontSize = mainState.fontSize!!.sp,
|
fontSize = mainState.value.fontSize!!.sp,
|
||||||
lineHeight = lineHeight
|
lineHeight = lineHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.book.text.isNotEmpty()) {
|
if (state.value.book.text.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
DisableSelection {
|
DisableSelection {
|
||||||
ReaderEndItem(
|
ReaderEndItem(
|
||||||
libraryViewModel = libraryViewModel,
|
state = state,
|
||||||
historyViewModel = historyViewModel,
|
onEvent = onEvent,
|
||||||
viewModel = viewModel,
|
onLibraryEvent = onLibraryEvent,
|
||||||
listState = listState,
|
onHistoryUpdateEvent = onHistoryEvent,
|
||||||
navigator = navigator
|
listState = listState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -346,7 +386,7 @@ fun ReaderScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || state.errorMessage != null) {
|
if (loading.value || state.value.errorMessage != null) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -354,20 +394,20 @@ fun ReaderScreen(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
if (!loading && state.errorMessage != null) {
|
if (!loading.value) {
|
||||||
IsError(
|
IsError(
|
||||||
errorMessage = state.errorMessage!!.asString(),
|
errorMessage = state.value.errorMessage!!.asString(),
|
||||||
icon = painterResource(id = R.drawable.error),
|
icon = painterResource(id = R.drawable.error),
|
||||||
actionTitle = stringResource(id = R.string.go_back),
|
actionTitle = stringResource(id = R.string.go_back),
|
||||||
action = {
|
action = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
onLibraryEvent(LibraryEvent.OnLoadList)
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnGoBack(
|
ReaderEvent.OnGoBack(
|
||||||
context,
|
context,
|
||||||
navigator,
|
navigator,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
listState = listState,
|
listState = listState,
|
||||||
navigate = {
|
navigate = {
|
||||||
|
|
@ -396,13 +436,13 @@ fun ReaderScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnGoBack(
|
ReaderEvent.OnGoBack(
|
||||||
context,
|
context,
|
||||||
navigator,
|
navigator,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
listState = listState,
|
listState = listState,
|
||||||
navigate = {
|
navigate = {
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -32,14 +31,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reader end item. Displays at the end of the book.
|
* Reader end item. Displays at the end of the book.
|
||||||
|
|
@ -47,18 +44,18 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderEndItem(
|
fun ReaderEndItem(
|
||||||
libraryViewModel: LibraryViewModel,
|
state: State<ReaderState>,
|
||||||
historyViewModel: HistoryViewModel,
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
viewModel: ReaderViewModel,
|
onLibraryEvent: (LibraryEvent) -> Unit,
|
||||||
|
onHistoryUpdateEvent: (HistoryEvent.OnUpdateBook) -> Unit,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
navigator: Navigator
|
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val navigator = LocalNavigator.current
|
||||||
val context = LocalContext.current as ComponentActivity
|
val context = LocalContext.current as ComponentActivity
|
||||||
|
|
||||||
val buttonText = remember {
|
val buttonText = remember {
|
||||||
context.getString(
|
context.getString(
|
||||||
if (state.book.category != Category.ALREADY_READ) R.string.move_to_read
|
if (state.value.book.category != Category.ALREADY_READ) R.string.move_to_read
|
||||||
else R.string.back_to_library
|
else R.string.back_to_library
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +73,7 @@ fun ReaderEndItem(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(id = R.string.thanks_for_reading, state.book.title),
|
stringResource(id = R.string.thanks_for_reading, state.value.book.title),
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
|
@ -86,8 +83,8 @@ fun ReaderEndItem(
|
||||||
Text(
|
Text(
|
||||||
stringResource(
|
stringResource(
|
||||||
id = R.string.letters_and_words,
|
id = R.string.letters_and_words,
|
||||||
state.book.letters,
|
state.value.book.letters,
|
||||||
state.book.words
|
state.value.book.words
|
||||||
),
|
),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
|
@ -96,15 +93,15 @@ fun ReaderEndItem(
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (state.book.category != Category.ALREADY_READ) {
|
if (state.value.book.category != Category.ALREADY_READ) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnMoveBookToAlreadyRead(
|
ReaderEvent.OnMoveBookToAlreadyRead(
|
||||||
onUpdateCategories = {
|
onUpdateCategories = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
updatePage = {
|
updatePage = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
onLibraryEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
||||||
},
|
},
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
|
|
@ -118,13 +115,13 @@ fun ReaderEndItem(
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
} else {
|
} else {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnGoBack(
|
ReaderEvent.OnGoBack(
|
||||||
context,
|
context,
|
||||||
navigator,
|
navigator,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
listState = listState,
|
listState = listState,
|
||||||
navigate = {
|
navigate = {
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,7 @@ import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
|
@ -33,15 +32,14 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.CustomCoverImage
|
import ua.acclorite.book_story.presentation.components.CustomCoverImage
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reader start item. Displays at the beginning of the book.
|
* Reader start item. Displays at the beginning of the book.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderStartItem(viewModel: ReaderViewModel) {
|
fun ReaderStartItem(state: State<ReaderState>) {
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -61,9 +59,9 @@ fun ReaderStartItem(viewModel: ReaderViewModel) {
|
||||||
.clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
) {
|
) {
|
||||||
if (state.book.coverImage != null) {
|
if (state.value.book.coverImage != null) {
|
||||||
CustomCoverImage(
|
CustomCoverImage(
|
||||||
uri = state.book.coverImage!!,
|
uri = state.value.book.coverImage!!,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
|
@ -85,7 +83,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) {
|
||||||
|
|
||||||
Column(verticalArrangement = Arrangement.Center) {
|
Column(verticalArrangement = Arrangement.Center) {
|
||||||
Text(
|
Text(
|
||||||
state.book.title,
|
state.value.book.title,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.SliderDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -23,32 +23,30 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.removeDigits
|
import ua.acclorite.book_story.presentation.data.removeDigits
|
||||||
import ua.acclorite.book_story.presentation.data.removeTrailingZero
|
import ua.acclorite.book_story.presentation.data.removeTrailingZero
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reader bottom bar. Has a slider to change progress.
|
* Reader bottom bar. Has a slider to change progress.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderBottomBar(
|
fun ReaderBottomBar(
|
||||||
viewModel: ReaderViewModel,
|
state: State<ReaderState>,
|
||||||
libraryViewModel: LibraryViewModel,
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
historyViewModel: HistoryViewModel,
|
onLibraryUpdateEvent: (LibraryEvent.OnUpdateBook) -> Unit,
|
||||||
|
onHistoryUpdateEvent: (HistoryEvent.OnUpdateBook) -> Unit,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
navigator: Navigator,
|
containerColor: Color
|
||||||
systemBarsColor: Color
|
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val navigator = LocalNavigator.current
|
||||||
val progress by remember(state.book.progress) {
|
val progress by remember(state.value.book.progress) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
(state.book.progress * 100)
|
(state.value.book.progress * 100)
|
||||||
.toDouble()
|
.toDouble()
|
||||||
.removeDigits(4)
|
.removeDigits(4)
|
||||||
.removeTrailingZero()
|
.removeTrailingZero()
|
||||||
|
|
@ -59,7 +57,7 @@ fun ReaderBottomBar(
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(systemBarsColor)
|
.background(containerColor)
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
indication = null,
|
indication = null,
|
||||||
|
|
@ -78,20 +76,20 @@ fun ReaderBottomBar(
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(3.dp))
|
Spacer(modifier = Modifier.height(3.dp))
|
||||||
Slider(
|
Slider(
|
||||||
value = state.book.progress,
|
value = state.value.book.progress,
|
||||||
enabled = !state.lockMenu,
|
enabled = !state.value.lockMenu,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (listState.layoutInfo.totalItemsCount > 0) {
|
if (listState.layoutInfo.totalItemsCount > 0) {
|
||||||
viewModel.onEvent(ReaderEvent.OnScroll(listState, it))
|
onEvent(ReaderEvent.OnScroll(listState, it))
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnChangeProgress(
|
ReaderEvent.OnChangeProgress(
|
||||||
progress = it,
|
progress = it,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
firstVisibleItemIndex = listState.firstVisibleItemIndex,
|
firstVisibleItemIndex = listState.firstVisibleItemIndex,
|
||||||
firstVisibleItemOffset = 0,
|
firstVisibleItemOffset = 0,
|
||||||
refreshList = { book ->
|
refreshList = { book ->
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(book))
|
onLibraryUpdateEvent(LibraryEvent.OnUpdateBook(book))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(book))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(book))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -28,16 +28,14 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.data.removeDigits
|
import ua.acclorite.book_story.presentation.data.removeDigits
|
||||||
import ua.acclorite.book_story.presentation.data.removeTrailingZero
|
import ua.acclorite.book_story.presentation.data.removeTrailingZero
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reader top bar. Displays title of the book.
|
* Reader top bar. Displays title of the book.
|
||||||
|
|
@ -45,19 +43,19 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderTopBar(
|
fun ReaderTopBar(
|
||||||
viewModel: ReaderViewModel,
|
state: State<ReaderState>,
|
||||||
libraryViewModel: LibraryViewModel,
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
historyViewModel: HistoryViewModel,
|
onLibraryUpdateEvent: (LibraryEvent.OnUpdateBook) -> Unit,
|
||||||
|
onHistoryUpdateEvent: (HistoryEvent.OnUpdateBook) -> Unit,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
navigator: Navigator,
|
|
||||||
containerColor: Color
|
containerColor: Color
|
||||||
) {
|
) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
val context = LocalContext.current as ComponentActivity
|
val context = LocalContext.current as ComponentActivity
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
|
|
||||||
val progress by remember(state.book.progress) {
|
val progress by remember(state.value.book.progress) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
(state.book.progress * 100)
|
(state.value.book.progress * 100)
|
||||||
.toDouble()
|
.toDouble()
|
||||||
.removeDigits(2)
|
.removeDigits(2)
|
||||||
.removeTrailingZero()
|
.removeTrailingZero()
|
||||||
|
|
@ -72,13 +70,13 @@ fun ReaderTopBar(
|
||||||
contentDescription = R.string.go_back_content_desc,
|
contentDescription = R.string.go_back_content_desc,
|
||||||
disableOnClick = true
|
disableOnClick = true
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnGoBack(
|
ReaderEvent.OnGoBack(
|
||||||
context,
|
context,
|
||||||
navigator,
|
navigator,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryUpdateEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
listState = listState,
|
listState = listState,
|
||||||
navigate = {
|
navigate = {
|
||||||
|
|
@ -91,7 +89,7 @@ fun ReaderTopBar(
|
||||||
title = {
|
title = {
|
||||||
Column(verticalArrangement = Arrangement.Center) {
|
Column(verticalArrangement = Arrangement.Center) {
|
||||||
Text(
|
Text(
|
||||||
state.book.title,
|
state.value.book.title,
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = FontFamily.Default,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
|
|
@ -99,17 +97,17 @@ fun ReaderTopBar(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
enabled = !state.lockMenu,
|
enabled = !state.value.lockMenu,
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
indication = null,
|
indication = null,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnGoBack(
|
ReaderEvent.OnGoBack(
|
||||||
context,
|
context,
|
||||||
navigator,
|
navigator,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryUpdateEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
onHistoryUpdateEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
listState = listState,
|
listState = listState,
|
||||||
navigate = {
|
navigate = {
|
||||||
|
|
@ -141,9 +139,9 @@ fun ReaderTopBar(
|
||||||
icon = Icons.Default.Settings,
|
icon = Icons.Default.Settings,
|
||||||
contentDescription = R.string.open_reader_settings_content_desc,
|
contentDescription = R.string.open_reader_settings_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !state.lockMenu
|
enabled = !state.value.lockMenu
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(ReaderEvent.OnShowHideSettingsBottomSheet)
|
onEvent(ReaderEvent.OnShowHideSettingsBottomSheet)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -39,9 +39,8 @@ import ua.acclorite.book_story.domain.model.ButtonItem
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainSettingsState
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.CheckboxWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.CheckboxWithTitle
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
|
||||||
|
|
@ -54,7 +53,11 @@ import ua.acclorite.book_story.presentation.screens.settings.components.SliderWi
|
||||||
ExperimentalMaterial3Api::class
|
ExperimentalMaterial3Api::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderViewModel) {
|
fun ReaderSettingsBottomSheet(
|
||||||
|
mainState: State<MainSettingsState>,
|
||||||
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
|
onMainEvent: (MainEvent) -> Unit
|
||||||
|
) {
|
||||||
val pagerState = rememberPagerState { 2 }
|
val pagerState = rememberPagerState { 2 }
|
||||||
val currentPage by remember { derivedStateOf { pagerState.currentPage } }
|
val currentPage by remember { derivedStateOf { pagerState.currentPage } }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -62,11 +65,9 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
val navigationBarPadding =
|
val navigationBarPadding =
|
||||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||||
|
|
||||||
val state by mainViewModel.state.collectAsState()
|
val fontFamily = remember(mainState.value.fontFamily) {
|
||||||
|
|
||||||
val fontFamily = remember(state.fontFamily) {
|
|
||||||
Constants.FONTS.find {
|
Constants.FONTS.find {
|
||||||
it.id == state.fontFamily
|
it.id == mainState.value.fontFamily
|
||||||
} ?: Constants.FONTS[0]
|
} ?: Constants.FONTS[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +90,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(currentPage) {
|
LaunchedEffect(currentPage) {
|
||||||
viewModel.onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnShowHideMenu(
|
ReaderEvent.OnShowHideMenu(
|
||||||
currentPage != 1,
|
currentPage != 1,
|
||||||
context as ComponentActivity
|
context as ComponentActivity
|
||||||
|
|
@ -106,13 +107,16 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.fillMaxHeight(animatedHeight),
|
.fillMaxHeight(animatedHeight),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
viewModel.onEvent(ReaderEvent.OnShowHideSettingsBottomSheet)
|
onEvent(ReaderEvent.OnShowHideSettingsBottomSheet)
|
||||||
},
|
},
|
||||||
sheetState = rememberModalBottomSheetState(true),
|
sheetState = rememberModalBottomSheetState(true),
|
||||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
) {
|
) {
|
||||||
ReaderSettingsBottomSheetTabRow(viewModel = viewModel, pagerState = pagerState)
|
ReaderSettingsBottomSheetTabRow(
|
||||||
|
onEvent = onEvent,
|
||||||
|
pagerState = pagerState
|
||||||
|
)
|
||||||
|
|
||||||
HorizontalPager(state = pagerState) { page ->
|
HorizontalPager(state = pagerState) { page ->
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
|
|
@ -144,7 +148,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
mainViewModel.onEvent(MainEvent.OnChangeFontFamily(it.id))
|
onMainEvent(MainEvent.OnChangeFontFamily(it.id))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +163,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
fontFamily = fontFamily.font,
|
fontFamily = fontFamily.font,
|
||||||
fontStyle = FontStyle.Normal
|
fontStyle = FontStyle.Normal
|
||||||
),
|
),
|
||||||
!state.isItalic!!
|
!mainState.value.isItalic!!
|
||||||
),
|
),
|
||||||
ButtonItem(
|
ButtonItem(
|
||||||
"italic",
|
"italic",
|
||||||
|
|
@ -168,11 +172,11 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
fontFamily = fontFamily.font,
|
fontFamily = fontFamily.font,
|
||||||
fontStyle = FontStyle.Italic
|
fontStyle = FontStyle.Italic
|
||||||
),
|
),
|
||||||
state.isItalic!!
|
mainState.value.isItalic!!
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onClick = {
|
onClick = {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeFontStyle(
|
MainEvent.OnChangeFontStyle(
|
||||||
when (it.id) {
|
when (it.id) {
|
||||||
"italic" -> true
|
"italic" -> true
|
||||||
|
|
@ -185,12 +189,12 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = state.fontSize!! to "pt",
|
value = mainState.value.fontSize!! to "pt",
|
||||||
fromValue = 10,
|
fromValue = 10,
|
||||||
toValue = 35,
|
toValue = 35,
|
||||||
title = stringResource(id = R.string.font_size_option),
|
title = stringResource(id = R.string.font_size_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeFontSize(it)
|
MainEvent.OnChangeFontSize(it)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -210,12 +214,12 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = state.lineHeight!! to "pt",
|
value = mainState.value.lineHeight!! to "pt",
|
||||||
fromValue = 1,
|
fromValue = 1,
|
||||||
toValue = 16,
|
toValue = 16,
|
||||||
title = stringResource(id = R.string.line_height_option),
|
title = stringResource(id = R.string.line_height_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeLineHeight(it)
|
MainEvent.OnChangeLineHeight(it)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -223,12 +227,12 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = state.paragraphHeight!! to "pt",
|
value = mainState.value.paragraphHeight!! to "pt",
|
||||||
fromValue = 0,
|
fromValue = 0,
|
||||||
toValue = 24,
|
toValue = 24,
|
||||||
title = stringResource(id = R.string.paragraph_height_option),
|
title = stringResource(id = R.string.paragraph_height_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeParagraphHeight(it)
|
MainEvent.OnChangeParagraphHeight(it)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -236,11 +240,11 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
CheckboxWithTitle(
|
CheckboxWithTitle(
|
||||||
selected = state.paragraphIndentation!!,
|
selected = mainState.value.paragraphIndentation!!,
|
||||||
title = stringResource(id = R.string.paragraph_indentation_option)
|
title = stringResource(id = R.string.paragraph_indentation_option)
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeParagraphIndentation(!state.paragraphIndentation!!)
|
MainEvent.OnChangeParagraphIndentation(!mainState.value.paragraphIndentation!!)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,10 +256,10 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
value = Color(state.backgroundColor!!.toULong()),
|
value = Color(mainState.value.backgroundColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.background_color_option),
|
title = stringResource(id = R.string.background_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeBackgroundColor(
|
MainEvent.OnChangeBackgroundColor(
|
||||||
it.value.toLong()
|
it.value.toLong()
|
||||||
)
|
)
|
||||||
|
|
@ -265,10 +269,10 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
value = Color(state.fontColor!!.toULong()),
|
value = Color(mainState.value.fontColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.font_color_option),
|
title = stringResource(id = R.string.font_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeFontColor(
|
MainEvent.OnChangeFontColor(
|
||||||
it.value.toLong()
|
it.value.toLong()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,16 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings bottom sheet tab row. It is used to switch between categories.
|
* Settings bottom sheet tab row. It is used to switch between categories.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderSettingsBottomSheetTabRow(viewModel: ReaderViewModel, pagerState: PagerState) {
|
fun ReaderSettingsBottomSheetTabRow(
|
||||||
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
|
pagerState: PagerState
|
||||||
|
) {
|
||||||
val tabItems = listOf(
|
val tabItems = listOf(
|
||||||
stringResource(id = R.string.general_tab),
|
stringResource(id = R.string.general_tab),
|
||||||
stringResource(id = R.string.color_tab)
|
stringResource(id = R.string.color_tab)
|
||||||
|
|
@ -37,7 +39,7 @@ fun ReaderSettingsBottomSheetTabRow(viewModel: ReaderViewModel, pagerState: Page
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
selected = pagerState.currentPage == index,
|
selected = pagerState.currentPage == index,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onEvent(ReaderEvent.OnScrollToSettingsPage(index, pagerState))
|
onEvent(ReaderEvent.OnScrollToSettingsPage(index, pagerState))
|
||||||
},
|
},
|
||||||
selectedContentColor = MaterialTheme.colorScheme.primary,
|
selectedContentColor = MaterialTheme.colorScheme.primary,
|
||||||
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ package ua.acclorite.book_story.presentation.screens.reader.data
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed class ReaderEvent {
|
sealed class ReaderEvent {
|
||||||
data class OnTextIsEmpty(val onLoaded: () -> Unit) : ReaderEvent()
|
data class OnTextIsEmpty(val onLoaded: () -> Unit) : ReaderEvent()
|
||||||
data class OnLoadText(
|
data class OnLoadText(
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,25 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.SettingsCategoryItem
|
import ua.acclorite.book_story.presentation.screens.settings.components.SettingsCategoryItem
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreenRoot() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
|
||||||
|
SettingsScreen(navigator = navigator)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
private fun SettingsScreen(
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.settings.components
|
package ua.acclorite.book_story.presentation.screens.settings.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checkbox with title.
|
* Checkbox with title.
|
||||||
|
|
@ -21,24 +24,58 @@ import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
fun CheckboxWithTitle(
|
fun CheckboxWithTitle(
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
title: String,
|
title: String,
|
||||||
|
description: String? = null,
|
||||||
horizontalPadding: Dp = 18.dp,
|
horizontalPadding: Dp = 18.dp,
|
||||||
verticalPadding: Dp = 12.dp,
|
verticalPadding: Dp = 8.dp,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.clickable { onClick() }
|
||||||
.clickable {
|
|
||||||
onClick()
|
|
||||||
}
|
|
||||||
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
|
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Checkbox(
|
Column(Modifier.weight(1f), verticalArrangement = Arrangement.Center) {
|
||||||
checked = selected,
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
onCheckedChange = null,
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
description?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(24.dp))
|
|
||||||
CategoryTitle(title = title, padding = 0.dp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = selected,
|
||||||
|
onCheckedChange = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Row(
|
||||||
|
// Modifier
|
||||||
|
// .fillMaxWidth()
|
||||||
|
// .clickable {
|
||||||
|
// onClick()
|
||||||
|
// }
|
||||||
|
// .padding(horizontal = horizontalPadding, vertical = verticalPadding),
|
||||||
|
// verticalAlignment = Alignment.CenterVertically,
|
||||||
|
// ) {
|
||||||
|
// Checkbox(
|
||||||
|
// checked = selected,
|
||||||
|
// onCheckedChange = null,
|
||||||
|
// )
|
||||||
|
// Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
// CategoryTitle(title = title, padding = 0.dp)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
|
@ -61,7 +60,6 @@ fun SettingsCategoryItem(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
|
||||||
Text(
|
Text(
|
||||||
description,
|
description,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
@file:OptIn(ExperimentalPermissionsApi::class)
|
||||||
|
|
||||||
|
package ua.acclorite.book_story.presentation.screens.settings.data
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
import com.google.accompanist.permissions.PermissionState
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed class SettingsEvent {
|
||||||
|
data class OnGeneralChangeCheckForUpdates(
|
||||||
|
val enable: Boolean,
|
||||||
|
val activity: ComponentActivity,
|
||||||
|
val notificationsPermissionState: PermissionState,
|
||||||
|
val onChangeCheckForUpdates: (Boolean) -> Unit
|
||||||
|
) : SettingsEvent()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.settings.data
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
import com.google.accompanist.permissions.isGranted
|
||||||
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
|
@HiltViewModel
|
||||||
|
class SettingsViewModel @Inject constructor(
|
||||||
|
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private var notificationsPermissionJob: Job? = null
|
||||||
|
|
||||||
|
fun onEvent(event: SettingsEvent) {
|
||||||
|
when (event) {
|
||||||
|
|
||||||
|
is SettingsEvent.OnGeneralChangeCheckForUpdates -> {
|
||||||
|
if (!event.enable) {
|
||||||
|
event.onChangeCheckForUpdates(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
event.onChangeCheckForUpdates(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.notificationsPermissionState.status.isGranted) {
|
||||||
|
event.onChangeCheckForUpdates(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.notificationsPermissionState.status.shouldShowRationale) {
|
||||||
|
event.notificationsPermissionState.launchPermissionRequest()
|
||||||
|
} else {
|
||||||
|
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||||
|
intent.putExtra(Settings.EXTRA_APP_PACKAGE, event.activity.packageName)
|
||||||
|
|
||||||
|
if (intent.resolveActivity(event.activity.packageManager) != null) {
|
||||||
|
event.activity.startActivity(intent)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationsPermissionJob?.cancel()
|
||||||
|
notificationsPermissionJob = viewModelScope.launch {
|
||||||
|
for (i in 1..10) {
|
||||||
|
if (!event.notificationsPermissionState.status.isGranted) {
|
||||||
|
delay(1000)
|
||||||
|
yield()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
event.onChangeCheckForUpdates(true)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -16,23 +16,25 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.ButtonItem
|
import ua.acclorite.book_story.domain.model.ButtonItem
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
import ua.acclorite.book_story.presentation.components.collapsibleScrollBehaviorWithLazyListState
|
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
|
||||||
|
import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainSettingsState
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
|
||||||
|
|
@ -47,20 +49,33 @@ import ua.acclorite.book_story.presentation.ui.ThemeContrast
|
||||||
import ua.acclorite.book_story.presentation.ui.isDark
|
import ua.acclorite.book_story.presentation.ui.isDark
|
||||||
import ua.acclorite.book_story.presentation.ui.isPureDark
|
import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppearanceSettingsRoot() {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val mainViewModel: MainViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
val state = mainViewModel.state.collectAsState()
|
||||||
|
|
||||||
|
AppearanceSettings(
|
||||||
|
state = state,
|
||||||
|
navigator = navigator,
|
||||||
|
onMainEvent = mainViewModel::onEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppearanceSettings(
|
private fun AppearanceSettings(
|
||||||
mainViewModel: MainViewModel,
|
state: State<MainSettingsState>,
|
||||||
navigator: Navigator
|
navigator: Navigator,
|
||||||
|
onMainEvent: (MainEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val scrollState = TopAppBarDefaults.collapsibleScrollBehaviorWithLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
|
||||||
|
val themeContrastTheme = remember { mutableStateOf(state.value.theme!!) }
|
||||||
|
|
||||||
val state by mainViewModel.state.collectAsState()
|
LaunchedEffect(state.value.theme) {
|
||||||
var themeContrastTheme by remember { mutableStateOf(state.theme!!) }
|
if (themeContrastTheme.value != state.value.theme && state.value.theme != Theme.DYNAMIC) {
|
||||||
|
themeContrastTheme.value = state.value.theme!!
|
||||||
LaunchedEffect(state.theme) {
|
|
||||||
if (themeContrastTheme != state.theme && state.theme != Theme.DYNAMIC) {
|
|
||||||
themeContrastTheme = state.theme!!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,11 +143,11 @@ fun AppearanceSettings(
|
||||||
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
|
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
|
||||||
},
|
},
|
||||||
MaterialTheme.typography.labelLarge,
|
MaterialTheme.typography.labelLarge,
|
||||||
it == state.darkTheme
|
it == state.value.darkTheme
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeDarkTheme(
|
MainEvent.OnChangeDarkTheme(
|
||||||
it.id
|
it.id
|
||||||
)
|
)
|
||||||
|
|
@ -143,27 +158,28 @@ fun AppearanceSettings(
|
||||||
item {
|
item {
|
||||||
AppearanceSettingsThemeSwitcher(
|
AppearanceSettingsThemeSwitcher(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
mainViewModel = mainViewModel
|
state = state,
|
||||||
|
onMainEvent = onMainEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
BookStoryTheme(
|
BookStoryTheme(
|
||||||
theme = themeContrastTheme,
|
theme = themeContrastTheme.value,
|
||||||
isDark = state.darkTheme!!.isDark(),
|
isDark = state.value.darkTheme!!.isDark(),
|
||||||
isPureDark = state.pureDark!!.isPureDark(context = LocalContext.current),
|
isPureDark = state.value.pureDark!!.isPureDark(context = LocalContext.current),
|
||||||
themeContrast = state.themeContrast!!
|
themeContrast = state.value.themeContrast!!
|
||||||
) {
|
) {
|
||||||
SlidingTransition(
|
SlidingTransition(
|
||||||
modifier = Modifier.animateItem(
|
modifier = Modifier.animateItem(
|
||||||
fadeInSpec = null,
|
fadeInSpec = null,
|
||||||
fadeOutSpec = null
|
fadeOutSpec = null
|
||||||
),
|
),
|
||||||
visible = state.theme != Theme.DYNAMIC,
|
visible = state.value.theme != Theme.DYNAMIC,
|
||||||
) {
|
) {
|
||||||
SegmentedButtonWithTitle(
|
SegmentedButtonWithTitle(
|
||||||
title = stringResource(id = R.string.theme_contrast_option),
|
title = stringResource(id = R.string.theme_contrast_option),
|
||||||
locked = state.theme != Theme.DYNAMIC,
|
locked = state.value.theme != Theme.DYNAMIC,
|
||||||
buttons = ThemeContrast.entries.map {
|
buttons = ThemeContrast.entries.map {
|
||||||
ButtonItem(
|
ButtonItem(
|
||||||
it.toString(),
|
it.toString(),
|
||||||
|
|
@ -173,11 +189,11 @@ fun AppearanceSettings(
|
||||||
ThemeContrast.HIGH -> stringResource(id = R.string.theme_contrast_high)
|
ThemeContrast.HIGH -> stringResource(id = R.string.theme_contrast_high)
|
||||||
},
|
},
|
||||||
MaterialTheme.typography.labelLarge,
|
MaterialTheme.typography.labelLarge,
|
||||||
it == state.themeContrast
|
it == state.value.themeContrast
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeThemeContrast(
|
MainEvent.OnChangeThemeContrast(
|
||||||
it.id
|
it.id
|
||||||
)
|
)
|
||||||
|
|
@ -193,7 +209,7 @@ fun AppearanceSettings(
|
||||||
fadeInSpec = null,
|
fadeInSpec = null,
|
||||||
fadeOutSpec = null
|
fadeOutSpec = null
|
||||||
),
|
),
|
||||||
visible = state.darkTheme!!.isDark(),
|
visible = state.value.darkTheme!!.isDark(),
|
||||||
) {
|
) {
|
||||||
SegmentedButtonWithTitle(
|
SegmentedButtonWithTitle(
|
||||||
title = stringResource(id = R.string.pure_dark_option),
|
title = stringResource(id = R.string.pure_dark_option),
|
||||||
|
|
@ -207,11 +223,11 @@ fun AppearanceSettings(
|
||||||
PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver)
|
PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver)
|
||||||
},
|
},
|
||||||
MaterialTheme.typography.labelLarge,
|
MaterialTheme.typography.labelLarge,
|
||||||
it == state.pureDark
|
it == state.value.pureDark
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangePureDark(
|
MainEvent.OnChangePureDark(
|
||||||
it.id
|
it.id
|
||||||
)
|
)
|
||||||
|
|
@ -241,10 +257,10 @@ fun AppearanceSettings(
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
value = Color(state.backgroundColor!!.toULong()),
|
value = Color(state.value.backgroundColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.background_color_option),
|
title = stringResource(id = R.string.background_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeBackgroundColor(
|
MainEvent.OnChangeBackgroundColor(
|
||||||
it.value.toLong()
|
it.value.toLong()
|
||||||
)
|
)
|
||||||
|
|
@ -255,10 +271,10 @@ fun AppearanceSettings(
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
value = Color(state.fontColor!!.toULong()),
|
value = Color(state.value.fontColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.font_color_option),
|
title = stringResource(id = R.string.font_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeFontColor(
|
MainEvent.OnChangeFontColor(
|
||||||
it.value.toLong()
|
it.value.toLong()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -22,7 +21,7 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainSettingsState
|
||||||
import ua.acclorite.book_story.presentation.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
import ua.acclorite.book_story.presentation.ui.isDark
|
import ua.acclorite.book_story.presentation.ui.isDark
|
||||||
import ua.acclorite.book_story.presentation.ui.isPureDark
|
import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||||
|
|
@ -33,10 +32,10 @@ import ua.acclorite.book_story.presentation.ui.isPureDark
|
||||||
@Composable
|
@Composable
|
||||||
fun AppearanceSettingsThemeSwitcher(
|
fun AppearanceSettingsThemeSwitcher(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
mainViewModel: MainViewModel,
|
state: State<MainSettingsState>,
|
||||||
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
verticalPadding: Dp = 8.dp
|
verticalPadding: Dp = 8.dp
|
||||||
) {
|
) {
|
||||||
val state by mainViewModel.state.collectAsState()
|
|
||||||
val themes = remember {
|
val themes = remember {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Constants.THEMES
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Constants.THEMES
|
||||||
else Constants.THEMES.dropWhile { it.first == Theme.DYNAMIC }
|
else Constants.THEMES.dropWhile { it.first == Theme.DYNAMIC }
|
||||||
|
|
@ -64,12 +63,12 @@ fun AppearanceSettingsThemeSwitcher(
|
||||||
|
|
||||||
AppearanceSettingsThemeSwitcherItem(
|
AppearanceSettingsThemeSwitcherItem(
|
||||||
theme = themeEntry,
|
theme = themeEntry,
|
||||||
darkTheme = state.darkTheme!!.isDark(),
|
darkTheme = state.value.darkTheme!!.isDark(),
|
||||||
themeContrast = state.themeContrast!!,
|
themeContrast = state.value.themeContrast!!,
|
||||||
isPureDark = state.pureDark!!.isPureDark(context = LocalContext.current),
|
isPureDark = state.value.pureDark!!.isPureDark(context = LocalContext.current),
|
||||||
selected = state.theme == themeEntry.first
|
selected = state.value.theme == themeEntry.first
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
onMainEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index == themes.lastIndex) {
|
if (index == themes.lastIndex) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher
|
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
|
@ -34,6 +33,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
import ua.acclorite.book_story.presentation.ui.ThemeContrast
|
import ua.acclorite.book_story.presentation.ui.ThemeContrast
|
||||||
import ua.acclorite.book_story.presentation.ui.animatedColorScheme
|
import ua.acclorite.book_story.presentation.ui.animatedColorScheme
|
||||||
|
|
@ -94,7 +94,7 @@ fun AppearanceSettingsThemeSwitcherItem(
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
AnimatedVisibility(
|
CustomAnimatedVisibility(
|
||||||
visible = selected,
|
visible = selected,
|
||||||
enter = scaleIn(tween(300), initialScale = 0.5f) +
|
enter = scaleIn(tween(300), initialScale = 0.5f) +
|
||||||
fadeIn(tween(300)),
|
fadeIn(tween(300)),
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue