0.9.4 - About Screen + New scrolling system in Reader(saving exact scroll position)
This commit is contained in:
parent
4e948bc5d7
commit
1689e9d7b9
109 changed files with 1870 additions and 994 deletions
|
|
@ -137,8 +137,10 @@ dependencies {
|
||||||
implementation("org.slf4j:slf4j-android:latest.release")
|
implementation("org.slf4j:slf4j-android:latest.release")
|
||||||
implementation("org.jsoup:jsoup:latest.release")
|
implementation("org.jsoup:jsoup:latest.release")
|
||||||
|
|
||||||
|
// App Compat for Language Switcher
|
||||||
implementation("androidx.appcompat:appcompat:latest.release")
|
implementation("androidx.appcompat:appcompat:latest.release")
|
||||||
implementation("androidx.appcompat:appcompat-resources:latest.release")
|
implementation("androidx.appcompat:appcompat-resources:latest.release")
|
||||||
|
|
||||||
|
// Coil for loading bitmaps
|
||||||
implementation("io.coil-kt:coil-compose:2.6.0")
|
implementation("io.coil-kt:coil-compose:2.6.0")
|
||||||
}
|
}
|
||||||
|
|
@ -19,19 +19,24 @@
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.WEB_SEARCH" />
|
<action android:name="android.intent.action.WEB_SEARCH" />
|
||||||
</intent>
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent>
|
||||||
|
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".util.BookApplication"
|
android:name=".Application"
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/app_icon"
|
android:icon="@mipmap/app_icon"
|
||||||
android:roundIcon="@mipmap/app_icon_round"
|
android:roundIcon="@mipmap/app_icon_round"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Start.Splash"
|
android:theme="@style/Theme.Start.Splash"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu"
|
||||||
|
tools:ignore="DataExtractionRules">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".Activity"
|
android:name=".Activity"
|
||||||
|
|
@ -44,6 +49,15 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
|
android:enabled="false"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="autoStoreLocales"
|
||||||
|
android:value="true" />
|
||||||
|
</service>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -9,6 +9,8 @@ import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -18,17 +20,20 @@ import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
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
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
|
||||||
import ua.acclorite.book_story.presentation.components.bottom_navigation_bar.BottomNavigationBar
|
import ua.acclorite.book_story.presentation.components.bottom_navigation_bar.BottomNavigationBar
|
||||||
import ua.acclorite.book_story.presentation.components.custom_navigation_rail.CustomNavigationRail
|
import ua.acclorite.book_story.presentation.components.custom_navigation_rail.CustomNavigationRail
|
||||||
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.book_info.BookInfoScreen
|
import ua.acclorite.book_story.presentation.screens.book_info.BookInfoScreen
|
||||||
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.BrowseScreen
|
||||||
|
|
@ -43,11 +48,9 @@ import ua.acclorite.book_story.presentation.screens.settings.SettingsScreen
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettings
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettings
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettings
|
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettings
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettings
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettings
|
||||||
import ua.acclorite.book_story.ui.BooksHistoryResurrectionTheme
|
import ua.acclorite.book_story.presentation.ui.BooksHistoryResurrectionTheme
|
||||||
import ua.acclorite.book_story.ui.DarkTheme
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
import ua.acclorite.book_story.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.isDark
|
||||||
import ua.acclorite.book_story.ui.Transitions
|
|
||||||
import ua.acclorite.book_story.ui.isDark
|
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -89,18 +92,26 @@ class Activity : AppCompatActivity() {
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val windowClass = calculateWindowSizeClass(activity = this)
|
|
||||||
val isLoaded by mainViewModel.isReady.collectAsState()
|
val isLoaded by mainViewModel.isReady.collectAsState()
|
||||||
|
val state by mainViewModel.state.collectAsState()
|
||||||
|
|
||||||
val theme = mainViewModel.theme.collectAsState().value ?: Theme.BLUE
|
val windowClass = calculateWindowSizeClass(activity = this)
|
||||||
val darkTheme =
|
val tabletUI = remember(windowClass) {
|
||||||
mainViewModel.darkTheme.collectAsState().value ?: DarkTheme.FOLLOW_SYSTEM
|
windowClass.widthSizeClass != WindowWidthSizeClass.Compact
|
||||||
val tabletUI = windowClass.widthSizeClass != WindowWidthSizeClass.Compact
|
}
|
||||||
|
val localLayoutDirection = LocalLayoutDirection.current
|
||||||
|
val layoutDirection = remember(localLayoutDirection) {
|
||||||
|
if (localLayoutDirection == LayoutDirection.Ltr) {
|
||||||
|
LayoutDirection.Ltr
|
||||||
|
} else {
|
||||||
|
LayoutDirection.Rtl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoaded) {
|
if (isLoaded) {
|
||||||
BooksHistoryResurrectionTheme(
|
BooksHistoryResurrectionTheme(
|
||||||
theme = theme,
|
theme = state.theme!!,
|
||||||
isDark = darkTheme.isDark()
|
isDark = state.darkTheme!!.isDark()
|
||||||
) {
|
) {
|
||||||
NavigationHost(startScreen = Screen.LIBRARY) {
|
NavigationHost(startScreen = Screen.LIBRARY) {
|
||||||
val currentScreen by this.getCurrentScreen().collectAsState()
|
val currentScreen by this.getCurrentScreen().collectAsState()
|
||||||
|
|
@ -119,24 +130,36 @@ class Activity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
containerColor = MaterialTheme.colorScheme.surface
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(
|
.padding(
|
||||||
start = if (tabletUI) 80.dp else 0.dp,
|
end = it.calculateEndPadding(
|
||||||
bottom = it.calculateBottomPadding()
|
layoutDirection
|
||||||
|
),
|
||||||
|
bottom = it.calculateBottomPadding(),
|
||||||
|
start = if (tabletUI) {
|
||||||
|
80.dp + it.calculateStartPadding(
|
||||||
|
layoutDirection
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it.calculateStartPadding(
|
||||||
|
layoutDirection
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
composable(screen = Screen.LIBRARY) {
|
composable(screen = Screen.LIBRARY) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
LibraryScreen(
|
LibraryScreen(
|
||||||
viewModel = libraryViewModel,
|
viewModel = libraryViewModel,
|
||||||
historyViewModel = historyViewModel,
|
historyViewModel = historyViewModel,
|
||||||
browseViewModel = browseViewModel,
|
browseViewModel = browseViewModel,
|
||||||
navigator = this@NavigationHost,
|
navigator = this@NavigationHost
|
||||||
addedBooks = retrieveArgument("added_books") as? List<Book>
|
|
||||||
?: emptyList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +181,11 @@ class Activity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tabletUI) {
|
if (tabletUI) {
|
||||||
CustomNavigationRail(navigator = this@NavigationHost)
|
CustomNavigationRail(
|
||||||
|
navigator = this@NavigationHost,
|
||||||
|
layoutDirection = layoutDirection
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -234,6 +261,15 @@ class Activity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About screen
|
||||||
|
composable(
|
||||||
|
screen = Screen.ABOUT,
|
||||||
|
enterAnim = Transitions.SlidingTransitionIn,
|
||||||
|
exitAnim = Transitions.SlidingTransitionOut
|
||||||
|
) {
|
||||||
|
AboutScreen(navigator = this@NavigationHost)
|
||||||
|
}
|
||||||
|
|
||||||
// Start screen (later)
|
// Start screen (later)
|
||||||
// composable(screen = Screen.START) {
|
// composable(screen = Screen.START) {
|
||||||
// StartScreen()
|
// StartScreen()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package ua.acclorite.book_story.util
|
package ua.acclorite.book_story
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class BookApplication : Application()
|
class Application : Application()
|
||||||
|
|
@ -22,6 +22,6 @@ object AppModule {
|
||||||
app,
|
app,
|
||||||
BookDatabase::class.java,
|
BookDatabase::class.java,
|
||||||
"book_db"
|
"book_db"
|
||||||
).build().dao
|
).allowMainThreadQueries().build().dao
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,6 +12,8 @@ data class BookEntity(
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val text: String,
|
val text: String,
|
||||||
val filePath: String,
|
val filePath: String,
|
||||||
|
val scrollIndex: Int,
|
||||||
|
val scrollOffset: Int,
|
||||||
val progress: Float,
|
val progress: Float,
|
||||||
val image: String? = null,
|
val image: String? = null,
|
||||||
val category: Category
|
val category: Category
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,9 @@ interface BookDao {
|
||||||
@Query("SELECT * FROM historyentity")
|
@Query("SELECT * FROM historyentity")
|
||||||
suspend fun getHistory(): List<HistoryEntity>
|
suspend fun getHistory(): List<HistoryEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM historyentity WHERE bookId = :bookId ORDER BY time DESC LIMIT 1")
|
||||||
|
fun getLatestHistoryForBook(bookId: Int): HistoryEntity?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertHistory(
|
suspend fun insertHistory(
|
||||||
history: List<HistoryEntity>
|
history: List<HistoryEntity>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import android.net.Uri
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.local.dto.BookEntity
|
import ua.acclorite.book_story.data.local.dto.BookEntity
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -18,6 +18,8 @@ class BookMapperImpl @Inject constructor() : BookMapper {
|
||||||
id = book.id,
|
id = book.id,
|
||||||
title = book.title,
|
title = book.title,
|
||||||
filePath = book.filePath,
|
filePath = book.filePath,
|
||||||
|
scrollIndex = book.scrollIndex,
|
||||||
|
scrollOffset = book.scrollOffset,
|
||||||
progress = book.progress,
|
progress = book.progress,
|
||||||
author = book.author.string,
|
author = book.author.string,
|
||||||
text = textAsString,
|
text = textAsString,
|
||||||
|
|
@ -37,9 +39,13 @@ class BookMapperImpl @Inject constructor() : BookMapper {
|
||||||
R.string.unknown_author
|
R.string.unknown_author
|
||||||
),
|
),
|
||||||
description = bookEntity.description,
|
description = bookEntity.description,
|
||||||
|
scrollIndex = bookEntity.scrollIndex,
|
||||||
|
scrollOffset = bookEntity.scrollOffset,
|
||||||
progress = bookEntity.progress,
|
progress = bookEntity.progress,
|
||||||
file = if (file.exists()) file else null,
|
file = if (file.exists()) file else null,
|
||||||
text = emptyList(),
|
text = emptyList(),
|
||||||
|
letters = 0,
|
||||||
|
words = 0,
|
||||||
filePath = bookEntity.filePath,
|
filePath = bookEntity.filePath,
|
||||||
lastOpened = null,
|
lastOpened = null,
|
||||||
category = bookEntity.category,
|
category = bookEntity.category,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ class HistoryMapperImpl @Inject constructor() : HistoryMapper {
|
||||||
return History(
|
return History(
|
||||||
historyEntity.id,
|
historyEntity.id,
|
||||||
bookId = historyEntity.bookId,
|
bookId = historyEntity.bookId,
|
||||||
|
book = null,
|
||||||
time = historyEntity.time
|
time = historyEntity.time
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package ua.acclorite.book_story.data.parser
|
package ua.acclorite.book_story.data.parser
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import ua.acclorite.book_story.data.parser.epub.EpubFileParser
|
||||||
import ua.acclorite.book_story.data.parser.pdf.PdfFileParser
|
import ua.acclorite.book_story.data.parser.pdf.PdfFileParser
|
||||||
import ua.acclorite.book_story.data.parser.txt.TxtFileParser
|
import ua.acclorite.book_story.data.parser.txt.TxtFileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package ua.acclorite.book_story.data.parser
|
package ua.acclorite.book_story.data.parser
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.model.StringWithId
|
import ua.acclorite.book_story.domain.model.StringWithId
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface TextParser {
|
interface TextParser {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import ua.acclorite.book_story.data.parser.epub.EpubTextParser
|
||||||
import ua.acclorite.book_story.data.parser.pdf.PdfTextParser
|
import ua.acclorite.book_story.data.parser.pdf.PdfTextParser
|
||||||
import ua.acclorite.book_story.data.parser.txt.TxtTextParser
|
import ua.acclorite.book_story.data.parser.txt.TxtTextParser
|
||||||
import ua.acclorite.book_story.domain.model.StringWithId
|
import ua.acclorite.book_story.domain.model.StringWithId
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import nl.siegmann.epublib.epub.EpubReader
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
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.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -50,6 +50,10 @@ class EpubFileParser @Inject constructor() : FileParser {
|
||||||
author = author,
|
author = author,
|
||||||
description = description?.toString(),
|
description = description?.toString(),
|
||||||
text = emptyList(),
|
text = emptyList(),
|
||||||
|
letters = 0,
|
||||||
|
words = 0,
|
||||||
|
scrollIndex = 0,
|
||||||
|
scrollOffset = 0,
|
||||||
progress = 0f,
|
progress = 0f,
|
||||||
file = file,
|
file = file,
|
||||||
filePath = file.path,
|
filePath = file.path,
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import org.jsoup.Jsoup
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.TextParser
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
import ua.acclorite.book_story.domain.model.StringWithId
|
import ua.acclorite.book_story.domain.model.StringWithId
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
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.model.StringWithId
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.util.CoverImage
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.util.UIText
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -29,8 +28,6 @@ class PdfFileParser @Inject constructor(private val application: Application) :
|
||||||
val fileAuthor = document.documentInformation.author
|
val fileAuthor = document.documentInformation.author
|
||||||
val author = if (fileAuthor != null) UIText.StringValue(fileAuthor)
|
val author = if (fileAuthor != null) UIText.StringValue(fileAuthor)
|
||||||
else UIText.StringResource(R.string.unknown_author)
|
else UIText.StringResource(R.string.unknown_author)
|
||||||
|
|
||||||
val text = emptyList<StringWithId>()
|
|
||||||
val description = document.documentInformation.subject ?: null
|
val description = document.documentInformation.subject ?: null
|
||||||
|
|
||||||
document.close()
|
document.close()
|
||||||
|
|
@ -40,7 +37,11 @@ class PdfFileParser @Inject constructor(private val application: Application) :
|
||||||
title = title,
|
title = title,
|
||||||
author = author,
|
author = author,
|
||||||
description = description,
|
description = description,
|
||||||
text = text,
|
text = emptyList(),
|
||||||
|
letters = 0,
|
||||||
|
words = 0,
|
||||||
|
scrollIndex = 0,
|
||||||
|
scrollOffset = 0,
|
||||||
progress = 0f,
|
progress = 0f,
|
||||||
file = file,
|
file = file,
|
||||||
filePath = file.path,
|
filePath = file.path,
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import com.tom_roush.pdfbox.text.PDFTextStripper
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.TextParser
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
import ua.acclorite.book_story.domain.model.StringWithId
|
import ua.acclorite.book_story.domain.model.StringWithId
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -28,11 +28,6 @@ class PdfTextParser @Inject constructor(private val application: Application) :
|
||||||
val pdfStripper = PDFTextStripper()
|
val pdfStripper = PDFTextStripper()
|
||||||
pdfStripper.paragraphStart = "_new_line_"
|
pdfStripper.paragraphStart = "_new_line_"
|
||||||
|
|
||||||
if (document.isEncrypted) {
|
|
||||||
document.close()
|
|
||||||
return Resource.Error(UIText.StringResource(R.string.error_file_encrypted))
|
|
||||||
}
|
|
||||||
|
|
||||||
val oldText = pdfStripper.getText(document)
|
val oldText = pdfStripper.getText(document)
|
||||||
.replace("\r", "")
|
.replace("\r", "")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
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.model.StringWithId
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.util.CoverImage
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.util.UIText
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -20,14 +19,17 @@ class TxtFileParser @Inject constructor() : FileParser {
|
||||||
try {
|
try {
|
||||||
val title = file.name.trim().dropLast(4)
|
val title = file.name.trim().dropLast(4)
|
||||||
val author = UIText.StringResource(R.string.unknown_author)
|
val author = UIText.StringResource(R.string.unknown_author)
|
||||||
val text = emptyList<StringWithId>()
|
|
||||||
|
|
||||||
return Book(
|
return Book(
|
||||||
id = null,
|
id = null,
|
||||||
title = title,
|
title = title,
|
||||||
author = author,
|
author = author,
|
||||||
description = null,
|
description = null,
|
||||||
text = text,
|
text = emptyList(),
|
||||||
|
letters = 0,
|
||||||
|
words = 0,
|
||||||
|
scrollIndex = 0,
|
||||||
|
scrollOffset = 0,
|
||||||
progress = 0f,
|
progress = 0f,
|
||||||
file = file,
|
file = file,
|
||||||
filePath = file.path,
|
filePath = file.path,
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import kotlinx.coroutines.withContext
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.TextParser
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
import ua.acclorite.book_story.domain.model.StringWithId
|
import ua.acclorite.book_story.domain.model.StringWithId
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
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.dto.BookEntity
|
|
||||||
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
|
||||||
|
|
@ -20,12 +21,13 @@ 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.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.presentation.data.calculateFamiliarity
|
import ua.acclorite.book_story.presentation.data.calculateFamiliarity
|
||||||
import ua.acclorite.book_story.util.Constants
|
|
||||||
import ua.acclorite.book_story.util.CoverImage
|
|
||||||
import ua.acclorite.book_story.util.Resource
|
|
||||||
import ua.acclorite.book_story.util.UIText
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
@ -47,17 +49,15 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
override suspend fun getBooks(query: String): Flow<Resource<List<Book>>> {
|
override suspend fun getBooks(query: String): Flow<Resource<List<Book>>> {
|
||||||
return flow {
|
return flow {
|
||||||
emit(Resource.Loading(true))
|
emit(Resource.Loading(true))
|
||||||
|
|
||||||
val books = database.searchBooks(query)
|
val books = database.searchBooks(query)
|
||||||
val history = database.getHistory()
|
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
Resource.Success(
|
Resource.Success(
|
||||||
data = books.map { entity ->
|
data = books.map { entity ->
|
||||||
val book = bookMapper.toBook(entity)
|
val book = bookMapper.toBook(entity)
|
||||||
val lastHistory = history
|
val lastHistory = database.getLatestHistoryForBook(
|
||||||
.filter { it.bookId == book.id }
|
book.id ?: -1
|
||||||
.maxByOrNull { it.time }
|
)
|
||||||
|
|
||||||
book.copy(
|
book.copy(
|
||||||
lastOpened = lastHistory?.time
|
lastOpened = lastHistory?.time
|
||||||
|
|
@ -70,12 +70,12 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
override suspend fun fastGetBooks(query: String): List<Book> {
|
override suspend fun fastGetBooks(query: String): List<Book> {
|
||||||
val books = database.searchBooks(query)
|
val books = database.searchBooks(query)
|
||||||
val history = database.getHistory()
|
|
||||||
|
|
||||||
return books.map { entity ->
|
return books.map { entity ->
|
||||||
val book = bookMapper.toBook(entity)
|
val book = bookMapper.toBook(entity)
|
||||||
val lastHistory =
|
val lastHistory = database.getLatestHistoryForBook(
|
||||||
history.filter { it.bookId == book.id }.maxByOrNull { it.time }
|
book.id ?: -1
|
||||||
|
)
|
||||||
|
|
||||||
book.copy(
|
book.copy(
|
||||||
lastOpened = lastHistory?.time
|
lastOpened = lastHistory?.time
|
||||||
|
|
@ -84,7 +84,18 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getBooksById(ids: List<Int>): List<Book> {
|
override suspend fun getBooksById(ids: List<Int>): List<Book> {
|
||||||
return database.findBooksById(ids).map { bookMapper.toBook(it) }
|
val books = database.findBooksById(ids)
|
||||||
|
|
||||||
|
return books.map { entity ->
|
||||||
|
val book = bookMapper.toBook(entity)
|
||||||
|
val lastHistory = database.getLatestHistoryForBook(
|
||||||
|
book.id ?: -1
|
||||||
|
)
|
||||||
|
|
||||||
|
book.copy(
|
||||||
|
lastOpened = lastHistory?.time
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getBookTextById(bookId: Int): String {
|
override suspend fun getBookTextById(bookId: Int): String {
|
||||||
|
|
@ -92,22 +103,26 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
return book.text
|
return book.text
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findBook(
|
|
||||||
id: Int
|
|
||||||
): BookEntity {
|
|
||||||
return database.findBookById(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun insertBooks(
|
override suspend fun insertBooks(
|
||||||
books: List<Pair<Book, CoverImage?>>
|
books: List<Pair<Book, CoverImage?>>
|
||||||
) {
|
) {
|
||||||
|
val filesDir = application.filesDir
|
||||||
|
val coversDir = File(filesDir, "covers")
|
||||||
|
|
||||||
|
if (!coversDir.exists()) {
|
||||||
|
coversDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
val booksWithCover = books.map {
|
val booksWithCover = books.map {
|
||||||
var uri = ""
|
var uri = ""
|
||||||
|
|
||||||
if (it.second != null) {
|
if (it.second != null) {
|
||||||
try {
|
try {
|
||||||
uri = "${UUID.randomUUID()}.webp"
|
uri = "${UUID.randomUUID()}.webp"
|
||||||
application.openFileOutput(uri, 0x0000).use { stream ->
|
val cover = File(coversDir, uri)
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
FileOutputStream(cover).use { stream ->
|
||||||
if (
|
if (
|
||||||
!it.second!!.compress(
|
!it.second!!.compress(
|
||||||
Bitmap.CompressFormat.WEBP,
|
Bitmap.CompressFormat.WEBP,
|
||||||
|
|
@ -118,6 +133,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
throw Exception("Couldn't save cover image")
|
throw Exception("Couldn't save cover image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
uri = ""
|
uri = ""
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
@ -126,7 +142,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
val book = it.first.copy(
|
val book = it.first.copy(
|
||||||
coverImage = if (uri.isNotBlank()) {
|
coverImage = if (uri.isNotBlank()) {
|
||||||
Uri.fromFile(File("${application.filesDir}/$uri"))
|
Uri.fromFile(File("$coversDir/$uri"))
|
||||||
} else null
|
} else null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -175,10 +191,20 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
val book = database.findBookById(bookWithOldCover.id ?: return)
|
val book = database.findBookById(bookWithOldCover.id ?: return)
|
||||||
var uri: String? = null
|
var uri: String? = null
|
||||||
|
|
||||||
|
val filesDir = application.filesDir
|
||||||
|
val coversDir = File(filesDir, "covers")
|
||||||
|
|
||||||
|
if (!coversDir.exists()) {
|
||||||
|
coversDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
if (newCoverImage != null) {
|
if (newCoverImage != null) {
|
||||||
try {
|
try {
|
||||||
uri = "${UUID.randomUUID()}.webp"
|
uri = "${UUID.randomUUID()}.webp"
|
||||||
application.openFileOutput(uri, 0x0000).use { stream ->
|
val cover = File(coversDir, uri)
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
FileOutputStream(cover).use { stream ->
|
||||||
if (
|
if (
|
||||||
!newCoverImage.compress(
|
!newCoverImage.compress(
|
||||||
Bitmap.CompressFormat.WEBP,
|
Bitmap.CompressFormat.WEBP,
|
||||||
|
|
@ -189,6 +215,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
throw Exception("Couldn't save cover image")
|
throw Exception("Couldn't save cover image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return
|
return
|
||||||
|
|
@ -197,16 +224,20 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
if (book.image != null) {
|
if (book.image != null) {
|
||||||
try {
|
try {
|
||||||
application.deleteFile(
|
val fileToDelete = File(
|
||||||
book.image.substringAfterLast("/")
|
"$coversDir/${book.image.substringAfterLast("/")}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (fileToDelete.exists()) {
|
||||||
|
fileToDelete.delete()
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val newCoverImageUri = if (uri != null) {
|
val newCoverImageUri = if (uri != null) {
|
||||||
Uri.fromFile(File("${application.filesDir}/$uri"))
|
Uri.fromFile(File("$coversDir/$uri"))
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
@ -228,15 +259,26 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteBooks(books: List<Book>) {
|
override suspend fun deleteBooks(books: List<Book>) {
|
||||||
|
val filesDir = application.filesDir
|
||||||
|
val coversDir = File(filesDir, "covers")
|
||||||
|
|
||||||
|
if (!coversDir.exists()) {
|
||||||
|
coversDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
database.deleteBooks(
|
database.deleteBooks(
|
||||||
books.map {
|
books.map {
|
||||||
val book = database.findBookById(it.id ?: return)
|
val book = database.findBookById(it.id ?: return)
|
||||||
|
|
||||||
if (book.image != null) {
|
if (book.image != null) {
|
||||||
try {
|
try {
|
||||||
application.deleteFile(
|
val fileToDelete = File(
|
||||||
book.image.substringAfterLast("/")
|
"$coversDir/${book.image.substringAfterLast("/")}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (fileToDelete.exists()) {
|
||||||
|
fileToDelete.delete()
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
@ -301,14 +343,28 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
ignoreCase = true
|
ignoreCase = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val isFileAlreadyAdded = existingBooks.all {
|
|
||||||
|
if (!isFileSupported) {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
|
|
||||||
|
val isFileNotAdded = existingBooks.all {
|
||||||
it.filePath.substringAfterLast("/") !=
|
it.filePath.substringAfterLast("/") !=
|
||||||
file.path.substringAfterLast("/")
|
file.path.substringAfterLast("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isFileNotAdded) {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
|
|
||||||
val isQuery = if (query.isEmpty()) true else file.name.lowercase()
|
val isQuery = if (query.isEmpty()) true else file.name.lowercase()
|
||||||
.contains(query.trim().lowercase())
|
.contains(query.trim().lowercase())
|
||||||
|
|
||||||
isFileAlreadyAdded && isFileSupported && isQuery
|
if (!isQuery) {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}.forEach {
|
}.forEach {
|
||||||
filteredFiles.add(
|
filteredFiles.add(
|
||||||
it
|
it
|
||||||
|
|
@ -414,7 +470,6 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
override suspend fun getHistory(): Flow<Resource<List<History>>> {
|
override suspend fun getHistory(): Flow<Resource<List<History>>> {
|
||||||
return flow {
|
return flow {
|
||||||
emit(Resource.Loading(true))
|
emit(Resource.Loading(true))
|
||||||
|
|
||||||
val history = database.getHistory()
|
val history = database.getHistory()
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
|
|
@ -429,6 +484,11 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getLatestBookHistory(bookId: Int): History? {
|
||||||
|
val history = database.getLatestHistoryForBook(bookId)
|
||||||
|
return history?.let { historyMapper.toHistory(it) }
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun deleteWholeHistory() {
|
override suspend fun deleteWholeHistory() {
|
||||||
database.deleteWholeHistory()
|
database.deleteWholeHistory()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,26 @@ package ua.acclorite.book_story.domain.model
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class Book(
|
data class Book(
|
||||||
val id: Int?,
|
val id: Int?,
|
||||||
|
|
||||||
val title: String,
|
val title: String,
|
||||||
val author: UIText,
|
val author: UIText,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
|
|
||||||
|
// val textPath: String,
|
||||||
val text: List<StringWithId> = emptyList(),
|
val text: List<StringWithId> = emptyList(),
|
||||||
|
val letters: Int,
|
||||||
|
val words: Int,
|
||||||
|
|
||||||
|
val scrollIndex: Int,
|
||||||
|
val scrollOffset: Int,
|
||||||
val progress: Float,
|
val progress: Float,
|
||||||
|
|
||||||
val file: File?,
|
val file: File?,
|
||||||
val filePath: String,
|
val filePath: String,
|
||||||
val lastOpened: Long?,
|
val lastOpened: Long?,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package ua.acclorite.book_story.domain.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class FontWithName(
|
data class FontWithName(
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ import androidx.compose.runtime.Immutable
|
||||||
data class History(
|
data class History(
|
||||||
val id: Int?,
|
val id: Int?,
|
||||||
val bookId: Int,
|
val bookId: Int,
|
||||||
|
val book: Book?,
|
||||||
val time: Long
|
val time: Long
|
||||||
)
|
)
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package ua.acclorite.book_story.domain.model
|
package ua.acclorite.book_story.domain.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import ua.acclorite.book_story.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class NullableBook(
|
sealed class NullableBook(
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ package ua.acclorite.book_story.domain.repository
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import ua.acclorite.book_story.data.local.dto.BookEntity
|
|
||||||
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.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface BookRepository {
|
interface BookRepository {
|
||||||
|
|
@ -28,10 +27,6 @@ interface BookRepository {
|
||||||
bookId: Int
|
bookId: Int
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun findBook(
|
|
||||||
id: Int
|
|
||||||
): BookEntity
|
|
||||||
|
|
||||||
suspend fun insertBooks(
|
suspend fun insertBooks(
|
||||||
books: List<Pair<Book, CoverImage?>>
|
books: List<Pair<Book, CoverImage?>>
|
||||||
)
|
)
|
||||||
|
|
@ -71,6 +66,8 @@ interface BookRepository {
|
||||||
|
|
||||||
suspend fun getHistory(): Flow<Resource<List<History>>>
|
suspend fun getHistory(): Flow<Resource<List<History>>>
|
||||||
|
|
||||||
|
suspend fun getLatestBookHistory(bookId: Int): History?
|
||||||
|
|
||||||
suspend fun deleteWholeHistory()
|
suspend fun deleteWholeHistory()
|
||||||
|
|
||||||
suspend fun deleteBookHistory(bookId: Int)
|
suspend fun deleteBookHistory(bookId: Int)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
package ua.acclorite.book_story.domain.use_case
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.util.DataStoreConstants
|
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ChangeLanguage @Inject constructor(private val repository: BookRepository) {
|
class ChangeLanguage @Inject constructor(private val repository: BookRepository) {
|
||||||
@SuppressLint("AppBundleLocaleChanges")
|
|
||||||
suspend fun execute(language: String) {
|
suspend fun execute(language: String) {
|
||||||
val appLocale = LocaleListCompat.forLanguageTags(language)
|
val appLocale = LocaleListCompat.forLanguageTags(language)
|
||||||
AppCompatDelegate.setApplicationLocales(appLocale)
|
AppCompatDelegate.setApplicationLocales(appLocale)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package ua.acclorite.book_story.domain.use_case
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetBooks @Inject constructor(private val repository: BookRepository) {
|
class GetBooks @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package ua.acclorite.book_story.domain.use_case
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetHistory @Inject constructor(private val repository: BookRepository) {
|
class GetHistory @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.domain.model.History
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetLatestHistory @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(bookId: Int): History? {
|
||||||
|
return repository.getLatestBookHistory(bookId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class InsertBooks @Inject constructor(private val repository: BookRepository) {
|
class InsertBooks @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class UpdateCoverImageOfBook @Inject constructor(private val repository: BookRepository) {
|
class UpdateCoverImageOfBook @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.util
|
package ua.acclorite.book_story.domain.util
|
||||||
|
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
|
@ -8,23 +8,12 @@ import ua.acclorite.book_story.R
|
||||||
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.model.FontWithName
|
import ua.acclorite.book_story.domain.model.FontWithName
|
||||||
import ua.acclorite.book_story.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
|
|
||||||
// SavedStateHandle constants(actually just all settings params)
|
// Main State Constant
|
||||||
const val LANGUAGE = "language"
|
const val MAIN_STATE = "main_state"
|
||||||
const val THEME = "theme"
|
|
||||||
const val DARK_THEME = "dark_theme"
|
|
||||||
const val SHOW_START_SCREEN = "guide"
|
|
||||||
const val BACKGROUND_COLOR = "background_color"
|
|
||||||
const val FONT_COLOR = "font_color"
|
|
||||||
const val FONT = "font"
|
|
||||||
const val IS_ITALIC = "font_style"
|
|
||||||
const val FONT_SIZE = "font_size"
|
|
||||||
const val LINE_HEIGHT = "line_height"
|
|
||||||
const val PARAGRAPH_HEIGHT = "paragraph_height"
|
|
||||||
const val PARAGRAPH_INDENTATION = "paragraph_indentation"
|
|
||||||
|
|
||||||
// Supported file extensions
|
// Supported file extensions
|
||||||
val EXTENSIONS = listOf(".txt", ".pdf", ".epub")
|
val EXTENSIONS = listOf(".txt", ".pdf", ".epub")
|
||||||
|
|
@ -182,6 +171,10 @@ object Constants {
|
||||||
UIText.StringValue(""),
|
UIText.StringValue(""),
|
||||||
null,
|
null,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
0f,
|
0f,
|
||||||
null,
|
null,
|
||||||
"",
|
"",
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.util
|
package ua.acclorite.book_story.domain.util
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
|
@ -10,7 +10,6 @@ object DataStoreConstants {
|
||||||
val THEME = stringPreferencesKey("theme")
|
val THEME = stringPreferencesKey("theme")
|
||||||
val DARK_THEME = stringPreferencesKey("dark_theme")
|
val DARK_THEME = stringPreferencesKey("dark_theme")
|
||||||
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
|
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
|
||||||
|
|
||||||
val BACKGROUND_COLOR = longPreferencesKey("background_color")
|
val BACKGROUND_COLOR = longPreferencesKey("background_color")
|
||||||
val FONT_COLOR = longPreferencesKey("font_color")
|
val FONT_COLOR = longPreferencesKey("font_color")
|
||||||
val FONT = stringPreferencesKey("font")
|
val FONT = stringPreferencesKey("font")
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.util
|
package ua.acclorite.book_story.domain.util
|
||||||
|
|
||||||
sealed class Resource<T>(val data: T? = null, val message: UIText? = null) {
|
sealed class Resource<T>(val data: T? = null, val message: UIText? = null) {
|
||||||
class Success<T>(data: T?) : Resource<T>(data)
|
class Success<T>(data: T?) : Resource<T>(data)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.util
|
package ua.acclorite.book_story.domain.util
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.util
|
package ua.acclorite.book_story.domain.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
|
@ -28,7 +28,7 @@ import androidx.compose.ui.platform.LocalDensity
|
||||||
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.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Animated top app bar. Has up to 3 different top bars inside it. Follow [TopAppBar] for more info.
|
* Animated top app bar. Has up to 3 different top bars inside it. Follow [TopAppBar] for more info.
|
||||||
|
|
@ -102,10 +102,12 @@ fun AnimatedTopAppBar(
|
||||||
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
windowInsets = WindowInsets.statusBars,
|
||||||
navigationIcon = content1NavigationIcon,
|
navigationIcon = content1NavigationIcon,
|
||||||
title = content1Title,
|
title = content1Title,
|
||||||
actions = content1Actions,
|
actions = content1Actions,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = animatedContainerColor,
|
containerColor = animatedContainerColor,
|
||||||
scrolledContainerColor = scrolledContainerColor
|
scrolledContainerColor = scrolledContainerColor
|
||||||
|
|
@ -120,10 +122,12 @@ fun AnimatedTopAppBar(
|
||||||
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
windowInsets = WindowInsets.statusBars,
|
||||||
navigationIcon = content2NavigationIcon,
|
navigationIcon = content2NavigationIcon,
|
||||||
title = content2Title,
|
title = content2Title,
|
||||||
actions = content2Actions,
|
actions = content2Actions,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = animatedContainerColor,
|
containerColor = animatedContainerColor,
|
||||||
scrolledContainerColor = scrolledContainerColor
|
scrolledContainerColor = scrolledContainerColor
|
||||||
|
|
@ -139,10 +143,12 @@ fun AnimatedTopAppBar(
|
||||||
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
windowInsets = WindowInsets.statusBars,
|
||||||
navigationIcon = content3NavigationIcon,
|
navigationIcon = content3NavigationIcon,
|
||||||
title = content3Title,
|
title = content3Title,
|
||||||
actions = content3Actions,
|
actions = content3Actions,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = animatedContainerColor,
|
containerColor = animatedContainerColor,
|
||||||
scrolledContainerColor = scrolledContainerColor
|
scrolledContainerColor = scrolledContainerColor
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import androidx.compose.ui.res.stringResource
|
||||||
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.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Checkbox. Has a Circle shape.
|
* Custom Checkbox. Has a Circle shape.
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@ import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
* Go back arrow button. Prevents double or triple clicking on it while going back action is performed.
|
* Go back arrow button. Prevents double or triple clicking on it while going back action is performed.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun GoBackButton(navigator: Navigator, customOnClick: () -> Unit = {}) {
|
fun GoBackButton(navigator: Navigator, enabled: Boolean = true, customOnClick: () -> Unit = {}) {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = stringResource(id = R.string.go_back_content_desc),
|
contentDescription = stringResource(id = R.string.go_back_content_desc),
|
||||||
disableOnClick = true
|
disableOnClick = true,
|
||||||
|
enabled = enabled
|
||||||
) {
|
) {
|
||||||
navigator.navigateBack()
|
|
||||||
customOnClick()
|
customOnClick()
|
||||||
|
navigator.navigateBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ 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.Navigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bottom navigation bar, uses default [NavigationBar].
|
* Bottom navigation bar, uses default [NavigationBar].
|
||||||
|
|
|
||||||
|
|
@ -36,14 +36,13 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.ui.ElevationDefaults
|
import ua.acclorite.book_story.presentation.ui.ElevationDefaults
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Dialog with custom content.
|
* Custom Dialog with custom content.
|
||||||
*
|
*
|
||||||
* @param modifier Modifier to be applied.
|
* @param modifier Modifier to be applied.
|
||||||
* @param properties Dialog properties.
|
|
||||||
* @param drawableIcon Drawable icon to show(1/2).
|
* @param drawableIcon Drawable icon to show(1/2).
|
||||||
* @param imageVectorIcon Image vector icon to show(2/2).
|
* @param imageVectorIcon Image vector icon to show(2/2).
|
||||||
* @param backgroundTransparency The transparency of the background behind the dialog.
|
* @param backgroundTransparency The transparency of the background behind the dialog.
|
||||||
|
|
@ -60,7 +59,6 @@ import ua.acclorite.book_story.ui.elevation
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomDialogWithContent(
|
fun CustomDialogWithContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
properties: DialogProperties = DialogProperties(),
|
|
||||||
drawableIcon: Painter? = null,
|
drawableIcon: Painter? = null,
|
||||||
imageVectorIcon: ImageVector? = null,
|
imageVectorIcon: ImageVector? = null,
|
||||||
backgroundTransparency: Float = 0.3f,
|
backgroundTransparency: Float = 0.3f,
|
||||||
|
|
@ -76,7 +74,10 @@ fun CustomDialogWithContent(
|
||||||
var actionClicked by remember { mutableStateOf(false) }
|
var actionClicked by remember { mutableStateOf(false) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { onDismiss() },
|
onDismissRequest = { onDismiss() },
|
||||||
properties = properties
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = !actionClicked,
|
||||||
|
dismissOnClickOutside = !actionClicked
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
(LocalView.current.parent as DialogWindowProvider).window.setDimAmount(
|
(LocalView.current.parent as DialogWindowProvider).window.setDimAmount(
|
||||||
backgroundTransparency
|
backgroundTransparency
|
||||||
|
|
|
||||||
|
|
@ -37,15 +37,14 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.ui.ElevationDefaults
|
import ua.acclorite.book_story.presentation.ui.ElevationDefaults
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Dialog with lazy column.
|
* Custom Dialog with lazy column.
|
||||||
*
|
*
|
||||||
* @param modifier Modifier to be applied.
|
* @param modifier Modifier to be applied.
|
||||||
* @param properties Dialog properties.
|
|
||||||
* @param drawableIcon Drawable icon to show(1/2).
|
* @param drawableIcon Drawable icon to show(1/2).
|
||||||
* @param imageVectorIcon Image vector icon to show(2/2).
|
* @param imageVectorIcon Image vector icon to show(2/2).
|
||||||
* @param backgroundTransparency The transparency of the background behind the dialog.
|
* @param backgroundTransparency The transparency of the background behind the dialog.
|
||||||
|
|
@ -62,7 +61,6 @@ import ua.acclorite.book_story.ui.elevation
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomDialogWithLazyColumn(
|
fun CustomDialogWithLazyColumn(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
properties: DialogProperties = DialogProperties(),
|
|
||||||
drawableIcon: Painter? = null,
|
drawableIcon: Painter? = null,
|
||||||
imageVectorIcon: ImageVector? = null,
|
imageVectorIcon: ImageVector? = null,
|
||||||
backgroundTransparency: Float = 0.3f,
|
backgroundTransparency: Float = 0.3f,
|
||||||
|
|
@ -78,7 +76,10 @@ fun CustomDialogWithLazyColumn(
|
||||||
var actionClicked by remember { mutableStateOf(false) }
|
var actionClicked by remember { mutableStateOf(false) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { onDismiss() },
|
onDismissRequest = { onDismiss() },
|
||||||
properties = properties
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = !actionClicked,
|
||||||
|
dismissOnClickOutside = !actionClicked
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
(LocalView.current.parent as DialogWindowProvider).window.setDimAmount(
|
(LocalView.current.parent as DialogWindowProvider).window.setDimAmount(
|
||||||
backgroundTransparency
|
backgroundTransparency
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@ package ua.acclorite.book_story.presentation.components.custom_navigation_rail
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
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.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -18,19 +23,21 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.Navigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 CustomNavigationRail(
|
fun BoxScope.CustomNavigationRail(
|
||||||
navigator: Navigator
|
navigator: Navigator,
|
||||||
|
layoutDirection: LayoutDirection
|
||||||
) {
|
) {
|
||||||
var currentScreen: Screen? by remember { mutableStateOf(null) }
|
var currentScreen: Screen? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
|
|
@ -44,10 +51,19 @@ fun CustomNavigationRail(
|
||||||
|
|
||||||
NavigationRail(
|
NavigationRail(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(80.dp)
|
.align(
|
||||||
|
Alignment.CenterStart
|
||||||
|
)
|
||||||
.background(MaterialTheme.elevation())
|
.background(MaterialTheme.elevation())
|
||||||
|
.padding(
|
||||||
|
start = WindowInsets.navigationBars
|
||||||
|
.asPaddingValues()
|
||||||
|
.calculateStartPadding(layoutDirection)
|
||||||
|
)
|
||||||
|
.width(80.dp)
|
||||||
.padding(horizontal = 12.dp),
|
.padding(horizontal = 12.dp),
|
||||||
containerColor = MaterialTheme.elevation()
|
containerColor = MaterialTheme.elevation(),
|
||||||
|
windowInsets = WindowInsets(0, 0, 0, 0)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ sealed class MainEvent {
|
||||||
data class OnChangeParagraphIndentation(val bool: Boolean) : MainEvent()
|
data class OnChangeParagraphIndentation(val bool: Boolean) : 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()
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package ua.acclorite.book_story.presentation.data
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
||||||
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main State. All app's settings are here. Wrapped in SavedStateHandle, so it won't reset.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
data class MainState(
|
||||||
|
val language: String? = null,
|
||||||
|
val theme: Theme? = null,
|
||||||
|
val darkTheme: DarkTheme? = null,
|
||||||
|
val fontFamily: String? = null,
|
||||||
|
val isItalic: Boolean? = null,
|
||||||
|
val fontSize: Int? = null,
|
||||||
|
val lineHeight: Int? = null,
|
||||||
|
val paragraphHeight: Int? = null,
|
||||||
|
val paragraphIndentation: Boolean? = null,
|
||||||
|
val backgroundColor: Long? = null,
|
||||||
|
val fontColor: Long? = null,
|
||||||
|
val showStartScreen: Boolean? = null,
|
||||||
|
) : Parcelable {
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
// String
|
||||||
|
language = parcel.readString(),
|
||||||
|
// String
|
||||||
|
theme = Theme.valueOf(parcel.readString() ?: Theme.BLUE.name),
|
||||||
|
// String
|
||||||
|
darkTheme = DarkTheme.valueOf(parcel.readString() ?: DarkTheme.FOLLOW_SYSTEM.name),
|
||||||
|
// String
|
||||||
|
fontFamily = parcel.readString(),
|
||||||
|
// Boolean
|
||||||
|
isItalic = if (parcel.readByte().toInt() != 0) parcel.readByte().toInt() == 1 else null,
|
||||||
|
// Int
|
||||||
|
fontSize = if (parcel.readByte().toInt() != 0) parcel.readInt() else null,
|
||||||
|
// Int
|
||||||
|
lineHeight = if (parcel.readByte().toInt() != 0) parcel.readInt() else null,
|
||||||
|
// Int
|
||||||
|
paragraphHeight = if (parcel.readByte().toInt() != 0) parcel.readInt() else null,
|
||||||
|
// Boolean
|
||||||
|
paragraphIndentation = if (parcel.readByte().toInt() != 0) parcel.readByte()
|
||||||
|
.toInt() == 1 else null,
|
||||||
|
// Long
|
||||||
|
backgroundColor = if (parcel.readByte().toInt() != 0) parcel.readLong() else null,
|
||||||
|
// Long
|
||||||
|
fontColor = if (parcel.readByte().toInt() != 0) parcel.readLong() else null,
|
||||||
|
// Boolean
|
||||||
|
showStartScreen = if (parcel.readByte().toInt() != 0) parcel.readByte()
|
||||||
|
.toInt() == 1 else null,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
// Language
|
||||||
|
parcel.writeString(language)
|
||||||
|
// Theme
|
||||||
|
parcel.writeString(theme?.name)
|
||||||
|
// Dark Theme
|
||||||
|
parcel.writeString(darkTheme?.name)
|
||||||
|
// Font Family
|
||||||
|
parcel.writeString(fontFamily)
|
||||||
|
// Is Italic
|
||||||
|
parcel.writeByte((if (isItalic != null) 1 else 0).toByte())
|
||||||
|
isItalic?.let { parcel.writeByte(if (it) 1 else 0) }
|
||||||
|
// Font Size
|
||||||
|
parcel.writeByte((if (fontSize != null) 1 else 0).toByte())
|
||||||
|
fontSize?.let { parcel.writeInt(it) }
|
||||||
|
// Line Height
|
||||||
|
parcel.writeByte((if (lineHeight != null) 1 else 0).toByte())
|
||||||
|
lineHeight?.let { parcel.writeInt(it) }
|
||||||
|
// Paragraph Height
|
||||||
|
parcel.writeByte((if (paragraphHeight != null) 1 else 0).toByte())
|
||||||
|
paragraphHeight?.let { parcel.writeInt(it) }
|
||||||
|
// Paragraph Indentation
|
||||||
|
parcel.writeByte((if (paragraphIndentation != null) 1 else 0).toByte())
|
||||||
|
paragraphIndentation?.let { parcel.writeByte(if (it) 1 else 0) }
|
||||||
|
// Background color
|
||||||
|
parcel.writeByte((if (backgroundColor != null) 1 else 0).toByte())
|
||||||
|
backgroundColor?.let { parcel.writeLong(it) }
|
||||||
|
// Font color
|
||||||
|
parcel.writeByte((if (fontColor != null) 1 else 0).toByte())
|
||||||
|
fontColor?.let { parcel.writeLong(it) }
|
||||||
|
// Show Start Screen
|
||||||
|
parcel.writeByte((if (showStartScreen != null) 1 else 0).toByte())
|
||||||
|
showStartScreen?.let { parcel.writeByte(if (it) 1 else 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<MainState> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): MainState {
|
||||||
|
return MainState(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<MainState?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
@ -17,13 +16,13 @@ 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.GetDatastore
|
import ua.acclorite.book_story.domain.use_case.GetDatastore
|
||||||
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.DataStoreConstants
|
||||||
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.ui.DarkTheme
|
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
||||||
import ua.acclorite.book_story.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
import ua.acclorite.book_story.ui.toDarkTheme
|
import ua.acclorite.book_story.presentation.ui.toDarkTheme
|
||||||
import ua.acclorite.book_story.ui.toTheme
|
import ua.acclorite.book_story.presentation.ui.toTheme
|
||||||
import ua.acclorite.book_story.util.Constants
|
|
||||||
import ua.acclorite.book_story.util.DataStoreConstants
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -42,212 +41,143 @@ class MainViewModel @Inject constructor(
|
||||||
private val _isReady = MutableStateFlow(false)
|
private val _isReady = MutableStateFlow(false)
|
||||||
val isReady = _isReady.asStateFlow()
|
val isReady = _isReady.asStateFlow()
|
||||||
|
|
||||||
/* -- Language ----------------------------------------------------- */
|
private val _state: MutableStateFlow<MainState> = MutableStateFlow(
|
||||||
private var _language: String =
|
stateHandle[Constants.MAIN_STATE] ?: MainState()
|
||||||
stateHandle[Constants.LANGUAGE]
|
)
|
||||||
?: if (
|
val state = _state.asStateFlow()
|
||||||
Constants.LANGUAGES.any { Locale.getDefault().language.take(2) == it.first }
|
|
||||||
) {
|
|
||||||
Locale.getDefault().language.take(2)
|
|
||||||
} else {
|
|
||||||
"en"
|
|
||||||
}
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.LANGUAGE] = value
|
|
||||||
}
|
|
||||||
val language: StateFlow<String?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.LANGUAGE, null)
|
|
||||||
|
|
||||||
/* -- Theme -------------------------------------------------------- */
|
|
||||||
private var _theme: Theme =
|
|
||||||
stateHandle[Constants.THEME]
|
|
||||||
?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Theme.DYNAMIC else Theme.BLUE
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.THEME] = value
|
|
||||||
}
|
|
||||||
val theme: StateFlow<Theme?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.THEME, null)
|
|
||||||
|
|
||||||
/* -- Dark Theme --------------------------------------------------- */
|
|
||||||
private var _darkTheme: DarkTheme =
|
|
||||||
stateHandle[Constants.DARK_THEME] ?: DarkTheme.FOLLOW_SYSTEM
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.DARK_THEME] = value
|
|
||||||
}
|
|
||||||
val darkTheme: StateFlow<DarkTheme?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.DARK_THEME, null)
|
|
||||||
|
|
||||||
/* -- Show Start Screen -------------------------------------------- */
|
|
||||||
private var _showStartScreen: Boolean =
|
|
||||||
stateHandle[Constants.SHOW_START_SCREEN] ?: true
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.SHOW_START_SCREEN] = value
|
|
||||||
}
|
|
||||||
val showStartScreen: StateFlow<Boolean?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.SHOW_START_SCREEN, null)
|
|
||||||
|
|
||||||
/* -- Background Color --------------------------------------------- */
|
|
||||||
private var _backgroundColor: Long =
|
|
||||||
stateHandle[Constants.BACKGROUND_COLOR] ?: Color.DarkGray.value.toLong()
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.BACKGROUND_COLOR] = value
|
|
||||||
}
|
|
||||||
val backgroundColor: StateFlow<Long?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.BACKGROUND_COLOR, null)
|
|
||||||
|
|
||||||
/* -- Font Color --------------------------------------------------- */
|
|
||||||
private var _fontColor: Long =
|
|
||||||
stateHandle[Constants.FONT_COLOR] ?: Color.LightGray.value.toLong()
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.FONT_COLOR] = value
|
|
||||||
}
|
|
||||||
val fontColor: StateFlow<Long?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.FONT_COLOR, null)
|
|
||||||
|
|
||||||
/* -- Font Family -------------------------------------------------- */
|
|
||||||
private var _fontFamily: String =
|
|
||||||
stateHandle[Constants.FONT] ?: Constants.FONTS[0].id
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.FONT] = value
|
|
||||||
}
|
|
||||||
val fontFamily: StateFlow<String?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.FONT, null)
|
|
||||||
|
|
||||||
/* -- Is Italic ---------------------------------------------------- */
|
|
||||||
private var _isItalic: Boolean =
|
|
||||||
stateHandle[Constants.IS_ITALIC] ?: false
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.IS_ITALIC] = value
|
|
||||||
}
|
|
||||||
val isItalic: StateFlow<Boolean?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.IS_ITALIC, null)
|
|
||||||
|
|
||||||
/* -- Font Size ---------------------------------------------------- */
|
|
||||||
private var _fontSize: Int =
|
|
||||||
stateHandle[Constants.FONT_SIZE] ?: 16
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.FONT_SIZE] = value
|
|
||||||
}
|
|
||||||
val fontSize: StateFlow<Int?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.FONT_SIZE, null)
|
|
||||||
|
|
||||||
/* -- Line Height -------------------------------------------------- */
|
|
||||||
private var _lineHeight: Int =
|
|
||||||
stateHandle[Constants.LINE_HEIGHT] ?: 4
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.LINE_HEIGHT] = value
|
|
||||||
}
|
|
||||||
val lineHeight: StateFlow<Int?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.LINE_HEIGHT, null)
|
|
||||||
|
|
||||||
/* -- Paragraph Height --------------------------------------------- */
|
|
||||||
private var _paragraphHeight: Int =
|
|
||||||
stateHandle[Constants.PARAGRAPH_HEIGHT] ?: 8
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.PARAGRAPH_HEIGHT] = value
|
|
||||||
}
|
|
||||||
val paragraphHeight: StateFlow<Int?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.PARAGRAPH_HEIGHT, null)
|
|
||||||
|
|
||||||
/* -- Paragraph Indentation ---------------------------------------- */
|
|
||||||
private var _paragraphIndentation: Boolean =
|
|
||||||
stateHandle[Constants.PARAGRAPH_INDENTATION] ?: false
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
stateHandle[Constants.PARAGRAPH_INDENTATION] = value
|
|
||||||
}
|
|
||||||
val paragraphIndentation: StateFlow<Boolean?>
|
|
||||||
get() = stateHandle.getStateFlow(Constants.PARAGRAPH_INDENTATION, null)
|
|
||||||
/* --------------------------------------------------------------- */
|
|
||||||
|
|
||||||
fun onEvent(event: MainEvent) {
|
fun onEvent(event: MainEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is MainEvent.OnChangeLanguage -> {
|
is MainEvent.OnChangeLanguage -> {
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
changeLanguage.execute(event.lang)
|
changeLanguage.execute(event.lang)
|
||||||
_language = event.lang
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
language = event.lang
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeDarkTheme -> {
|
is MainEvent.OnChangeDarkTheme -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.DARK_THEME, event.darkTheme)
|
setDatastore.execute(DataStoreConstants.DARK_THEME, event.darkTheme)
|
||||||
_darkTheme = event.darkTheme.toDarkTheme()
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
darkTheme = event.darkTheme.toDarkTheme()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeTheme -> {
|
is MainEvent.OnChangeTheme -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.THEME, event.theme)
|
setDatastore.execute(DataStoreConstants.THEME, event.theme)
|
||||||
_theme = event.theme.toTheme()
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
theme = event.theme.toTheme()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeFontFamily -> {
|
is MainEvent.OnChangeFontFamily -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.FONT, event.fontFamily)
|
setDatastore.execute(DataStoreConstants.FONT, event.fontFamily)
|
||||||
_fontFamily = Constants.FONTS.find { font -> font.id == event.fontFamily }?.id
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
fontFamily = Constants.FONTS.find { font -> font.id == event.fontFamily }?.id
|
||||||
?: Constants.FONTS[0].id
|
?: Constants.FONTS[0].id
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeFontStyle -> {
|
is MainEvent.OnChangeFontStyle -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.IS_ITALIC, event.fontStyle)
|
setDatastore.execute(DataStoreConstants.IS_ITALIC, event.fontStyle)
|
||||||
_isItalic = event.fontStyle
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
isItalic = event.fontStyle
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeFontSize -> {
|
is MainEvent.OnChangeFontSize -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.FONT_SIZE, event.fontSize)
|
setDatastore.execute(DataStoreConstants.FONT_SIZE, event.fontSize)
|
||||||
_fontSize = event.fontSize
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
fontSize = event.fontSize
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeLineHeight -> {
|
is MainEvent.OnChangeLineHeight -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, event.lineHeight)
|
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, event.lineHeight)
|
||||||
_lineHeight = event.lineHeight
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
lineHeight = event.lineHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeParagraphHeight -> {
|
is MainEvent.OnChangeParagraphHeight -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, event.paragraphHeight)
|
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, event.paragraphHeight)
|
||||||
_paragraphHeight = event.paragraphHeight
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
paragraphHeight = event.paragraphHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeParagraphIndentation -> {
|
is MainEvent.OnChangeParagraphIndentation -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, event.bool)
|
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, event.bool)
|
||||||
_paragraphIndentation = event.bool
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
paragraphIndentation = event.bool
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeBackgroundColor -> {
|
is MainEvent.OnChangeBackgroundColor -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, event.color)
|
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, event.color)
|
||||||
_backgroundColor = event.color
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
backgroundColor = event.color
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeFontColor -> {
|
is MainEvent.OnChangeFontColor -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.FONT_COLOR, event.color)
|
setDatastore.execute(DataStoreConstants.FONT_COLOR, event.color)
|
||||||
_fontColor = event.color
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
fontColor = event.color
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeShowStartScreen -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.SHOW_START_SCREEN, event.bool)
|
||||||
|
_state.updateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
showStartScreen = event.bool
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -263,20 +193,22 @@ class MainViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDataReady = combine(
|
val isDataReady = combine(
|
||||||
language,
|
_state
|
||||||
theme,
|
) {
|
||||||
darkTheme,
|
val value = it.first()
|
||||||
showStartScreen,
|
|
||||||
backgroundColor,
|
return@combine value.language != null &&
|
||||||
fontColor,
|
value.theme != null &&
|
||||||
fontFamily,
|
value.darkTheme != null &&
|
||||||
isItalic,
|
value.fontFamily != null &&
|
||||||
fontSize,
|
value.isItalic != null &&
|
||||||
lineHeight,
|
value.fontSize != null &&
|
||||||
paragraphHeight,
|
value.lineHeight != null &&
|
||||||
paragraphIndentation
|
value.paragraphHeight != null &&
|
||||||
) { values ->
|
value.paragraphIndentation != null &&
|
||||||
values.all { it != null }
|
value.backgroundColor != null &&
|
||||||
|
value.fontColor != null &&
|
||||||
|
value.showStartScreen != null
|
||||||
}
|
}
|
||||||
|
|
||||||
val isReady = combine(
|
val isReady = combine(
|
||||||
|
|
@ -300,7 +232,14 @@ class MainViewModel @Inject constructor(
|
||||||
// Language
|
// Language
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.LANGUAGE, _language)
|
.execute(
|
||||||
|
DataStoreConstants.LANGUAGE,
|
||||||
|
if (Constants.LANGUAGES.any { Locale.getDefault().language.take(2) == it.first }) {
|
||||||
|
Locale.getDefault().language.take(2)
|
||||||
|
} else {
|
||||||
|
"en"
|
||||||
|
}
|
||||||
|
)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeLanguage(it))
|
onEvent(MainEvent.OnChangeLanguage(it))
|
||||||
it.isNotBlank()
|
it.isNotBlank()
|
||||||
|
|
@ -310,7 +249,11 @@ class MainViewModel @Inject constructor(
|
||||||
// Theme
|
// Theme
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.THEME, _theme.toString())
|
.execute(
|
||||||
|
DataStoreConstants.THEME,
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Theme.DYNAMIC.name
|
||||||
|
else Theme.BLUE.name
|
||||||
|
)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeTheme(it))
|
onEvent(MainEvent.OnChangeTheme(it))
|
||||||
it.isNotBlank()
|
it.isNotBlank()
|
||||||
|
|
@ -320,7 +263,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Dark Theme
|
// Dark Theme
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.DARK_THEME, _darkTheme.toString())
|
.execute(DataStoreConstants.DARK_THEME, DarkTheme.FOLLOW_SYSTEM.name)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeDarkTheme(it))
|
onEvent(MainEvent.OnChangeDarkTheme(it))
|
||||||
it.isNotBlank()
|
it.isNotBlank()
|
||||||
|
|
@ -330,11 +273,9 @@ class MainViewModel @Inject constructor(
|
||||||
// Show Start Screen
|
// Show Start Screen
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.SHOW_START_SCREEN, _showStartScreen)
|
.execute(DataStoreConstants.SHOW_START_SCREEN, true)
|
||||||
.first {
|
.first {
|
||||||
setDatastore.execute(DataStoreConstants.SHOW_START_SCREEN, it)
|
onEvent(MainEvent.OnChangeShowStartScreen(it))
|
||||||
_showStartScreen = it
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -342,7 +283,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Background Color
|
// Background Color
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.BACKGROUND_COLOR, _backgroundColor)
|
.execute(DataStoreConstants.BACKGROUND_COLOR, Color.DarkGray.value.toLong())
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeBackgroundColor(it))
|
onEvent(MainEvent.OnChangeBackgroundColor(it))
|
||||||
true
|
true
|
||||||
|
|
@ -352,7 +293,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Font Color
|
// Font Color
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.FONT_COLOR, _fontColor)
|
.execute(DataStoreConstants.FONT_COLOR, Color.LightGray.value.toLong())
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeFontColor(it))
|
onEvent(MainEvent.OnChangeFontColor(it))
|
||||||
true
|
true
|
||||||
|
|
@ -362,7 +303,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Font Family
|
// Font Family
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.FONT, _fontFamily)
|
.execute(DataStoreConstants.FONT, Constants.FONTS[0].id)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeFontFamily(it))
|
onEvent(MainEvent.OnChangeFontFamily(it))
|
||||||
true
|
true
|
||||||
|
|
@ -372,7 +313,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Font Style
|
// Font Style
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.IS_ITALIC, _isItalic)
|
.execute(DataStoreConstants.IS_ITALIC, false)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeFontStyle(it))
|
onEvent(MainEvent.OnChangeFontStyle(it))
|
||||||
true
|
true
|
||||||
|
|
@ -382,7 +323,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Font Size
|
// Font Size
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.FONT_SIZE, _fontSize)
|
.execute(DataStoreConstants.FONT_SIZE, 16)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeFontSize(it))
|
onEvent(MainEvent.OnChangeFontSize(it))
|
||||||
it > 0
|
it > 0
|
||||||
|
|
@ -392,7 +333,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Line Height
|
// Line Height
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.LINE_HEIGHT, _lineHeight)
|
.execute(DataStoreConstants.LINE_HEIGHT, 4)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeLineHeight(it))
|
onEvent(MainEvent.OnChangeLineHeight(it))
|
||||||
true
|
true
|
||||||
|
|
@ -402,7 +343,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Paragraph Height
|
// Paragraph Height
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.PARAGRAPH_HEIGHT, _paragraphHeight)
|
.execute(DataStoreConstants.PARAGRAPH_HEIGHT, 8)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeParagraphHeight(it))
|
onEvent(MainEvent.OnChangeParagraphHeight(it))
|
||||||
true
|
true
|
||||||
|
|
@ -412,7 +353,7 @@ class MainViewModel @Inject constructor(
|
||||||
// Paragraph Indentation
|
// Paragraph Indentation
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
getDatastore
|
getDatastore
|
||||||
.execute(DataStoreConstants.PARAGRAPH_INDENTATION, _paragraphIndentation)
|
.execute(DataStoreConstants.PARAGRAPH_INDENTATION, false)
|
||||||
.first {
|
.first {
|
||||||
onEvent(MainEvent.OnChangeParagraphIndentation(it))
|
onEvent(MainEvent.OnChangeParagraphIndentation(it))
|
||||||
true
|
true
|
||||||
|
|
@ -421,4 +362,17 @@ class MainViewModel @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates [MutableStateFlow] along with [SavedStateHandle].
|
||||||
|
*/
|
||||||
|
private fun <T> MutableStateFlow<T>.updateWithSavedHandle(
|
||||||
|
const: String = Constants.MAIN_STATE,
|
||||||
|
function: (T) -> T
|
||||||
|
) {
|
||||||
|
val nextValue = function(value)
|
||||||
|
update {
|
||||||
|
stateHandle[const] = nextValue
|
||||||
|
nextValue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -28,7 +29,7 @@ import dagger.hilt.android.lifecycle.withCreationCallback
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ua.acclorite.book_story.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
|
|
||||||
|
|
||||||
private const val CURRENT_SCREEN = "current_screen"
|
private const val CURRENT_SCREEN = "current_screen"
|
||||||
|
|
@ -56,12 +57,12 @@ enum class Screen {
|
||||||
START
|
START
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class Argument(
|
data class Argument(
|
||||||
val key: String,
|
val key: String,
|
||||||
val arg: Any?
|
val arg: Any?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigator. Using for navigation between screens.
|
* Navigator. Using for navigation between screens.
|
||||||
*
|
*
|
||||||
|
|
@ -181,7 +182,6 @@ class Navigator @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(startScreen: Screen): Navigator
|
fun create(startScreen: Screen): Navigator
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.about
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
|
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.data.AboutEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.about.data.AboutViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AboutScreen(
|
||||||
|
viewModel: AboutViewModel = hiltViewModel(),
|
||||||
|
navigator: Navigator
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||||
|
canScroll = {
|
||||||
|
listState.canScrollForward || listState.canScrollBackward
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
|
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
topBar = {
|
||||||
|
LargeTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(stringResource(id = R.string.about_screen))
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
GoBackButton(navigator = navigator)
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
scrolledContainerColor = MaterialTheme.elevation()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(top = paddingValues.calculateTopPadding()),
|
||||||
|
state = listState
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(id = R.drawable.app_icon_monochrome),
|
||||||
|
contentDescription = stringResource(id = R.string.app_icon_content_desc),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(180.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(36.dp))
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
color = MaterialTheme.colorScheme.outlineVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
AboutItem(
|
||||||
|
title = stringResource(id = R.string.app_version_option),
|
||||||
|
description = stringResource(
|
||||||
|
id = R.string.app_version_option_desc,
|
||||||
|
stringResource(id = R.string.app_version)
|
||||||
|
),
|
||||||
|
isOnClickEnabled = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
AboutItem(
|
||||||
|
title = stringResource(id = R.string.all_releases_option),
|
||||||
|
description = stringResource(
|
||||||
|
id = R.string.all_releases_option_desc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"${context.getString(R.string.app_version_option)}:" +
|
||||||
|
" ${context.getString(R.string.app_version)}",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
viewModel.onEvent(
|
||||||
|
AboutEvent.OnNavigateToBrowserPage(
|
||||||
|
context.getString(R.string.releases_page),
|
||||||
|
context,
|
||||||
|
noAppsFound = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_no_browser),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
AboutItem(
|
||||||
|
title = stringResource(id = R.string.issues_option),
|
||||||
|
description = stringResource(
|
||||||
|
id = R.string.issues_option_desc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
viewModel.onEvent(
|
||||||
|
AboutEvent.OnNavigateToBrowserPage(
|
||||||
|
context.getString(R.string.issues_page),
|
||||||
|
context,
|
||||||
|
noAppsFound = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_no_browser),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
AboutItem(
|
||||||
|
title = stringResource(id = R.string.project_page_option),
|
||||||
|
description = stringResource(
|
||||||
|
id = R.string.project_page_option_desc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
viewModel.onEvent(
|
||||||
|
AboutEvent.OnNavigateToBrowserPage(
|
||||||
|
context.getString(R.string.project_page),
|
||||||
|
context,
|
||||||
|
noAppsFound = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_no_browser),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
AboutItem(
|
||||||
|
title = stringResource(id = R.string.support_option),
|
||||||
|
description = stringResource(
|
||||||
|
id = R.string.support_option_desc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
viewModel.onEvent(
|
||||||
|
AboutEvent.OnNavigateToBrowserPage(
|
||||||
|
context.getString(R.string.support_page),
|
||||||
|
context,
|
||||||
|
noAppsFound = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_no_browser),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
AboutItem(
|
||||||
|
title = stringResource(id = R.string.slava_ukraini_option),
|
||||||
|
description = null
|
||||||
|
) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.slava_ukraini_option_desc),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item { Spacer(modifier = Modifier.height(48.dp)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.about.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
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.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* About screen item.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun AboutItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
title: String,
|
||||||
|
description: String?,
|
||||||
|
verticalPadding: Dp = 12.dp,
|
||||||
|
isOnClickEnabled: Boolean = true,
|
||||||
|
onClick: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(enabled = isOnClickEnabled) {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
.padding(horizontal = 18.dp, vertical = verticalPadding),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
description?.let {
|
||||||
|
Text(
|
||||||
|
it,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.about.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
sealed class AboutEvent {
|
||||||
|
data class OnNavigateToBrowserPage(
|
||||||
|
val page: String,
|
||||||
|
val context: Context,
|
||||||
|
val noAppsFound: () -> Unit
|
||||||
|
) : AboutEvent()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.about.data
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class AboutViewModel @Inject constructor(
|
||||||
|
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
fun onEvent(event: AboutEvent) {
|
||||||
|
when (event) {
|
||||||
|
is AboutEvent.OnNavigateToBrowserPage -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val intent = Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(event.page)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (intent.resolveActivity(event.context.packageManager) != null) {
|
||||||
|
event.context.startActivity(intent)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
event.noAppsFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.book_info
|
package ua.acclorite.book_story.presentation.screens.book_info
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
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
|
||||||
|
|
@ -55,9 +56,7 @@ import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
||||||
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.Argument
|
|
||||||
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.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
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoInfoSection
|
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoInfoSection
|
||||||
|
|
@ -74,9 +73,9 @@ 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.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.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -99,8 +98,8 @@ fun BookInfoScreen(
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
BookInfoEvent.OnUpdateBook(
|
BookInfoEvent.OnUpdateBook(
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
snackbarState,
|
snackbarState,
|
||||||
context
|
context
|
||||||
|
|
@ -138,6 +137,7 @@ fun BookInfoScreen(
|
||||||
if (state.showMoveDialog) {
|
if (state.showMoveDialog) {
|
||||||
BookInfoMoveDialog(
|
BookInfoMoveDialog(
|
||||||
libraryViewModel = libraryViewModel,
|
libraryViewModel = libraryViewModel,
|
||||||
|
historyViewModel = historyViewModel,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navigator = navigator
|
navigator = navigator
|
||||||
)
|
)
|
||||||
|
|
@ -158,7 +158,7 @@ fun BookInfoScreen(
|
||||||
isTopBarScrolled = null,
|
isTopBarScrolled = null,
|
||||||
|
|
||||||
content1NavigationIcon = {
|
content1NavigationIcon = {
|
||||||
GoBackButton(navigator = navigator)
|
GoBackButton(navigator = navigator, enabled = !state.isRefreshing)
|
||||||
},
|
},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
DefaultTransition(visible = firstVisibleItemIndex > 0) {
|
DefaultTransition(visible = firstVisibleItemIndex > 0) {
|
||||||
|
|
@ -181,8 +181,8 @@ fun BookInfoScreen(
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
BookInfoEvent.OnUpdateBook(
|
BookInfoEvent.OnUpdateBook(
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
},
|
},
|
||||||
snackbarState,
|
snackbarState,
|
||||||
context
|
context
|
||||||
|
|
@ -218,9 +218,8 @@ fun BookInfoScreen(
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BookInfoEvent.OnUpdateTitle(
|
viewModel.onEvent(BookInfoEvent.OnUpdateTitle(
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
LibraryEvent.OnLoadList
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
@ -282,11 +281,9 @@ fun BookInfoScreen(
|
||||||
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
navigator.navigate(
|
if (!state.isRefreshing) {
|
||||||
Screen.READER, false, Argument(
|
viewModel.onEvent(BookInfoEvent.OnNavigateToReaderScreen(navigator))
|
||||||
"book", state.book.id
|
}
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
shape = MaterialTheme.shapes.large,
|
shape = MaterialTheme.shapes.large,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -332,4 +329,10 @@ fun BookInfoScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
if (!state.isRefreshing) {
|
||||||
|
navigator.navigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
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.BookInfoViewModel
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Info section.
|
* Info section.
|
||||||
|
|
@ -65,7 +65,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(195.dp)
|
.height(195.dp)
|
||||||
.width(130.dp)
|
.width(130.dp)
|
||||||
.clip(MaterialTheme.shapes.large)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.background(MaterialTheme.elevation())
|
.background(MaterialTheme.elevation())
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
|
@ -82,7 +82,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
|
||||||
contentDescription = stringResource(id = R.string.cover_image_content_desc),
|
contentDescription = stringResource(id = R.string.cover_image_content_desc),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(MaterialTheme.shapes.large),
|
.clip(MaterialTheme.shapes.medium),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,11 @@ fun BookInfoStatisticSection(book: Book) {
|
||||||
.removeTrailingZero()
|
.removeTrailingZero()
|
||||||
}%"
|
}%"
|
||||||
}
|
}
|
||||||
val description = if (book.progress < 1f) stringResource(
|
val description = stringResource(
|
||||||
id = R.string.you_read_query,
|
if (book.progress == 1f) R.string.read_done
|
||||||
progress
|
else if (book.progress > 0.2f) R.string.read_keep
|
||||||
) + " " + stringResource(
|
|
||||||
if (book.progress > 0.2f) R.string.read_keep
|
|
||||||
else R.string.read_more
|
else R.string.read_more
|
||||||
) else stringResource(id = R.string.read_done)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ 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.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.ui.ElevationDefaults
|
import ua.acclorite.book_story.presentation.ui.ElevationDefaults
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
|
@ -58,8 +58,8 @@ fun BookInfoChangeCoverBottomSheet(
|
||||||
uri,
|
uri,
|
||||||
context,
|
context,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -98,12 +98,14 @@ fun BookInfoChangeCoverBottomSheet(
|
||||||
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(BookInfoEvent.OnDeleteCover(
|
viewModel.onEvent(
|
||||||
|
BookInfoEvent.OnDeleteCover(
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
}
|
}
|
||||||
))
|
)
|
||||||
|
)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.cover_image_deleted),
|
context.getString(R.string.cover_image_deleted),
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ 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.BookInfoViewModel
|
||||||
import ua.acclorite.book_story.ui.ElevationDefaults
|
import ua.acclorite.book_story.presentation.ui.ElevationDefaults
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import ua.acclorite.book_story.presentation.components.custom_dialog.SelectableD
|
||||||
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.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.BookInfoViewModel
|
||||||
|
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.LibraryViewModel
|
||||||
|
|
||||||
|
|
@ -27,6 +29,7 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoMoveDialog(
|
fun BookInfoMoveDialog(
|
||||||
libraryViewModel: LibraryViewModel,
|
libraryViewModel: LibraryViewModel,
|
||||||
|
historyViewModel: HistoryViewModel,
|
||||||
viewModel: BookInfoViewModel,
|
viewModel: BookInfoViewModel,
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
|
|
@ -54,9 +57,8 @@ fun BookInfoMoveDialog(
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
BookInfoEvent.OnMoveBook(
|
BookInfoEvent.OnMoveBook(
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
LibraryEvent.OnLoadList
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
)
|
|
||||||
},
|
},
|
||||||
updatePage = {
|
updatePage = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,27 @@ 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.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
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
|
||||||
|
|
||||||
sealed class BookInfoEvent {
|
sealed class BookInfoEvent {
|
||||||
data object OnShowHideChangeCoverBottomSheet : BookInfoEvent()
|
data object OnShowHideChangeCoverBottomSheet : BookInfoEvent()
|
||||||
data object OnShowHideDetailsBottomSheet : BookInfoEvent()
|
data object OnShowHideDetailsBottomSheet : BookInfoEvent()
|
||||||
data class OnDeleteCover(val refreshList: () -> Unit) : BookInfoEvent()
|
data class OnDeleteCover(val refreshList: (Book) -> Unit) : BookInfoEvent()
|
||||||
data class OnChangeCover(val uri: Uri, val context: Context, val refreshList: () -> Unit) :
|
data class OnChangeCover(val uri: Uri, val context: Context, val refreshList: (Book) -> Unit) :
|
||||||
BookInfoEvent()
|
BookInfoEvent()
|
||||||
|
|
||||||
data object OnShowHideEditTitle : BookInfoEvent()
|
data object OnShowHideEditTitle : BookInfoEvent()
|
||||||
data class OnRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent()
|
data class OnRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent()
|
||||||
data class OnTitleValueChange(val value: String) : BookInfoEvent()
|
data class OnTitleValueChange(val value: String) : BookInfoEvent()
|
||||||
data class OnUpdateTitle(val refreshList: () -> Unit) : BookInfoEvent()
|
data class OnUpdateTitle(val refreshList: (Book) -> Unit) : BookInfoEvent()
|
||||||
data object OnShowHideMoreDropDown : BookInfoEvent()
|
data object OnShowHideMoreDropDown : BookInfoEvent()
|
||||||
data object OnShowHideDeleteDialog : BookInfoEvent()
|
data object OnShowHideDeleteDialog : BookInfoEvent()
|
||||||
data object OnShowHideMoveDialog : BookInfoEvent()
|
data object OnShowHideMoveDialog : BookInfoEvent()
|
||||||
data class OnDeleteBook(val refreshList: () -> Unit, val navigator: Navigator) : BookInfoEvent()
|
data class OnDeleteBook(val refreshList: () -> Unit, val navigator: Navigator) : BookInfoEvent()
|
||||||
data class OnMoveBook(
|
data class OnMoveBook(
|
||||||
val refreshList: () -> Unit,
|
val refreshList: (Book) -> Unit,
|
||||||
val updatePage: (Int) -> Unit,
|
val updatePage: (Int) -> Unit,
|
||||||
val navigator: Navigator
|
val navigator: Navigator
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
@ -44,8 +45,12 @@ sealed class BookInfoEvent {
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
data class OnUpdateBook(
|
data class OnUpdateBook(
|
||||||
val refreshList: () -> Unit,
|
val refreshList: (Book) -> Unit,
|
||||||
val snackbarState: SnackbarHostState,
|
val snackbarState: SnackbarHostState,
|
||||||
val context: Context
|
val context: Context
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
|
data class OnNavigateToReaderScreen(
|
||||||
|
val navigator: Navigator
|
||||||
|
) : BookInfoEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ package ua.acclorite.book_story.presentation.screens.book_info.data
|
||||||
import androidx.compose.runtime.Immutable
|
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.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class BookInfoState(
|
data class BookInfoState(
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,21 @@ 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.R
|
import ua.acclorite.book_story.R
|
||||||
|
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.model.History
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
import ua.acclorite.book_story.domain.use_case.DeleteBooks
|
import ua.acclorite.book_story.domain.use_case.DeleteBooks
|
||||||
import ua.acclorite.book_story.domain.use_case.GetBookFromFile
|
import ua.acclorite.book_story.domain.use_case.GetBookFromFile
|
||||||
import ua.acclorite.book_story.domain.use_case.GetBooksById
|
import ua.acclorite.book_story.domain.use_case.GetBooksById
|
||||||
|
import ua.acclorite.book_story.domain.use_case.InsertHistory
|
||||||
import ua.acclorite.book_story.domain.use_case.UpdateBooks
|
import ua.acclorite.book_story.domain.use_case.UpdateBooks
|
||||||
import ua.acclorite.book_story.domain.use_case.UpdateBooksWithText
|
import ua.acclorite.book_story.domain.use_case.UpdateBooksWithText
|
||||||
import ua.acclorite.book_story.domain.use_case.UpdateCoverImageOfBook
|
import ua.acclorite.book_story.domain.use_case.UpdateCoverImageOfBook
|
||||||
|
import ua.acclorite.book_story.presentation.data.Argument
|
||||||
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 java.util.Date
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,6 +40,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
private val updateBooks: UpdateBooks,
|
private val updateBooks: UpdateBooks,
|
||||||
private val updateBooksWithText: UpdateBooksWithText,
|
private val updateBooksWithText: UpdateBooksWithText,
|
||||||
private val updateCoverImageOfBook: UpdateCoverImageOfBook,
|
private val updateCoverImageOfBook: UpdateCoverImageOfBook,
|
||||||
|
private val insertHistory: InsertHistory,
|
||||||
private val deleteBooks: DeleteBooks,
|
private val deleteBooks: DeleteBooks,
|
||||||
private val getBookFromFile: GetBookFromFile,
|
private val getBookFromFile: GetBookFromFile,
|
||||||
private val getBookById: GetBooksById
|
private val getBookById: GetBooksById
|
||||||
|
|
@ -65,7 +71,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
_state.value.book,
|
_state.value.book,
|
||||||
image
|
image
|
||||||
)
|
)
|
||||||
event.refreshList()
|
|
||||||
val newCoverImage = getBookById.execute(
|
val newCoverImage = getBookById.execute(
|
||||||
listOf(
|
listOf(
|
||||||
_state.value.book.id ?: return@launch
|
_state.value.book.id ?: return@launch
|
||||||
|
|
@ -80,6 +86,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
showChangeCoverBottomSheet = false
|
showChangeCoverBottomSheet = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
event.refreshList(_state.value.book)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,8 +100,6 @@ class BookInfoViewModel @Inject constructor(
|
||||||
bookWithOldCover = _state.value.book,
|
bookWithOldCover = _state.value.book,
|
||||||
newCoverImage = null
|
newCoverImage = null
|
||||||
)
|
)
|
||||||
event.refreshList()
|
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
|
|
@ -103,6 +108,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
showChangeCoverBottomSheet = false
|
showChangeCoverBottomSheet = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
event.refreshList(_state.value.book)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,8 +161,6 @@ class BookInfoViewModel @Inject constructor(
|
||||||
_state.value.book.copy(title = title)
|
_state.value.book.copy(title = title)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
event.refreshList()
|
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
|
|
@ -164,6 +168,8 @@ class BookInfoViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
event.refreshList(_state.value.book)
|
||||||
|
|
||||||
onEvent(BookInfoEvent.OnShowHideEditTitle)
|
onEvent(BookInfoEvent.OnShowHideEditTitle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,13 +229,16 @@ class BookInfoViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val book = _state.value.book.copy(
|
_state.update {
|
||||||
category = _state.value.selectedCategory
|
it.copy(
|
||||||
|
book = it.book.copy(
|
||||||
|
category = it.selectedCategory
|
||||||
)
|
)
|
||||||
updateBooks.execute(listOf(book))
|
)
|
||||||
|
}
|
||||||
event.refreshList()
|
updateBooks.execute(listOf(_state.value.book))
|
||||||
|
|
||||||
|
event.refreshList(_state.value.book)
|
||||||
event.updatePage(
|
event.updatePage(
|
||||||
Category.entries.dropLastWhile {
|
Category.entries.dropLastWhile {
|
||||||
it != _state.value.selectedCategory
|
it != _state.value.selectedCategory
|
||||||
|
|
@ -375,7 +384,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
event.refreshList()
|
event.refreshList(_state.value.book)
|
||||||
|
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnShowSnackbar(
|
BookInfoEvent.OnShowSnackbar(
|
||||||
|
|
@ -394,20 +403,42 @@ class BookInfoViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is BookInfoEvent.OnNavigateToReaderScreen -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_state.value.book.id?.let {
|
||||||
|
insertHistory.execute(
|
||||||
|
listOf(
|
||||||
|
History(
|
||||||
|
null,
|
||||||
|
it,
|
||||||
|
null,
|
||||||
|
Date().time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
event.navigator.navigate(
|
||||||
|
Screen.READER,
|
||||||
|
false,
|
||||||
|
Argument(
|
||||||
|
"book", _state.value.book
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(navigator: Navigator) {
|
fun init(navigator: Navigator) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val bookIndex = navigator.retrieveArgument("book") as? Int
|
val book = navigator.retrieveArgument("book") as? Book
|
||||||
|
|
||||||
if (bookIndex == null || bookIndex < 0) {
|
if (book == null) {
|
||||||
navigator.navigateBack()
|
navigator.navigateBack()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val book = getBookById.execute(listOf(bookIndex)).first()
|
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
BookInfoState(book = book)
|
BookInfoState(book = book)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.browse
|
package ua.acclorite.book_story.presentation.screens.browse
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
|
@ -41,6 +42,7 @@ 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.graphics.SolidColor
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
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.TextStyle
|
||||||
|
|
@ -63,9 +65,9 @@ import ua.acclorite.book_story.presentation.screens.browse.components.adding_dia
|
||||||
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.BrowseViewModel
|
||||||
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.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class,
|
ExperimentalMaterial3Api::class,
|
||||||
|
|
@ -79,6 +81,7 @@ fun BrowseScreen(
|
||||||
libraryViewModel: LibraryViewModel,
|
libraryViewModel: LibraryViewModel,
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val permissionState = rememberPermissionState(
|
val permissionState = rememberPermissionState(
|
||||||
permission = Manifest.permission.READ_EXTERNAL_STORAGE
|
permission = Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
)
|
)
|
||||||
|
|
@ -130,20 +133,13 @@ fun BrowseScreen(
|
||||||
content1Visibility = !state.hasSelectedItems && !state.showSearch,
|
content1Visibility = !state.hasSelectedItems && !state.showSearch,
|
||||||
content1NavigationIcon = {},
|
content1NavigationIcon = {},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
Text(
|
Text(stringResource(id = R.string.browse_screen))
|
||||||
stringResource(id = R.string.browse_screen),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
content1Actions = {
|
content1Actions = {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.Default.Search,
|
icon = Icons.Default.Search,
|
||||||
contentDescription = stringResource(id = R.string.search_content_desc),
|
contentDescription = stringResource(id = R.string.search_content_desc),
|
||||||
disableOnClick = false,
|
disableOnClick = true
|
||||||
enabled = !state.showSearch
|
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(BrowseEvent.OnSearchShowHide)
|
viewModel.onEvent(BrowseEvent.OnSearchShowHide)
|
||||||
}
|
}
|
||||||
|
|
@ -167,8 +163,6 @@ fun BrowseScreen(
|
||||||
id = R.string.selected_items_count_query,
|
id = R.string.selected_items_count_query,
|
||||||
state.selectedItemsCount.coerceAtLeast(1)
|
state.selectedItemsCount.coerceAtLeast(1)
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
|
|
@ -265,6 +259,16 @@ fun BrowseScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItemPlacement(),
|
.animateItemPlacement(),
|
||||||
hasSelectedFiles = state.selectableFiles.any { it.second },
|
hasSelectedFiles = state.selectableFiles.any { it.second },
|
||||||
|
onLongClick = {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(
|
||||||
|
R.string.file_path_query,
|
||||||
|
selectableFile.first.path
|
||||||
|
),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onEvent(BrowseEvent.OnSelectFile(selectableFile))
|
viewModel.onEvent(BrowseEvent.OnSelectFile(selectableFile))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ package ua.acclorite.book_story.presentation.screens.browse.components
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -33,9 +34,9 @@ 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 ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
import ua.acclorite.book_story.presentation.components.CustomCheckbox
|
import ua.acclorite.book_story.presentation.components.CustomCheckbox
|
||||||
import ua.acclorite.book_story.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.util.Selected
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
@ -44,11 +45,14 @@ import java.util.Locale
|
||||||
/**
|
/**
|
||||||
* Browse list element item. Can be selected.
|
* Browse list element item. Can be selected.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseFileItem(
|
fun BrowseFileItem(
|
||||||
file: Pair<File, Selected>,
|
file: Pair<File, Selected>,
|
||||||
hasSelectedFiles: Boolean,
|
hasSelectedFiles: Boolean,
|
||||||
modifier: Modifier, onClick: () -> Unit
|
modifier: Modifier,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val pattern = remember { SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault()) }
|
val pattern = remember { SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault()) }
|
||||||
val lastModified = remember { pattern.format(Date(file.first.lastModified())) }
|
val lastModified = remember { pattern.format(Date(file.first.lastModified())) }
|
||||||
|
|
@ -85,9 +89,14 @@ fun BrowseFileItem(
|
||||||
.padding(horizontal = 8.dp, vertical = 3.dp)
|
.padding(horizontal = 8.dp, vertical = 3.dp)
|
||||||
.clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(animatedBackgroundColor)
|
.background(animatedBackgroundColor)
|
||||||
.clickable {
|
.combinedClickable(
|
||||||
|
onLongClick = {
|
||||||
|
onLongClick()
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
onClick()
|
onClick()
|
||||||
}
|
}
|
||||||
|
)
|
||||||
.padding(horizontal = 8.dp, vertical = 7.dp)
|
.padding(horizontal = 8.dp, vertical = 7.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ fun BrowseAddingDialog(
|
||||||
navigator,
|
navigator,
|
||||||
resetScroll = {
|
resetScroll = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(0))
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(0))
|
||||||
|
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -83,7 +84,9 @@ fun BrowseAddingDialog(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
items(state.selectedBooks) { book ->
|
items(state.selectedBooks) { book ->
|
||||||
BrowseAddingDialogItem(result = book) {
|
BrowseAddingDialogItem(
|
||||||
|
result = book
|
||||||
|
) {
|
||||||
if (it) {
|
if (it) {
|
||||||
viewModel.onEvent(BrowseEvent.OnSelectBook(book))
|
viewModel.onEvent(BrowseEvent.OnSelectBook(book))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Error
|
import androidx.compose.material.icons.filled.Error
|
||||||
import androidx.compose.material.icons.filled.Lock
|
|
||||||
import androidx.compose.material3.Icon
|
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
|
||||||
|
|
@ -23,8 +22,8 @@ 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.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
import ua.acclorite.book_story.presentation.components.CustomCheckbox
|
import ua.acclorite.book_story.presentation.components.CustomCheckbox
|
||||||
import ua.acclorite.book_story.util.Selected
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adding dialog item.
|
* Adding dialog item.
|
||||||
|
|
@ -64,13 +63,6 @@ fun BrowseAddingDialogItem(result: Pair<NullableBook, Selected>, onClick: (Boole
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val icon =
|
|
||||||
if (result.first.message?.asString() == stringResource(R.string.error_file_encrypted)) {
|
|
||||||
Icons.Default.Lock
|
|
||||||
} else {
|
|
||||||
Icons.Default.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -81,7 +73,7 @@ fun BrowseAddingDialogItem(result: Pair<NullableBook, Selected>, onClick: (Boole
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = Icons.Default.Error,
|
||||||
contentDescription = stringResource(id = R.string.error_content_desc),
|
contentDescription = stringResource(id = R.string.error_content_desc),
|
||||||
modifier = Modifier.size(26.dp),
|
modifier = Modifier.size(26.dp),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ 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
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
|
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 ua.acclorite.book_story.util.Selected
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
sealed class BrowseEvent {
|
sealed class BrowseEvent {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package ua.acclorite.book_story.presentation.screens.browse.data
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
import ua.acclorite.book_story.util.Selected
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -18,9 +18,6 @@ data class BrowseState(
|
||||||
val selectedItemsCount: Int = 0,
|
val selectedItemsCount: Int = 0,
|
||||||
val hasSelectedItems: Boolean = false,
|
val hasSelectedItems: Boolean = false,
|
||||||
|
|
||||||
val scrollIndex: Int = 0,
|
|
||||||
val scrollOffset: Int = 0,
|
|
||||||
|
|
||||||
val showSearch: Boolean = false,
|
val showSearch: Boolean = false,
|
||||||
val searchQuery: String = "",
|
val searchQuery: String = "",
|
||||||
val hasFocused: Boolean = false,
|
val hasFocused: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ import ua.acclorite.book_story.domain.use_case.FastGetBooks
|
||||||
import ua.acclorite.book_story.domain.use_case.GetBooksFromFiles
|
import ua.acclorite.book_story.domain.use_case.GetBooksFromFiles
|
||||||
import ua.acclorite.book_story.domain.use_case.GetFilesFromDevice
|
import ua.acclorite.book_story.domain.use_case.GetFilesFromDevice
|
||||||
import ua.acclorite.book_story.domain.use_case.InsertBooks
|
import ua.acclorite.book_story.domain.use_case.InsertBooks
|
||||||
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.presentation.data.Argument
|
import ua.acclorite.book_story.presentation.data.Argument
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.util.Resource
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
|
|
@ -41,6 +41,7 @@ class BrowseViewModel @Inject constructor(
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
private var job2: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|
@ -127,7 +128,8 @@ class BrowseViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BrowseEvent.OnRefreshList -> {
|
is BrowseEvent.OnRefreshList -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
job2?.cancel()
|
||||||
|
job2 = viewModelScope.launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = true,
|
isRefreshing = true,
|
||||||
|
|
@ -169,20 +171,19 @@ class BrowseViewModel @Inject constructor(
|
||||||
|
|
||||||
is BrowseEvent.OnSelectFile -> {
|
is BrowseEvent.OnSelectFile -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val indexOfFile = _state.value.selectableFiles.indexOf(event.file)
|
val editedList = _state.value.selectableFiles.map {
|
||||||
|
if (event.file.first.path == it.first.path) {
|
||||||
if (indexOfFile == -1) {
|
it.copy(
|
||||||
return@launch
|
second = !it.second
|
||||||
}
|
|
||||||
|
|
||||||
val editedList = _state.value.selectableFiles.toMutableList()
|
|
||||||
editedList[indexOfFile] = editedList[indexOfFile].copy(
|
|
||||||
second = !editedList[indexOfFile].second
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
selectableFiles = editedList.toList(),
|
selectableFiles = editedList,
|
||||||
selectedItemsCount = editedList.filter { file -> file.second }.size,
|
selectedItemsCount = editedList.filter { file -> file.second }.size,
|
||||||
hasSelectedItems = editedList.any { file -> file.second }
|
hasSelectedItems = editedList.any { file -> file.second }
|
||||||
)
|
)
|
||||||
|
|
@ -267,7 +268,7 @@ class BrowseViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = viewModelScope.launch {
|
job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
delay(500)
|
delay(500)
|
||||||
getFilesFromDownloads()
|
getFilesFromDownloads()
|
||||||
}
|
}
|
||||||
|
|
@ -352,6 +353,7 @@ class BrowseViewModel @Inject constructor(
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = true,
|
isLoading = true,
|
||||||
|
hasSelectedItems = false,
|
||||||
listState = LazyListState(0, 0)
|
listState = LazyListState(0, 0)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,9 @@ 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.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.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
|
|
@ -79,7 +79,6 @@ fun HistoryScreen(
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val books = libraryViewModel.state.collectAsState().value.books.map { it.first }
|
|
||||||
val refreshState = rememberPullRefreshState(
|
val refreshState = rememberPullRefreshState(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.isRefreshing,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
|
|
@ -108,19 +107,14 @@ fun HistoryScreen(
|
||||||
content1NavigationIcon = {},
|
content1NavigationIcon = {},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(id = R.string.history_screen),
|
stringResource(id = R.string.history_screen)
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content1Actions = {
|
content1Actions = {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.Default.Search,
|
icon = Icons.Default.Search,
|
||||||
contentDescription = stringResource(id = R.string.search_content_desc),
|
contentDescription = stringResource(id = R.string.search_content_desc),
|
||||||
disableOnClick = false,
|
disableOnClick = true,
|
||||||
enabled = !state.showSearch
|
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(HistoryEvent.OnSearchShowHide)
|
viewModel.onEvent(HistoryEvent.OnSearchShowHide)
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +127,7 @@ fun HistoryScreen(
|
||||||
enabled = !state.isLoading
|
enabled = !state.isLoading
|
||||||
&& !state.isRefreshing
|
&& !state.isRefreshing
|
||||||
&& state.history.isNotEmpty()
|
&& state.history.isNotEmpty()
|
||||||
|
&& !state.showDeleteWholeHistoryDialog
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog)
|
viewModel.onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog)
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +209,6 @@ fun HistoryScreen(
|
||||||
state = state.listState,
|
state = state.listState,
|
||||||
contentPadding = PaddingValues(vertical = 12.dp)
|
contentPadding = PaddingValues(vertical = 12.dp)
|
||||||
) {
|
) {
|
||||||
if (!state.isLoading) {
|
|
||||||
state.history.forEachIndexed { index, groupedHistory ->
|
state.history.forEachIndexed { index, groupedHistory ->
|
||||||
item(key = groupedHistory.title) {
|
item(key = groupedHistory.title) {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
|
|
@ -230,32 +224,30 @@ fun HistoryScreen(
|
||||||
},
|
},
|
||||||
padding = 16.dp
|
padding = 16.dp
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
items(
|
items(
|
||||||
groupedHistory.history, key = { it.id ?: UUID.randomUUID() }
|
groupedHistory.history, key = { it.id ?: UUID.randomUUID() }
|
||||||
) {
|
) {
|
||||||
val book = remember {
|
|
||||||
books.find { book -> book.id == it.bookId }
|
|
||||||
} ?: return@items
|
|
||||||
|
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
history = it,
|
history = it,
|
||||||
book = book,
|
isOnClickEnabled = !state.isRefreshing,
|
||||||
onBodyClick = {
|
onBodyClick = {
|
||||||
navigator.navigate(
|
navigator.navigate(
|
||||||
Screen.BOOK_INFO,
|
Screen.BOOK_INFO,
|
||||||
false,
|
false,
|
||||||
Argument("book", book.id)
|
Argument("book", it.book)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
navigator.navigate(
|
viewModel.onEvent(
|
||||||
Screen.READER,
|
HistoryEvent.OnNavigateToReaderScreen(
|
||||||
false,
|
navigator,
|
||||||
Argument("book", book.id)
|
it.book
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
isDeleteEnabled = !state.isRefreshing,
|
isDeleteEnabled = !state.isRefreshing,
|
||||||
|
|
@ -266,7 +258,9 @@ fun HistoryScreen(
|
||||||
snackbarState = snackbarState,
|
snackbarState = snackbarState,
|
||||||
context = context,
|
context = context,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
libraryViewModel.onEvent(
|
||||||
|
LibraryEvent.OnLoadList
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -276,10 +270,11 @@ fun HistoryScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = !state.isLoading && state.history.isEmpty() && !state.isRefreshing,
|
visible = !state.isLoading
|
||||||
|
&& state.history.isEmpty()
|
||||||
|
&& !state.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))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,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.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Image
|
import androidx.compose.material.icons.filled.Image
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
|
@ -30,11 +31,11 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
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.compose.ui.unit.sp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
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.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
@ -46,7 +47,7 @@ import java.util.Locale
|
||||||
fun HistoryItem(
|
fun HistoryItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
history: History,
|
history: History,
|
||||||
book: Book,
|
isOnClickEnabled: Boolean,
|
||||||
onBodyClick: () -> Unit,
|
onBodyClick: () -> Unit,
|
||||||
onTitleClick: () -> Unit,
|
onTitleClick: () -> Unit,
|
||||||
isDeleteEnabled: Boolean,
|
isDeleteEnabled: Boolean,
|
||||||
|
|
@ -61,7 +62,7 @@ fun HistoryItem(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable(enabled = isOnClickEnabled) {
|
||||||
onBodyClick()
|
onBodyClick()
|
||||||
}
|
}
|
||||||
.padding(top = 6.dp, bottom = 6.dp, start = 16.dp, end = 10.dp)
|
.padding(top = 6.dp, bottom = 6.dp, start = 16.dp, end = 10.dp)
|
||||||
|
|
@ -74,16 +75,16 @@ fun HistoryItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(90.dp)
|
.height(90.dp)
|
||||||
.width(60.dp)
|
.width(60.dp)
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(MaterialTheme.elevation())
|
.background(MaterialTheme.elevation())
|
||||||
) {
|
) {
|
||||||
if (book.coverImage != null) {
|
if (history.book?.coverImage != null) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = book.coverImage,
|
model = history.book.coverImage,
|
||||||
contentDescription = stringResource(id = R.string.cover_image_content_desc),
|
contentDescription = stringResource(id = R.string.cover_image_content_desc),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(MaterialTheme.shapes.medium),
|
.clip(RoundedCornerShape(10.dp)),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -108,14 +109,16 @@ fun HistoryItem(
|
||||||
modifier = Modifier.fillMaxHeight()
|
modifier = Modifier.fillMaxHeight()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
book.title,
|
history.book?.title ?: return,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
lineHeight = 21.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
indication = null,
|
indication = null,
|
||||||
|
enabled = isOnClickEnabled,
|
||||||
onClick = onTitleClick
|
onClick = onTitleClick
|
||||||
),
|
),
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
|
|
@ -135,7 +138,7 @@ fun HistoryItem(
|
||||||
}
|
}
|
||||||
Box(modifier = Modifier.weight(0.11f), contentAlignment = Alignment.CenterEnd) {
|
Box(modifier = Modifier.weight(0.11f), contentAlignment = Alignment.CenterEnd) {
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = isDeleteEnabled,
|
enabled = isDeleteEnabled && isOnClickEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
onDeleteClick()
|
onDeleteClick()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ 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.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
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
|
||||||
|
|
||||||
sealed class HistoryEvent {
|
sealed class HistoryEvent {
|
||||||
data object OnRefreshList : HistoryEvent()
|
data object OnRefreshList : HistoryEvent()
|
||||||
|
|
@ -20,4 +22,9 @@ sealed class HistoryEvent {
|
||||||
data class OnSearchQueryChange(val query: String) : HistoryEvent()
|
data class OnSearchQueryChange(val query: String) : 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 OnNavigateToReaderScreen(
|
||||||
|
val navigator: Navigator,
|
||||||
|
val book: Book?
|
||||||
|
) : HistoryEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -15,8 +15,4 @@ data class HistoryState(
|
||||||
val showSearch: Boolean = false,
|
val showSearch: Boolean = false,
|
||||||
val searchQuery: String = "",
|
val searchQuery: String = "",
|
||||||
val hasFocused: Boolean = false,
|
val hasFocused: Boolean = false,
|
||||||
|
|
||||||
val scrollIndex: Int = 0,
|
|
||||||
val scrollOffset: Int = 0,
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,12 @@ import ua.acclorite.book_story.domain.use_case.DeleteWholeHistory
|
||||||
import ua.acclorite.book_story.domain.use_case.GetBooksById
|
import ua.acclorite.book_story.domain.use_case.GetBooksById
|
||||||
import ua.acclorite.book_story.domain.use_case.GetHistory
|
import ua.acclorite.book_story.domain.use_case.GetHistory
|
||||||
import ua.acclorite.book_story.domain.use_case.InsertHistory
|
import ua.acclorite.book_story.domain.use_case.InsertHistory
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
|
import ua.acclorite.book_story.presentation.data.Argument
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -40,6 +43,7 @@ class HistoryViewModel @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
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|
@ -55,7 +59,8 @@ class HistoryViewModel @Inject constructor(
|
||||||
fun onEvent(event: HistoryEvent) {
|
fun onEvent(event: HistoryEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is HistoryEvent.OnRefreshList -> {
|
is HistoryEvent.OnRefreshList -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
job3?.cancel()
|
||||||
|
job3 = viewModelScope.launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = true,
|
isRefreshing = true,
|
||||||
|
|
@ -115,6 +120,7 @@ class HistoryViewModel @Inject constructor(
|
||||||
deleteHistory.execute(
|
deleteHistory.execute(
|
||||||
listOf(event.historyToDelete)
|
listOf(event.historyToDelete)
|
||||||
)
|
)
|
||||||
|
|
||||||
onEvent(HistoryEvent.OnRefreshList)
|
onEvent(HistoryEvent.OnRefreshList)
|
||||||
event.refreshList()
|
event.refreshList()
|
||||||
|
|
||||||
|
|
@ -172,7 +178,7 @@ class HistoryViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = viewModelScope.launch {
|
job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
delay(500)
|
delay(500)
|
||||||
getHistoryFromDatabase()
|
getHistoryFromDatabase()
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +194,54 @@ class HistoryViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is HistoryEvent.OnUpdateBook -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val history = _state.value.history.map {
|
||||||
|
it.copy(
|
||||||
|
history = it.history.map { history ->
|
||||||
|
if (history.bookId == event.book.id) {
|
||||||
|
history.copy(
|
||||||
|
book = event.book
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
history
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
history = history
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is HistoryEvent.OnNavigateToReaderScreen -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
event.book?.id?.let {
|
||||||
|
insertHistory.execute(
|
||||||
|
listOf(
|
||||||
|
History(
|
||||||
|
null,
|
||||||
|
it,
|
||||||
|
null,
|
||||||
|
Date().time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
event.navigator.navigate(
|
||||||
|
Screen.READER,
|
||||||
|
false,
|
||||||
|
Argument(
|
||||||
|
"book", event.book
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,9 +264,10 @@ class HistoryViewModel @Inject constructor(
|
||||||
getHistory.execute().collect { result ->
|
getHistory.execute().collect { result ->
|
||||||
when (result) {
|
when (result) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
val history = result.data?.sortedByDescending { it.time } ?: emptyList()
|
val historyWithoutBook =
|
||||||
|
result.data?.sortedByDescending { it.time } ?: emptyList()
|
||||||
|
|
||||||
if (history.isEmpty()) {
|
if (historyWithoutBook.isEmpty()) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
history = emptyList(),
|
history = emptyList(),
|
||||||
|
|
@ -223,8 +278,26 @@ class HistoryViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val books = getBooksById.execute(
|
val books = getBooksById.execute(
|
||||||
history.map { it.bookId }.distinct()
|
historyWithoutBook.map { it.bookId }.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (books.isEmpty()) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
history = emptyList(),
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return@collect
|
||||||
|
}
|
||||||
|
|
||||||
|
val history = historyWithoutBook.map {
|
||||||
|
val book = books.find { book -> book.id == it.bookId }!!
|
||||||
|
it.copy(
|
||||||
|
book = book
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val groupedHistory = mutableListOf<GroupedHistory>()
|
val groupedHistory = mutableListOf<GroupedHistory>()
|
||||||
|
|
||||||
history
|
history
|
||||||
|
|
@ -278,20 +351,3 @@ class HistoryViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ import kotlinx.coroutines.launch
|
||||||
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.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.presentation.components.AnimatedTopAppBar
|
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
|
||||||
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
import ua.acclorite.book_story.presentation.components.MoreDropDown
|
import ua.acclorite.book_story.presentation.components.MoreDropDown
|
||||||
|
|
@ -83,10 +84,9 @@ import ua.acclorite.book_story.presentation.screens.library.components.dialog.Li
|
||||||
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.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
||||||
import ua.acclorite.book_story.ui.DefaultTransition
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
import ua.acclorite.book_story.ui.Transitions
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import ua.acclorite.book_story.util.Selected
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
|
|
@ -98,8 +98,7 @@ fun LibraryScreen(
|
||||||
viewModel: LibraryViewModel,
|
viewModel: LibraryViewModel,
|
||||||
browseViewModel: BrowseViewModel,
|
browseViewModel: BrowseViewModel,
|
||||||
historyViewModel: HistoryViewModel,
|
historyViewModel: HistoryViewModel,
|
||||||
navigator: Navigator,
|
navigator: Navigator
|
||||||
addedBooks: List<Book>
|
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val pagerState = rememberPagerState(
|
val pagerState = rememberPagerState(
|
||||||
|
|
@ -114,21 +113,21 @@ fun LibraryScreen(
|
||||||
)
|
)
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (addedBooks.isNotEmpty()) {
|
|
||||||
viewModel.onEvent(LibraryEvent.OnPreloadBooks(addedBooks))
|
|
||||||
navigator.clearArgument("added_books")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.onEvent(LibraryEvent.OnScrollToPage(state.currentPage, pagerState))
|
viewModel.onEvent(LibraryEvent.OnScrollToPage(state.currentPage, pagerState))
|
||||||
}
|
}
|
||||||
LaunchedEffect(pagerState.currentPage) {
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
|
if (pagerState.currentPage != state.currentPage) {
|
||||||
viewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(pagerState.currentPage))
|
viewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(pagerState.currentPage))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state.showMoveDialog) {
|
if (state.showMoveDialog) {
|
||||||
LibraryMoveDialog(viewModel = viewModel, pagerState = pagerState)
|
LibraryMoveDialog(
|
||||||
|
viewModel = viewModel,
|
||||||
|
historyViewModel = historyViewModel,
|
||||||
|
pagerState = pagerState
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (state.showDeleteDialog) {
|
if (state.showDeleteDialog) {
|
||||||
LibraryDeleteDialog(
|
LibraryDeleteDialog(
|
||||||
|
|
@ -156,13 +155,7 @@ fun LibraryScreen(
|
||||||
content1NavigationIcon = {},
|
content1NavigationIcon = {},
|
||||||
content1Title = {
|
content1Title = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(
|
Text(stringResource(id = R.string.library_screen))
|
||||||
stringResource(id = R.string.library_screen),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(6.dp))
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
Text(
|
Text(
|
||||||
text = state.books.size.toString(),
|
text = state.books.size.toString(),
|
||||||
|
|
@ -179,8 +172,7 @@ fun LibraryScreen(
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.Default.Search,
|
icon = Icons.Default.Search,
|
||||||
contentDescription = stringResource(id = R.string.search_content_desc),
|
contentDescription = stringResource(id = R.string.search_content_desc),
|
||||||
disableOnClick = false,
|
disableOnClick = true,
|
||||||
enabled = !state.showSearch
|
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(LibraryEvent.OnSearchShowHide)
|
viewModel.onEvent(LibraryEvent.OnSearchShowHide)
|
||||||
}
|
}
|
||||||
|
|
@ -204,8 +196,6 @@ fun LibraryScreen(
|
||||||
id = R.string.selected_items_count_query,
|
id = R.string.selected_items_count_query,
|
||||||
state.selectedItemsCount.coerceAtLeast(1)
|
state.selectedItemsCount.coerceAtLeast(1)
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
|
|
@ -216,7 +206,11 @@ fun LibraryScreen(
|
||||||
contentDescription = stringResource(
|
contentDescription = stringResource(
|
||||||
id = R.string.move_books_content_desc
|
id = R.string.move_books_content_desc
|
||||||
),
|
),
|
||||||
disableOnClick = true
|
enabled = !state.isLoading
|
||||||
|
&& !state.isRefreshing
|
||||||
|
&& !state.showMoveDialog,
|
||||||
|
disableOnClick = false,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(LibraryEvent.OnShowHideMoveDialog)
|
viewModel.onEvent(LibraryEvent.OnShowHideMoveDialog)
|
||||||
}
|
}
|
||||||
|
|
@ -225,7 +219,10 @@ fun LibraryScreen(
|
||||||
contentDescription = stringResource(
|
contentDescription = stringResource(
|
||||||
id = R.string.delete_books_content_desc
|
id = R.string.delete_books_content_desc
|
||||||
),
|
),
|
||||||
disableOnClick = true
|
enabled = !state.isLoading
|
||||||
|
&& !state.isRefreshing
|
||||||
|
&& !state.showDeleteDialog,
|
||||||
|
disableOnClick = false
|
||||||
) {
|
) {
|
||||||
viewModel.onEvent(LibraryEvent.OnShowHideDeleteDialog)
|
viewModel.onEvent(LibraryEvent.OnShowHideDeleteDialog)
|
||||||
}
|
}
|
||||||
|
|
@ -310,7 +307,9 @@ fun LibraryScreen(
|
||||||
|
|
||||||
LaunchedEffect(state.books) {
|
LaunchedEffect(state.books) {
|
||||||
categorizedBooks.clear()
|
categorizedBooks.clear()
|
||||||
categorizedBooks.addAll(state.books.filter { it.first.category == category }
|
categorizedBooks.addAll(
|
||||||
|
state.books
|
||||||
|
.filter { it.first.category == category }
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
compareByDescending<Pair<Book, Selected>> { it.first.lastOpened }
|
compareByDescending<Pair<Book, Selected>> { it.first.lastOpened }
|
||||||
.thenBy { it.first.title }
|
.thenBy { it.first.title }
|
||||||
|
|
@ -323,8 +322,7 @@ fun LibraryScreen(
|
||||||
DefaultTransition(visible = !state.isLoading && !categoryIsLoading) {
|
DefaultTransition(visible = !state.isLoading && !categoryIsLoading) {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Adaptive(120.dp),
|
columns = GridCells.Adaptive(120.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.fillMaxSize(),
|
|
||||||
contentPadding = PaddingValues(8.dp)
|
contentPadding = PaddingValues(8.dp)
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
|
|
@ -341,7 +339,7 @@ fun LibraryScreen(
|
||||||
navigator.navigate(
|
navigator.navigate(
|
||||||
Screen.BOOK_INFO,
|
Screen.BOOK_INFO,
|
||||||
false,
|
false,
|
||||||
Argument("book", it.first.id)
|
Argument("book", it.first)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -351,10 +349,11 @@ fun LibraryScreen(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onButtonClick = {
|
onButtonClick = {
|
||||||
navigator.navigate(
|
viewModel.onEvent(
|
||||||
Screen.READER,
|
LibraryEvent.OnNavigateToReaderScreen(
|
||||||
false,
|
navigator,
|
||||||
Argument("book", it.first.id)
|
it.first
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -363,7 +362,9 @@ fun LibraryScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = !state.isLoading && !state.isRefreshing && categorizedBooks.isEmpty()
|
visible = !state.isLoading
|
||||||
|
&& !state.isRefreshing
|
||||||
|
&& categorizedBooks.isEmpty()
|
||||||
&& !categoryIsLoading,
|
&& !categoryIsLoading,
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
enter = Transitions.DefaultTransitionIn,
|
enter = Transitions.DefaultTransitionIn,
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,10 @@ import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
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.domain.model.Book
|
||||||
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
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.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import ua.acclorite.book_story.util.Selected
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Library list element item.
|
* Library list element item.
|
||||||
|
|
@ -93,7 +93,7 @@ fun LibraryBookItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.aspectRatio(1f / 1.5f)
|
.aspectRatio(1f / 1.5f)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(MaterialTheme.shapes.large)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.background(MaterialTheme.elevation())
|
.background(MaterialTheme.elevation())
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -110,7 +110,7 @@ fun LibraryBookItem(
|
||||||
contentDescription = stringResource(id = R.string.cover_image_content_desc),
|
contentDescription = stringResource(id = R.string.cover_image_content_desc),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(MaterialTheme.shapes.large),
|
.clip(MaterialTheme.shapes.medium),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package ua.acclorite.book_story.presentation.screens.library.components
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
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.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
|
||||||
|
|
@ -13,7 +14,6 @@ import androidx.compose.material3.Divider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ScrollableTabRow
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
|
||||||
import androidx.compose.material3.TabRowDefaults
|
import androidx.compose.material3.TabRowDefaults
|
||||||
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -22,7 +22,7 @@ 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.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
|
|
@ -30,7 +30,7 @@ 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.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.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tab row, either scrollable or static, depends on screen width.
|
* Tab row, either scrollable or static, depends on screen width.
|
||||||
|
|
@ -60,83 +60,34 @@ fun LibraryTabRow(viewModel: LibraryViewModel, books: List<Book>, pagerState: Pa
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LocalConfiguration.current.screenWidthDp > 450) {
|
Box(Modifier.fillMaxWidth()) {
|
||||||
TabRow(
|
Divider(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
selectedTabIndex = pagerState.currentPage,
|
color = MaterialTheme.colorScheme.surfaceVariant
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
|
||||||
divider = {
|
|
||||||
Divider(color = MaterialTheme.colorScheme.surfaceVariant)
|
|
||||||
},
|
|
||||||
indicator = { tabPositions ->
|
|
||||||
TabRowDefaults.Indicator(
|
|
||||||
Modifier
|
|
||||||
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.clip(
|
|
||||||
RoundedCornerShape(
|
|
||||||
topStartPercent = 100,
|
|
||||||
topEndPercent = 100
|
|
||||||
)
|
)
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
tabItems.forEachIndexed { index, tabItem ->
|
|
||||||
Tab(
|
|
||||||
modifier = Modifier,
|
|
||||||
selected = pagerState.currentPage == index,
|
|
||||||
onClick = {
|
|
||||||
viewModel.onEvent(LibraryEvent.OnScrollToPage(index, pagerState))
|
|
||||||
},
|
|
||||||
selectedContentColor = MaterialTheme.colorScheme.primary,
|
|
||||||
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
text = {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text(
|
|
||||||
tabItem.first,
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(6.dp))
|
|
||||||
Text(
|
|
||||||
text = tabItem.second.toString(),
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.background(MaterialTheme.elevation(6.dp))
|
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ScrollableTabRow(
|
ScrollableTabRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 0.5.dp),
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = Color.Transparent,
|
||||||
divider = {
|
|
||||||
Divider(color = MaterialTheme.colorScheme.surfaceVariant)
|
|
||||||
},
|
|
||||||
edgePadding = 0.dp,
|
edgePadding = 0.dp,
|
||||||
|
divider = {},
|
||||||
indicator = { tabPositions ->
|
indicator = { tabPositions ->
|
||||||
TabRowDefaults.Indicator(
|
TabRowDefaults.Indicator(
|
||||||
Modifier
|
Modifier
|
||||||
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
|
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(end = 5.5.dp)
|
||||||
.clip(
|
.clip(
|
||||||
RoundedCornerShape(
|
RoundedCornerShape(
|
||||||
topStartPercent = 100,
|
100
|
||||||
topEndPercent = 100
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
height = 3.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ 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.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.LibraryViewModel
|
||||||
|
|
||||||
|
|
@ -23,7 +25,11 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryMoveDialog(viewModel: LibraryViewModel, pagerState: PagerState) {
|
fun LibraryMoveDialog(
|
||||||
|
viewModel: LibraryViewModel,
|
||||||
|
historyViewModel: HistoryViewModel,
|
||||||
|
pagerState: PagerState
|
||||||
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
|
@ -38,7 +44,14 @@ fun LibraryMoveDialog(viewModel: LibraryViewModel, pagerState: PagerState) {
|
||||||
isActionEnabled = true,
|
isActionEnabled = true,
|
||||||
onDismiss = { viewModel.onEvent(LibraryEvent.OnShowHideMoveDialog) },
|
onDismiss = { viewModel.onEvent(LibraryEvent.OnShowHideMoveDialog) },
|
||||||
onAction = {
|
onAction = {
|
||||||
viewModel.onEvent(LibraryEvent.OnMoveBooks(pagerState))
|
viewModel.onEvent(
|
||||||
|
LibraryEvent.OnMoveBooks(
|
||||||
|
pagerState,
|
||||||
|
refreshList = {
|
||||||
|
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.books_moved),
|
context.getString(R.string.books_moved),
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import androidx.compose.foundation.pager.PagerState
|
||||||
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.util.Selected
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
|
||||||
sealed class LibraryEvent {
|
sealed class LibraryEvent {
|
||||||
data class OnPreloadBooks(val books: List<Book>) : LibraryEvent()
|
|
||||||
data object OnRefreshList : LibraryEvent()
|
data object OnRefreshList : LibraryEvent()
|
||||||
data object OnLoadList : LibraryEvent()
|
data object OnLoadList : LibraryEvent()
|
||||||
data class OnScrollToPage(val index: Int, val pagerState: PagerState) : LibraryEvent()
|
data class OnScrollToPage(val index: Int, val pagerState: PagerState) : LibraryEvent()
|
||||||
|
|
@ -24,8 +24,12 @@ sealed class LibraryEvent {
|
||||||
data object OnClearSelectedBooks : LibraryEvent()
|
data object OnClearSelectedBooks : LibraryEvent()
|
||||||
data object OnShowHideMoveDialog : LibraryEvent()
|
data object OnShowHideMoveDialog : LibraryEvent()
|
||||||
data class OnSelectCategory(val category: Category) : LibraryEvent()
|
data class OnSelectCategory(val category: Category) : LibraryEvent()
|
||||||
data class OnMoveBooks(val pagerState: PagerState) : LibraryEvent()
|
data class OnMoveBooks(val pagerState: PagerState, val refreshList: () -> Unit) : LibraryEvent()
|
||||||
data object OnShowHideDeleteDialog : LibraryEvent()
|
data object OnShowHideDeleteDialog : LibraryEvent()
|
||||||
data class OnDeleteBooks(val refreshList: () -> Unit) : LibraryEvent()
|
data class OnDeleteBooks(val refreshList: () -> Unit) : LibraryEvent()
|
||||||
data class OnUpdateBook(val book: Book) : LibraryEvent()
|
data class OnUpdateBook(val book: Book) : LibraryEvent()
|
||||||
|
data class OnNavigateToReaderScreen(
|
||||||
|
val navigator: Navigator,
|
||||||
|
val book: Book
|
||||||
|
) : LibraryEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ package ua.acclorite.book_story.presentation.screens.library.data
|
||||||
import androidx.compose.runtime.Immutable
|
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.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
import ua.acclorite.book_story.util.Selected
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class LibraryState(
|
data class LibraryState(
|
||||||
|
|
@ -25,6 +25,4 @@ data class LibraryState(
|
||||||
val selectedCategory: Category = Category.READING,
|
val selectedCategory: Category = Category.READING,
|
||||||
|
|
||||||
val showDeleteDialog: Boolean = false,
|
val showDeleteDialog: Boolean = false,
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,15 @@ 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.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.use_case.DeleteBooks
|
import ua.acclorite.book_story.domain.use_case.DeleteBooks
|
||||||
import ua.acclorite.book_story.domain.use_case.GetBooks
|
import ua.acclorite.book_story.domain.use_case.GetBooks
|
||||||
|
import ua.acclorite.book_story.domain.use_case.InsertHistory
|
||||||
import ua.acclorite.book_story.domain.use_case.UpdateBooks
|
import ua.acclorite.book_story.domain.use_case.UpdateBooks
|
||||||
import ua.acclorite.book_story.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
|
import ua.acclorite.book_story.presentation.data.Argument
|
||||||
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
|
import java.util.Date
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
|
@ -23,7 +28,8 @@ import javax.inject.Inject
|
||||||
class LibraryViewModel @Inject constructor(
|
class LibraryViewModel @Inject constructor(
|
||||||
private val getBooks: GetBooks,
|
private val getBooks: GetBooks,
|
||||||
private val updateBooks: UpdateBooks,
|
private val updateBooks: UpdateBooks,
|
||||||
private val deleteBooks: DeleteBooks
|
private val deleteBooks: DeleteBooks,
|
||||||
|
private val insertHistory: InsertHistory
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _state = MutableStateFlow(LibraryState())
|
private val _state = MutableStateFlow(LibraryState())
|
||||||
|
|
@ -33,6 +39,7 @@ class LibraryViewModel @Inject constructor(
|
||||||
val isReady = _isReady.asStateFlow()
|
val isReady = _isReady.asStateFlow()
|
||||||
|
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
private var job2: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|
@ -69,16 +76,9 @@ class LibraryViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is LibraryEvent.OnPreloadBooks -> {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
books = event.books.map { book -> Pair(book, false) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is LibraryEvent.OnRefreshList -> {
|
is LibraryEvent.OnRefreshList -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
job2?.cancel()
|
||||||
|
job2 = viewModelScope.launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = true,
|
isRefreshing = true,
|
||||||
|
|
@ -101,7 +101,8 @@ class LibraryViewModel @Inject constructor(
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = true
|
isLoading = true,
|
||||||
|
hasSelectedItems = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
getBooksFromDatabase()
|
getBooksFromDatabase()
|
||||||
|
|
@ -149,7 +150,7 @@ class LibraryViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = viewModelScope.launch {
|
job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
delay(500)
|
delay(500)
|
||||||
getBooksFromDatabase()
|
getBooksFromDatabase()
|
||||||
}
|
}
|
||||||
|
|
@ -157,20 +158,19 @@ class LibraryViewModel @Inject constructor(
|
||||||
|
|
||||||
is LibraryEvent.OnSelectBook -> {
|
is LibraryEvent.OnSelectBook -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val indexOfBook = _state.value.books.indexOf(event.book)
|
val editedList = _state.value.books.map {
|
||||||
|
if (it.first.id == event.book.first.id) {
|
||||||
if (indexOfBook == -1) {
|
it.copy(
|
||||||
return@launch
|
second = event.select ?: !it.second
|
||||||
}
|
|
||||||
|
|
||||||
val editedList = _state.value.books.toMutableList()
|
|
||||||
editedList[indexOfBook] = editedList[indexOfBook].copy(
|
|
||||||
second = event.select ?: !editedList[indexOfBook].second
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
books = editedList.toList(),
|
books = editedList,
|
||||||
selectedItemsCount = editedList.filter { book -> book.second }.size,
|
selectedItemsCount = editedList.filter { book -> book.second }.size,
|
||||||
hasSelectedItems = editedList.any { book -> book.second }
|
hasSelectedItems = editedList.any { book -> book.second }
|
||||||
)
|
)
|
||||||
|
|
@ -234,6 +234,7 @@ class LibraryViewModel @Inject constructor(
|
||||||
updateBooks.execute(books)
|
updateBooks.execute(books)
|
||||||
|
|
||||||
getBooksFromDatabase()
|
getBooksFromDatabase()
|
||||||
|
event.refreshList()
|
||||||
|
|
||||||
onEvent(LibraryEvent.OnClearSelectedBooks)
|
onEvent(LibraryEvent.OnClearSelectedBooks)
|
||||||
onEvent(
|
onEvent(
|
||||||
|
|
@ -278,14 +279,16 @@ class LibraryViewModel @Inject constructor(
|
||||||
|
|
||||||
is LibraryEvent.OnUpdateBook -> {
|
is LibraryEvent.OnUpdateBook -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val books = _state.value.books.toMutableList()
|
val books = _state.value.books.map {
|
||||||
val index = books.indexOfFirst { it.first.id == event.book.id }
|
if (it.first.id == event.book.id) {
|
||||||
|
it.copy(
|
||||||
if (index == -1) {
|
first = event.book
|
||||||
return@launch
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
books[index] = Pair(event.book, books[index].second)
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
books = books
|
books = books
|
||||||
|
|
@ -293,6 +296,30 @@ class LibraryViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is LibraryEvent.OnNavigateToReaderScreen -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
event.book.id?.let {
|
||||||
|
insertHistory.execute(
|
||||||
|
listOf(
|
||||||
|
History(
|
||||||
|
null,
|
||||||
|
it,
|
||||||
|
null,
|
||||||
|
Date().time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
event.navigator.navigate(
|
||||||
|
Screen.READER,
|
||||||
|
false,
|
||||||
|
Argument(
|
||||||
|
"book", event.book
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ 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.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
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
|
||||||
|
|
@ -54,6 +55,7 @@ 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.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.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
|
|
@ -69,8 +71,7 @@ import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.Re
|
||||||
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.ReaderViewModel
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import ua.acclorite.book_story.util.Constants
|
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
|
|
@ -86,8 +87,10 @@ fun ReaderScreen(
|
||||||
val systemBarsColor = MaterialTheme.elevation(20.dp).copy(0.85f)
|
val systemBarsColor = MaterialTheme.elevation(20.dp).copy(0.85f)
|
||||||
|
|
||||||
var loading by remember { mutableStateOf(true) }
|
var loading by remember { mutableStateOf(true) }
|
||||||
val state by viewModel.state.collectAsState()
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val canScrollForward by remember { derivedStateOf { listState.canScrollForward } }
|
||||||
val nestedScrollConnection = remember {
|
val nestedScrollConnection = remember {
|
||||||
object : NestedScrollConnection {
|
object : NestedScrollConnection {
|
||||||
override fun onPostScroll(
|
override fun onPostScroll(
|
||||||
|
|
@ -106,47 +109,45 @@ fun ReaderScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val fontFamily = Constants.FONTS.find {
|
val mainState by mainViewModel.state.collectAsState()
|
||||||
it.id == mainViewModel.fontFamily.collectAsState().value!!
|
val fontFamily = remember(mainState.fontFamily) {
|
||||||
|
Constants.FONTS.find {
|
||||||
|
it.id == mainState.fontFamily
|
||||||
} ?: Constants.FONTS[0]
|
} ?: Constants.FONTS[0]
|
||||||
val fontStyle = when (mainViewModel.isItalic.collectAsState().value!!) {
|
|
||||||
false -> FontStyle.Normal
|
|
||||||
true -> FontStyle.Italic
|
|
||||||
}
|
}
|
||||||
val fontSize = mainViewModel.fontSize.collectAsState().value!!
|
|
||||||
val lineHeight = mainViewModel.lineHeight.collectAsState().value!!
|
|
||||||
val paragraphHeight = mainViewModel.paragraphHeight.collectAsState().value!!
|
|
||||||
val paragraphIndentation = mainViewModel.paragraphIndentation.collectAsState().value!!
|
|
||||||
val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!!
|
|
||||||
val fontColor = mainViewModel.fontColor.collectAsState().value!!
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.init(
|
viewModel.init(
|
||||||
navigator,
|
navigator,
|
||||||
context,
|
context,
|
||||||
listState,
|
|
||||||
refreshList = {
|
refreshList = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
||||||
},
|
},
|
||||||
|
listState = listState,
|
||||||
onLoaded = {
|
onLoaded = {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(canScrollForward) {
|
||||||
|
if (!canScrollForward && !state.showMenu && !loading) {
|
||||||
|
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(listState) {
|
LaunchedEffect(listState) {
|
||||||
snapshotFlow {
|
snapshotFlow {
|
||||||
listState.firstVisibleItemIndex
|
listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset
|
||||||
}.debounce(1000).collectLatest {
|
}.debounce(500).collectLatest { items ->
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
val lastVisibleItemIndex = listState.layoutInfo.visibleItemsInfo.last().index
|
val lastVisibleItemIndex = listState.layoutInfo.visibleItemsInfo.last().index
|
||||||
|
|
||||||
val progress = if (it > 0) {
|
val progress = if (items.first > 0) {
|
||||||
if (lastVisibleItemIndex >= (listState.layoutInfo.totalItemsCount - 1)) {
|
if (lastVisibleItemIndex >= (listState.layoutInfo.totalItemsCount - 1)) {
|
||||||
1f
|
1f
|
||||||
} else {
|
} else {
|
||||||
(it.toFloat() / (state.book.text.lastIndex).toFloat())
|
(items.first.toFloat() / (state.book.text.lastIndex).toFloat())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
0f
|
0f
|
||||||
|
|
@ -154,14 +155,13 @@ fun ReaderScreen(
|
||||||
|
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
ReaderEvent.OnChangeProgress(
|
ReaderEvent.OnChangeProgress(
|
||||||
progress,
|
progress = progress,
|
||||||
navigator,
|
navigator = navigator,
|
||||||
|
firstVisibleItemIndex = items.first,
|
||||||
|
firstVisibleItemOffset = items.second,
|
||||||
refreshList = { book ->
|
refreshList = { book ->
|
||||||
libraryViewModel.onEvent(
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(book))
|
||||||
LibraryEvent.OnUpdateBook(
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(book))
|
||||||
book
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -186,7 +186,10 @@ fun ReaderScreen(
|
||||||
) {
|
) {
|
||||||
ReaderTopBar(
|
ReaderTopBar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
libraryViewModel = libraryViewModel,
|
||||||
|
historyViewModel = historyViewModel,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
|
listState = listState,
|
||||||
containerColor = systemBarsColor
|
containerColor = systemBarsColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -200,8 +203,9 @@ fun ReaderScreen(
|
||||||
ReaderBottomBar(
|
ReaderBottomBar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
libraryViewModel = libraryViewModel,
|
libraryViewModel = libraryViewModel,
|
||||||
|
historyViewModel = historyViewModel,
|
||||||
|
listState = listState,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
scrollState = listState,
|
|
||||||
systemBarsColor = systemBarsColor
|
systemBarsColor = systemBarsColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -280,36 +284,44 @@ fun ReaderScreen(
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
state.book.text, key = { _, key -> key.id }
|
state.book.text, key = { _, key -> key.id }
|
||||||
) { index, line ->
|
) { index, line ->
|
||||||
val text =
|
val text = remember(mainState.paragraphIndentation) {
|
||||||
remember(paragraphIndentation) { "${if (paragraphIndentation) " " else ""}${line.line}" }
|
"${if (mainState.paragraphIndentation!!) " " else ""}${line.line}"
|
||||||
val fontCol = remember(fontColor) { Color(fontColor.toULong()) }
|
}
|
||||||
val backgroundCol =
|
val fontColor = remember(mainState.fontColor) {
|
||||||
remember(backgroundColor) { Color(backgroundColor.toULong()) }
|
Color(mainState.fontColor!!.toULong())
|
||||||
val lineHeightSp = remember(fontSize, lineHeight) { (fontSize + lineHeight).sp }
|
}
|
||||||
|
val backgroundColor = remember(mainState.backgroundColor) {
|
||||||
|
Color(mainState.backgroundColor!!.toULong())
|
||||||
|
}
|
||||||
|
val lineHeight = remember(mainState.fontSize, mainState.lineHeight) {
|
||||||
|
(mainState.fontSize!! + mainState.lineHeight!!).sp
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.background(backgroundCol)
|
.background(backgroundColor)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(
|
||||||
top = if (index == 0) 36.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) 36.dp
|
bottom = if (index == state.book.text.lastIndex) 18.dp
|
||||||
else (paragraphHeight * 3).dp
|
else (mainState.paragraphHeight!! * 3).dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
color = fontCol,
|
color = fontColor,
|
||||||
|
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
lineBreak = LineBreak.Paragraph
|
lineBreak = LineBreak.Paragraph
|
||||||
),
|
),
|
||||||
fontFamily = fontFamily.font,
|
fontFamily = fontFamily.font,
|
||||||
fontStyle = fontStyle,
|
fontStyle = when (mainState.isItalic!!) {
|
||||||
fontSize = fontSize.sp,
|
true -> FontStyle.Italic
|
||||||
lineHeight = lineHeightSp
|
false -> FontStyle.Normal
|
||||||
|
},
|
||||||
|
fontSize = mainState.fontSize!!.sp,
|
||||||
|
lineHeight = lineHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +331,9 @@ fun ReaderScreen(
|
||||||
DisableSelection {
|
DisableSelection {
|
||||||
ReaderEndItem(
|
ReaderEndItem(
|
||||||
libraryViewModel = libraryViewModel,
|
libraryViewModel = libraryViewModel,
|
||||||
|
historyViewModel = historyViewModel,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
listState = listState,
|
||||||
navigator = navigator
|
navigator = navigator
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -343,10 +357,21 @@ fun ReaderScreen(
|
||||||
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 = {
|
||||||
navigator.navigateBack()
|
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
||||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
viewModel.onEvent(
|
||||||
viewModel.onEvent(ReaderEvent.OnShowSystemBars(context))
|
ReaderEvent.OnGoBack(
|
||||||
|
context,
|
||||||
|
navigator,
|
||||||
|
refreshList = {
|
||||||
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
|
},
|
||||||
|
listState = listState,
|
||||||
|
navigate = {
|
||||||
|
it.navigateBack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -369,7 +394,19 @@ fun ReaderScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
navigator.navigateBack()
|
viewModel.onEvent(
|
||||||
viewModel.onEvent(ReaderEvent.OnShowSystemBars(context))
|
ReaderEvent.OnGoBack(
|
||||||
|
context,
|
||||||
|
navigator,
|
||||||
|
refreshList = {
|
||||||
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
|
},
|
||||||
|
listState = listState,
|
||||||
|
navigate = {
|
||||||
|
it.navigateBack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,11 +8,12 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
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.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBarsIgnoringVisibility
|
import androidx.compose.foundation.layout.navigationBarsIgnoringVisibility
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -32,6 +33,8 @@ 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.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.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.LibraryViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
|
|
@ -44,7 +47,9 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderEndItem(
|
fun ReaderEndItem(
|
||||||
libraryViewModel: LibraryViewModel,
|
libraryViewModel: LibraryViewModel,
|
||||||
|
historyViewModel: HistoryViewModel,
|
||||||
viewModel: ReaderViewModel,
|
viewModel: ReaderViewModel,
|
||||||
|
listState: LazyListState,
|
||||||
navigator: Navigator
|
navigator: Navigator
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
@ -54,12 +59,11 @@ fun ReaderEndItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBarsIgnoringVisibility)
|
|
||||||
.padding(
|
.padding(
|
||||||
top = 24.dp,
|
horizontal = 36.dp,
|
||||||
start = 24.dp,
|
vertical = 108.dp + WindowInsets.navigationBarsIgnoringVisibility
|
||||||
end = 24.dp,
|
.asPaddingValues()
|
||||||
bottom = 8.dp
|
.calculateBottomPadding()
|
||||||
),
|
),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
|
@ -72,7 +76,11 @@ fun ReaderEndItem(
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
stringResource(id = R.string.letters_and_words, state.letters, state.words),
|
stringResource(
|
||||||
|
id = R.string.letters_and_words,
|
||||||
|
state.book.letters,
|
||||||
|
state.book.words
|
||||||
|
),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|
@ -83,15 +91,15 @@ fun ReaderEndItem(
|
||||||
if (state.book.category != Category.ALREADY_READ) {
|
if (state.book.category != Category.ALREADY_READ) {
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
ReaderEvent.OnMoveBookToAlreadyRead(
|
ReaderEvent.OnMoveBookToAlreadyRead(
|
||||||
refreshList = {
|
onUpdateCategories = {
|
||||||
libraryViewModel.onEvent(
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
LibraryEvent.OnLoadList
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
)
|
|
||||||
},
|
},
|
||||||
updatePage = {
|
updatePage = {
|
||||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
|
||||||
},
|
},
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
|
listState = listState,
|
||||||
context = context
|
context = context
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -103,11 +111,19 @@ fun ReaderEndItem(
|
||||||
).show()
|
).show()
|
||||||
} else {
|
} else {
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
ReaderEvent.OnShowSystemBars(
|
ReaderEvent.OnGoBack(
|
||||||
context
|
context,
|
||||||
|
navigator,
|
||||||
|
refreshList = {
|
||||||
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
|
},
|
||||||
|
listState = listState,
|
||||||
|
navigate = {
|
||||||
|
it.navigate(Screen.LIBRARY, true)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
navigator.navigate(Screen.LIBRARY, true)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
contentPadding = PaddingValues(horizontal = 12.dp),
|
contentPadding = PaddingValues(horizontal = 12.dp),
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.statusBarsIgnoringVisibility
|
import androidx.compose.foundation.layout.statusBarsIgnoringVisibility
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Image
|
import androidx.compose.material.icons.filled.Image
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
|
@ -34,7 +35,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
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.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reader start item. Displays at the beginning of the book.
|
* Reader start item. Displays at the beginning of the book.
|
||||||
|
|
@ -49,7 +50,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) {
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
.windowInsetsPadding(WindowInsets.statusBarsIgnoringVisibility)
|
.windowInsetsPadding(WindowInsets.statusBarsIgnoringVisibility)
|
||||||
.padding(24.dp)
|
.padding(horizontal = 24.dp, vertical = 80.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||||
|
|
@ -59,7 +60,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) {
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(135.dp)
|
.height(135.dp)
|
||||||
.width(90.dp)
|
.width(90.dp)
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(MaterialTheme.elevation())
|
.background(MaterialTheme.elevation())
|
||||||
) {
|
) {
|
||||||
if (state.book.coverImage != null) {
|
if (state.book.coverImage != null) {
|
||||||
|
|
@ -68,7 +69,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) {
|
||||||
contentDescription = stringResource(id = R.string.cover_image_content_desc),
|
contentDescription = stringResource(id = R.string.cover_image_content_desc),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(MaterialTheme.shapes.medium),
|
.clip(RoundedCornerShape(10.dp)),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
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.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.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
|
|
@ -36,8 +38,9 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
fun ReaderBottomBar(
|
fun ReaderBottomBar(
|
||||||
viewModel: ReaderViewModel,
|
viewModel: ReaderViewModel,
|
||||||
libraryViewModel: LibraryViewModel,
|
libraryViewModel: LibraryViewModel,
|
||||||
|
historyViewModel: HistoryViewModel,
|
||||||
|
listState: LazyListState,
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
scrollState: LazyListState,
|
|
||||||
systemBarsColor: Color
|
systemBarsColor: Color
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
@ -74,20 +77,19 @@ fun ReaderBottomBar(
|
||||||
Slider(
|
Slider(
|
||||||
value = state.book.progress,
|
value = state.book.progress,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
|
viewModel.onEvent(ReaderEvent.OnScroll(listState, it))
|
||||||
viewModel.onEvent(
|
viewModel.onEvent(
|
||||||
ReaderEvent.OnChangeProgress(
|
ReaderEvent.OnChangeProgress(
|
||||||
progress = it,
|
progress = it,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
|
firstVisibleItemIndex = listState.firstVisibleItemIndex,
|
||||||
|
firstVisibleItemOffset = 0,
|
||||||
refreshList = { book ->
|
refreshList = { book ->
|
||||||
libraryViewModel.onEvent(
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(book))
|
||||||
LibraryEvent.OnUpdateBook(
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(book))
|
||||||
book
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
viewModel.onEvent(ReaderEvent.OnScroll(scrollState, it))
|
|
||||||
},
|
},
|
||||||
colors = SliderDefaults.colors(
|
colors = SliderDefaults.colors(
|
||||||
activeTrackColor = MaterialTheme.colorScheme.primary,
|
activeTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import androidx.compose.foundation.basicMarquee
|
||||||
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.lazy.LazyListState
|
||||||
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.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -26,12 +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.components.GoBackButton
|
|
||||||
import ua.acclorite.book_story.presentation.data.Argument
|
|
||||||
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.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.HistoryViewModel
|
||||||
|
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.ReaderViewModel
|
||||||
|
|
||||||
|
|
@ -40,7 +44,14 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColor: Color) {
|
fun ReaderTopBar(
|
||||||
|
viewModel: ReaderViewModel,
|
||||||
|
libraryViewModel: LibraryViewModel,
|
||||||
|
historyViewModel: HistoryViewModel,
|
||||||
|
listState: LazyListState,
|
||||||
|
navigator: Navigator,
|
||||||
|
containerColor: Color
|
||||||
|
) {
|
||||||
val context = LocalContext.current as ComponentActivity
|
val context = LocalContext.current as ComponentActivity
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
|
@ -56,8 +67,25 @@ fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColo
|
||||||
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
GoBackButton(navigator = navigator) {
|
CustomIconButton(
|
||||||
viewModel.onEvent(ReaderEvent.OnShowSystemBars(context))
|
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = stringResource(id = R.string.go_back_content_desc),
|
||||||
|
disableOnClick = true
|
||||||
|
) {
|
||||||
|
viewModel.onEvent(
|
||||||
|
ReaderEvent.OnGoBack(
|
||||||
|
context,
|
||||||
|
navigator,
|
||||||
|
refreshList = {
|
||||||
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
|
},
|
||||||
|
listState = listState,
|
||||||
|
navigate = {
|
||||||
|
it.navigateBack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
|
|
@ -74,12 +102,21 @@ fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColo
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
indication = null,
|
indication = null,
|
||||||
onClick = {
|
onClick = {
|
||||||
navigator.navigateWithoutBackStack(
|
viewModel.onEvent(
|
||||||
|
ReaderEvent.OnGoBack(
|
||||||
|
context,
|
||||||
|
navigator,
|
||||||
|
refreshList = {
|
||||||
|
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
|
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||||
|
},
|
||||||
|
listState = listState,
|
||||||
|
navigate = {
|
||||||
|
it.navigateWithoutBackStack(
|
||||||
Screen.BOOK_INFO,
|
Screen.BOOK_INFO,
|
||||||
true,
|
true
|
||||||
Argument(
|
)
|
||||||
"book",
|
}
|
||||||
state.book.id
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import androidx.compose.ui.text.font.FontStyle
|
||||||
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.ChipItem
|
import ua.acclorite.book_story.domain.model.ChipItem
|
||||||
|
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.MainViewModel
|
||||||
|
|
@ -44,9 +45,8 @@ import ua.acclorite.book_story.presentation.screens.settings.components.Checkbox
|
||||||
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
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
|
||||||
import ua.acclorite.book_story.ui.ElevationDefaults
|
import ua.acclorite.book_story.presentation.ui.ElevationDefaults
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import ua.acclorite.book_story.util.Constants
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings bottom sheet. Has General and Colors categories.
|
* Settings bottom sheet. Has General and Colors categories.
|
||||||
|
|
@ -63,16 +63,13 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
val navigationBarPadding =
|
val navigationBarPadding =
|
||||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||||
|
|
||||||
val fontFamily = Constants.FONTS.find {
|
val state by mainViewModel.state.collectAsState()
|
||||||
it.id == mainViewModel.fontFamily.collectAsState().value!!
|
|
||||||
|
val fontFamily = remember(state.fontFamily) {
|
||||||
|
Constants.FONTS.find {
|
||||||
|
it.id == state.fontFamily
|
||||||
} ?: Constants.FONTS[0]
|
} ?: Constants.FONTS[0]
|
||||||
val fontStyle = mainViewModel.isItalic.collectAsState().value!!
|
}
|
||||||
val fontSize = mainViewModel.fontSize.collectAsState().value!!
|
|
||||||
val lineHeight = mainViewModel.lineHeight.collectAsState().value!!
|
|
||||||
val paragraphHeight = mainViewModel.paragraphHeight.collectAsState().value!!
|
|
||||||
val paragraphIndentation = mainViewModel.paragraphIndentation.collectAsState().value!!
|
|
||||||
val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!!
|
|
||||||
val fontColor = mainViewModel.fontColor.collectAsState().value!!
|
|
||||||
|
|
||||||
val scrimColor = if (currentPage == 1) Color.Transparent
|
val scrimColor = if (currentPage == 1) Color.Transparent
|
||||||
else BottomSheetDefaults.ScrimColor
|
else BottomSheetDefaults.ScrimColor
|
||||||
|
|
@ -161,7 +158,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
fontFamily = fontFamily.font,
|
fontFamily = fontFamily.font,
|
||||||
fontStyle = FontStyle.Normal
|
fontStyle = FontStyle.Normal
|
||||||
),
|
),
|
||||||
!fontStyle
|
!state.isItalic!!
|
||||||
),
|
),
|
||||||
ChipItem(
|
ChipItem(
|
||||||
"italic",
|
"italic",
|
||||||
|
|
@ -170,7 +167,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
fontFamily = fontFamily.font,
|
fontFamily = fontFamily.font,
|
||||||
fontStyle = FontStyle.Italic
|
fontStyle = FontStyle.Italic
|
||||||
),
|
),
|
||||||
fontStyle
|
state.isItalic!!
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -187,7 +184,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = fontSize to "pt",
|
value = state.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),
|
||||||
|
|
@ -212,7 +209,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = lineHeight to "pt",
|
value = state.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),
|
||||||
|
|
@ -225,7 +222,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = paragraphHeight to "pt",
|
value = state.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),
|
||||||
|
|
@ -238,11 +235,11 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
CheckboxWithTitle(
|
CheckboxWithTitle(
|
||||||
selected = paragraphIndentation,
|
selected = state.paragraphIndentation!!,
|
||||||
title = stringResource(id = R.string.paragraph_indentation_option)
|
title = stringResource(id = R.string.paragraph_indentation_option)
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(
|
mainViewModel.onEvent(
|
||||||
MainEvent.OnChangeParagraphIndentation(!paragraphIndentation)
|
MainEvent.OnChangeParagraphIndentation(!state.paragraphIndentation!!)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +251,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
value = Color(backgroundColor.toULong()),
|
value = Color(state.backgroundColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.background_color_option),
|
title = stringResource(id = R.string.background_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
mainViewModel.onEvent(
|
||||||
|
|
@ -267,7 +264,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
value = Color(fontColor.toULong()),
|
value = Color(state.fontColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.font_color_option),
|
title = stringResource(id = R.string.font_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
mainViewModel.onEvent(
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@ import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
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(
|
||||||
val scrollState: LazyListState,
|
val listState: LazyListState,
|
||||||
val navigator: Navigator,
|
|
||||||
val refreshList: (Book) -> Unit,
|
val refreshList: (Book) -> Unit,
|
||||||
val onLoaded: () -> Unit,
|
val onLoaded: () -> Unit,
|
||||||
val onTextIsEmpty: () -> Unit
|
val onTextIsEmpty: () -> Unit
|
||||||
|
|
@ -22,19 +21,29 @@ sealed class ReaderEvent {
|
||||||
data class OnShowHideMenu(val show: Boolean? = null, val context: ComponentActivity) :
|
data class OnShowHideMenu(val show: Boolean? = null, val context: ComponentActivity) :
|
||||||
ReaderEvent()
|
ReaderEvent()
|
||||||
|
|
||||||
data class OnShowSystemBars(val context: ComponentActivity) : ReaderEvent()
|
data class OnGoBack(
|
||||||
|
val context: ComponentActivity,
|
||||||
|
val navigator: Navigator,
|
||||||
|
val listState: LazyListState,
|
||||||
|
val refreshList: (Book) -> Unit,
|
||||||
|
val navigate: (Navigator) -> Unit
|
||||||
|
) : ReaderEvent()
|
||||||
|
|
||||||
data class OnChangeProgress(
|
data class OnChangeProgress(
|
||||||
val progress: Float,
|
val progress: Float,
|
||||||
val navigator: Navigator,
|
val navigator: Navigator,
|
||||||
|
val firstVisibleItemIndex: Int,
|
||||||
|
val firstVisibleItemOffset: Int,
|
||||||
val refreshList: (Book) -> Unit
|
val refreshList: (Book) -> Unit
|
||||||
) : ReaderEvent()
|
) : ReaderEvent()
|
||||||
|
|
||||||
data class OnScroll(val scrollState: LazyListState, val progress: Float) : ReaderEvent()
|
data class OnScroll(val listState: LazyListState, val progress: Float) : ReaderEvent()
|
||||||
data object OnShowHideSettingsBottomSheet : ReaderEvent()
|
data object OnShowHideSettingsBottomSheet : ReaderEvent()
|
||||||
data class OnScrollToSettingsPage(val page: Int, val pagerState: PagerState) : ReaderEvent()
|
data class OnScrollToSettingsPage(val page: Int, val pagerState: PagerState) : ReaderEvent()
|
||||||
data class OnMoveBookToAlreadyRead(
|
data class OnMoveBookToAlreadyRead(
|
||||||
val context: ComponentActivity,
|
val context: ComponentActivity,
|
||||||
val refreshList: () -> Unit,
|
val listState: LazyListState,
|
||||||
|
val onUpdateCategories: (Book) -> Unit,
|
||||||
val updatePage: (Int) -> Unit,
|
val updatePage: (Int) -> Unit,
|
||||||
val navigator: Navigator
|
val navigator: Navigator
|
||||||
) : ReaderEvent()
|
) : ReaderEvent()
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,13 @@ package ua.acclorite.book_story.presentation.screens.reader.data
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
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.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ReaderState(
|
data class ReaderState(
|
||||||
val book: Book = Constants.EMPTY_BOOK,
|
val book: Book = Constants.EMPTY_BOOK,
|
||||||
val letters: Int = 0,
|
|
||||||
val words: Int = 0,
|
|
||||||
|
|
||||||
val errorMessage: UIText? = null,
|
val errorMessage: UIText? = null,
|
||||||
|
|
||||||
val showMenu: Boolean = false,
|
val showMenu: Boolean = false,
|
||||||
val showSettingsBottomSheet: Boolean = false,
|
val showSettingsBottomSheet: Boolean = false,
|
||||||
val currentPage: Int = 0,
|
val currentPage: Int = 0,
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,14 @@ import kotlinx.coroutines.launch
|
||||||
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.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.model.History
|
|
||||||
import ua.acclorite.book_story.domain.model.StringWithId
|
import ua.acclorite.book_story.domain.model.StringWithId
|
||||||
import ua.acclorite.book_story.domain.use_case.GetBooksById
|
import ua.acclorite.book_story.domain.use_case.GetLatestHistory
|
||||||
import ua.acclorite.book_story.domain.use_case.GetText
|
import ua.acclorite.book_story.domain.use_case.GetText
|
||||||
import ua.acclorite.book_story.domain.use_case.InsertHistory
|
|
||||||
import ua.acclorite.book_story.domain.use_case.UpdateBooks
|
import ua.acclorite.book_story.domain.use_case.UpdateBooks
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.presentation.data.Argument
|
import ua.acclorite.book_story.presentation.data.Argument
|
||||||
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.util.UIText
|
|
||||||
import java.util.Date
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
@ -40,9 +37,8 @@ import kotlin.math.roundToInt
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ReaderViewModel @Inject constructor(
|
class ReaderViewModel @Inject constructor(
|
||||||
private val updateBooks: UpdateBooks,
|
private val updateBooks: UpdateBooks,
|
||||||
private val insertHistory: InsertHistory,
|
|
||||||
private val getText: GetText,
|
private val getText: GetText,
|
||||||
private val getBooksById: GetBooksById
|
private val getLatestHistory: GetLatestHistory
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _state = MutableStateFlow(ReaderState())
|
private val _state = MutableStateFlow(ReaderState())
|
||||||
|
|
@ -67,7 +63,6 @@ class ReaderViewModel @Inject constructor(
|
||||||
event.onTextIsEmpty()
|
event.onTextIsEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
val time = Date().time
|
|
||||||
val letters = text
|
val letters = text
|
||||||
.replace("\n", "")
|
.replace("\n", "")
|
||||||
.length
|
.length
|
||||||
|
|
@ -76,50 +71,46 @@ class ReaderViewModel @Inject constructor(
|
||||||
.split("\\s+".toRegex())
|
.split("\\s+".toRegex())
|
||||||
.size
|
.size
|
||||||
|
|
||||||
|
val history = _state.value.book.id?.let {
|
||||||
|
getLatestHistory.execute(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
text = text
|
text = text
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map { line -> StringWithId(line.trim()) },
|
.map { line -> StringWithId(line.trim()) },
|
||||||
lastOpened = time
|
lastOpened = history?.time,
|
||||||
),
|
|
||||||
letters = letters,
|
letters = letters,
|
||||||
words = words
|
words = words
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBooks.execute(
|
updateBooks.execute(
|
||||||
listOf(_state.value.book)
|
listOf(_state.value.book)
|
||||||
)
|
)
|
||||||
insertHistory.execute(
|
|
||||||
listOf(
|
|
||||||
History(
|
|
||||||
null,
|
|
||||||
_state.value.book.id!!,
|
|
||||||
time
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
event.refreshList(_state.value.book)
|
event.refreshList(_state.value.book)
|
||||||
event.navigator.putArgument(
|
|
||||||
Argument("book", _state.value.book.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
snapshotFlow {
|
snapshotFlow {
|
||||||
event.scrollState.layoutInfo.totalItemsCount
|
event.listState.layoutInfo.totalItemsCount
|
||||||
}.collectLatest { itemsCount ->
|
}.collectLatest { itemsCount ->
|
||||||
val scrollTo =
|
val index = _state.value.book.scrollIndex
|
||||||
(_state.value.book.text.size * _state.value.book.progress)
|
val offset = _state.value.book.scrollOffset
|
||||||
.toInt()
|
|
||||||
|
|
||||||
if (itemsCount >= _state.value.book.text.size) {
|
if (itemsCount >= _state.value.book.text.size) {
|
||||||
if (scrollTo > 0) {
|
if (index > 0 || offset > 0) {
|
||||||
var loaded = false
|
var loaded = false
|
||||||
for (i in 1..100) {
|
for (i in 1..100) {
|
||||||
try {
|
try {
|
||||||
event.scrollState.scrollToItem(scrollTo)
|
event.listState.scrollToItem(
|
||||||
|
index,
|
||||||
|
offset
|
||||||
|
)
|
||||||
loaded = true
|
loaded = true
|
||||||
break
|
break
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -143,6 +134,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
|
|
||||||
is ReaderEvent.OnShowHideMenu -> {
|
is ReaderEvent.OnShowHideMenu -> {
|
||||||
val shouldShow = event.show ?: !_state.value.showMenu
|
val shouldShow = event.show ?: !_state.value.showMenu
|
||||||
|
|
||||||
val insetsController = WindowCompat.getInsetsController(
|
val insetsController = WindowCompat.getInsetsController(
|
||||||
event.context.window,
|
event.context.window,
|
||||||
event.context.window.decorView
|
event.context.window.decorView
|
||||||
|
|
@ -166,20 +158,57 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnShowSystemBars -> {
|
is ReaderEvent.OnGoBack -> {
|
||||||
|
viewModelScope.launch {
|
||||||
val insetsController = WindowCompat.getInsetsController(
|
val insetsController = WindowCompat.getInsetsController(
|
||||||
event.context.window,
|
event.context.window,
|
||||||
event.context.window.decorView
|
event.context.window.decorView
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val firstVisibleItemIndex = event.listState.firstVisibleItemIndex
|
||||||
|
val firstVisibleItemOffset = event.listState.firstVisibleItemScrollOffset
|
||||||
|
val lastVisibleItemIndex = event.listState.layoutInfo
|
||||||
|
.visibleItemsInfo.last().index
|
||||||
|
|
||||||
|
val progress = if (firstVisibleItemIndex > 0) {
|
||||||
|
if (lastVisibleItemIndex >= (event.listState.layoutInfo.totalItemsCount - 1)) {
|
||||||
|
1f
|
||||||
|
} else {
|
||||||
|
(firstVisibleItemIndex.toFloat() / (_state.value.book.text.lastIndex)
|
||||||
|
.toFloat())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
book = it.book.copy(
|
||||||
|
progress = progress,
|
||||||
|
scrollIndex = firstVisibleItemIndex,
|
||||||
|
scrollOffset = firstVisibleItemOffset
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBooks.execute(
|
||||||
|
listOf(_state.value.book)
|
||||||
|
)
|
||||||
|
event.navigator.putArgument(
|
||||||
|
Argument("book", _state.value.book)
|
||||||
|
)
|
||||||
|
event.refreshList(_state.value.book)
|
||||||
|
|
||||||
insetsController.show(WindowInsetsCompat.Type.systemBars())
|
insetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
event.navigate(event.navigator)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnScroll -> {
|
is ReaderEvent.OnScroll -> {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val scrollTo = (_state.value.book.text.size * event.progress).roundToInt()
|
val scrollTo = (_state.value.book.text.size * event.progress).roundToInt()
|
||||||
|
|
||||||
event.scrollState.scrollToItem(
|
event.listState.scrollToItem(
|
||||||
scrollTo
|
scrollTo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +219,9 @@ class ReaderViewModel @Inject constructor(
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
progress = event.progress
|
progress = event.progress,
|
||||||
|
scrollIndex = event.firstVisibleItemIndex,
|
||||||
|
scrollOffset = event.firstVisibleItemOffset
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -199,13 +230,17 @@ class ReaderViewModel @Inject constructor(
|
||||||
listOf(_state.value.book)
|
listOf(_state.value.book)
|
||||||
)
|
)
|
||||||
event.navigator.putArgument(
|
event.navigator.putArgument(
|
||||||
Argument("book", _state.value.book.id)
|
Argument(
|
||||||
|
"book",
|
||||||
|
_state.value.book
|
||||||
|
)
|
||||||
)
|
)
|
||||||
event.refreshList(_state.value.book)
|
event.refreshList(_state.value.book)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnShowHideSettingsBottomSheet -> {
|
is ReaderEvent.OnShowHideSettingsBottomSheet -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
currentPage = if (it.showSettingsBottomSheet) it.currentPage else 0,
|
currentPage = if (it.showSettingsBottomSheet) it.currentPage else 0,
|
||||||
|
|
@ -213,6 +248,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnScrollToSettingsPage -> {
|
is ReaderEvent.OnScrollToSettingsPage -> {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
@ -222,16 +258,32 @@ class ReaderViewModel @Inject constructor(
|
||||||
|
|
||||||
is ReaderEvent.OnMoveBookToAlreadyRead -> {
|
is ReaderEvent.OnMoveBookToAlreadyRead -> {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
onEvent(ReaderEvent.OnShowSystemBars(event.context))
|
onEvent(
|
||||||
|
ReaderEvent.OnGoBack(
|
||||||
val book = _state.value.book.copy(
|
event.context,
|
||||||
category = Category.ALREADY_READ,
|
event.navigator,
|
||||||
progress = 1f
|
event.listState,
|
||||||
|
refreshList = {},
|
||||||
|
navigate = {}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
updateBooks.execute(listOf(book))
|
|
||||||
|
|
||||||
event.refreshList()
|
val firstVisibleItemIndex = event.listState.firstVisibleItemIndex
|
||||||
|
val firstVisibleItemOffset = event.listState.firstVisibleItemScrollOffset
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
book = it.book.copy(
|
||||||
|
category = Category.ALREADY_READ,
|
||||||
|
progress = 1f,
|
||||||
|
scrollIndex = firstVisibleItemIndex,
|
||||||
|
scrollOffset = firstVisibleItemOffset
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
updateBooks.execute(listOf(_state.value.book))
|
||||||
|
|
||||||
|
event.onUpdateCategories(_state.value.book)
|
||||||
event.updatePage(
|
event.updatePage(
|
||||||
Category.entries.dropLastWhile {
|
Category.entries.dropLastWhile {
|
||||||
it != Category.ALREADY_READ
|
it != Category.ALREADY_READ
|
||||||
|
|
@ -298,30 +350,30 @@ class ReaderViewModel @Inject constructor(
|
||||||
fun init(
|
fun init(
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
context: ComponentActivity,
|
context: ComponentActivity,
|
||||||
scrollState: LazyListState,
|
listState: LazyListState,
|
||||||
refreshList: (Book) -> Unit,
|
refreshList: (Book) -> Unit,
|
||||||
onLoaded: () -> Unit
|
onLoaded: () -> Unit
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val bookIndex = navigator.retrieveArgument("book") as? Int
|
val book = navigator.retrieveArgument("book") as? Book
|
||||||
|
|
||||||
if (bookIndex == null || bookIndex < 0) {
|
if (book == null) {
|
||||||
navigator.navigateBack()
|
navigator.navigateBack()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val book = getBooksById.execute(listOf(bookIndex)).first()
|
viewModelScope.launch {
|
||||||
|
onEvent(ReaderEvent.OnShowHideMenu(false, context))
|
||||||
|
}
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
ReaderState(book = book)
|
ReaderState(book = book)
|
||||||
}
|
}
|
||||||
|
|
||||||
onEvent(ReaderEvent.OnShowHideMenu(false, context))
|
|
||||||
onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnLoadText(
|
ReaderEvent.OnLoadText(
|
||||||
scrollState,
|
|
||||||
navigator,
|
|
||||||
refreshList = { refreshList(it) },
|
refreshList = { refreshList(it) },
|
||||||
|
listState = listState,
|
||||||
onLoaded = {
|
onLoaded = {
|
||||||
onLoaded()
|
onLoaded()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
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
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -39,7 +39,7 @@ fun SettingsScreen(
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||||
canScroll = {
|
canScroll = {
|
||||||
listState.canScrollForward
|
listState.canScrollForward || listState.canScrollBackward
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -71,6 +71,10 @@ fun SettingsScreen(
|
||||||
.padding(top = paddingValues.calculateTopPadding()),
|
.padding(top = paddingValues.calculateTopPadding()),
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
SettingsCategoryItem(
|
SettingsCategoryItem(
|
||||||
icon = Icons.Default.DisplaySettings,
|
icon = Icons.Default.DisplaySettings,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,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.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
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
|
||||||
|
|
@ -32,8 +33,8 @@ import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
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
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher.AppearanceSettingsThemeSwitcher
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher.AppearanceSettingsThemeSwitcher
|
||||||
import ua.acclorite.book_story.ui.DarkTheme
|
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -44,13 +45,11 @@ fun AppearanceSettings(
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||||
canScroll = {
|
canScroll = {
|
||||||
listState.canScrollForward
|
listState.canScrollForward || listState.canScrollBackward
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val darkTheme = mainViewModel.darkTheme.collectAsState().value!!
|
val state by mainViewModel.state.collectAsState()
|
||||||
val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!!
|
|
||||||
val fontColor = mainViewModel.fontColor.collectAsState().value!!
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -81,7 +80,7 @@ fun AppearanceSettings(
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
CategoryTitle(
|
CategoryTitle(
|
||||||
|
|
@ -105,7 +104,7 @@ 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 == darkTheme
|
it == state.darkTheme
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
@ -136,7 +135,7 @@ fun AppearanceSettings(
|
||||||
|
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
value = Color(backgroundColor.toULong()),
|
value = Color(state.backgroundColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.background_color_option),
|
title = stringResource(id = R.string.background_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
mainViewModel.onEvent(
|
||||||
|
|
@ -149,7 +148,7 @@ fun AppearanceSettings(
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPickerWithTitle(
|
||||||
value = Color(fontColor.toULong()),
|
value = Color(state.fontColor!!.toULong()),
|
||||||
title = stringResource(id = R.string.font_color_option),
|
title = stringResource(id = R.string.font_color_option),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
mainViewModel.onEvent(
|
mainViewModel.onEvent(
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,18 @@ 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.collectAsState
|
||||||
|
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.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.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.MainViewModel
|
||||||
import ua.acclorite.book_story.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
import ua.acclorite.book_story.ui.isDark
|
import ua.acclorite.book_story.presentation.ui.isDark
|
||||||
import ua.acclorite.book_story.util.Constants
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme switcher.
|
* Theme switcher.
|
||||||
|
|
@ -29,8 +30,7 @@ import ua.acclorite.book_story.util.Constants
|
||||||
fun AppearanceSettingsThemeSwitcher(
|
fun AppearanceSettingsThemeSwitcher(
|
||||||
mainViewModel: MainViewModel
|
mainViewModel: MainViewModel
|
||||||
) {
|
) {
|
||||||
val theme = mainViewModel.theme.collectAsState().value!!
|
val state by mainViewModel.state.collectAsState()
|
||||||
val darkTheme = mainViewModel.darkTheme.collectAsState().value!!
|
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -56,8 +56,8 @@ fun AppearanceSettingsThemeSwitcher(
|
||||||
|
|
||||||
AppearanceSettingsThemeSwitcherItem(
|
AppearanceSettingsThemeSwitcherItem(
|
||||||
theme = themeEntry,
|
theme = themeEntry,
|
||||||
darkTheme = darkTheme.isDark(),
|
darkTheme = state.darkTheme!!.isDark(),
|
||||||
selected = theme == themeEntry.first
|
selected = state.theme == themeEntry.first
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
mainViewModel.onEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ 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.ui.Theme
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.ui.colorScheme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
import ua.acclorite.book_story.util.UIText
|
import ua.acclorite.book_story.presentation.ui.colorScheme
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme switcher item.
|
* Theme switcher item.
|
||||||
|
|
|
||||||
|
|
@ -17,19 +17,20 @@ 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.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
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 ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.ChipItem
|
import ua.acclorite.book_story.domain.model.ChipItem
|
||||||
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
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.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.ChipsWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import ua.acclorite.book_story.util.Constants
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -40,11 +41,11 @@ fun GeneralSettings(
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||||
canScroll = {
|
canScroll = {
|
||||||
listState.canScrollForward
|
listState.canScrollForward || listState.canScrollBackward
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val language = mainViewModel.language.collectAsState().value!!
|
val state by mainViewModel.state.collectAsState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -74,6 +75,9 @@ fun GeneralSettings(
|
||||||
.padding(top = paddingValues.calculateTopPadding()),
|
.padding(top = paddingValues.calculateTopPadding()),
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
item {
|
item {
|
||||||
ChipsWithTitle(
|
ChipsWithTitle(
|
||||||
title = stringResource(id = R.string.language_option),
|
title = stringResource(id = R.string.language_option),
|
||||||
|
|
@ -82,7 +86,7 @@ fun GeneralSettings(
|
||||||
it.first,
|
it.first,
|
||||||
it.second,
|
it.second,
|
||||||
MaterialTheme.typography.labelLarge,
|
MaterialTheme.typography.labelLarge,
|
||||||
it.first == language
|
it.first == state.language
|
||||||
)
|
)
|
||||||
}.sortedBy { it.title }
|
}.sortedBy { it.title }
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ 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.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
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
|
||||||
|
|
@ -24,6 +26,7 @@ import androidx.compose.ui.text.font.FontStyle
|
||||||
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.ChipItem
|
import ua.acclorite.book_story.domain.model.ChipItem
|
||||||
|
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.components.GoBackButton
|
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
|
|
@ -32,8 +35,7 @@ import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
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.SliderWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
|
||||||
import ua.acclorite.book_story.ui.elevation
|
import ua.acclorite.book_story.presentation.ui.elevation
|
||||||
import ua.acclorite.book_story.util.Constants
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -44,18 +46,16 @@ fun ReaderSettings(
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||||
canScroll = {
|
canScroll = {
|
||||||
listState.canScrollForward
|
listState.canScrollForward || listState.canScrollBackward
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val fontFamily = Constants.FONTS.find {
|
val state by mainViewModel.state.collectAsState()
|
||||||
it.id == mainViewModel.fontFamily.collectAsState().value!!
|
val fontFamily = remember(state.fontFamily) {
|
||||||
|
Constants.FONTS.find {
|
||||||
|
it.id == state.fontFamily
|
||||||
} ?: Constants.FONTS[0]
|
} ?: Constants.FONTS[0]
|
||||||
val fontStyle = mainViewModel.isItalic.collectAsState().value!!
|
}
|
||||||
val fontSize = mainViewModel.fontSize.collectAsState().value!!
|
|
||||||
val lineHeight = mainViewModel.lineHeight.collectAsState().value!!
|
|
||||||
val paragraphHeight = mainViewModel.paragraphHeight.collectAsState().value!!
|
|
||||||
val paragraphIndentation = mainViewModel.paragraphIndentation.collectAsState().value!!
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -86,7 +86,7 @@ fun ReaderSettings(
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
CategoryTitle(
|
CategoryTitle(
|
||||||
|
|
@ -127,7 +127,7 @@ fun ReaderSettings(
|
||||||
fontFamily = fontFamily.font,
|
fontFamily = fontFamily.font,
|
||||||
fontStyle = FontStyle.Normal
|
fontStyle = FontStyle.Normal
|
||||||
),
|
),
|
||||||
!fontStyle
|
!state.isItalic!!
|
||||||
),
|
),
|
||||||
ChipItem(
|
ChipItem(
|
||||||
"italic",
|
"italic",
|
||||||
|
|
@ -136,7 +136,7 @@ fun ReaderSettings(
|
||||||
fontFamily = fontFamily.font,
|
fontFamily = fontFamily.font,
|
||||||
fontStyle = FontStyle.Italic
|
fontStyle = FontStyle.Italic
|
||||||
),
|
),
|
||||||
fontStyle
|
state.isItalic!!
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -153,7 +153,7 @@ fun ReaderSettings(
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = fontSize to "pt",
|
value = state.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),
|
||||||
|
|
@ -178,7 +178,7 @@ fun ReaderSettings(
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = lineHeight to "pt",
|
value = state.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),
|
||||||
|
|
@ -191,7 +191,7 @@ fun ReaderSettings(
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
value = paragraphHeight to "pt",
|
value = state.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),
|
||||||
|
|
@ -204,11 +204,11 @@ fun ReaderSettings(
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
CheckboxWithTitle(
|
CheckboxWithTitle(
|
||||||
selected = paragraphIndentation,
|
selected = state.paragraphIndentation!!,
|
||||||
title = stringResource(id = R.string.paragraph_indentation_option)
|
title = stringResource(id = R.string.paragraph_indentation_option)
|
||||||
) {
|
) {
|
||||||
mainViewModel.onEvent(
|
mainViewModel.onEvent(
|
||||||
MainEvent.OnChangeParagraphIndentation(!paragraphIndentation)
|
MainEvent.OnChangeParagraphIndentation(!state.paragraphIndentation!!)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.ui
|
package ua.acclorite.book_story.presentation.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
|
|
@ -6,13 +6,13 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import ua.acclorite.book_story.ui.theme.aquaTheme
|
import ua.acclorite.book_story.presentation.ui.theme.aquaTheme
|
||||||
import ua.acclorite.book_story.ui.theme.blueTheme
|
import ua.acclorite.book_story.presentation.ui.theme.blueTheme
|
||||||
import ua.acclorite.book_story.ui.theme.greenTheme
|
import ua.acclorite.book_story.presentation.ui.theme.greenTheme
|
||||||
import ua.acclorite.book_story.ui.theme.pinkTheme
|
import ua.acclorite.book_story.presentation.ui.theme.pinkTheme
|
||||||
import ua.acclorite.book_story.ui.theme.purpleTheme
|
import ua.acclorite.book_story.presentation.ui.theme.purpleTheme
|
||||||
import ua.acclorite.book_story.ui.theme.redTheme
|
import ua.acclorite.book_story.presentation.ui.theme.redTheme
|
||||||
import ua.acclorite.book_story.ui.theme.yellowTheme
|
import ua.acclorite.book_story.presentation.ui.theme.yellowTheme
|
||||||
|
|
||||||
enum class Theme {
|
enum class Theme {
|
||||||
DYNAMIC,
|
DYNAMIC,
|
||||||
|
|
@ -29,16 +29,7 @@ enum class Theme {
|
||||||
* Converting [String] into [Theme].
|
* Converting [String] into [Theme].
|
||||||
*/
|
*/
|
||||||
fun String.toTheme(): Theme {
|
fun String.toTheme(): Theme {
|
||||||
return when (this) {
|
return Theme.valueOf(this)
|
||||||
"DYNAMIC" -> Theme.DYNAMIC
|
|
||||||
"PURPLE" -> Theme.PURPLE
|
|
||||||
"GREEN" -> Theme.GREEN
|
|
||||||
"PINK" -> Theme.PINK
|
|
||||||
"YELLOW" -> Theme.YELLOW
|
|
||||||
"RED" -> Theme.RED
|
|
||||||
"AQUA" -> Theme.AQUA
|
|
||||||
else -> Theme.BLUE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.ui
|
package ua.acclorite.book_story.presentation.ui
|
||||||
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.ui
|
package ua.acclorite.book_story.presentation.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
|
@ -22,11 +22,7 @@ enum class DarkTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.toDarkTheme(): DarkTheme {
|
fun String.toDarkTheme(): DarkTheme {
|
||||||
return when (this) {
|
return DarkTheme.valueOf(this)
|
||||||
"OFF" -> DarkTheme.OFF
|
|
||||||
"ON" -> DarkTheme.ON
|
|
||||||
else -> DarkTheme.FOLLOW_SYSTEM
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.ui
|
package ua.acclorite.book_story.presentation.ui
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.ui
|
package ua.acclorite.book_story.presentation.ui
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
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