diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d00a6ede..9517a289 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -137,8 +137,10 @@ dependencies {
implementation("org.slf4j:slf4j-android:latest.release")
implementation("org.jsoup:jsoup:latest.release")
+ // App Compat for Language Switcher
implementation("androidx.appcompat:appcompat:latest.release")
implementation("androidx.appcompat:appcompat-resources:latest.release")
+ // Coil for loading bitmaps
implementation("io.coil-kt:coil-compose:2.6.0")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7c9c53e6..0f77188d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,19 +19,24 @@
+
+
+
+
+ tools:targetApi="tiramisu"
+ tools:ignore="DataExtractionRules">
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/Activity.kt b/app/src/main/java/ua/acclorite/book_story/Activity.kt
index c687bce6..65b14e2e 100644
--- a/app/src/main/java/ua/acclorite/book_story/Activity.kt
+++ b/app/src/main/java/ua/acclorite/book_story/Activity.kt
@@ -9,6 +9,8 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
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.padding
import androidx.compose.material3.MaterialTheme
@@ -18,17 +20,20 @@ import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
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.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
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.custom_navigation_rail.CustomNavigationRail
import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.data.NavigationHost
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.data.BookInfoViewModel
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.general.GeneralSettings
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettings
-import ua.acclorite.book_story.ui.BooksHistoryResurrectionTheme
-import ua.acclorite.book_story.ui.DarkTheme
-import ua.acclorite.book_story.ui.Theme
-import ua.acclorite.book_story.ui.Transitions
-import ua.acclorite.book_story.ui.isDark
+import ua.acclorite.book_story.presentation.ui.BooksHistoryResurrectionTheme
+import ua.acclorite.book_story.presentation.ui.Transitions
+import ua.acclorite.book_story.presentation.ui.isDark
import java.lang.reflect.Field
@@ -89,18 +92,26 @@ class Activity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
- val windowClass = calculateWindowSizeClass(activity = this)
val isLoaded by mainViewModel.isReady.collectAsState()
+ val state by mainViewModel.state.collectAsState()
- val theme = mainViewModel.theme.collectAsState().value ?: Theme.BLUE
- val darkTheme =
- mainViewModel.darkTheme.collectAsState().value ?: DarkTheme.FOLLOW_SYSTEM
- val tabletUI = windowClass.widthSizeClass != WindowWidthSizeClass.Compact
+ val windowClass = calculateWindowSizeClass(activity = this)
+ val tabletUI = remember(windowClass) {
+ windowClass.widthSizeClass != WindowWidthSizeClass.Compact
+ }
+ val localLayoutDirection = LocalLayoutDirection.current
+ val layoutDirection = remember(localLayoutDirection) {
+ if (localLayoutDirection == LayoutDirection.Ltr) {
+ LayoutDirection.Ltr
+ } else {
+ LayoutDirection.Rtl
+ }
+ }
if (isLoaded) {
BooksHistoryResurrectionTheme(
- theme = theme,
- isDark = darkTheme.isDark()
+ theme = state.theme!!,
+ isDark = state.darkTheme!!.isDark()
) {
NavigationHost(startScreen = Screen.LIBRARY) {
val currentScreen by this.getCurrentScreen().collectAsState()
@@ -123,42 +134,58 @@ class Activity : AppCompatActivity() {
Box(
modifier = Modifier
.fillMaxSize()
- .padding(
- start = if (tabletUI) 80.dp else 0.dp,
- bottom = it.calculateBottomPadding()
- )
) {
- composable(screen = Screen.LIBRARY) {
- @Suppress("UNCHECKED_CAST")
- LibraryScreen(
- viewModel = libraryViewModel,
- historyViewModel = historyViewModel,
- browseViewModel = browseViewModel,
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(
+ end = it.calculateEndPadding(
+ layoutDirection
+ ),
+ bottom = it.calculateBottomPadding(),
+ start = if (tabletUI) {
+ 80.dp + it.calculateStartPadding(
+ layoutDirection
+ )
+ } else {
+ it.calculateStartPadding(
+ layoutDirection
+ )
+ },
+ )
+ ) {
+ composable(screen = Screen.LIBRARY) {
+ LibraryScreen(
+ viewModel = libraryViewModel,
+ historyViewModel = historyViewModel,
+ browseViewModel = browseViewModel,
+ navigator = this@NavigationHost
+ )
+ }
+
+ composable(screen = Screen.HISTORY) {
+ HistoryScreen(
+ viewModel = historyViewModel,
+ libraryViewModel = libraryViewModel,
+ navigator = this@NavigationHost
+ )
+ }
+
+ composable(screen = Screen.BROWSE) {
+ BrowseScreen(
+ viewModel = browseViewModel,
+ libraryViewModel = libraryViewModel,
+ navigator = this@NavigationHost
+ )
+ }
+ }
+
+ if (tabletUI) {
+ CustomNavigationRail(
navigator = this@NavigationHost,
- addedBooks = retrieveArgument("added_books") as? List
- ?: emptyList()
+ layoutDirection = layoutDirection
)
}
-
- composable(screen = Screen.HISTORY) {
- HistoryScreen(
- viewModel = historyViewModel,
- libraryViewModel = libraryViewModel,
- navigator = this@NavigationHost
- )
- }
-
- composable(screen = Screen.BROWSE) {
- BrowseScreen(
- viewModel = browseViewModel,
- libraryViewModel = libraryViewModel,
- navigator = this@NavigationHost
- )
- }
- }
-
- if (tabletUI) {
- CustomNavigationRail(navigator = this@NavigationHost)
}
}
}
@@ -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)
// composable(screen = Screen.START) {
// StartScreen()
diff --git a/app/src/main/java/ua/acclorite/book_story/util/BookApplication.kt b/app/src/main/java/ua/acclorite/book_story/Application.kt
similarity index 55%
rename from app/src/main/java/ua/acclorite/book_story/util/BookApplication.kt
rename to app/src/main/java/ua/acclorite/book_story/Application.kt
index d203292c..5c1589f7 100644
--- a/app/src/main/java/ua/acclorite/book_story/util/BookApplication.kt
+++ b/app/src/main/java/ua/acclorite/book_story/Application.kt
@@ -1,7 +1,7 @@
-package ua.acclorite.book_story.util
+package ua.acclorite.book_story
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
-class BookApplication : Application()
\ No newline at end of file
+class Application : Application()
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt b/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt
index 2202be40..1afd91e0 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt
@@ -22,6 +22,6 @@ object AppModule {
app,
BookDatabase::class.java,
"book_db"
- ).build().dao
+ ).allowMainThreadQueries().build().dao
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/dto/BookEntity.kt b/app/src/main/java/ua/acclorite/book_story/data/local/dto/BookEntity.kt
index 85810b09..8254b5aa 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/local/dto/BookEntity.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/local/dto/BookEntity.kt
@@ -12,6 +12,8 @@ data class BookEntity(
val description: String?,
val text: String,
val filePath: String,
+ val scrollIndex: Int,
+ val scrollOffset: Int,
val progress: Float,
val image: String? = null,
val category: Category
diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt
index e110fb27..50867348 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt
@@ -43,6 +43,9 @@ interface BookDao {
@Query("SELECT * FROM historyentity")
suspend fun getHistory(): List
+ @Query("SELECT * FROM historyentity WHERE bookId = :bookId ORDER BY time DESC LIMIT 1")
+ fun getLatestHistoryForBook(bookId: Int): HistoryEntity?
+
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertHistory(
history: List
diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt
index 95b1c9e0..d2e73a66 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt
@@ -4,7 +4,7 @@ import android.net.Uri
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.local.dto.BookEntity
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 javax.inject.Inject
@@ -18,6 +18,8 @@ class BookMapperImpl @Inject constructor() : BookMapper {
id = book.id,
title = book.title,
filePath = book.filePath,
+ scrollIndex = book.scrollIndex,
+ scrollOffset = book.scrollOffset,
progress = book.progress,
author = book.author.string,
text = textAsString,
@@ -37,9 +39,13 @@ class BookMapperImpl @Inject constructor() : BookMapper {
R.string.unknown_author
),
description = bookEntity.description,
+ scrollIndex = bookEntity.scrollIndex,
+ scrollOffset = bookEntity.scrollOffset,
progress = bookEntity.progress,
file = if (file.exists()) file else null,
text = emptyList(),
+ letters = 0,
+ words = 0,
filePath = bookEntity.filePath,
lastOpened = null,
category = bookEntity.category,
diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapperImpl.kt
index ff6292f8..d8cab3d1 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapperImpl.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/history/HistoryMapperImpl.kt
@@ -17,6 +17,7 @@ class HistoryMapperImpl @Inject constructor() : HistoryMapper {
return History(
historyEntity.id,
bookId = historyEntity.bookId,
+ book = null,
time = historyEntity.time
)
}
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/FileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/FileParser.kt
index 41974c7c..7d7ee00a 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/FileParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/FileParser.kt
@@ -1,7 +1,7 @@
package ua.acclorite.book_story.data.parser
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
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/FileParserImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/FileParserImpl.kt
index 27b4f864..069ba80d 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/FileParserImpl.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/FileParserImpl.kt
@@ -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.txt.TxtFileParser
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 javax.inject.Inject
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt
index fcee309e..870d523d 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt
@@ -1,7 +1,7 @@
package ua.acclorite.book_story.data.parser
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
interface TextParser {
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParserImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParserImpl.kt
index e98b60e5..76362747 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParserImpl.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParserImpl.kt
@@ -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.txt.TxtTextParser
import ua.acclorite.book_story.domain.model.StringWithId
-import ua.acclorite.book_story.util.Resource
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.Resource
+import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import javax.inject.Inject
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt
index eea796a2..96971bfc 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt
@@ -7,8 +7,8 @@ import nl.siegmann.epublib.epub.EpubReader
import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
-import ua.acclorite.book_story.util.CoverImage
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.CoverImage
+import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import java.io.FileInputStream
import javax.inject.Inject
@@ -50,6 +50,10 @@ class EpubFileParser @Inject constructor() : FileParser {
author = author,
description = description?.toString(),
text = emptyList(),
+ letters = 0,
+ words = 0,
+ scrollIndex = 0,
+ scrollOffset = 0,
progress = 0f,
file = file,
filePath = file.path,
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt
index dfbdb7af..a39ddc78 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt
@@ -7,8 +7,8 @@ import org.jsoup.Jsoup
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.TextParser
import ua.acclorite.book_story.domain.model.StringWithId
-import ua.acclorite.book_story.util.Resource
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.Resource
+import ua.acclorite.book_story.domain.util.UIText
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt
index eb458ebb..fbd733fa 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt
@@ -7,9 +7,8 @@ import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
-import ua.acclorite.book_story.domain.model.StringWithId
-import ua.acclorite.book_story.util.CoverImage
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.CoverImage
+import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import javax.inject.Inject
@@ -29,8 +28,6 @@ class PdfFileParser @Inject constructor(private val application: Application) :
val fileAuthor = document.documentInformation.author
val author = if (fileAuthor != null) UIText.StringValue(fileAuthor)
else UIText.StringResource(R.string.unknown_author)
-
- val text = emptyList()
val description = document.documentInformation.subject ?: null
document.close()
@@ -40,7 +37,11 @@ class PdfFileParser @Inject constructor(private val application: Application) :
title = title,
author = author,
description = description,
- text = text,
+ text = emptyList(),
+ letters = 0,
+ words = 0,
+ scrollIndex = 0,
+ scrollOffset = 0,
progress = 0f,
file = file,
filePath = file.path,
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt
index 19759293..e9b1496e 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt
@@ -7,8 +7,8 @@ import com.tom_roush.pdfbox.text.PDFTextStripper
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.TextParser
import ua.acclorite.book_story.domain.model.StringWithId
-import ua.acclorite.book_story.util.Resource
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.Resource
+import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import javax.inject.Inject
@@ -28,11 +28,6 @@ class PdfTextParser @Inject constructor(private val application: Application) :
val pdfStripper = PDFTextStripper()
pdfStripper.paragraphStart = "_new_line_"
- if (document.isEncrypted) {
- document.close()
- return Resource.Error(UIText.StringResource(R.string.error_file_encrypted))
- }
-
val oldText = pdfStripper.getText(document)
.replace("\r", "")
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt
index 92cfcfd6..83d26fc8 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt
@@ -4,9 +4,8 @@ import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
-import ua.acclorite.book_story.domain.model.StringWithId
-import ua.acclorite.book_story.util.CoverImage
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.CoverImage
+import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import javax.inject.Inject
@@ -20,14 +19,17 @@ class TxtFileParser @Inject constructor() : FileParser {
try {
val title = file.name.trim().dropLast(4)
val author = UIText.StringResource(R.string.unknown_author)
- val text = emptyList()
return Book(
id = null,
title = title,
author = author,
description = null,
- text = text,
+ text = emptyList(),
+ letters = 0,
+ words = 0,
+ scrollIndex = 0,
+ scrollOffset = 0,
progress = 0f,
file = file,
filePath = file.path,
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt
index d1756dda..303abad7 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt
@@ -5,8 +5,8 @@ import kotlinx.coroutines.withContext
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.TextParser
import ua.acclorite.book_story.domain.model.StringWithId
-import ua.acclorite.book_story.util.Resource
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.Resource
+import ua.acclorite.book_story.domain.util.UIText
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt
index d8d6e28a..d6b24f9e 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt
@@ -5,11 +5,12 @@ import android.graphics.Bitmap
import android.net.Uri
import android.os.Environment
import androidx.datastore.preferences.core.Preferences
+import kotlinx.coroutines.Dispatchers
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.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.mapper.book.BookMapper
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.StringWithId
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.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.FileOutputStream
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@@ -47,17 +49,15 @@ class BookRepositoryImpl @Inject constructor(
override suspend fun getBooks(query: String): Flow>> {
return flow {
emit(Resource.Loading(true))
-
val books = database.searchBooks(query)
- val history = database.getHistory()
emit(
Resource.Success(
data = books.map { entity ->
val book = bookMapper.toBook(entity)
- val lastHistory = history
- .filter { it.bookId == book.id }
- .maxByOrNull { it.time }
+ val lastHistory = database.getLatestHistoryForBook(
+ book.id ?: -1
+ )
book.copy(
lastOpened = lastHistory?.time
@@ -70,12 +70,12 @@ class BookRepositoryImpl @Inject constructor(
override suspend fun fastGetBooks(query: String): List {
val books = database.searchBooks(query)
- val history = database.getHistory()
return books.map { entity ->
val book = bookMapper.toBook(entity)
- val lastHistory =
- history.filter { it.bookId == book.id }.maxByOrNull { it.time }
+ val lastHistory = database.getLatestHistoryForBook(
+ book.id ?: -1
+ )
book.copy(
lastOpened = lastHistory?.time
@@ -84,7 +84,18 @@ class BookRepositoryImpl @Inject constructor(
}
override suspend fun getBooksById(ids: List): List {
- 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 {
@@ -92,30 +103,35 @@ class BookRepositoryImpl @Inject constructor(
return book.text
}
- override suspend fun findBook(
- id: Int
- ): BookEntity {
- return database.findBookById(id)
- }
-
override suspend fun insertBooks(
books: List>
) {
+ val filesDir = application.filesDir
+ val coversDir = File(filesDir, "covers")
+
+ if (!coversDir.exists()) {
+ coversDir.mkdirs()
+ }
+
val booksWithCover = books.map {
var uri = ""
if (it.second != null) {
try {
uri = "${UUID.randomUUID()}.webp"
- application.openFileOutput(uri, 0x0000).use { stream ->
- if (
- !it.second!!.compress(
- Bitmap.CompressFormat.WEBP,
- 30,
- stream
- )
- ) {
- throw Exception("Couldn't save cover image")
+ val cover = File(coversDir, uri)
+
+ withContext(Dispatchers.IO) {
+ FileOutputStream(cover).use { stream ->
+ if (
+ !it.second!!.compress(
+ Bitmap.CompressFormat.WEBP,
+ 30,
+ stream
+ )
+ ) {
+ throw Exception("Couldn't save cover image")
+ }
}
}
} catch (e: Exception) {
@@ -126,7 +142,7 @@ class BookRepositoryImpl @Inject constructor(
val book = it.first.copy(
coverImage = if (uri.isNotBlank()) {
- Uri.fromFile(File("${application.filesDir}/$uri"))
+ Uri.fromFile(File("$coversDir/$uri"))
} else null
)
@@ -175,18 +191,29 @@ class BookRepositoryImpl @Inject constructor(
val book = database.findBookById(bookWithOldCover.id ?: return)
var uri: String? = null
+ val filesDir = application.filesDir
+ val coversDir = File(filesDir, "covers")
+
+ if (!coversDir.exists()) {
+ coversDir.mkdirs()
+ }
+
if (newCoverImage != null) {
try {
uri = "${UUID.randomUUID()}.webp"
- application.openFileOutput(uri, 0x0000).use { stream ->
- if (
- !newCoverImage.compress(
- Bitmap.CompressFormat.WEBP,
- 30,
- stream
- )
- ) {
- throw Exception("Couldn't save cover image")
+ val cover = File(coversDir, uri)
+
+ withContext(Dispatchers.IO) {
+ FileOutputStream(cover).use { stream ->
+ if (
+ !newCoverImage.compress(
+ Bitmap.CompressFormat.WEBP,
+ 30,
+ stream
+ )
+ ) {
+ throw Exception("Couldn't save cover image")
+ }
}
}
} catch (e: Exception) {
@@ -197,16 +224,20 @@ class BookRepositoryImpl @Inject constructor(
if (book.image != null) {
try {
- application.deleteFile(
- book.image.substringAfterLast("/")
+ val fileToDelete = File(
+ "$coversDir/${book.image.substringAfterLast("/")}"
)
+
+ if (fileToDelete.exists()) {
+ fileToDelete.delete()
+ }
} catch (e: Exception) {
e.printStackTrace()
}
}
val newCoverImageUri = if (uri != null) {
- Uri.fromFile(File("${application.filesDir}/$uri"))
+ Uri.fromFile(File("$coversDir/$uri"))
} else {
null
}
@@ -228,15 +259,26 @@ class BookRepositoryImpl @Inject constructor(
}
override suspend fun deleteBooks(books: List) {
+ val filesDir = application.filesDir
+ val coversDir = File(filesDir, "covers")
+
+ if (!coversDir.exists()) {
+ coversDir.mkdirs()
+ }
+
database.deleteBooks(
books.map {
val book = database.findBookById(it.id ?: return)
if (book.image != null) {
try {
- application.deleteFile(
- book.image.substringAfterLast("/")
+ val fileToDelete = File(
+ "$coversDir/${book.image.substringAfterLast("/")}"
)
+
+ if (fileToDelete.exists()) {
+ fileToDelete.delete()
+ }
} catch (e: Exception) {
e.printStackTrace()
}
@@ -301,14 +343,28 @@ class BookRepositoryImpl @Inject constructor(
ignoreCase = true
)
}
- val isFileAlreadyAdded = existingBooks.all {
+
+ if (!isFileSupported) {
+ return@filter false
+ }
+
+ val isFileNotAdded = existingBooks.all {
it.filePath.substringAfterLast("/") !=
file.path.substringAfterLast("/")
}
+
+ if (!isFileNotAdded) {
+ return@filter false
+ }
+
val isQuery = if (query.isEmpty()) true else file.name.lowercase()
.contains(query.trim().lowercase())
- isFileAlreadyAdded && isFileSupported && isQuery
+ if (!isQuery) {
+ return@filter false
+ }
+
+ true
}.forEach {
filteredFiles.add(
it
@@ -414,7 +470,6 @@ class BookRepositoryImpl @Inject constructor(
override suspend fun getHistory(): Flow>> {
return flow {
emit(Resource.Loading(true))
-
val history = database.getHistory()
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() {
database.deleteWholeHistory()
}
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt
index 124612bc..20b38271 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt
@@ -2,17 +2,26 @@ package ua.acclorite.book_story.domain.model
import android.net.Uri
import androidx.compose.runtime.Immutable
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.UIText
import java.io.File
@Immutable
data class Book(
val id: Int?,
+
val title: String,
val author: UIText,
val description: String?,
+
+// val textPath: String,
val text: List = emptyList(),
+ val letters: Int,
+ val words: Int,
+
+ val scrollIndex: Int,
+ val scrollOffset: Int,
val progress: Float,
+
val file: File?,
val filePath: String,
val lastOpened: Long?,
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/FontWithName.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/FontWithName.kt
index a6242f1b..fd1fef61 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/model/FontWithName.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/model/FontWithName.kt
@@ -2,7 +2,7 @@ package ua.acclorite.book_story.domain.model
import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.font.FontFamily
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.UIText
@Immutable
data class FontWithName(
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/History.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/History.kt
index 4bf92477..46b82b99 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/model/History.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/model/History.kt
@@ -6,5 +6,6 @@ import androidx.compose.runtime.Immutable
data class History(
val id: Int?,
val bookId: Int,
+ val book: Book?,
val time: Long
)
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt
index 88ee5acb..39ab8b1b 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt
@@ -1,8 +1,8 @@
package ua.acclorite.book_story.domain.model
import androidx.compose.runtime.Immutable
-import ua.acclorite.book_story.util.CoverImage
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.CoverImage
+import ua.acclorite.book_story.domain.util.UIText
@Immutable
sealed class NullableBook(
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt
index a7d8853e..b2f43946 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt
@@ -2,12 +2,11 @@ package ua.acclorite.book_story.domain.repository
import androidx.datastore.preferences.core.Preferences
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.History
import ua.acclorite.book_story.domain.model.NullableBook
-import ua.acclorite.book_story.util.CoverImage
-import ua.acclorite.book_story.util.Resource
+import ua.acclorite.book_story.domain.util.CoverImage
+import ua.acclorite.book_story.domain.util.Resource
import java.io.File
interface BookRepository {
@@ -28,10 +27,6 @@ interface BookRepository {
bookId: Int
): String
- suspend fun findBook(
- id: Int
- ): BookEntity
-
suspend fun insertBooks(
books: List>
)
@@ -71,6 +66,8 @@ interface BookRepository {
suspend fun getHistory(): Flow>>
+ suspend fun getLatestBookHistory(bookId: Int): History?
+
suspend fun deleteWholeHistory()
suspend fun deleteBookHistory(bookId: Int)
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/ChangeLanguage.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/ChangeLanguage.kt
index f9c4ef1e..c7799c12 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/ChangeLanguage.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/ChangeLanguage.kt
@@ -1,14 +1,13 @@
package ua.acclorite.book_story.domain.use_case
-import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
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
class ChangeLanguage @Inject constructor(private val repository: BookRepository) {
- @SuppressLint("AppBundleLocaleChanges")
+
suspend fun execute(language: String) {
val appLocale = LocaleListCompat.forLanguageTags(language)
AppCompatDelegate.setApplicationLocales(appLocale)
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooks.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooks.kt
index c6eeb698..76af9fb6 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooks.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetBooks.kt
@@ -3,7 +3,7 @@ package ua.acclorite.book_story.domain.use_case
import kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.domain.model.Book
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
class GetBooks @Inject constructor(private val repository: BookRepository) {
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetFilesFromDevice.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetFilesFromDevice.kt
index 2afd126e..83806822 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetFilesFromDevice.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetFilesFromDevice.kt
@@ -2,7 +2,7 @@ package ua.acclorite.book_story.domain.use_case
import kotlinx.coroutines.flow.Flow
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 javax.inject.Inject
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetHistory.kt
index 2745db62..bf7b1efc 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetHistory.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetHistory.kt
@@ -3,7 +3,7 @@ package ua.acclorite.book_story.domain.use_case
import kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.domain.model.History
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
class GetHistory @Inject constructor(private val repository: BookRepository) {
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetLatestHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetLatestHistory.kt
new file mode 100644
index 00000000..23bc1682
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetLatestHistory.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBooks.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBooks.kt
index 5caeabcb..028c4441 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBooks.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBooks.kt
@@ -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.repository.BookRepository
-import ua.acclorite.book_story.util.CoverImage
+import ua.acclorite.book_story.domain.util.CoverImage
import javax.inject.Inject
class InsertBooks @Inject constructor(private val repository: BookRepository) {
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateCoverImageOfBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateCoverImageOfBook.kt
index 4e574d7d..db25f60b 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateCoverImageOfBook.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateCoverImageOfBook.kt
@@ -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.repository.BookRepository
-import ua.acclorite.book_story.util.CoverImage
+import ua.acclorite.book_story.domain.util.CoverImage
import javax.inject.Inject
class UpdateCoverImageOfBook @Inject constructor(private val repository: BookRepository) {
diff --git a/app/src/main/java/ua/acclorite/book_story/util/Constants.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt
similarity index 92%
rename from app/src/main/java/ua/acclorite/book_story/util/Constants.kt
rename to app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt
index fd69ea8e..8483c930 100644
--- a/app/src/main/java/ua/acclorite/book_story/util/Constants.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt
@@ -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.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.Category
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 {
- // SavedStateHandle constants(actually just all settings params)
- const val LANGUAGE = "language"
- 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"
+ // Main State Constant
+ const val MAIN_STATE = "main_state"
// Supported file extensions
val EXTENSIONS = listOf(".txt", ".pdf", ".epub")
@@ -182,6 +171,10 @@ object Constants {
UIText.StringValue(""),
null,
emptyList(),
+ 0,
+ 0,
+ 0,
+ 0,
0f,
null,
"",
diff --git a/app/src/main/java/ua/acclorite/book_story/util/DataStoreConstants.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt
similarity index 95%
rename from app/src/main/java/ua/acclorite/book_story/util/DataStoreConstants.kt
rename to app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt
index d350dfac..365d26a0 100644
--- a/app/src/main/java/ua/acclorite/book_story/util/DataStoreConstants.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt
@@ -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.intPreferencesKey
@@ -10,7 +10,6 @@ object DataStoreConstants {
val THEME = stringPreferencesKey("theme")
val DARK_THEME = stringPreferencesKey("dark_theme")
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
-
val BACKGROUND_COLOR = longPreferencesKey("background_color")
val FONT_COLOR = longPreferencesKey("font_color")
val FONT = stringPreferencesKey("font")
diff --git a/app/src/main/java/ua/acclorite/book_story/util/Resource.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/Resource.kt
similarity index 86%
rename from app/src/main/java/ua/acclorite/book_story/util/Resource.kt
rename to app/src/main/java/ua/acclorite/book_story/domain/util/Resource.kt
index 1edb7509..60f0705e 100644
--- a/app/src/main/java/ua/acclorite/book_story/util/Resource.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/util/Resource.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.util
+package ua.acclorite.book_story.domain.util
sealed class Resource(val data: T? = null, val message: UIText? = null) {
class Success(data: T?) : Resource(data)
diff --git a/app/src/main/java/ua/acclorite/book_story/util/Typealias.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/Typealias.kt
similarity index 67%
rename from app/src/main/java/ua/acclorite/book_story/util/Typealias.kt
rename to app/src/main/java/ua/acclorite/book_story/domain/util/Typealias.kt
index 21734d6f..7855cb8a 100644
--- a/app/src/main/java/ua/acclorite/book_story/util/Typealias.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/util/Typealias.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.util
+package ua.acclorite.book_story.domain.util
import android.graphics.Bitmap
diff --git a/app/src/main/java/ua/acclorite/book_story/util/UIText.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/UIText.kt
similarity index 94%
rename from app/src/main/java/ua/acclorite/book_story/util/UIText.kt
rename to app/src/main/java/ua/acclorite/book_story/domain/util/UIText.kt
index 223ccf24..a95beeea 100644
--- a/app/src/main/java/ua/acclorite/book_story/util/UIText.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/util/UIText.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.util
+package ua.acclorite.book_story.domain.util
import android.content.Context
import androidx.annotation.StringRes
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt
index b1322265..424e449f 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/AnimatedTopAppBar.kt
@@ -28,7 +28,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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.
@@ -102,10 +102,12 @@ fun AnimatedTopAppBar(
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
) {
TopAppBar(
+ windowInsets = WindowInsets.statusBars,
navigationIcon = content1NavigationIcon,
title = content1Title,
actions = content1Actions,
scrollBehavior = scrollBehavior,
+ modifier = Modifier.fillMaxWidth(),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = animatedContainerColor,
scrolledContainerColor = scrolledContainerColor
@@ -120,10 +122,12 @@ fun AnimatedTopAppBar(
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
) {
TopAppBar(
+ windowInsets = WindowInsets.statusBars,
navigationIcon = content2NavigationIcon,
title = content2Title,
actions = content2Actions,
scrollBehavior = scrollBehavior,
+ modifier = Modifier.fillMaxWidth(),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = animatedContainerColor,
scrolledContainerColor = scrolledContainerColor
@@ -139,10 +143,12 @@ fun AnimatedTopAppBar(
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow))
) {
TopAppBar(
+ windowInsets = WindowInsets.statusBars,
navigationIcon = content3NavigationIcon,
title = content3Title,
actions = content3Actions,
scrollBehavior = scrollBehavior,
+ modifier = Modifier.fillMaxWidth(),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = animatedContainerColor,
scrolledContainerColor = scrolledContainerColor
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt
index 3c2cc67a..94ddcb5d 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt
@@ -17,7 +17,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.elevation
/**
* Custom Checkbox. Has a Circle shape.
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/GoBackButton.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/GoBackButton.kt
index c11e8360..58fee9b1 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/components/GoBackButton.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/GoBackButton.kt
@@ -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.
*/
@Composable
-fun GoBackButton(navigator: Navigator, customOnClick: () -> Unit = {}) {
+fun GoBackButton(navigator: Navigator, enabled: Boolean = true, customOnClick: () -> Unit = {}) {
CustomIconButton(
icon = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(id = R.string.go_back_content_desc),
- disableOnClick = true
+ disableOnClick = true,
+ enabled = enabled
) {
- navigator.navigateBack()
customOnClick()
+ navigator.navigateBack()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationBar.kt
index f154b5d1..db283aa6 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationBar.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/bottom_navigation_bar/BottomNavigationBar.kt
@@ -14,7 +14,7 @@ import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.NavigationItem
import ua.acclorite.book_story.presentation.data.Navigator
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].
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt
index cba44a90..1a631584 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt
@@ -36,14 +36,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import ua.acclorite.book_story.R
-import ua.acclorite.book_story.ui.ElevationDefaults
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.ElevationDefaults
+import ua.acclorite.book_story.presentation.ui.elevation
/**
* Custom Dialog with custom content.
*
* @param modifier Modifier to be applied.
- * @param properties Dialog properties.
* @param drawableIcon Drawable icon to show(1/2).
* @param imageVectorIcon Image vector icon to show(2/2).
* @param backgroundTransparency The transparency of the background behind the dialog.
@@ -60,7 +59,6 @@ import ua.acclorite.book_story.ui.elevation
@Composable
fun CustomDialogWithContent(
modifier: Modifier = Modifier,
- properties: DialogProperties = DialogProperties(),
drawableIcon: Painter? = null,
imageVectorIcon: ImageVector? = null,
backgroundTransparency: Float = 0.3f,
@@ -76,7 +74,10 @@ fun CustomDialogWithContent(
var actionClicked by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = { onDismiss() },
- properties = properties
+ properties = DialogProperties(
+ dismissOnBackPress = !actionClicked,
+ dismissOnClickOutside = !actionClicked
+ )
) {
(LocalView.current.parent as DialogWindowProvider).window.setDimAmount(
backgroundTransparency
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt
index cce7512f..5f7c8ce0 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt
@@ -37,15 +37,14 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import ua.acclorite.book_story.R
-import ua.acclorite.book_story.ui.ElevationDefaults
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.ElevationDefaults
+import ua.acclorite.book_story.presentation.ui.elevation
/**
* Custom Dialog with lazy column.
*
* @param modifier Modifier to be applied.
- * @param properties Dialog properties.
* @param drawableIcon Drawable icon to show(1/2).
* @param imageVectorIcon Image vector icon to show(2/2).
* @param backgroundTransparency The transparency of the background behind the dialog.
@@ -62,7 +61,6 @@ import ua.acclorite.book_story.ui.elevation
@Composable
fun CustomDialogWithLazyColumn(
modifier: Modifier = Modifier,
- properties: DialogProperties = DialogProperties(),
drawableIcon: Painter? = null,
imageVectorIcon: ImageVector? = null,
backgroundTransparency: Float = 0.3f,
@@ -78,7 +76,10 @@ fun CustomDialogWithLazyColumn(
var actionClicked by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = { onDismiss() },
- properties = properties
+ properties = DialogProperties(
+ dismissOnBackPress = !actionClicked,
+ dismissOnClickOutside = !actionClicked
+ )
) {
(LocalView.current.parent as DialogWindowProvider).window.setDimAmount(
backgroundTransparency
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRail.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRail.kt
index 939d68ea..8bc8a27a 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRail.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_navigation_rail/CustomNavigationRail.kt
@@ -2,8 +2,13 @@ package ua.acclorite.book_story.presentation.components.custom_navigation_rail
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.BoxScope
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.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
@@ -18,19 +23,21 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.NavigationItem
import ua.acclorite.book_story.presentation.data.Navigator
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.
*/
@Composable
-fun CustomNavigationRail(
- navigator: Navigator
+fun BoxScope.CustomNavigationRail(
+ navigator: Navigator,
+ layoutDirection: LayoutDirection
) {
var currentScreen: Screen? by remember { mutableStateOf(null) }
@@ -44,10 +51,19 @@ fun CustomNavigationRail(
NavigationRail(
modifier = Modifier
- .width(80.dp)
+ .align(
+ Alignment.CenterStart
+ )
.background(MaterialTheme.elevation())
+ .padding(
+ start = WindowInsets.navigationBars
+ .asPaddingValues()
+ .calculateStartPadding(layoutDirection)
+ )
+ .width(80.dp)
.padding(horizontal = 12.dp),
- containerColor = MaterialTheme.elevation()
+ containerColor = MaterialTheme.elevation(),
+ windowInsets = WindowInsets(0, 0, 0, 0)
) {
Column(
Modifier
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt
index dfd3ba13..a3c965eb 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt
@@ -12,5 +12,5 @@ sealed class MainEvent {
data class OnChangeParagraphIndentation(val bool: Boolean) : MainEvent()
data class OnChangeBackgroundColor(val color: Long) : MainEvent()
data class OnChangeFontColor(val color: Long) : MainEvent()
-
+ data class OnChangeShowStartScreen(val bool: Boolean) : MainEvent()
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt
new file mode 100644
index 00000000..0791ebfe
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt
@@ -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 {
+ override fun createFromParcel(parcel: Parcel): MainState {
+ return MainState(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt
index 87831515..b31313c3 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt
@@ -8,7 +8,6 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
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.GetDatastore
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.ui.DarkTheme
-import ua.acclorite.book_story.ui.Theme
-import ua.acclorite.book_story.ui.toDarkTheme
-import ua.acclorite.book_story.ui.toTheme
-import ua.acclorite.book_story.util.Constants
-import ua.acclorite.book_story.util.DataStoreConstants
+import ua.acclorite.book_story.presentation.ui.DarkTheme
+import ua.acclorite.book_story.presentation.ui.Theme
+import ua.acclorite.book_story.presentation.ui.toDarkTheme
+import ua.acclorite.book_story.presentation.ui.toTheme
import java.util.Locale
import javax.inject.Inject
@@ -42,212 +41,143 @@ class MainViewModel @Inject constructor(
private val _isReady = MutableStateFlow(false)
val isReady = _isReady.asStateFlow()
- /* -- Language ----------------------------------------------------- */
- private var _language: String =
- stateHandle[Constants.LANGUAGE]
- ?: if (
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- get() = stateHandle.getStateFlow(Constants.PARAGRAPH_INDENTATION, null)
- /* --------------------------------------------------------------- */
+ private val _state: MutableStateFlow = MutableStateFlow(
+ stateHandle[Constants.MAIN_STATE] ?: MainState()
+ )
+ val state = _state.asStateFlow()
fun onEvent(event: MainEvent) {
when (event) {
is MainEvent.OnChangeLanguage -> {
viewModelScope.launch(Dispatchers.Main) {
changeLanguage.execute(event.lang)
- _language = event.lang
+ _state.updateWithSavedHandle {
+ it.copy(
+ language = event.lang
+ )
+ }
}
}
is MainEvent.OnChangeDarkTheme -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.DARK_THEME, event.darkTheme)
- _darkTheme = event.darkTheme.toDarkTheme()
+ _state.updateWithSavedHandle {
+ it.copy(
+ darkTheme = event.darkTheme.toDarkTheme()
+ )
+ }
}
}
is MainEvent.OnChangeTheme -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.THEME, event.theme)
- _theme = event.theme.toTheme()
+ _state.updateWithSavedHandle {
+ it.copy(
+ theme = event.theme.toTheme()
+ )
+ }
}
}
is MainEvent.OnChangeFontFamily -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.FONT, event.fontFamily)
- _fontFamily = Constants.FONTS.find { font -> font.id == event.fontFamily }?.id
- ?: Constants.FONTS[0].id
+ _state.updateWithSavedHandle {
+ it.copy(
+ fontFamily = Constants.FONTS.find { font -> font.id == event.fontFamily }?.id
+ ?: Constants.FONTS[0].id
+ )
+ }
}
}
is MainEvent.OnChangeFontStyle -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.IS_ITALIC, event.fontStyle)
- _isItalic = event.fontStyle
+ _state.updateWithSavedHandle {
+ it.copy(
+ isItalic = event.fontStyle
+ )
+ }
}
}
is MainEvent.OnChangeFontSize -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.FONT_SIZE, event.fontSize)
- _fontSize = event.fontSize
+ _state.updateWithSavedHandle {
+ it.copy(
+ fontSize = event.fontSize
+ )
+ }
}
}
is MainEvent.OnChangeLineHeight -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.LINE_HEIGHT, event.lineHeight)
- _lineHeight = event.lineHeight
+ _state.updateWithSavedHandle {
+ it.copy(
+ lineHeight = event.lineHeight
+ )
+ }
}
}
is MainEvent.OnChangeParagraphHeight -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.PARAGRAPH_HEIGHT, event.paragraphHeight)
- _paragraphHeight = event.paragraphHeight
+ _state.updateWithSavedHandle {
+ it.copy(
+ paragraphHeight = event.paragraphHeight
+ )
+ }
}
}
is MainEvent.OnChangeParagraphIndentation -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.PARAGRAPH_INDENTATION, event.bool)
- _paragraphIndentation = event.bool
+ _state.updateWithSavedHandle {
+ it.copy(
+ paragraphIndentation = event.bool
+ )
+ }
}
}
is MainEvent.OnChangeBackgroundColor -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, event.color)
- _backgroundColor = event.color
+ _state.updateWithSavedHandle {
+ it.copy(
+ backgroundColor = event.color
+ )
+ }
}
}
is MainEvent.OnChangeFontColor -> {
viewModelScope.launch(Dispatchers.IO) {
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(
- language,
- theme,
- darkTheme,
- showStartScreen,
- backgroundColor,
- fontColor,
- fontFamily,
- isItalic,
- fontSize,
- lineHeight,
- paragraphHeight,
- paragraphIndentation
- ) { values ->
- values.all { it != null }
+ _state
+ ) {
+ val value = it.first()
+
+ return@combine value.language != null &&
+ value.theme != null &&
+ value.darkTheme != null &&
+ value.fontFamily != null &&
+ value.isItalic != null &&
+ value.fontSize != null &&
+ value.lineHeight != null &&
+ value.paragraphHeight != null &&
+ value.paragraphIndentation != null &&
+ value.backgroundColor != null &&
+ value.fontColor != null &&
+ value.showStartScreen != null
}
val isReady = combine(
@@ -300,7 +232,14 @@ class MainViewModel @Inject constructor(
// Language
viewModelScope.launch(Dispatchers.Main) {
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 {
onEvent(MainEvent.OnChangeLanguage(it))
it.isNotBlank()
@@ -310,7 +249,11 @@ class MainViewModel @Inject constructor(
// Theme
viewModelScope.launch(Dispatchers.IO) {
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 {
onEvent(MainEvent.OnChangeTheme(it))
it.isNotBlank()
@@ -320,7 +263,7 @@ class MainViewModel @Inject constructor(
// Dark Theme
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.DARK_THEME, _darkTheme.toString())
+ .execute(DataStoreConstants.DARK_THEME, DarkTheme.FOLLOW_SYSTEM.name)
.first {
onEvent(MainEvent.OnChangeDarkTheme(it))
it.isNotBlank()
@@ -330,11 +273,9 @@ class MainViewModel @Inject constructor(
// Show Start Screen
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.SHOW_START_SCREEN, _showStartScreen)
+ .execute(DataStoreConstants.SHOW_START_SCREEN, true)
.first {
- setDatastore.execute(DataStoreConstants.SHOW_START_SCREEN, it)
- _showStartScreen = it
-
+ onEvent(MainEvent.OnChangeShowStartScreen(it))
true
}
}
@@ -342,7 +283,7 @@ class MainViewModel @Inject constructor(
// Background Color
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.BACKGROUND_COLOR, _backgroundColor)
+ .execute(DataStoreConstants.BACKGROUND_COLOR, Color.DarkGray.value.toLong())
.first {
onEvent(MainEvent.OnChangeBackgroundColor(it))
true
@@ -352,7 +293,7 @@ class MainViewModel @Inject constructor(
// Font Color
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.FONT_COLOR, _fontColor)
+ .execute(DataStoreConstants.FONT_COLOR, Color.LightGray.value.toLong())
.first {
onEvent(MainEvent.OnChangeFontColor(it))
true
@@ -362,7 +303,7 @@ class MainViewModel @Inject constructor(
// Font Family
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.FONT, _fontFamily)
+ .execute(DataStoreConstants.FONT, Constants.FONTS[0].id)
.first {
onEvent(MainEvent.OnChangeFontFamily(it))
true
@@ -372,7 +313,7 @@ class MainViewModel @Inject constructor(
// Font Style
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.IS_ITALIC, _isItalic)
+ .execute(DataStoreConstants.IS_ITALIC, false)
.first {
onEvent(MainEvent.OnChangeFontStyle(it))
true
@@ -382,7 +323,7 @@ class MainViewModel @Inject constructor(
// Font Size
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.FONT_SIZE, _fontSize)
+ .execute(DataStoreConstants.FONT_SIZE, 16)
.first {
onEvent(MainEvent.OnChangeFontSize(it))
it > 0
@@ -392,7 +333,7 @@ class MainViewModel @Inject constructor(
// Line Height
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.LINE_HEIGHT, _lineHeight)
+ .execute(DataStoreConstants.LINE_HEIGHT, 4)
.first {
onEvent(MainEvent.OnChangeLineHeight(it))
true
@@ -402,7 +343,7 @@ class MainViewModel @Inject constructor(
// Paragraph Height
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.PARAGRAPH_HEIGHT, _paragraphHeight)
+ .execute(DataStoreConstants.PARAGRAPH_HEIGHT, 8)
.first {
onEvent(MainEvent.OnChangeParagraphHeight(it))
true
@@ -412,7 +353,7 @@ class MainViewModel @Inject constructor(
// Paragraph Indentation
viewModelScope.launch(Dispatchers.IO) {
getDatastore
- .execute(DataStoreConstants.PARAGRAPH_INDENTATION, _paragraphIndentation)
+ .execute(DataStoreConstants.PARAGRAPH_INDENTATION, false)
.first {
onEvent(MainEvent.OnChangeParagraphIndentation(it))
true
@@ -421,4 +362,17 @@ class MainViewModel @Inject constructor(
}
+ /**
+ * Updates [MutableStateFlow] along with [SavedStateHandle].
+ */
+ private fun MutableStateFlow.updateWithSavedHandle(
+ const: String = Constants.MAIN_STATE,
+ function: (T) -> T
+ ) {
+ val nextValue = function(value)
+ update {
+ stateHandle[const] = nextValue
+ nextValue
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt
index c9cf246e..72f96578 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.Modifier
@@ -28,7 +29,7 @@ import dagger.hilt.android.lifecycle.withCreationCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow
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"
@@ -56,12 +57,12 @@ enum class Screen {
START
}
+@Immutable
data class Argument(
val key: String,
val arg: Any?
)
-
/**
* Navigator. Using for navigation between screens.
*
@@ -181,7 +182,6 @@ class Navigator @AssistedInject constructor(
}
}
-
@AssistedFactory
interface Factory {
fun create(startScreen: Screen): Navigator
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/AboutScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/AboutScreen.kt
new file mode 100644
index 00000000..adf50af6
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/AboutScreen.kt
@@ -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)) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutItem.kt
new file mode 100644
index 00000000..c35e2d4e
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutItem.kt
@@ -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,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutEvent.kt
new file mode 100644
index 00000000..7466e796
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutEvent.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutViewModel.kt
new file mode 100644
index 00000000..7a6e7a1e
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutViewModel.kt
@@ -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()
+ }
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt
index 6b23e85f..f36169db 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/BookInfoScreen.kt
@@ -1,6 +1,7 @@
package ua.acclorite.book_story.presentation.screens.book_info
import android.widget.Toast
+import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
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.CustomSnackbar
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.Screen
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.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.library.data.LibraryEvent
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
-import ua.acclorite.book_story.ui.DefaultTransition
-import ua.acclorite.book_story.ui.Transitions
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.DefaultTransition
+import ua.acclorite.book_story.presentation.ui.Transitions
+import ua.acclorite.book_story.presentation.ui.elevation
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
@@ -99,8 +98,8 @@ fun BookInfoScreen(
viewModel.onEvent(
BookInfoEvent.OnUpdateBook(
refreshList = {
- libraryViewModel.onEvent(LibraryEvent.OnLoadList)
- historyViewModel.onEvent(HistoryEvent.OnLoadList)
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
},
snackbarState,
context
@@ -138,6 +137,7 @@ fun BookInfoScreen(
if (state.showMoveDialog) {
BookInfoMoveDialog(
libraryViewModel = libraryViewModel,
+ historyViewModel = historyViewModel,
viewModel = viewModel,
navigator = navigator
)
@@ -158,7 +158,7 @@ fun BookInfoScreen(
isTopBarScrolled = null,
content1NavigationIcon = {
- GoBackButton(navigator = navigator)
+ GoBackButton(navigator = navigator, enabled = !state.isRefreshing)
},
content1Title = {
DefaultTransition(visible = firstVisibleItemIndex > 0) {
@@ -181,8 +181,8 @@ fun BookInfoScreen(
viewModel.onEvent(
BookInfoEvent.OnUpdateBook(
refreshList = {
- libraryViewModel.onEvent(LibraryEvent.OnLoadList)
- historyViewModel.onEvent(HistoryEvent.OnLoadList)
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
},
snackbarState,
context
@@ -218,9 +218,8 @@ fun BookInfoScreen(
) {
viewModel.onEvent(BookInfoEvent.OnUpdateTitle(
refreshList = {
- libraryViewModel.onEvent(
- LibraryEvent.OnLoadList
- )
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
}
))
Toast.makeText(
@@ -282,11 +281,9 @@ fun BookInfoScreen(
FloatingActionButton(
onClick = {
- navigator.navigate(
- Screen.READER, false, Argument(
- "book", state.book.id
- )
- )
+ if (!state.isRefreshing) {
+ viewModel.onEvent(BookInfoEvent.OnNavigateToReaderScreen(navigator))
+ }
},
shape = MaterialTheme.shapes.large,
modifier = Modifier
@@ -332,4 +329,10 @@ fun BookInfoScreen(
)
}
}
+
+ BackHandler {
+ if (!state.isRefreshing) {
+ navigator.navigateBack()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt
index 89075bc0..98011d93 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoInfoSection.kt
@@ -45,7 +45,7 @@ import ua.acclorite.book_story.R
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.BookInfoViewModel
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.elevation
/**
* Info section.
@@ -65,7 +65,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
modifier = Modifier
.height(195.dp)
.width(130.dp)
- .clip(MaterialTheme.shapes.large)
+ .clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.elevation())
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
@@ -82,7 +82,7 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
contentDescription = stringResource(id = R.string.cover_image_content_desc),
modifier = Modifier
.fillMaxSize()
- .clip(MaterialTheme.shapes.large),
+ .clip(MaterialTheme.shapes.medium),
contentScale = ContentScale.Crop
)
} else {
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoStatisticSection.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoStatisticSection.kt
index e22b9f4d..f2407008 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoStatisticSection.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/BookInfoStatisticSection.kt
@@ -33,13 +33,11 @@ fun BookInfoStatisticSection(book: Book) {
.removeTrailingZero()
}%"
}
- val description = if (book.progress < 1f) stringResource(
- id = R.string.you_read_query,
- progress
- ) + " " + stringResource(
- if (book.progress > 0.2f) R.string.read_keep
+ val description = stringResource(
+ if (book.progress == 1f) R.string.read_done
+ else if (book.progress > 0.2f) R.string.read_keep
else R.string.read_more
- ) else stringResource(id = R.string.read_done)
+ )
Column(
Modifier
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheet.kt
index 95b0d1ff..0f024e6c 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheet.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/change_cover_bottom_sheet/BookInfoChangeCoverBottomSheet.kt
@@ -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.library.data.LibraryEvent
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
-import ua.acclorite.book_story.ui.ElevationDefaults
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.ElevationDefaults
+import ua.acclorite.book_story.presentation.ui.elevation
/**
* Change cover bottom sheet. Lets user select photo from the gallery and replaces old one.
@@ -58,8 +58,8 @@ fun BookInfoChangeCoverBottomSheet(
uri,
context,
refreshList = {
- libraryViewModel.onEvent(LibraryEvent.OnLoadList)
- historyViewModel.onEvent(HistoryEvent.OnLoadList)
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
}
)
)
@@ -98,12 +98,14 @@ fun BookInfoChangeCoverBottomSheet(
text = stringResource(id = R.string.delete_cover),
description = stringResource(id = R.string.delete_cover_desc)
) {
- viewModel.onEvent(BookInfoEvent.OnDeleteCover(
- refreshList = {
- libraryViewModel.onEvent(LibraryEvent.OnLoadList)
- historyViewModel.onEvent(HistoryEvent.OnLoadList)
- }
- ))
+ viewModel.onEvent(
+ BookInfoEvent.OnDeleteCover(
+ refreshList = {
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
+ }
+ )
+ )
Toast.makeText(
context,
context.getString(R.string.cover_image_deleted),
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt
index d5a952df..256311e1 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt
@@ -22,8 +22,8 @@ import androidx.compose.ui.unit.dp
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.BookInfoViewModel
-import ua.acclorite.book_story.ui.ElevationDefaults
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.ElevationDefaults
+import ua.acclorite.book_story.presentation.ui.elevation
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoMoveDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoMoveDialog.kt
index 12355b58..952a0267 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoMoveDialog.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/dialog/BookInfoMoveDialog.kt
@@ -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.screens.book_info.data.BookInfoEvent
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.LibraryViewModel
@@ -27,6 +29,7 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode
@Composable
fun BookInfoMoveDialog(
libraryViewModel: LibraryViewModel,
+ historyViewModel: HistoryViewModel,
viewModel: BookInfoViewModel,
navigator: Navigator
) {
@@ -54,9 +57,8 @@ fun BookInfoMoveDialog(
viewModel.onEvent(
BookInfoEvent.OnMoveBook(
refreshList = {
- libraryViewModel.onEvent(
- LibraryEvent.OnLoadList
- )
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
},
updatePage = {
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt
index f30828d2..0bc42428 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt
@@ -4,26 +4,27 @@ import android.content.Context
import android.net.Uri
import androidx.compose.material3.SnackbarHostState
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.presentation.data.Navigator
sealed class BookInfoEvent {
data object OnShowHideChangeCoverBottomSheet : BookInfoEvent()
data object OnShowHideDetailsBottomSheet : BookInfoEvent()
- data class OnDeleteCover(val refreshList: () -> Unit) : BookInfoEvent()
- data class OnChangeCover(val uri: Uri, val context: Context, val refreshList: () -> Unit) :
+ data class OnDeleteCover(val refreshList: (Book) -> Unit) : BookInfoEvent()
+ data class OnChangeCover(val uri: Uri, val context: Context, val refreshList: (Book) -> Unit) :
BookInfoEvent()
data object OnShowHideEditTitle : BookInfoEvent()
data class OnRequestFocus(val focusRequester: FocusRequester) : 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 OnShowHideDeleteDialog : BookInfoEvent()
data object OnShowHideMoveDialog : BookInfoEvent()
data class OnDeleteBook(val refreshList: () -> Unit, val navigator: Navigator) : BookInfoEvent()
data class OnMoveBook(
- val refreshList: () -> Unit,
+ val refreshList: (Book) -> Unit,
val updatePage: (Int) -> Unit,
val navigator: Navigator
) : BookInfoEvent()
@@ -44,8 +45,12 @@ sealed class BookInfoEvent {
) : BookInfoEvent()
data class OnUpdateBook(
- val refreshList: () -> Unit,
+ val refreshList: (Book) -> Unit,
val snackbarState: SnackbarHostState,
val context: Context
) : BookInfoEvent()
+
+ data class OnNavigateToReaderScreen(
+ val navigator: Navigator
+ ) : BookInfoEvent()
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt
index 650e4522..9b3667bd 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt
@@ -3,7 +3,7 @@ package ua.acclorite.book_story.presentation.screens.book_info.data
import androidx.compose.runtime.Immutable
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
-import ua.acclorite.book_story.util.Constants
+import ua.acclorite.book_story.domain.util.Constants
@Immutable
data class BookInfoState(
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt
index f6533883..24d4310e 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt
@@ -17,16 +17,21 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
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.History
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.GetBookFromFile
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.UpdateBooksWithText
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.Screen
+import java.util.Date
import javax.inject.Inject
@@ -35,6 +40,7 @@ class BookInfoViewModel @Inject constructor(
private val updateBooks: UpdateBooks,
private val updateBooksWithText: UpdateBooksWithText,
private val updateCoverImageOfBook: UpdateCoverImageOfBook,
+ private val insertHistory: InsertHistory,
private val deleteBooks: DeleteBooks,
private val getBookFromFile: GetBookFromFile,
private val getBookById: GetBooksById
@@ -65,7 +71,7 @@ class BookInfoViewModel @Inject constructor(
_state.value.book,
image
)
- event.refreshList()
+
val newCoverImage = getBookById.execute(
listOf(
_state.value.book.id ?: return@launch
@@ -80,6 +86,7 @@ class BookInfoViewModel @Inject constructor(
showChangeCoverBottomSheet = false
)
}
+ event.refreshList(_state.value.book)
}
}
@@ -93,8 +100,6 @@ class BookInfoViewModel @Inject constructor(
bookWithOldCover = _state.value.book,
newCoverImage = null
)
- event.refreshList()
-
_state.update {
it.copy(
book = it.book.copy(
@@ -103,6 +108,7 @@ class BookInfoViewModel @Inject constructor(
showChangeCoverBottomSheet = false
)
}
+ event.refreshList(_state.value.book)
}
}
@@ -155,8 +161,6 @@ class BookInfoViewModel @Inject constructor(
_state.value.book.copy(title = title)
)
)
- event.refreshList()
-
_state.update {
it.copy(
book = it.book.copy(
@@ -164,6 +168,8 @@ class BookInfoViewModel @Inject constructor(
)
)
}
+ event.refreshList(_state.value.book)
+
onEvent(BookInfoEvent.OnShowHideEditTitle)
}
}
@@ -223,13 +229,16 @@ class BookInfoViewModel @Inject constructor(
)
}
- val book = _state.value.book.copy(
- category = _state.value.selectedCategory
- )
- updateBooks.execute(listOf(book))
-
- event.refreshList()
+ _state.update {
+ it.copy(
+ book = it.book.copy(
+ category = it.selectedCategory
+ )
+ )
+ }
+ updateBooks.execute(listOf(_state.value.book))
+ event.refreshList(_state.value.book)
event.updatePage(
Category.entries.dropLastWhile {
it != _state.value.selectedCategory
@@ -375,7 +384,7 @@ class BookInfoViewModel @Inject constructor(
)
)
)
- event.refreshList()
+ event.refreshList(_state.value.book)
onEvent(
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) {
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()
return@launch
}
- val book = getBookById.execute(listOf(bookIndex)).first()
-
_state.update {
BookInfoState(book = book)
}
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/BrowseScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/BrowseScreen.kt
index e0e3c143..ed9c5dec 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/BrowseScreen.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/BrowseScreen.kt
@@ -1,6 +1,7 @@
package ua.acclorite.book_story.presentation.screens.browse
import android.Manifest
+import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
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.graphics.SolidColor
import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
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.BrowseViewModel
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
-import ua.acclorite.book_story.ui.DefaultTransition
-import ua.acclorite.book_story.ui.Transitions
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.DefaultTransition
+import ua.acclorite.book_story.presentation.ui.Transitions
+import ua.acclorite.book_story.presentation.ui.elevation
@OptIn(
ExperimentalMaterial3Api::class,
@@ -79,6 +81,7 @@ fun BrowseScreen(
libraryViewModel: LibraryViewModel,
navigator: Navigator
) {
+ val context = LocalContext.current
val permissionState = rememberPermissionState(
permission = Manifest.permission.READ_EXTERNAL_STORAGE
)
@@ -130,20 +133,13 @@ fun BrowseScreen(
content1Visibility = !state.hasSelectedItems && !state.showSearch,
content1NavigationIcon = {},
content1Title = {
- Text(
- stringResource(id = R.string.browse_screen),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onSurface,
- overflow = TextOverflow.Ellipsis,
- maxLines = 1
- )
+ Text(stringResource(id = R.string.browse_screen))
},
content1Actions = {
CustomIconButton(
icon = Icons.Default.Search,
contentDescription = stringResource(id = R.string.search_content_desc),
- disableOnClick = false,
- enabled = !state.showSearch
+ disableOnClick = true
) {
viewModel.onEvent(BrowseEvent.OnSearchShowHide)
}
@@ -167,8 +163,6 @@ fun BrowseScreen(
id = R.string.selected_items_count_query,
state.selectedItemsCount.coerceAtLeast(1)
),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onSurface,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
@@ -265,6 +259,16 @@ fun BrowseScreen(
modifier = Modifier
.animateItemPlacement(),
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 = {
viewModel.onEvent(BrowseEvent.OnSelectFile(selectableFile))
}
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseFIleItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseFIleItem.kt
index 4931fa47..63a4b2df 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseFIleItem.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/BrowseFIleItem.kt
@@ -2,9 +2,10 @@ package ua.acclorite.book_story.presentation.screens.browse.components
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.sp
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.ui.DefaultTransition
-import ua.acclorite.book_story.util.Selected
+import ua.acclorite.book_story.presentation.ui.DefaultTransition
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
@@ -44,11 +45,14 @@ import java.util.Locale
/**
* Browse list element item. Can be selected.
*/
+@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BrowseFileItem(
file: Pair,
hasSelectedFiles: Boolean,
- modifier: Modifier, onClick: () -> Unit
+ modifier: Modifier,
+ onClick: () -> Unit,
+ onLongClick: () -> Unit
) {
val pattern = remember { SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault()) }
val lastModified = remember { pattern.format(Date(file.first.lastModified())) }
@@ -85,9 +89,14 @@ fun BrowseFileItem(
.padding(horizontal = 8.dp, vertical = 3.dp)
.clip(RoundedCornerShape(10.dp))
.background(animatedBackgroundColor)
- .clickable {
- onClick()
- }
+ .combinedClickable(
+ onLongClick = {
+ onLongClick()
+ },
+ onClick = {
+ onClick()
+ }
+ )
.padding(horizontal = 8.dp, vertical = 7.dp)
) {
Row(
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialog.kt
index 6e822334..4079d293 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialog.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialog.kt
@@ -54,6 +54,7 @@ fun BrowseAddingDialog(
navigator,
resetScroll = {
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(0))
+ libraryViewModel.onEvent(LibraryEvent.OnLoadList)
}
)
)
@@ -83,7 +84,9 @@ fun BrowseAddingDialog(
}
} else {
items(state.selectedBooks) { book ->
- BrowseAddingDialogItem(result = book) {
+ BrowseAddingDialogItem(
+ result = book
+ ) {
if (it) {
viewModel.onEvent(BrowseEvent.OnSelectBook(book))
} else {
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt
index 1e23efb3..c1346e7a 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Error
-import androidx.compose.material.icons.filled.Lock
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -23,8 +22,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
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.util.Selected
/**
* Adding dialog item.
@@ -64,13 +63,6 @@ fun BrowseAddingDialogItem(result: Pair, onClick: (Boole
}
}
} else {
- val icon =
- if (result.first.message?.asString() == stringResource(R.string.error_file_encrypted)) {
- Icons.Default.Lock
- } else {
- Icons.Default.Error
- }
-
Row(
Modifier
.fillMaxWidth()
@@ -81,7 +73,7 @@ fun BrowseAddingDialogItem(result: Pair, onClick: (Boole
verticalAlignment = Alignment.CenterVertically
) {
Icon(
- imageVector = icon,
+ imageVector = Icons.Default.Error,
contentDescription = stringResource(id = R.string.error_content_desc),
modifier = Modifier.size(26.dp),
tint = MaterialTheme.colorScheme.error
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseEvent.kt
index d641538b..c29a6708 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseEvent.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseEvent.kt
@@ -7,8 +7,8 @@ import androidx.compose.ui.focus.FocusRequester
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
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.util.Selected
import java.io.File
sealed class BrowseEvent {
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseState.kt
index fb453989..b6b0a4fa 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseState.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseState.kt
@@ -3,7 +3,7 @@ package ua.acclorite.book_story.presentation.screens.browse.data
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Immutable
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
@Immutable
@@ -18,9 +18,6 @@ data class BrowseState(
val selectedItemsCount: Int = 0,
val hasSelectedItems: Boolean = false,
- val scrollIndex: Int = 0,
- val scrollOffset: Int = 0,
-
val showSearch: Boolean = false,
val searchQuery: String = "",
val hasFocused: Boolean = false,
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt
index c48e0fd1..2fe1b30d 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt
@@ -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.GetFilesFromDevice
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.Screen
-import ua.acclorite.book_story.util.Resource
import javax.inject.Inject
@OptIn(ExperimentalPermissionsApi::class)
@@ -41,6 +41,7 @@ class BrowseViewModel @Inject constructor(
val state = _state.asStateFlow()
private var job: Job? = null
+ private var job2: Job? = null
init {
viewModelScope.launch(Dispatchers.IO) {
@@ -127,7 +128,8 @@ class BrowseViewModel @Inject constructor(
}
is BrowseEvent.OnRefreshList -> {
- viewModelScope.launch(Dispatchers.IO) {
+ job2?.cancel()
+ job2 = viewModelScope.launch(Dispatchers.IO) {
_state.update {
it.copy(
isRefreshing = true,
@@ -169,20 +171,19 @@ class BrowseViewModel @Inject constructor(
is BrowseEvent.OnSelectFile -> {
viewModelScope.launch(Dispatchers.IO) {
- val indexOfFile = _state.value.selectableFiles.indexOf(event.file)
-
- if (indexOfFile == -1) {
- return@launch
+ val editedList = _state.value.selectableFiles.map {
+ if (event.file.first.path == it.first.path) {
+ it.copy(
+ second = !it.second
+ )
+ } else {
+ it
+ }
}
- val editedList = _state.value.selectableFiles.toMutableList()
- editedList[indexOfFile] = editedList[indexOfFile].copy(
- second = !editedList[indexOfFile].second
- )
-
_state.update {
it.copy(
- selectableFiles = editedList.toList(),
+ selectableFiles = editedList,
selectedItemsCount = editedList.filter { file -> file.second }.size,
hasSelectedItems = editedList.any { file -> file.second }
)
@@ -267,7 +268,7 @@ class BrowseViewModel @Inject constructor(
)
}
job?.cancel()
- job = viewModelScope.launch {
+ job = viewModelScope.launch(Dispatchers.IO) {
delay(500)
getFilesFromDownloads()
}
@@ -352,6 +353,7 @@ class BrowseViewModel @Inject constructor(
_state.update {
it.copy(
isLoading = true,
+ hasSelectedItems = false,
listState = LazyListState(0, 0)
)
}
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/HistoryScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/HistoryScreen.kt
index 2390b880..48b67a3f 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/HistoryScreen.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/HistoryScreen.kt
@@ -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.library.data.LibraryEvent
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
-import ua.acclorite.book_story.ui.DefaultTransition
-import ua.acclorite.book_story.ui.Transitions
-import ua.acclorite.book_story.ui.elevation
+import ua.acclorite.book_story.presentation.ui.DefaultTransition
+import ua.acclorite.book_story.presentation.ui.Transitions
+import ua.acclorite.book_story.presentation.ui.elevation
import java.util.UUID
@OptIn(
@@ -79,7 +79,6 @@ fun HistoryScreen(
) {
val context = LocalContext.current
val state by viewModel.state.collectAsState()
- val books = libraryViewModel.state.collectAsState().value.books.map { it.first }
val refreshState = rememberPullRefreshState(
refreshing = state.isRefreshing,
onRefresh = {
@@ -108,19 +107,14 @@ fun HistoryScreen(
content1NavigationIcon = {},
content1Title = {
Text(
- stringResource(id = R.string.history_screen),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onSurface,
- overflow = TextOverflow.Ellipsis,
- maxLines = 1
+ stringResource(id = R.string.history_screen)
)
},
content1Actions = {
CustomIconButton(
icon = Icons.Default.Search,
contentDescription = stringResource(id = R.string.search_content_desc),
- disableOnClick = false,
- enabled = !state.showSearch
+ disableOnClick = true,
) {
viewModel.onEvent(HistoryEvent.OnSearchShowHide)
}
@@ -133,6 +127,7 @@ fun HistoryScreen(
enabled = !state.isLoading
&& !state.isRefreshing
&& state.history.isNotEmpty()
+ && !state.showDeleteWholeHistoryDialog
) {
viewModel.onEvent(HistoryEvent.OnShowHideDeleteWholeHistoryDialog)
}
@@ -214,72 +209,72 @@ fun HistoryScreen(
state = state.listState,
contentPadding = PaddingValues(vertical = 12.dp)
) {
- if (!state.isLoading) {
- state.history.forEachIndexed { index, groupedHistory ->
- item(key = groupedHistory.title) {
- if (index > 0) {
- Spacer(modifier = Modifier.height(8.dp))
- }
-
- CategoryTitle(
- modifier = Modifier.animateItemPlacement(),
- title = when (groupedHistory.title) {
- "today" -> stringResource(id = R.string.today)
- "yesterday" -> stringResource(id = R.string.yesterday)
- else -> groupedHistory.title
- },
- padding = 16.dp
- )
+ state.history.forEachIndexed { index, groupedHistory ->
+ item(key = groupedHistory.title) {
+ if (index > 0) {
Spacer(modifier = Modifier.height(8.dp))
}
- items(
- groupedHistory.history, key = { it.id ?: UUID.randomUUID() }
- ) {
- val book = remember {
- books.find { book -> book.id == it.bookId }
- } ?: return@items
+ CategoryTitle(
+ modifier = Modifier.animateItemPlacement(),
+ title = when (groupedHistory.title) {
+ "today" -> stringResource(id = R.string.today)
+ "yesterday" -> stringResource(id = R.string.yesterday)
+ else -> groupedHistory.title
+ },
+ padding = 16.dp
+ )
- HistoryItem(
- modifier = Modifier.animateItemPlacement(),
- history = it,
- book = book,
- onBodyClick = {
- navigator.navigate(
- Screen.BOOK_INFO,
- false,
- Argument("book", book.id)
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+
+ items(
+ groupedHistory.history, key = { it.id ?: UUID.randomUUID() }
+ ) {
+ HistoryItem(
+ modifier = Modifier.animateItemPlacement(),
+ history = it,
+ isOnClickEnabled = !state.isRefreshing,
+ onBodyClick = {
+ navigator.navigate(
+ Screen.BOOK_INFO,
+ false,
+ Argument("book", it.book)
+ )
+ },
+ onTitleClick = {
+ viewModel.onEvent(
+ HistoryEvent.OnNavigateToReaderScreen(
+ navigator,
+ it.book
)
- },
- onTitleClick = {
- navigator.navigate(
- Screen.READER,
- false,
- Argument("book", book.id)
+ )
+ },
+ isDeleteEnabled = !state.isRefreshing,
+ onDeleteClick = {
+ viewModel.onEvent(
+ HistoryEvent.OnDeleteHistoryElement(
+ historyToDelete = it,
+ snackbarState = snackbarState,
+ context = context,
+ refreshList = {
+ libraryViewModel.onEvent(
+ LibraryEvent.OnLoadList
+ )
+ }
)
- },
- isDeleteEnabled = !state.isRefreshing,
- onDeleteClick = {
- viewModel.onEvent(
- HistoryEvent.OnDeleteHistoryElement(
- historyToDelete = it,
- snackbarState = snackbarState,
- context = context,
- refreshList = {
- libraryViewModel.onEvent(LibraryEvent.OnLoadList)
- }
- )
- )
- }
- )
- }
+ )
+ }
+ )
}
}
}
}
AnimatedVisibility(
- visible = !state.isLoading && state.history.isEmpty() && !state.isRefreshing,
+ visible = !state.isLoading
+ && state.history.isEmpty()
+ && !state.isRefreshing,
modifier = Modifier.align(Alignment.Center),
enter = Transitions.DefaultTransitionIn,
exit = fadeOut(tween(0))
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryItem.kt
index 105b1562..64ba65ff 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryItem.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/components/HistoryItem.kt
@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Image
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.text.style.TextOverflow
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
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.ui.elevation
+import ua.acclorite.book_story.presentation.ui.elevation
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -46,7 +47,7 @@ import java.util.Locale
fun HistoryItem(
modifier: Modifier = Modifier,
history: History,
- book: Book,
+ isOnClickEnabled: Boolean,
onBodyClick: () -> Unit,
onTitleClick: () -> Unit,
isDeleteEnabled: Boolean,
@@ -61,7 +62,7 @@ fun HistoryItem(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
- .clickable {
+ .clickable(enabled = isOnClickEnabled) {
onBodyClick()
}
.padding(top = 6.dp, bottom = 6.dp, start = 16.dp, end = 10.dp)
@@ -74,16 +75,16 @@ fun HistoryItem(
modifier = Modifier
.height(90.dp)
.width(60.dp)
- .clip(MaterialTheme.shapes.medium)
+ .clip(RoundedCornerShape(10.dp))
.background(MaterialTheme.elevation())
) {
- if (book.coverImage != null) {
+ if (history.book?.coverImage != null) {
AsyncImage(
- model = book.coverImage,
+ model = history.book.coverImage,
contentDescription = stringResource(id = R.string.cover_image_content_desc),
modifier = Modifier
.fillMaxSize()
- .clip(MaterialTheme.shapes.medium),
+ .clip(RoundedCornerShape(10.dp)),
contentScale = ContentScale.Crop
)
} else {
@@ -108,14 +109,16 @@ fun HistoryItem(
modifier = Modifier.fillMaxHeight()
) {
Text(
- book.title,
+ history.book?.title ?: return,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyLarge,
+ lineHeight = 21.sp,
modifier = Modifier
.fillMaxWidth()
.clickable(
interactionSource = null,
indication = null,
+ enabled = isOnClickEnabled,
onClick = onTitleClick
),
maxLines = 2,
@@ -135,7 +138,7 @@ fun HistoryItem(
}
Box(modifier = Modifier.weight(0.11f), contentAlignment = Alignment.CenterEnd) {
IconButton(
- enabled = isDeleteEnabled,
+ enabled = isDeleteEnabled && isOnClickEnabled,
onClick = {
onDeleteClick()
}
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryEvent.kt
index 5318fe90..2d51220e 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryEvent.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryEvent.kt
@@ -3,7 +3,9 @@ package ua.acclorite.book_story.presentation.screens.history.data
import android.content.Context
import androidx.compose.material3.SnackbarHostState
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.presentation.data.Navigator
sealed class HistoryEvent {
data object OnRefreshList : HistoryEvent()
@@ -20,4 +22,9 @@ sealed class HistoryEvent {
data class OnSearchQueryChange(val query: String) : HistoryEvent()
data object OnSearchShowHide : 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()
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryState.kt
index 72ba639c..3c02d5ab 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryState.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryState.kt
@@ -15,8 +15,4 @@ data class HistoryState(
val showSearch: Boolean = false,
val searchQuery: String = "",
val hasFocused: Boolean = false,
-
- val scrollIndex: Int = 0,
- val scrollOffset: Int = 0,
-
- )
+)
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt
index 8c50dc17..031c40c3 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt
@@ -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.GetHistory
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.util.Calendar
+import java.util.Date
import java.util.Locale
import javax.inject.Inject
@@ -40,6 +43,7 @@ class HistoryViewModel @Inject constructor(
private var job: Job? = null
private var job2: Job? = null
+ private var job3: Job? = null
init {
viewModelScope.launch(Dispatchers.IO) {
@@ -55,7 +59,8 @@ class HistoryViewModel @Inject constructor(
fun onEvent(event: HistoryEvent) {
when (event) {
is HistoryEvent.OnRefreshList -> {
- viewModelScope.launch(Dispatchers.IO) {
+ job3?.cancel()
+ job3 = viewModelScope.launch(Dispatchers.IO) {
_state.update {
it.copy(
isRefreshing = true,
@@ -115,6 +120,7 @@ class HistoryViewModel @Inject constructor(
deleteHistory.execute(
listOf(event.historyToDelete)
)
+
onEvent(HistoryEvent.OnRefreshList)
event.refreshList()
@@ -172,7 +178,7 @@ class HistoryViewModel @Inject constructor(
)
}
job?.cancel()
- job = viewModelScope.launch {
+ job = viewModelScope.launch(Dispatchers.IO) {
delay(500)
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 ->
when (result) {
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 {
it.copy(
history = emptyList(),
@@ -223,8 +278,26 @@ class HistoryViewModel @Inject constructor(
}
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()
history
@@ -278,20 +351,3 @@ class HistoryViewModel @Inject constructor(
}
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/LibraryScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/LibraryScreen.kt
index e38e38bc..e19d140b 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/LibraryScreen.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/LibraryScreen.kt
@@ -68,6 +68,7 @@ import kotlinx.coroutines.launch
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.util.Selected
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
import ua.acclorite.book_story.presentation.components.CustomIconButton
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.data.LibraryEvent
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
-import ua.acclorite.book_story.ui.DefaultTransition
-import ua.acclorite.book_story.ui.Transitions
-import ua.acclorite.book_story.ui.elevation
-import ua.acclorite.book_story.util.Selected
+import ua.acclorite.book_story.presentation.ui.DefaultTransition
+import ua.acclorite.book_story.presentation.ui.Transitions
+import ua.acclorite.book_story.presentation.ui.elevation
import java.util.UUID
@OptIn(
@@ -98,8 +98,7 @@ fun LibraryScreen(
viewModel: LibraryViewModel,
browseViewModel: BrowseViewModel,
historyViewModel: HistoryViewModel,
- navigator: Navigator,
- addedBooks: List
+ navigator: Navigator
) {
val state by viewModel.state.collectAsState()
val pagerState = rememberPagerState(
@@ -114,21 +113,21 @@ fun LibraryScreen(
)
val focusRequester = remember { FocusRequester() }
- LaunchedEffect(Unit) {
- if (addedBooks.isNotEmpty()) {
- viewModel.onEvent(LibraryEvent.OnPreloadBooks(addedBooks))
- navigator.clearArgument("added_books")
- }
- }
LaunchedEffect(Unit) {
viewModel.onEvent(LibraryEvent.OnScrollToPage(state.currentPage, pagerState))
}
LaunchedEffect(pagerState.currentPage) {
- viewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(pagerState.currentPage))
+ if (pagerState.currentPage != state.currentPage) {
+ viewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(pagerState.currentPage))
+ }
}
if (state.showMoveDialog) {
- LibraryMoveDialog(viewModel = viewModel, pagerState = pagerState)
+ LibraryMoveDialog(
+ viewModel = viewModel,
+ historyViewModel = historyViewModel,
+ pagerState = pagerState
+ )
}
if (state.showDeleteDialog) {
LibraryDeleteDialog(
@@ -156,13 +155,7 @@ fun LibraryScreen(
content1NavigationIcon = {},
content1Title = {
Row(verticalAlignment = Alignment.CenterVertically) {
- Text(
- stringResource(id = R.string.library_screen),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onSurface,
- overflow = TextOverflow.Ellipsis,
- maxLines = 1
- )
+ Text(stringResource(id = R.string.library_screen))
Spacer(modifier = Modifier.width(6.dp))
Text(
text = state.books.size.toString(),
@@ -179,8 +172,7 @@ fun LibraryScreen(
CustomIconButton(
icon = Icons.Default.Search,
contentDescription = stringResource(id = R.string.search_content_desc),
- disableOnClick = false,
- enabled = !state.showSearch
+ disableOnClick = true,
) {
viewModel.onEvent(LibraryEvent.OnSearchShowHide)
}
@@ -204,8 +196,6 @@ fun LibraryScreen(
id = R.string.selected_items_count_query,
state.selectedItemsCount.coerceAtLeast(1)
),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onSurface,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
@@ -216,8 +206,12 @@ fun LibraryScreen(
contentDescription = stringResource(
id = R.string.move_books_content_desc
),
- disableOnClick = true
- ) {
+ enabled = !state.isLoading
+ && !state.isRefreshing
+ && !state.showMoveDialog,
+ disableOnClick = false,
+
+ ) {
viewModel.onEvent(LibraryEvent.OnShowHideMoveDialog)
}
CustomIconButton(
@@ -225,7 +219,10 @@ fun LibraryScreen(
contentDescription = stringResource(
id = R.string.delete_books_content_desc
),
- disableOnClick = true
+ enabled = !state.isLoading
+ && !state.isRefreshing
+ && !state.showDeleteDialog,
+ disableOnClick = false
) {
viewModel.onEvent(LibraryEvent.OnShowHideDeleteDialog)
}
@@ -310,11 +307,13 @@ fun LibraryScreen(
LaunchedEffect(state.books) {
categorizedBooks.clear()
- categorizedBooks.addAll(state.books.filter { it.first.category == category }
- .sortedWith(
- compareByDescending> { it.first.lastOpened }
- .thenBy { it.first.title }
- )
+ categorizedBooks.addAll(
+ state.books
+ .filter { it.first.category == category }
+ .sortedWith(
+ compareByDescending> { it.first.lastOpened }
+ .thenBy { it.first.title }
+ )
)
categoryIsLoading = false
}
@@ -323,8 +322,7 @@ fun LibraryScreen(
DefaultTransition(visible = !state.isLoading && !categoryIsLoading) {
LazyVerticalGrid(
columns = GridCells.Adaptive(120.dp),
- modifier = Modifier
- .fillMaxSize(),
+ modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(8.dp)
) {
items(
@@ -341,7 +339,7 @@ fun LibraryScreen(
navigator.navigate(
Screen.BOOK_INFO,
false,
- Argument("book", it.first.id)
+ Argument("book", it.first)
)
}
},
@@ -351,10 +349,11 @@ fun LibraryScreen(
}
},
onButtonClick = {
- navigator.navigate(
- Screen.READER,
- false,
- Argument("book", it.first.id)
+ viewModel.onEvent(
+ LibraryEvent.OnNavigateToReaderScreen(
+ navigator,
+ it.first
+ )
)
}
)
@@ -363,7 +362,9 @@ fun LibraryScreen(
}
AnimatedVisibility(
- visible = !state.isLoading && !state.isRefreshing && categorizedBooks.isEmpty()
+ visible = !state.isLoading
+ && !state.isRefreshing
+ && categorizedBooks.isEmpty()
&& !categoryIsLoading,
modifier = Modifier.align(Alignment.Center),
enter = Transitions.DefaultTransitionIn,
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryBookItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryBookItem.kt
index 10b956a2..17e1e5c0 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryBookItem.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryBookItem.kt
@@ -37,10 +37,10 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import ua.acclorite.book_story.R
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.removeTrailingZero
-import ua.acclorite.book_story.ui.elevation
-import ua.acclorite.book_story.util.Selected
+import ua.acclorite.book_story.presentation.ui.elevation
/**
* Library list element item.
@@ -93,7 +93,7 @@ fun LibraryBookItem(
modifier = Modifier
.aspectRatio(1f / 1.5f)
.fillMaxWidth()
- .clip(MaterialTheme.shapes.large)
+ .clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.elevation())
.combinedClickable(
onClick = {
@@ -110,7 +110,7 @@ fun LibraryBookItem(
contentDescription = stringResource(id = R.string.cover_image_content_desc),
modifier = Modifier
.fillMaxSize()
- .clip(MaterialTheme.shapes.large),
+ .clip(MaterialTheme.shapes.medium),
contentScale = ContentScale.Crop
)
} else {
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt
index efbc3e97..3390d941 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/LibraryTabRow.kt
@@ -2,6 +2,7 @@ package ua.acclorite.book_story.presentation.screens.library.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -13,7 +14,6 @@ import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
-import androidx.compose.material3.TabRow
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
@@ -22,7 +22,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.unit.dp
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.presentation.screens.library.data.LibraryEvent
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.
@@ -60,83 +60,34 @@ fun LibraryTabRow(viewModel: LibraryViewModel, books: List, pagerState: Pa
)
}
- if (LocalConfiguration.current.screenWidthDp > 450) {
- TabRow(
+ Box(Modifier.fillMaxWidth()) {
+ Divider(
modifier = Modifier
+ .align(Alignment.BottomCenter)
.fillMaxWidth(),
- selectedTabIndex = pagerState.currentPage,
- 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 {
+ color = MaterialTheme.colorScheme.surfaceVariant
+ )
ScrollableTabRow(
modifier = Modifier
- .fillMaxWidth(),
+ .fillMaxWidth()
+ .padding(bottom = 0.5.dp),
selectedTabIndex = pagerState.currentPage,
- containerColor = MaterialTheme.colorScheme.surface,
- divider = {
- Divider(color = MaterialTheme.colorScheme.surfaceVariant)
- },
+ containerColor = Color.Transparent,
edgePadding = 0.dp,
+ divider = {},
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
.padding(horizontal = 16.dp)
+ .padding(end = 5.5.dp)
.clip(
RoundedCornerShape(
- topStartPercent = 100,
- topEndPercent = 100
+ 100
)
),
- color = MaterialTheme.colorScheme.primary
+ color = MaterialTheme.colorScheme.primary,
+ height = 3.dp
)
}
) {
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryMoveDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryMoveDialog.kt
index a904a4cb..398b1d86 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryMoveDialog.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/components/dialog/LibraryMoveDialog.kt
@@ -15,6 +15,8 @@ import ua.acclorite.book_story.R
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.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.LibraryViewModel
@@ -23,7 +25,11 @@ import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewMode
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun LibraryMoveDialog(viewModel: LibraryViewModel, pagerState: PagerState) {
+fun LibraryMoveDialog(
+ viewModel: LibraryViewModel,
+ historyViewModel: HistoryViewModel,
+ pagerState: PagerState
+) {
val state by viewModel.state.collectAsState()
val context = LocalContext.current
@@ -38,7 +44,14 @@ fun LibraryMoveDialog(viewModel: LibraryViewModel, pagerState: PagerState) {
isActionEnabled = true,
onDismiss = { viewModel.onEvent(LibraryEvent.OnShowHideMoveDialog) },
onAction = {
- viewModel.onEvent(LibraryEvent.OnMoveBooks(pagerState))
+ viewModel.onEvent(
+ LibraryEvent.OnMoveBooks(
+ pagerState,
+ refreshList = {
+ historyViewModel.onEvent(HistoryEvent.OnLoadList)
+ }
+ )
+ )
Toast.makeText(
context,
context.getString(R.string.books_moved),
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryEvent.kt
index ed83a084..d6f8b374 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryEvent.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryEvent.kt
@@ -7,10 +7,10 @@ import androidx.compose.foundation.pager.PagerState
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.util.Selected
+import ua.acclorite.book_story.domain.util.Selected
+import ua.acclorite.book_story.presentation.data.Navigator
sealed class LibraryEvent {
- data class OnPreloadBooks(val books: List) : LibraryEvent()
data object OnRefreshList : LibraryEvent()
data object OnLoadList : LibraryEvent()
data class OnScrollToPage(val index: Int, val pagerState: PagerState) : LibraryEvent()
@@ -24,8 +24,12 @@ sealed class LibraryEvent {
data object OnClearSelectedBooks : LibraryEvent()
data object OnShowHideMoveDialog : 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 class OnDeleteBooks(val refreshList: () -> Unit) : LibraryEvent()
data class OnUpdateBook(val book: Book) : LibraryEvent()
+ data class OnNavigateToReaderScreen(
+ val navigator: Navigator,
+ val book: Book
+ ) : LibraryEvent()
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryState.kt
index deca028e..402fc0d1 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryState.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryState.kt
@@ -3,7 +3,7 @@ package ua.acclorite.book_story.presentation.screens.library.data
import androidx.compose.runtime.Immutable
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
-import ua.acclorite.book_story.util.Selected
+import ua.acclorite.book_story.domain.util.Selected
@Immutable
data class LibraryState(
@@ -25,6 +25,4 @@ data class LibraryState(
val selectedCategory: Category = Category.READING,
val showDeleteDialog: Boolean = false,
-
-
- )
+)
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt
index 65020b90..eabc42d5 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt
@@ -12,10 +12,15 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
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.GetBooks
+import ua.acclorite.book_story.domain.use_case.InsertHistory
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
@OptIn(ExperimentalFoundationApi::class)
@@ -23,7 +28,8 @@ import javax.inject.Inject
class LibraryViewModel @Inject constructor(
private val getBooks: GetBooks,
private val updateBooks: UpdateBooks,
- private val deleteBooks: DeleteBooks
+ private val deleteBooks: DeleteBooks,
+ private val insertHistory: InsertHistory
) : ViewModel() {
private val _state = MutableStateFlow(LibraryState())
@@ -33,6 +39,7 @@ class LibraryViewModel @Inject constructor(
val isReady = _isReady.asStateFlow()
private var job: Job? = null
+ private var job2: Job? = null
init {
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 -> {
- viewModelScope.launch(Dispatchers.IO) {
+ job2?.cancel()
+ job2 = viewModelScope.launch(Dispatchers.IO) {
_state.update {
it.copy(
isRefreshing = true,
@@ -101,7 +101,8 @@ class LibraryViewModel @Inject constructor(
viewModelScope.launch(Dispatchers.IO) {
_state.update {
it.copy(
- isLoading = true
+ isLoading = true,
+ hasSelectedItems = false
)
}
getBooksFromDatabase()
@@ -149,7 +150,7 @@ class LibraryViewModel @Inject constructor(
)
}
job?.cancel()
- job = viewModelScope.launch {
+ job = viewModelScope.launch(Dispatchers.IO) {
delay(500)
getBooksFromDatabase()
}
@@ -157,20 +158,19 @@ class LibraryViewModel @Inject constructor(
is LibraryEvent.OnSelectBook -> {
viewModelScope.launch(Dispatchers.IO) {
- val indexOfBook = _state.value.books.indexOf(event.book)
-
- if (indexOfBook == -1) {
- return@launch
+ val editedList = _state.value.books.map {
+ if (it.first.id == event.book.first.id) {
+ it.copy(
+ second = event.select ?: !it.second
+ )
+ } else {
+ it
+ }
}
- val editedList = _state.value.books.toMutableList()
- editedList[indexOfBook] = editedList[indexOfBook].copy(
- second = event.select ?: !editedList[indexOfBook].second
- )
-
_state.update {
it.copy(
- books = editedList.toList(),
+ books = editedList,
selectedItemsCount = editedList.filter { book -> book.second }.size,
hasSelectedItems = editedList.any { book -> book.second }
)
@@ -234,6 +234,7 @@ class LibraryViewModel @Inject constructor(
updateBooks.execute(books)
getBooksFromDatabase()
+ event.refreshList()
onEvent(LibraryEvent.OnClearSelectedBooks)
onEvent(
@@ -278,14 +279,16 @@ class LibraryViewModel @Inject constructor(
is LibraryEvent.OnUpdateBook -> {
viewModelScope.launch(Dispatchers.IO) {
- val books = _state.value.books.toMutableList()
- val index = books.indexOfFirst { it.first.id == event.book.id }
-
- if (index == -1) {
- return@launch
+ val books = _state.value.books.map {
+ if (it.first.id == event.book.id) {
+ it.copy(
+ first = event.book
+ )
+ } else {
+ it
+ }
}
- books[index] = Pair(event.book, books[index].second)
_state.update {
it.copy(
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
+ )
+ )
+ }
+ }
}
}
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt
index d963d002..466d6b0b 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt
@@ -28,6 +28,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -54,6 +55,7 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
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.is_messages.IsError
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.data.ReaderEvent
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
-import ua.acclorite.book_story.ui.elevation
-import ua.acclorite.book_story.util.Constants
+import ua.acclorite.book_story.presentation.ui.elevation
@OptIn(FlowPreview::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@@ -86,8 +87,10 @@ fun ReaderScreen(
val systemBarsColor = MaterialTheme.elevation(20.dp).copy(0.85f)
var loading by remember { mutableStateOf(true) }
- val state by viewModel.state.collectAsState()
val listState = rememberLazyListState()
+
+ val state by viewModel.state.collectAsState()
+ val canScrollForward by remember { derivedStateOf { listState.canScrollForward } }
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPostScroll(
@@ -106,47 +109,45 @@ fun ReaderScreen(
}
}
- val fontFamily = Constants.FONTS.find {
- it.id == mainViewModel.fontFamily.collectAsState().value!!
- } ?: Constants.FONTS[0]
- val fontStyle = when (mainViewModel.isItalic.collectAsState().value!!) {
- false -> FontStyle.Normal
- true -> FontStyle.Italic
+ val mainState by mainViewModel.state.collectAsState()
+ val fontFamily = remember(mainState.fontFamily) {
+ Constants.FONTS.find {
+ it.id == mainState.fontFamily
+ } ?: Constants.FONTS[0]
}
- 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) {
viewModel.init(
navigator,
context,
- listState,
refreshList = {
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
historyViewModel.onEvent(HistoryEvent.OnLoadList)
},
+ listState = listState,
onLoaded = {
loading = false
}
)
}
+ LaunchedEffect(canScrollForward) {
+ if (!canScrollForward && !state.showMenu && !loading) {
+ viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
+ }
+ }
LaunchedEffect(listState) {
snapshotFlow {
- listState.firstVisibleItemIndex
- }.debounce(1000).collectLatest {
+ listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset
+ }.debounce(500).collectLatest { items ->
if (!loading) {
val lastVisibleItemIndex = listState.layoutInfo.visibleItemsInfo.last().index
- val progress = if (it > 0) {
+ val progress = if (items.first > 0) {
if (lastVisibleItemIndex >= (listState.layoutInfo.totalItemsCount - 1)) {
1f
} else {
- (it.toFloat() / (state.book.text.lastIndex).toFloat())
+ (items.first.toFloat() / (state.book.text.lastIndex).toFloat())
}
} else {
0f
@@ -154,14 +155,13 @@ fun ReaderScreen(
viewModel.onEvent(
ReaderEvent.OnChangeProgress(
- progress,
- navigator,
+ progress = progress,
+ navigator = navigator,
+ firstVisibleItemIndex = items.first,
+ firstVisibleItemOffset = items.second,
refreshList = { book ->
- libraryViewModel.onEvent(
- LibraryEvent.OnUpdateBook(
- book
- )
- )
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(book))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(book))
}
)
)
@@ -186,7 +186,10 @@ fun ReaderScreen(
) {
ReaderTopBar(
viewModel = viewModel,
+ libraryViewModel = libraryViewModel,
+ historyViewModel = historyViewModel,
navigator = navigator,
+ listState = listState,
containerColor = systemBarsColor
)
}
@@ -200,8 +203,9 @@ fun ReaderScreen(
ReaderBottomBar(
viewModel = viewModel,
libraryViewModel = libraryViewModel,
+ historyViewModel = historyViewModel,
+ listState = listState,
navigator = navigator,
- scrollState = listState,
systemBarsColor = systemBarsColor
)
}
@@ -280,36 +284,44 @@ fun ReaderScreen(
itemsIndexed(
state.book.text, key = { _, key -> key.id }
) { index, line ->
- val text =
- remember(paragraphIndentation) { "${if (paragraphIndentation) " " else ""}${line.line}" }
- val fontCol = remember(fontColor) { Color(fontColor.toULong()) }
- val backgroundCol =
- remember(backgroundColor) { Color(backgroundColor.toULong()) }
- val lineHeightSp = remember(fontSize, lineHeight) { (fontSize + lineHeight).sp }
+ val text = remember(mainState.paragraphIndentation) {
+ "${if (mainState.paragraphIndentation!!) " " else ""}${line.line}"
+ }
+ val fontColor = remember(mainState.fontColor) {
+ Color(mainState.fontColor!!.toULong())
+ }
+ val backgroundColor = remember(mainState.backgroundColor) {
+ Color(mainState.backgroundColor!!.toULong())
+ }
+ val lineHeight = remember(mainState.fontSize, mainState.lineHeight) {
+ (mainState.fontSize!! + mainState.lineHeight!!).sp
+ }
Column(
Modifier
- .background(backgroundCol)
+ .background(backgroundColor)
.fillMaxWidth()
.padding(
- top = if (index == 0) 36.dp else 0.dp,
+ top = if (index == 0) 18.dp else 0.dp,
start = 18.dp,
end = 18.dp,
- bottom = if (index == state.book.text.lastIndex) 36.dp
- else (paragraphHeight * 3).dp
+ bottom = if (index == state.book.text.lastIndex) 18.dp
+ else (mainState.paragraphHeight!! * 3).dp
)
) {
Text(
text = text,
- color = fontCol,
-
+ color = fontColor,
style = TextStyle(
lineBreak = LineBreak.Paragraph
),
fontFamily = fontFamily.font,
- fontStyle = fontStyle,
- fontSize = fontSize.sp,
- lineHeight = lineHeightSp
+ fontStyle = when (mainState.isItalic!!) {
+ true -> FontStyle.Italic
+ false -> FontStyle.Normal
+ },
+ fontSize = mainState.fontSize!!.sp,
+ lineHeight = lineHeight
)
}
}
@@ -319,7 +331,9 @@ fun ReaderScreen(
DisableSelection {
ReaderEndItem(
libraryViewModel = libraryViewModel,
+ historyViewModel = historyViewModel,
viewModel = viewModel,
+ listState = listState,
navigator = navigator
)
}
@@ -343,10 +357,21 @@ fun ReaderScreen(
icon = painterResource(id = R.drawable.error),
actionTitle = stringResource(id = R.string.go_back),
action = {
- navigator.navigateBack()
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
- historyViewModel.onEvent(HistoryEvent.OnLoadList)
- viewModel.onEvent(ReaderEvent.OnShowSystemBars(context))
+ viewModel.onEvent(
+ ReaderEvent.OnGoBack(
+ context,
+ navigator,
+ refreshList = {
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
+ },
+ listState = listState,
+ navigate = {
+ it.navigateBack()
+ }
+ )
+ )
}
)
} else {
@@ -369,7 +394,19 @@ fun ReaderScreen(
}
BackHandler {
- navigator.navigateBack()
- viewModel.onEvent(ReaderEvent.OnShowSystemBars(context))
+ viewModel.onEvent(
+ ReaderEvent.OnGoBack(
+ context,
+ navigator,
+ refreshList = {
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
+ },
+ listState = listState,
+ navigate = {
+ it.navigateBack()
+ }
+ )
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt
index cc1b5c15..9ff45a5d 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt
@@ -8,11 +8,12 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsIgnoringVisibility
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.MaterialTheme
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.presentation.data.Navigator
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.LibraryViewModel
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
fun ReaderEndItem(
libraryViewModel: LibraryViewModel,
+ historyViewModel: HistoryViewModel,
viewModel: ReaderViewModel,
+ listState: LazyListState,
navigator: Navigator
) {
val state by viewModel.state.collectAsState()
@@ -54,12 +59,11 @@ fun ReaderEndItem(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
- .windowInsetsPadding(WindowInsets.navigationBarsIgnoringVisibility)
.padding(
- top = 24.dp,
- start = 24.dp,
- end = 24.dp,
- bottom = 8.dp
+ horizontal = 36.dp,
+ vertical = 108.dp + WindowInsets.navigationBarsIgnoringVisibility
+ .asPaddingValues()
+ .calculateBottomPadding()
),
horizontalAlignment = Alignment.CenterHorizontally
) {
@@ -72,7 +76,11 @@ fun ReaderEndItem(
)
Spacer(modifier = Modifier.height(4.dp))
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,
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
@@ -83,15 +91,15 @@ fun ReaderEndItem(
if (state.book.category != Category.ALREADY_READ) {
viewModel.onEvent(
ReaderEvent.OnMoveBookToAlreadyRead(
- refreshList = {
- libraryViewModel.onEvent(
- LibraryEvent.OnLoadList
- )
+ onUpdateCategories = {
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
},
updatePage = {
libraryViewModel.onEvent(LibraryEvent.OnUpdateCurrentPage(it))
},
navigator = navigator,
+ listState = listState,
context = context
)
)
@@ -103,11 +111,19 @@ fun ReaderEndItem(
).show()
} else {
viewModel.onEvent(
- ReaderEvent.OnShowSystemBars(
- context
+ ReaderEvent.OnGoBack(
+ 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),
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderStartItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderStartItem.kt
index 952b183b..0539f8ec 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderStartItem.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderStartItem.kt
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsIgnoringVisibility
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Image
import androidx.compose.material3.Icon
@@ -34,7 +35,7 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import ua.acclorite.book_story.R
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.
@@ -49,7 +50,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) {
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.windowInsetsPadding(WindowInsets.statusBarsIgnoringVisibility)
- .padding(24.dp)
+ .padding(horizontal = 24.dp, vertical = 80.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
@@ -59,7 +60,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) {
modifier = Modifier
.height(135.dp)
.width(90.dp)
- .clip(MaterialTheme.shapes.medium)
+ .clip(RoundedCornerShape(10.dp))
.background(MaterialTheme.elevation())
) {
if (state.book.coverImage != null) {
@@ -68,7 +69,7 @@ fun ReaderStartItem(viewModel: ReaderViewModel) {
contentDescription = stringResource(id = R.string.cover_image_content_desc),
modifier = Modifier
.fillMaxSize()
- .clip(MaterialTheme.shapes.medium),
+ .clip(RoundedCornerShape(10.dp)),
contentScale = ContentScale.Crop
)
} else {
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderBottomBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderBottomBar.kt
index e20b452b..0babba89 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderBottomBar.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderBottomBar.kt
@@ -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.removeDigits
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
@@ -36,8 +38,9 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
fun ReaderBottomBar(
viewModel: ReaderViewModel,
libraryViewModel: LibraryViewModel,
+ historyViewModel: HistoryViewModel,
+ listState: LazyListState,
navigator: Navigator,
- scrollState: LazyListState,
systemBarsColor: Color
) {
val state by viewModel.state.collectAsState()
@@ -74,20 +77,19 @@ fun ReaderBottomBar(
Slider(
value = state.book.progress,
onValueChange = {
+ viewModel.onEvent(ReaderEvent.OnScroll(listState, it))
viewModel.onEvent(
ReaderEvent.OnChangeProgress(
progress = it,
navigator = navigator,
+ firstVisibleItemIndex = listState.firstVisibleItemIndex,
+ firstVisibleItemOffset = 0,
refreshList = { book ->
- libraryViewModel.onEvent(
- LibraryEvent.OnUpdateBook(
- book
- )
- )
+ libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(book))
+ historyViewModel.onEvent(HistoryEvent.OnUpdateBook(book))
}
)
)
- viewModel.onEvent(ReaderEvent.OnScroll(scrollState, it))
},
colors = SliderDefaults.colors(
activeTrackColor = MaterialTheme.colorScheme.primary,
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt
index d747c949..15c581c6 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt
@@ -5,7 +5,9 @@ import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
@@ -26,12 +28,14 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.components.CustomIconButton
-import ua.acclorite.book_story.presentation.components.GoBackButton
-import ua.acclorite.book_story.presentation.data.Argument
import ua.acclorite.book_story.presentation.data.Navigator
import ua.acclorite.book_story.presentation.data.Screen
import ua.acclorite.book_story.presentation.data.removeDigits
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.ReaderViewModel
@@ -40,7 +44,14 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
*/
@OptIn(ExperimentalMaterial3Api::class)
@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 state by viewModel.state.collectAsState()
@@ -56,8 +67,25 @@ fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColo
TopAppBar(
navigationIcon = {
- GoBackButton(navigator = navigator) {
- viewModel.onEvent(ReaderEvent.OnShowSystemBars(context))
+ CustomIconButton(
+ 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 = {
@@ -74,12 +102,21 @@ fun ReaderTopBar(viewModel: ReaderViewModel, navigator: Navigator, containerColo
interactionSource = null,
indication = null,
onClick = {
- navigator.navigateWithoutBackStack(
- Screen.BOOK_INFO,
- true,
- Argument(
- "book",
- state.book.id
+ 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,
+ true
+ )
+ }
)
)
}
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt
index 3abe97d1..b4b1d6e1 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt
@@ -35,6 +35,7 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
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.data.MainEvent
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.ColorPickerWithTitle
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
-import ua.acclorite.book_story.ui.ElevationDefaults
-import ua.acclorite.book_story.ui.elevation
-import ua.acclorite.book_story.util.Constants
+import ua.acclorite.book_story.presentation.ui.ElevationDefaults
+import ua.acclorite.book_story.presentation.ui.elevation
/**
* Settings bottom sheet. Has General and Colors categories.
@@ -63,16 +63,13 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
val navigationBarPadding =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
- val fontFamily = Constants.FONTS.find {
- it.id == mainViewModel.fontFamily.collectAsState().value!!
- } ?: 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 state by mainViewModel.state.collectAsState()
+
+ val fontFamily = remember(state.fontFamily) {
+ Constants.FONTS.find {
+ it.id == state.fontFamily
+ } ?: Constants.FONTS[0]
+ }
val scrimColor = if (currentPage == 1) Color.Transparent
else BottomSheetDefaults.ScrimColor
@@ -161,7 +158,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
fontFamily = fontFamily.font,
fontStyle = FontStyle.Normal
),
- !fontStyle
+ !state.isItalic!!
),
ChipItem(
"italic",
@@ -170,7 +167,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
fontFamily = fontFamily.font,
fontStyle = FontStyle.Italic
),
- fontStyle
+ state.isItalic!!
),
),
onClick = {
@@ -187,7 +184,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
}
item {
SliderWithTitle(
- value = fontSize to "pt",
+ value = state.fontSize!! to "pt",
fromValue = 10,
toValue = 35,
title = stringResource(id = R.string.font_size_option),
@@ -212,7 +209,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
}
item {
SliderWithTitle(
- value = lineHeight to "pt",
+ value = state.lineHeight!! to "pt",
fromValue = 1,
toValue = 16,
title = stringResource(id = R.string.line_height_option),
@@ -225,7 +222,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
}
item {
SliderWithTitle(
- value = paragraphHeight to "pt",
+ value = state.paragraphHeight!! to "pt",
fromValue = 0,
toValue = 24,
title = stringResource(id = R.string.paragraph_height_option),
@@ -238,11 +235,11 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
}
item {
CheckboxWithTitle(
- selected = paragraphIndentation,
+ selected = state.paragraphIndentation!!,
title = stringResource(id = R.string.paragraph_indentation_option)
) {
mainViewModel.onEvent(
- MainEvent.OnChangeParagraphIndentation(!paragraphIndentation)
+ MainEvent.OnChangeParagraphIndentation(!state.paragraphIndentation!!)
)
}
}
@@ -254,7 +251,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
}
item {
ColorPickerWithTitle(
- value = Color(backgroundColor.toULong()),
+ value = Color(state.backgroundColor!!.toULong()),
title = stringResource(id = R.string.background_color_option),
onValueChange = {
mainViewModel.onEvent(
@@ -267,7 +264,7 @@ fun ReaderSettingsBottomSheet(mainViewModel: MainViewModel, viewModel: ReaderVie
}
item {
ColorPickerWithTitle(
- value = Color(fontColor.toULong()),
+ value = Color(state.fontColor!!.toULong()),
title = stringResource(id = R.string.font_color_option),
onValueChange = {
mainViewModel.onEvent(
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt
index ec1f6afe..3fa17f9a 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt
@@ -12,8 +12,7 @@ import ua.acclorite.book_story.presentation.data.Navigator
sealed class ReaderEvent {
data class OnTextIsEmpty(val onLoaded: () -> Unit) : ReaderEvent()
data class OnLoadText(
- val scrollState: LazyListState,
- val navigator: Navigator,
+ val listState: LazyListState,
val refreshList: (Book) -> Unit,
val onLoaded: () -> Unit,
val onTextIsEmpty: () -> Unit
@@ -22,19 +21,29 @@ sealed class ReaderEvent {
data class OnShowHideMenu(val show: Boolean? = null, val context: ComponentActivity) :
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(
val progress: Float,
val navigator: Navigator,
+ val firstVisibleItemIndex: Int,
+ val firstVisibleItemOffset: Int,
val refreshList: (Book) -> Unit
) : 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 class OnScrollToSettingsPage(val page: Int, val pagerState: PagerState) : ReaderEvent()
data class OnMoveBookToAlreadyRead(
val context: ComponentActivity,
- val refreshList: () -> Unit,
+ val listState: LazyListState,
+ val onUpdateCategories: (Book) -> Unit,
val updatePage: (Int) -> Unit,
val navigator: Navigator
) : ReaderEvent()
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt
index 62a91d8a..5614fb99 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt
@@ -2,17 +2,13 @@ package ua.acclorite.book_story.presentation.screens.reader.data
import androidx.compose.runtime.Immutable
import ua.acclorite.book_story.domain.model.Book
-import ua.acclorite.book_story.util.Constants
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.Constants
+import ua.acclorite.book_story.domain.util.UIText
@Immutable
data class ReaderState(
val book: Book = Constants.EMPTY_BOOK,
- val letters: Int = 0,
- val words: Int = 0,
-
val errorMessage: UIText? = null,
-
val showMenu: Boolean = false,
val showSettingsBottomSheet: Boolean = false,
val currentPage: Int = 0,
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt
index c77f51f1..53de02bf 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt
@@ -22,17 +22,14 @@ import kotlinx.coroutines.launch
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.History
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.InsertHistory
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.Navigator
import ua.acclorite.book_story.presentation.data.Screen
-import ua.acclorite.book_story.util.UIText
-import java.util.Date
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -40,9 +37,8 @@ import kotlin.math.roundToInt
@HiltViewModel
class ReaderViewModel @Inject constructor(
private val updateBooks: UpdateBooks,
- private val insertHistory: InsertHistory,
private val getText: GetText,
- private val getBooksById: GetBooksById
+ private val getLatestHistory: GetLatestHistory
) : ViewModel() {
private val _state = MutableStateFlow(ReaderState())
@@ -67,7 +63,6 @@ class ReaderViewModel @Inject constructor(
event.onTextIsEmpty()
}
- val time = Date().time
val letters = text
.replace("\n", "")
.length
@@ -76,50 +71,46 @@ class ReaderViewModel @Inject constructor(
.split("\\s+".toRegex())
.size
+ val history = _state.value.book.id?.let {
+ getLatestHistory.execute(
+ it
+ )
+ }
+
_state.update {
it.copy(
book = it.book.copy(
text = text
.split("\n")
.map { line -> StringWithId(line.trim()) },
- lastOpened = time
- ),
- letters = letters,
- words = words
+ lastOpened = history?.time,
+ letters = letters,
+ words = words
+ )
)
}
updateBooks.execute(
listOf(_state.value.book)
)
- insertHistory.execute(
- listOf(
- History(
- null,
- _state.value.book.id!!,
- time
- )
- )
- )
event.refreshList(_state.value.book)
- event.navigator.putArgument(
- Argument("book", _state.value.book.id)
- )
viewModelScope.launch {
snapshotFlow {
- event.scrollState.layoutInfo.totalItemsCount
+ event.listState.layoutInfo.totalItemsCount
}.collectLatest { itemsCount ->
- val scrollTo =
- (_state.value.book.text.size * _state.value.book.progress)
- .toInt()
+ val index = _state.value.book.scrollIndex
+ val offset = _state.value.book.scrollOffset
if (itemsCount >= _state.value.book.text.size) {
- if (scrollTo > 0) {
+ if (index > 0 || offset > 0) {
var loaded = false
for (i in 1..100) {
try {
- event.scrollState.scrollToItem(scrollTo)
+ event.listState.scrollToItem(
+ index,
+ offset
+ )
loaded = true
break
} catch (e: Exception) {
@@ -143,6 +134,7 @@ class ReaderViewModel @Inject constructor(
is ReaderEvent.OnShowHideMenu -> {
val shouldShow = event.show ?: !_state.value.showMenu
+
val insetsController = WindowCompat.getInsetsController(
event.context.window,
event.context.window.decorView
@@ -166,20 +158,57 @@ class ReaderViewModel @Inject constructor(
}
}
- is ReaderEvent.OnShowSystemBars -> {
- val insetsController = WindowCompat.getInsetsController(
- event.context.window,
- event.context.window.decorView
- )
+ is ReaderEvent.OnGoBack -> {
+ viewModelScope.launch {
+ val insetsController = WindowCompat.getInsetsController(
+ event.context.window,
+ event.context.window.decorView
+ )
- insetsController.show(WindowInsetsCompat.Type.systemBars())
+ 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())
+ event.navigate(event.navigator)
+ }
}
is ReaderEvent.OnScroll -> {
viewModelScope.launch {
val scrollTo = (_state.value.book.text.size * event.progress).roundToInt()
- event.scrollState.scrollToItem(
+ event.listState.scrollToItem(
scrollTo
)
}
@@ -190,7 +219,9 @@ class ReaderViewModel @Inject constructor(
_state.update {
it.copy(
book = it.book.copy(
- progress = event.progress
+ progress = event.progress,
+ scrollIndex = event.firstVisibleItemIndex,
+ scrollOffset = event.firstVisibleItemOffset
)
)
}
@@ -199,18 +230,23 @@ class ReaderViewModel @Inject constructor(
listOf(_state.value.book)
)
event.navigator.putArgument(
- Argument("book", _state.value.book.id)
+ Argument(
+ "book",
+ _state.value.book
+ )
)
event.refreshList(_state.value.book)
}
}
is ReaderEvent.OnShowHideSettingsBottomSheet -> {
- _state.update {
- it.copy(
- currentPage = if (it.showSettingsBottomSheet) it.currentPage else 0,
- showSettingsBottomSheet = !it.showSettingsBottomSheet
- )
+ viewModelScope.launch(Dispatchers.IO) {
+ _state.update {
+ it.copy(
+ currentPage = if (it.showSettingsBottomSheet) it.currentPage else 0,
+ showSettingsBottomSheet = !it.showSettingsBottomSheet
+ )
+ }
}
}
@@ -222,16 +258,32 @@ class ReaderViewModel @Inject constructor(
is ReaderEvent.OnMoveBookToAlreadyRead -> {
viewModelScope.launch {
- onEvent(ReaderEvent.OnShowSystemBars(event.context))
-
- val book = _state.value.book.copy(
- category = Category.ALREADY_READ,
- progress = 1f
+ onEvent(
+ ReaderEvent.OnGoBack(
+ event.context,
+ event.navigator,
+ 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(
Category.entries.dropLastWhile {
it != Category.ALREADY_READ
@@ -298,30 +350,30 @@ class ReaderViewModel @Inject constructor(
fun init(
navigator: Navigator,
context: ComponentActivity,
- scrollState: LazyListState,
+ listState: LazyListState,
refreshList: (Book) -> Unit,
onLoaded: () -> Unit
) {
- viewModelScope.launch {
- val bookIndex = navigator.retrieveArgument("book") as? Int
+ viewModelScope.launch(Dispatchers.IO) {
+ val book = navigator.retrieveArgument("book") as? Book
- if (bookIndex == null || bookIndex < 0) {
+ if (book == null) {
navigator.navigateBack()
return@launch
}
- val book = getBooksById.execute(listOf(bookIndex)).first()
+ viewModelScope.launch {
+ onEvent(ReaderEvent.OnShowHideMenu(false, context))
+ }
_state.update {
ReaderState(book = book)
}
- onEvent(ReaderEvent.OnShowHideMenu(false, context))
onEvent(
ReaderEvent.OnLoadText(
- scrollState,
- navigator,
refreshList = { refreshList(it) },
+ listState = listState,
onLoaded = {
onLoaded()
},
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/SettingsScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/SettingsScreen.kt
index 43f034fd..5fbb86a7 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/SettingsScreen.kt
@@ -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.Screen
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)
@Composable
@@ -39,7 +39,7 @@ fun SettingsScreen(
val listState = rememberLazyListState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
canScroll = {
- listState.canScrollForward
+ listState.canScrollForward || listState.canScrollBackward
}
)
@@ -71,6 +71,10 @@ fun SettingsScreen(
.padding(top = paddingValues.calculateTopPadding()),
state = listState
) {
+ item {
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+
item {
SettingsCategoryItem(
icon = Icons.Default.DisplaySettings,
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt
index 76f360f4..105b2d8e 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt
@@ -17,6 +17,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.ColorPickerWithTitle
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.ui.elevation
+import ua.acclorite.book_story.presentation.ui.DarkTheme
+import ua.acclorite.book_story.presentation.ui.elevation
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -44,13 +45,11 @@ fun AppearanceSettings(
val listState = rememberLazyListState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
canScroll = {
- listState.canScrollForward
+ listState.canScrollForward || listState.canScrollBackward
}
)
- val darkTheme = mainViewModel.darkTheme.collectAsState().value!!
- val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!!
- val fontColor = mainViewModel.fontColor.collectAsState().value!!
+ val state by mainViewModel.state.collectAsState()
Scaffold(
Modifier
@@ -81,7 +80,7 @@ fun AppearanceSettings(
state = listState
) {
item {
- Spacer(modifier = Modifier.height(8.dp))
+ Spacer(modifier = Modifier.height(16.dp))
}
item {
CategoryTitle(
@@ -105,7 +104,7 @@ fun AppearanceSettings(
DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system)
},
MaterialTheme.typography.labelLarge,
- it == darkTheme
+ it == state.darkTheme
)
},
) {
@@ -136,7 +135,7 @@ fun AppearanceSettings(
item {
ColorPickerWithTitle(
- value = Color(backgroundColor.toULong()),
+ value = Color(state.backgroundColor!!.toULong()),
title = stringResource(id = R.string.background_color_option),
onValueChange = {
mainViewModel.onEvent(
@@ -149,7 +148,7 @@ fun AppearanceSettings(
}
item {
ColorPickerWithTitle(
- value = Color(fontColor.toULong()),
+ value = Color(state.fontColor!!.toULong()),
title = stringResource(id = R.string.font_color_option),
onValueChange = {
mainViewModel.onEvent(
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt
index 9274a3d1..5ee744bf 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt
@@ -10,17 +10,18 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainViewModel
-import ua.acclorite.book_story.ui.Theme
-import ua.acclorite.book_story.ui.isDark
-import ua.acclorite.book_story.util.Constants
+import ua.acclorite.book_story.presentation.ui.Theme
+import ua.acclorite.book_story.presentation.ui.isDark
/**
* Theme switcher.
@@ -29,8 +30,7 @@ import ua.acclorite.book_story.util.Constants
fun AppearanceSettingsThemeSwitcher(
mainViewModel: MainViewModel
) {
- val theme = mainViewModel.theme.collectAsState().value!!
- val darkTheme = mainViewModel.darkTheme.collectAsState().value!!
+ val state by mainViewModel.state.collectAsState()
val themes = remember {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Constants.THEMES
@@ -56,8 +56,8 @@ fun AppearanceSettingsThemeSwitcher(
AppearanceSettingsThemeSwitcherItem(
theme = themeEntry,
- darkTheme = darkTheme.isDark(),
- selected = theme == themeEntry.first
+ darkTheme = state.darkTheme!!.isDark(),
+ selected = state.theme == themeEntry.first
) {
mainViewModel.onEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
}
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcherItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcherItem.kt
index 75df2813..686ce1ab 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcherItem.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcherItem.kt
@@ -31,9 +31,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
-import ua.acclorite.book_story.ui.Theme
-import ua.acclorite.book_story.ui.colorScheme
-import ua.acclorite.book_story.util.UIText
+import ua.acclorite.book_story.domain.util.UIText
+import ua.acclorite.book_story.presentation.ui.Theme
+import ua.acclorite.book_story.presentation.ui.colorScheme
/**
* Theme switcher item.
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt
index 24e1e168..743cc998 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt
@@ -17,19 +17,20 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
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.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.data.Navigator
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
-import ua.acclorite.book_story.ui.elevation
-import ua.acclorite.book_story.util.Constants
+import ua.acclorite.book_story.presentation.ui.elevation
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -40,11 +41,11 @@ fun GeneralSettings(
val listState = rememberLazyListState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
canScroll = {
- listState.canScrollForward
+ listState.canScrollForward || listState.canScrollBackward
}
)
- val language = mainViewModel.language.collectAsState().value!!
+ val state by mainViewModel.state.collectAsState()
Scaffold(
Modifier
@@ -74,6 +75,9 @@ fun GeneralSettings(
.padding(top = paddingValues.calculateTopPadding()),
state = listState
) {
+ item {
+ Spacer(modifier = Modifier.height(8.dp))
+ }
item {
ChipsWithTitle(
title = stringResource(id = R.string.language_option),
@@ -82,7 +86,7 @@ fun GeneralSettings(
it.first,
it.second,
MaterialTheme.typography.labelLarge,
- it.first == language
+ it.first == state.language
)
}.sortedBy { it.title }
) {
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt
index 13c36694..ee379b6e 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt
@@ -17,6 +17,8 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
@@ -24,6 +26,7 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
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.GoBackButton
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.ChipsWithTitle
import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle
-import ua.acclorite.book_story.ui.elevation
-import ua.acclorite.book_story.util.Constants
+import ua.acclorite.book_story.presentation.ui.elevation
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -44,18 +46,16 @@ fun ReaderSettings(
val listState = rememberLazyListState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
canScroll = {
- listState.canScrollForward
+ listState.canScrollForward || listState.canScrollBackward
}
)
- val fontFamily = Constants.FONTS.find {
- it.id == mainViewModel.fontFamily.collectAsState().value!!
- } ?: 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 state by mainViewModel.state.collectAsState()
+ val fontFamily = remember(state.fontFamily) {
+ Constants.FONTS.find {
+ it.id == state.fontFamily
+ } ?: Constants.FONTS[0]
+ }
Scaffold(
Modifier
@@ -86,7 +86,7 @@ fun ReaderSettings(
state = listState
) {
item {
- Spacer(modifier = Modifier.height(8.dp))
+ Spacer(modifier = Modifier.height(16.dp))
}
item {
CategoryTitle(
@@ -127,7 +127,7 @@ fun ReaderSettings(
fontFamily = fontFamily.font,
fontStyle = FontStyle.Normal
),
- !fontStyle
+ !state.isItalic!!
),
ChipItem(
"italic",
@@ -136,7 +136,7 @@ fun ReaderSettings(
fontFamily = fontFamily.font,
fontStyle = FontStyle.Italic
),
- fontStyle
+ state.isItalic!!
),
),
onClick = {
@@ -153,7 +153,7 @@ fun ReaderSettings(
}
item {
SliderWithTitle(
- value = fontSize to "pt",
+ value = state.fontSize!! to "pt",
fromValue = 10,
toValue = 35,
title = stringResource(id = R.string.font_size_option),
@@ -178,7 +178,7 @@ fun ReaderSettings(
}
item {
SliderWithTitle(
- value = lineHeight to "pt",
+ value = state.lineHeight!! to "pt",
fromValue = 1,
toValue = 16,
title = stringResource(id = R.string.line_height_option),
@@ -191,7 +191,7 @@ fun ReaderSettings(
}
item {
SliderWithTitle(
- value = paragraphHeight to "pt",
+ value = state.paragraphHeight!! to "pt",
fromValue = 0,
toValue = 24,
title = stringResource(id = R.string.paragraph_height_option),
@@ -204,11 +204,11 @@ fun ReaderSettings(
}
item {
CheckboxWithTitle(
- selected = paragraphIndentation,
+ selected = state.paragraphIndentation!!,
title = stringResource(id = R.string.paragraph_indentation_option)
) {
mainViewModel.onEvent(
- MainEvent.OnChangeParagraphIndentation(!paragraphIndentation)
+ MainEvent.OnChangeParagraphIndentation(!state.paragraphIndentation!!)
)
}
}
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/Color.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Color.kt
similarity index 72%
rename from app/src/main/java/ua/acclorite/book_story/ui/Color.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/Color.kt
index c676f833..a6624616 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/Color.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Color.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui
+package ua.acclorite.book_story.presentation.ui
import android.annotation.SuppressLint
import androidx.compose.material3.ColorScheme
@@ -6,13 +6,13 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
-import ua.acclorite.book_story.ui.theme.aquaTheme
-import ua.acclorite.book_story.ui.theme.blueTheme
-import ua.acclorite.book_story.ui.theme.greenTheme
-import ua.acclorite.book_story.ui.theme.pinkTheme
-import ua.acclorite.book_story.ui.theme.purpleTheme
-import ua.acclorite.book_story.ui.theme.redTheme
-import ua.acclorite.book_story.ui.theme.yellowTheme
+import ua.acclorite.book_story.presentation.ui.theme.aquaTheme
+import ua.acclorite.book_story.presentation.ui.theme.blueTheme
+import ua.acclorite.book_story.presentation.ui.theme.greenTheme
+import ua.acclorite.book_story.presentation.ui.theme.pinkTheme
+import ua.acclorite.book_story.presentation.ui.theme.purpleTheme
+import ua.acclorite.book_story.presentation.ui.theme.redTheme
+import ua.acclorite.book_story.presentation.ui.theme.yellowTheme
enum class Theme {
DYNAMIC,
@@ -29,16 +29,7 @@ enum class Theme {
* Converting [String] into [Theme].
*/
fun String.toTheme(): Theme {
- return when (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
- }
+ return Theme.valueOf(this)
}
/**
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/Elevation.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Elevation.kt
similarity index 70%
rename from app/src/main/java/ua/acclorite/book_story/ui/Elevation.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/Elevation.kt
index c12a5505..beca6587 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/Elevation.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Elevation.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui
+package ua.acclorite.book_story.presentation.ui
import androidx.compose.ui.unit.dp
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/Theme.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Theme.kt
similarity index 93%
rename from app/src/main/java/ua/acclorite/book_story/ui/Theme.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/Theme.kt
index e3c2bc07..3f7075ff 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/Theme.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Theme.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui
+package ua.acclorite.book_story.presentation.ui
import android.app.Activity
import android.os.Build
@@ -22,11 +22,7 @@ enum class DarkTheme {
}
fun String.toDarkTheme(): DarkTheme {
- return when (this) {
- "OFF" -> DarkTheme.OFF
- "ON" -> DarkTheme.ON
- else -> DarkTheme.FOLLOW_SYSTEM
- }
+ return DarkTheme.valueOf(this)
}
@Composable
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/Transitions.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Transitions.kt
similarity index 96%
rename from app/src/main/java/ua/acclorite/book_story/ui/Transitions.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/Transitions.kt
index 71b8279f..ec36796f 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/Transitions.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Transitions.kt
@@ -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.core.tween
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/Typography.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Typography.kt
similarity index 97%
rename from app/src/main/java/ua/acclorite/book_story/ui/Typography.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/Typography.kt
index 82258847..c8e919e8 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/Typography.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Typography.kt
@@ -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.ui.text.TextStyle
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/theme/AquaTheme.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/AquaTheme.kt
similarity index 99%
rename from app/src/main/java/ua/acclorite/book_story/ui/theme/AquaTheme.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/AquaTheme.kt
index 782d3c9c..290a8fcc 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/theme/AquaTheme.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/AquaTheme.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui.theme
+package ua.acclorite.book_story.presentation.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/theme/BlueTheme.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/BlueTheme.kt
similarity index 99%
rename from app/src/main/java/ua/acclorite/book_story/ui/theme/BlueTheme.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/BlueTheme.kt
index e3e09f53..f1966ac3 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/theme/BlueTheme.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/BlueTheme.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui.theme
+package ua.acclorite.book_story.presentation.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/theme/GreenTheme.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/GreenTheme.kt
similarity index 99%
rename from app/src/main/java/ua/acclorite/book_story/ui/theme/GreenTheme.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/GreenTheme.kt
index 19cebb58..308bba5a 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/theme/GreenTheme.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/GreenTheme.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui.theme
+package ua.acclorite.book_story.presentation.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/theme/PinkTheme.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/PinkTheme.kt
similarity index 99%
rename from app/src/main/java/ua/acclorite/book_story/ui/theme/PinkTheme.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/PinkTheme.kt
index 36a0f03a..fca9320f 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/theme/PinkTheme.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/PinkTheme.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui.theme
+package ua.acclorite.book_story.presentation.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/theme/PurpleTheme.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/PurpleTheme.kt
similarity index 99%
rename from app/src/main/java/ua/acclorite/book_story/ui/theme/PurpleTheme.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/PurpleTheme.kt
index 1c31f974..0767a954 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/theme/PurpleTheme.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/PurpleTheme.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui.theme
+package ua.acclorite.book_story.presentation.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/theme/RedTheme.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/RedTheme.kt
similarity index 99%
rename from app/src/main/java/ua/acclorite/book_story/ui/theme/RedTheme.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/RedTheme.kt
index bc98c65c..76bc92ae 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/theme/RedTheme.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/RedTheme.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui.theme
+package ua.acclorite.book_story.presentation.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/theme/YellowTheme.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/YellowTheme.kt
similarity index 99%
rename from app/src/main/java/ua/acclorite/book_story/ui/theme/YellowTheme.kt
rename to app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/YellowTheme.kt
index a601635d..03a8e281 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/theme/YellowTheme.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/theme/YellowTheme.kt
@@ -1,4 +1,4 @@
-package ua.acclorite.book_story.ui.theme
+package ua.acclorite.book_story.presentation.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 71eca021..d774d0bc 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -61,7 +61,6 @@
Хибний формат файлу.
Помилка: %1$s
- Цей файл зашифрований.
Цей файл пустий або пошкоджений.
Не знайдено жодного перекладача.
Не знайдено жодного браузера.
@@ -84,8 +83,8 @@
%1$s обрано
Знайти %1$s…
- Ви прочитали %1$s цієї книги.
%1$s прочитано
+ Шлях до файлу: %1$s
Відмінити
@@ -221,6 +220,22 @@
%1$s букв, %2$s слів.
+
+ Версія застосунку
+ Версія %1$s
+ Всі релізи
+ Перевірити на нову версію
+ Знайшли помилку?
+ Розкажіть мені!
+ Сторінка проєкту
+ Головна сторінка застосунку
+ Сподобався застосунок?
+ Перевірте мій профіль!
+ Хочете підтримати мою роботу?
+ Купіть мені каву ♥
+ Слава Україні! 🇺🇦
+ Героям Слава! 🐉
+
За замовчуванням
@@ -252,5 +267,6 @@
Видалити обрані книги
Повернути
Вибрано
+ Іконка застосунку
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 85fe7ae7..3a84c2cb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,13 +2,28 @@
0.9.3
Book\'s Story
+
+ https://github.com/Acclorite/book-story/releases/latest
+
+
+ https://github.com/Acclorite/book-story/issues
+
+
+ https://github.com/Acclorite/book-story
+
+
+ https://github.com/Acclorite
+
+
+ https://www.buymeacoffee.com/acclorite
+
Library
History
Browse
Settings
- About
+ About app
Help
@@ -62,7 +77,6 @@
Wrong file format.
Error: %1$s
- This file is encrypted.
This file is empty or corrupted.
No translator app found.
No browser app found.
@@ -84,8 +98,8 @@
%1$s selected
Search %1$s…
- You read %1$s of this book.
%1$s read
+ File path: %1$s
Cancel
@@ -221,6 +235,22 @@
%1$s characters, %2$s words.
+
+ App version
+ Version %1$s
+ All releases
+ Check for newer version
+ Found a bug?
+ Tell me!
+ Project\'s page
+ General app\'s page
+ Like the app?
+ Check my profile!
+ Want to support my work?
+ Buy me a coffee ♥
+ Slava Ukraini! 🇺🇦
+ Heroyam Slava! 🐉
+
Default
@@ -252,5 +282,6 @@
Delete selected books
Revert
Selected
+ App icon
\ No newline at end of file