0.9.4 - About Screen + New scrolling system in Reader(saving exact scroll position)
This commit is contained in:
parent
4e948bc5d7
commit
1689e9d7b9
109 changed files with 1870 additions and 994 deletions
|
|
@ -137,8 +137,10 @@ dependencies {
|
|||
implementation("org.slf4j:slf4j-android:latest.release")
|
||||
implementation("org.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")
|
||||
}
|
||||
|
|
@ -19,19 +19,24 @@
|
|||
<intent>
|
||||
<action android:name="android.intent.action.WEB_SEARCH" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name=".util.BookApplication"
|
||||
android:allowBackup="true"
|
||||
android:name=".Application"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/app_icon"
|
||||
android:roundIcon="@mipmap/app_icon_round"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Start.Splash"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:targetApi="tiramisu">
|
||||
tools:targetApi="tiramisu"
|
||||
tools:ignore="DataExtractionRules">
|
||||
|
||||
<activity
|
||||
android:name=".Activity"
|
||||
|
|
@ -44,6 +49,15 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -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()
|
||||
|
|
@ -119,24 +130,36 @@ class Activity : AppCompatActivity() {
|
|||
}
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
start = if (tabletUI) 80.dp else 0.dp,
|
||||
bottom = it.calculateBottomPadding()
|
||||
end = it.calculateEndPadding(
|
||||
layoutDirection
|
||||
),
|
||||
bottom = it.calculateBottomPadding(),
|
||||
start = if (tabletUI) {
|
||||
80.dp + it.calculateStartPadding(
|
||||
layoutDirection
|
||||
)
|
||||
} else {
|
||||
it.calculateStartPadding(
|
||||
layoutDirection
|
||||
)
|
||||
},
|
||||
)
|
||||
) {
|
||||
composable(screen = Screen.LIBRARY) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
LibraryScreen(
|
||||
viewModel = libraryViewModel,
|
||||
historyViewModel = historyViewModel,
|
||||
browseViewModel = browseViewModel,
|
||||
navigator = this@NavigationHost,
|
||||
addedBooks = retrieveArgument("added_books") as? List<Book>
|
||||
?: emptyList()
|
||||
navigator = this@NavigationHost
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +181,11 @@ class Activity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
if (tabletUI) {
|
||||
CustomNavigationRail(navigator = this@NavigationHost)
|
||||
CustomNavigationRail(
|
||||
navigator = this@NavigationHost,
|
||||
layoutDirection = layoutDirection
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -234,6 +261,15 @@ class Activity : AppCompatActivity() {
|
|||
)
|
||||
}
|
||||
|
||||
// About screen
|
||||
composable(
|
||||
screen = Screen.ABOUT,
|
||||
enterAnim = Transitions.SlidingTransitionIn,
|
||||
exitAnim = Transitions.SlidingTransitionOut
|
||||
) {
|
||||
AboutScreen(navigator = this@NavigationHost)
|
||||
}
|
||||
|
||||
// Start screen (later)
|
||||
// composable(screen = Screen.START) {
|
||||
// StartScreen()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
class Application : Application()
|
||||
|
|
@ -22,6 +22,6 @@ object AppModule {
|
|||
app,
|
||||
BookDatabase::class.java,
|
||||
"book_db"
|
||||
).build().dao
|
||||
).allowMainThreadQueries().build().dao
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ interface BookDao {
|
|||
@Query("SELECT * FROM historyentity")
|
||||
suspend fun getHistory(): List<HistoryEntity>
|
||||
|
||||
@Query("SELECT * FROM historyentity WHERE bookId = :bookId ORDER BY time DESC LIMIT 1")
|
||||
fun getLatestHistoryForBook(bookId: Int): HistoryEntity?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertHistory(
|
||||
history: List<HistoryEntity>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class HistoryMapperImpl @Inject constructor() : HistoryMapper {
|
|||
return History(
|
||||
historyEntity.id,
|
||||
bookId = historyEntity.bookId,
|
||||
book = null,
|
||||
time = historyEntity.time
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<StringWithId>()
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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", "")
|
||||
|
||||
|
|
|
|||
|
|
@ -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<StringWithId>()
|
||||
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Resource<List<Book>>> {
|
||||
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<Book> {
|
||||
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<Int>): List<Book> {
|
||||
return database.findBooksById(ids).map { bookMapper.toBook(it) }
|
||||
val books = database.findBooksById(ids)
|
||||
|
||||
return books.map { entity ->
|
||||
val book = bookMapper.toBook(entity)
|
||||
val lastHistory = database.getLatestHistoryForBook(
|
||||
book.id ?: -1
|
||||
)
|
||||
|
||||
book.copy(
|
||||
lastOpened = lastHistory?.time
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getBookTextById(bookId: Int): String {
|
||||
|
|
@ -92,22 +103,26 @@ class BookRepositoryImpl @Inject constructor(
|
|||
return book.text
|
||||
}
|
||||
|
||||
override suspend fun findBook(
|
||||
id: Int
|
||||
): BookEntity {
|
||||
return database.findBookById(id)
|
||||
}
|
||||
|
||||
override suspend fun insertBooks(
|
||||
books: List<Pair<Book, CoverImage?>>
|
||||
) {
|
||||
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 ->
|
||||
val cover = File(coversDir, uri)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
FileOutputStream(cover).use { stream ->
|
||||
if (
|
||||
!it.second!!.compress(
|
||||
Bitmap.CompressFormat.WEBP,
|
||||
|
|
@ -118,6 +133,7 @@ class BookRepositoryImpl @Inject constructor(
|
|||
throw Exception("Couldn't save cover image")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
uri = ""
|
||||
e.printStackTrace()
|
||||
|
|
@ -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,10 +191,20 @@ 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 ->
|
||||
val cover = File(coversDir, uri)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
FileOutputStream(cover).use { stream ->
|
||||
if (
|
||||
!newCoverImage.compress(
|
||||
Bitmap.CompressFormat.WEBP,
|
||||
|
|
@ -189,6 +215,7 @@ class BookRepositoryImpl @Inject constructor(
|
|||
throw Exception("Couldn't save cover image")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return
|
||||
|
|
@ -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<Book>) {
|
||||
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<Resource<List<History>>> {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<StringWithId> = 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?,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -6,5 +6,6 @@ import androidx.compose.runtime.Immutable
|
|||
data class History(
|
||||
val id: Int?,
|
||||
val bookId: Int,
|
||||
val book: Book?,
|
||||
val time: Long
|
||||
)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<Pair<Book, CoverImage?>>
|
||||
)
|
||||
|
|
@ -71,6 +66,8 @@ interface BookRepository {
|
|||
|
||||
suspend fun getHistory(): Flow<Resource<List<History>>>
|
||||
|
||||
suspend fun getLatestBookHistory(bookId: Int): History?
|
||||
|
||||
suspend fun deleteWholeHistory()
|
||||
|
||||
suspend fun deleteBookHistory(bookId: Int)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package ua.acclorite.book_story.domain.use_case
|
||||
|
||||
import ua.acclorite.book_story.domain.model.History
|
||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetLatestHistory @Inject constructor(private val repository: BookRepository) {
|
||||
|
||||
suspend fun execute(bookId: Int): History? {
|
||||
return repository.getLatestBookHistory(bookId)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package ua.acclorite.book_story.domain.use_case
|
|||
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
import ua.acclorite.book_story.domain.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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
"",
|
||||
|
|
@ -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")
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ua.acclorite.book_story.util
|
||||
package ua.acclorite.book_story.domain.util
|
||||
|
||||
sealed class Resource<T>(val data: T? = null, val message: UIText? = null) {
|
||||
class Success<T>(data: T?) : Resource<T>(data)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ua.acclorite.book_story.util
|
||||
package ua.acclorite.book_story.domain.util
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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].
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package ua.acclorite.book_story.presentation.data
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import ua.acclorite.book_story.presentation.ui.DarkTheme
|
||||
import ua.acclorite.book_story.presentation.ui.Theme
|
||||
|
||||
/**
|
||||
* Main State. All app's settings are here. Wrapped in SavedStateHandle, so it won't reset.
|
||||
*/
|
||||
@Immutable
|
||||
data class MainState(
|
||||
val language: String? = null,
|
||||
val theme: Theme? = null,
|
||||
val darkTheme: DarkTheme? = null,
|
||||
val fontFamily: String? = null,
|
||||
val isItalic: Boolean? = null,
|
||||
val fontSize: Int? = null,
|
||||
val lineHeight: Int? = null,
|
||||
val paragraphHeight: Int? = null,
|
||||
val paragraphIndentation: Boolean? = null,
|
||||
val backgroundColor: Long? = null,
|
||||
val fontColor: Long? = null,
|
||||
val showStartScreen: Boolean? = null,
|
||||
) : Parcelable {
|
||||
constructor(parcel: Parcel) : this(
|
||||
// String
|
||||
language = parcel.readString(),
|
||||
// String
|
||||
theme = Theme.valueOf(parcel.readString() ?: Theme.BLUE.name),
|
||||
// String
|
||||
darkTheme = DarkTheme.valueOf(parcel.readString() ?: DarkTheme.FOLLOW_SYSTEM.name),
|
||||
// String
|
||||
fontFamily = parcel.readString(),
|
||||
// Boolean
|
||||
isItalic = if (parcel.readByte().toInt() != 0) parcel.readByte().toInt() == 1 else null,
|
||||
// Int
|
||||
fontSize = if (parcel.readByte().toInt() != 0) parcel.readInt() else null,
|
||||
// Int
|
||||
lineHeight = if (parcel.readByte().toInt() != 0) parcel.readInt() else null,
|
||||
// Int
|
||||
paragraphHeight = if (parcel.readByte().toInt() != 0) parcel.readInt() else null,
|
||||
// Boolean
|
||||
paragraphIndentation = if (parcel.readByte().toInt() != 0) parcel.readByte()
|
||||
.toInt() == 1 else null,
|
||||
// Long
|
||||
backgroundColor = if (parcel.readByte().toInt() != 0) parcel.readLong() else null,
|
||||
// Long
|
||||
fontColor = if (parcel.readByte().toInt() != 0) parcel.readLong() else null,
|
||||
// Boolean
|
||||
showStartScreen = if (parcel.readByte().toInt() != 0) parcel.readByte()
|
||||
.toInt() == 1 else null,
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
// Language
|
||||
parcel.writeString(language)
|
||||
// Theme
|
||||
parcel.writeString(theme?.name)
|
||||
// Dark Theme
|
||||
parcel.writeString(darkTheme?.name)
|
||||
// Font Family
|
||||
parcel.writeString(fontFamily)
|
||||
// Is Italic
|
||||
parcel.writeByte((if (isItalic != null) 1 else 0).toByte())
|
||||
isItalic?.let { parcel.writeByte(if (it) 1 else 0) }
|
||||
// Font Size
|
||||
parcel.writeByte((if (fontSize != null) 1 else 0).toByte())
|
||||
fontSize?.let { parcel.writeInt(it) }
|
||||
// Line Height
|
||||
parcel.writeByte((if (lineHeight != null) 1 else 0).toByte())
|
||||
lineHeight?.let { parcel.writeInt(it) }
|
||||
// Paragraph Height
|
||||
parcel.writeByte((if (paragraphHeight != null) 1 else 0).toByte())
|
||||
paragraphHeight?.let { parcel.writeInt(it) }
|
||||
// Paragraph Indentation
|
||||
parcel.writeByte((if (paragraphIndentation != null) 1 else 0).toByte())
|
||||
paragraphIndentation?.let { parcel.writeByte(if (it) 1 else 0) }
|
||||
// Background color
|
||||
parcel.writeByte((if (backgroundColor != null) 1 else 0).toByte())
|
||||
backgroundColor?.let { parcel.writeLong(it) }
|
||||
// Font color
|
||||
parcel.writeByte((if (fontColor != null) 1 else 0).toByte())
|
||||
fontColor?.let { parcel.writeLong(it) }
|
||||
// Show Start Screen
|
||||
parcel.writeByte((if (showStartScreen != null) 1 else 0).toByte())
|
||||
showStartScreen?.let { parcel.writeByte(if (it) 1 else 0) }
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<MainState> {
|
||||
override fun createFromParcel(parcel: Parcel): MainState {
|
||||
return MainState(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<MainState?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import androidx.lifecycle.viewModelScope
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import 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<String?>
|
||||
get() = stateHandle.getStateFlow(Constants.LANGUAGE, null)
|
||||
|
||||
/* -- Theme -------------------------------------------------------- */
|
||||
private var _theme: Theme =
|
||||
stateHandle[Constants.THEME]
|
||||
?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Theme.DYNAMIC else Theme.BLUE
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.THEME] = value
|
||||
}
|
||||
val theme: StateFlow<Theme?>
|
||||
get() = stateHandle.getStateFlow(Constants.THEME, null)
|
||||
|
||||
/* -- Dark Theme --------------------------------------------------- */
|
||||
private var _darkTheme: DarkTheme =
|
||||
stateHandle[Constants.DARK_THEME] ?: DarkTheme.FOLLOW_SYSTEM
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.DARK_THEME] = value
|
||||
}
|
||||
val darkTheme: StateFlow<DarkTheme?>
|
||||
get() = stateHandle.getStateFlow(Constants.DARK_THEME, null)
|
||||
|
||||
/* -- Show Start Screen -------------------------------------------- */
|
||||
private var _showStartScreen: Boolean =
|
||||
stateHandle[Constants.SHOW_START_SCREEN] ?: true
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.SHOW_START_SCREEN] = value
|
||||
}
|
||||
val showStartScreen: StateFlow<Boolean?>
|
||||
get() = stateHandle.getStateFlow(Constants.SHOW_START_SCREEN, null)
|
||||
|
||||
/* -- Background Color --------------------------------------------- */
|
||||
private var _backgroundColor: Long =
|
||||
stateHandle[Constants.BACKGROUND_COLOR] ?: Color.DarkGray.value.toLong()
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.BACKGROUND_COLOR] = value
|
||||
}
|
||||
val backgroundColor: StateFlow<Long?>
|
||||
get() = stateHandle.getStateFlow(Constants.BACKGROUND_COLOR, null)
|
||||
|
||||
/* -- Font Color --------------------------------------------------- */
|
||||
private var _fontColor: Long =
|
||||
stateHandle[Constants.FONT_COLOR] ?: Color.LightGray.value.toLong()
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.FONT_COLOR] = value
|
||||
}
|
||||
val fontColor: StateFlow<Long?>
|
||||
get() = stateHandle.getStateFlow(Constants.FONT_COLOR, null)
|
||||
|
||||
/* -- Font Family -------------------------------------------------- */
|
||||
private var _fontFamily: String =
|
||||
stateHandle[Constants.FONT] ?: Constants.FONTS[0].id
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.FONT] = value
|
||||
}
|
||||
val fontFamily: StateFlow<String?>
|
||||
get() = stateHandle.getStateFlow(Constants.FONT, null)
|
||||
|
||||
/* -- Is Italic ---------------------------------------------------- */
|
||||
private var _isItalic: Boolean =
|
||||
stateHandle[Constants.IS_ITALIC] ?: false
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.IS_ITALIC] = value
|
||||
}
|
||||
val isItalic: StateFlow<Boolean?>
|
||||
get() = stateHandle.getStateFlow(Constants.IS_ITALIC, null)
|
||||
|
||||
/* -- Font Size ---------------------------------------------------- */
|
||||
private var _fontSize: Int =
|
||||
stateHandle[Constants.FONT_SIZE] ?: 16
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.FONT_SIZE] = value
|
||||
}
|
||||
val fontSize: StateFlow<Int?>
|
||||
get() = stateHandle.getStateFlow(Constants.FONT_SIZE, null)
|
||||
|
||||
/* -- Line Height -------------------------------------------------- */
|
||||
private var _lineHeight: Int =
|
||||
stateHandle[Constants.LINE_HEIGHT] ?: 4
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.LINE_HEIGHT] = value
|
||||
}
|
||||
val lineHeight: StateFlow<Int?>
|
||||
get() = stateHandle.getStateFlow(Constants.LINE_HEIGHT, null)
|
||||
|
||||
/* -- Paragraph Height --------------------------------------------- */
|
||||
private var _paragraphHeight: Int =
|
||||
stateHandle[Constants.PARAGRAPH_HEIGHT] ?: 8
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.PARAGRAPH_HEIGHT] = value
|
||||
}
|
||||
val paragraphHeight: StateFlow<Int?>
|
||||
get() = stateHandle.getStateFlow(Constants.PARAGRAPH_HEIGHT, null)
|
||||
|
||||
/* -- Paragraph Indentation ---------------------------------------- */
|
||||
private var _paragraphIndentation: Boolean =
|
||||
stateHandle[Constants.PARAGRAPH_INDENTATION] ?: false
|
||||
set(value) {
|
||||
field = value
|
||||
stateHandle[Constants.PARAGRAPH_INDENTATION] = value
|
||||
}
|
||||
val paragraphIndentation: StateFlow<Boolean?>
|
||||
get() = stateHandle.getStateFlow(Constants.PARAGRAPH_INDENTATION, null)
|
||||
/* --------------------------------------------------------------- */
|
||||
private val _state: MutableStateFlow<MainState> = 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
|
||||
_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 <T> MutableStateFlow<T>.updateWithSavedHandle(
|
||||
const: String = Constants.MAIN_STATE,
|
||||
function: (T) -> T
|
||||
) {
|
||||
val nextValue = function(value)
|
||||
update {
|
||||
stateHandle[const] = nextValue
|
||||
nextValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,258 @@
|
|||
package ua.acclorite.book_story.presentation.screens.about
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.presentation.components.GoBackButton
|
||||
import ua.acclorite.book_story.presentation.data.Navigator
|
||||
import ua.acclorite.book_story.presentation.screens.about.components.AboutItem
|
||||
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
|
||||
import ua.acclorite.book_story.presentation.screens.about.data.AboutViewModel
|
||||
import ua.acclorite.book_story.presentation.ui.elevation
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AboutScreen(
|
||||
viewModel: AboutViewModel = hiltViewModel(),
|
||||
navigator: Navigator
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val listState = rememberLazyListState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
canScroll = {
|
||||
listState.canScrollForward || listState.canScrollBackward
|
||||
}
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
topBar = {
|
||||
LargeTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(id = R.string.about_screen))
|
||||
},
|
||||
navigationIcon = {
|
||||
GoBackButton(navigator = navigator)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
scrolledContainerColor = MaterialTheme.elevation()
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding()),
|
||||
state = listState
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.app_icon_monochrome),
|
||||
contentDescription = stringResource(id = R.string.app_icon_content_desc),
|
||||
modifier = Modifier
|
||||
.size(180.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(36.dp))
|
||||
Divider(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
AboutItem(
|
||||
title = stringResource(id = R.string.app_version_option),
|
||||
description = stringResource(
|
||||
id = R.string.app_version_option_desc,
|
||||
stringResource(id = R.string.app_version)
|
||||
),
|
||||
isOnClickEnabled = false
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
AboutItem(
|
||||
title = stringResource(id = R.string.all_releases_option),
|
||||
description = stringResource(
|
||||
id = R.string.all_releases_option_desc
|
||||
)
|
||||
) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"${context.getString(R.string.app_version_option)}:" +
|
||||
" ${context.getString(R.string.app_version)}",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
viewModel.onEvent(
|
||||
AboutEvent.OnNavigateToBrowserPage(
|
||||
context.getString(R.string.releases_page),
|
||||
context,
|
||||
noAppsFound = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.error_no_browser),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
AboutItem(
|
||||
title = stringResource(id = R.string.issues_option),
|
||||
description = stringResource(
|
||||
id = R.string.issues_option_desc
|
||||
)
|
||||
) {
|
||||
viewModel.onEvent(
|
||||
AboutEvent.OnNavigateToBrowserPage(
|
||||
context.getString(R.string.issues_page),
|
||||
context,
|
||||
noAppsFound = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.error_no_browser),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
AboutItem(
|
||||
title = stringResource(id = R.string.project_page_option),
|
||||
description = stringResource(
|
||||
id = R.string.project_page_option_desc
|
||||
)
|
||||
) {
|
||||
viewModel.onEvent(
|
||||
AboutEvent.OnNavigateToBrowserPage(
|
||||
context.getString(R.string.project_page),
|
||||
context,
|
||||
noAppsFound = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.error_no_browser),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
AboutItem(
|
||||
title = stringResource(id = R.string.my_page_option),
|
||||
description = stringResource(
|
||||
id = R.string.my_page_option_desc
|
||||
)
|
||||
) {
|
||||
viewModel.onEvent(
|
||||
AboutEvent.OnNavigateToBrowserPage(
|
||||
context.getString(R.string.my_page),
|
||||
context,
|
||||
noAppsFound = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.error_no_browser),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
AboutItem(
|
||||
title = stringResource(id = R.string.support_option),
|
||||
description = stringResource(
|
||||
id = R.string.support_option_desc
|
||||
)
|
||||
) {
|
||||
viewModel.onEvent(
|
||||
AboutEvent.OnNavigateToBrowserPage(
|
||||
context.getString(R.string.support_page),
|
||||
context,
|
||||
noAppsFound = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.error_no_browser),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
AboutItem(
|
||||
title = stringResource(id = R.string.slava_ukraini_option),
|
||||
description = null
|
||||
) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.slava_ukraini_option_desc),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
item { Spacer(modifier = Modifier.height(48.dp)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package ua.acclorite.book_story.presentation.screens.about.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
* About screen item.
|
||||
*/
|
||||
@Composable
|
||||
fun AboutItem(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
description: String?,
|
||||
verticalPadding: Dp = 12.dp,
|
||||
isOnClickEnabled: Boolean = true,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = isOnClickEnabled) {
|
||||
onClick()
|
||||
}
|
||||
.padding(horizontal = 18.dp, vertical = verticalPadding),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
Text(
|
||||
title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
description?.let {
|
||||
Text(
|
||||
it,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package ua.acclorite.book_story.presentation.screens.about.data
|
||||
|
||||
import android.content.Context
|
||||
|
||||
sealed class AboutEvent {
|
||||
data class OnNavigateToBrowserPage(
|
||||
val page: String,
|
||||
val context: Context,
|
||||
val noAppsFound: () -> Unit
|
||||
) : AboutEvent()
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package ua.acclorite.book_story.presentation.screens.about.data
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AboutViewModel @Inject constructor(
|
||||
|
||||
) : ViewModel() {
|
||||
|
||||
fun onEvent(event: AboutEvent) {
|
||||
when (event) {
|
||||
is AboutEvent.OnNavigateToBrowserPage -> {
|
||||
viewModelScope.launch {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(event.page)
|
||||
)
|
||||
|
||||
if (intent.resolveActivity(event.context.packageManager) != null) {
|
||||
event.context.startActivity(intent)
|
||||
return@launch
|
||||
}
|
||||
|
||||
event.noAppsFound()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package ua.acclorite.book_story.presentation.screens.book_info
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
viewModel.onEvent(
|
||||
BookInfoEvent.OnDeleteCover(
|
||||
refreshList = {
|
||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
||||
historyViewModel.onEvent(HistoryEvent.OnLoadList)
|
||||
libraryViewModel.onEvent(LibraryEvent.OnUpdateBook(it))
|
||||
historyViewModel.onEvent(HistoryEvent.OnUpdateBook(it))
|
||||
}
|
||||
))
|
||||
)
|
||||
)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.cover_image_deleted),
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import androidx.compose.ui.unit.dp
|
|||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
_state.update {
|
||||
it.copy(
|
||||
book = it.book.copy(
|
||||
category = it.selectedCategory
|
||||
)
|
||||
updateBooks.execute(listOf(book))
|
||||
|
||||
event.refreshList()
|
||||
)
|
||||
}
|
||||
updateBooks.execute(listOf(_state.value.book))
|
||||
|
||||
event.refreshList(_state.value.book)
|
||||
event.updatePage(
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<File, Selected>,
|
||||
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 {
|
||||
.combinedClickable(
|
||||
onLongClick = {
|
||||
onLongClick()
|
||||
},
|
||||
onClick = {
|
||||
onClick()
|
||||
}
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 7.dp)
|
||||
) {
|
||||
Row(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<NullableBook, Selected>, 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<NullableBook, Selected>, 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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.toMutableList()
|
||||
editedList[indexOfFile] = editedList[indexOfFile].copy(
|
||||
second = !editedList[indexOfFile].second
|
||||
val editedList = _state.value.selectableFiles.map {
|
||||
if (event.file.first.path == it.first.path) {
|
||||
it.copy(
|
||||
second = !it.second
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
_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)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +209,6 @@ 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) {
|
||||
|
|
@ -230,32 +224,30 @@ fun HistoryScreen(
|
|||
},
|
||||
padding = 16.dp
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
HistoryItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
history = it,
|
||||
book = book,
|
||||
isOnClickEnabled = !state.isRefreshing,
|
||||
onBodyClick = {
|
||||
navigator.navigate(
|
||||
Screen.BOOK_INFO,
|
||||
false,
|
||||
Argument("book", book.id)
|
||||
Argument("book", it.book)
|
||||
)
|
||||
},
|
||||
onTitleClick = {
|
||||
navigator.navigate(
|
||||
Screen.READER,
|
||||
false,
|
||||
Argument("book", book.id)
|
||||
viewModel.onEvent(
|
||||
HistoryEvent.OnNavigateToReaderScreen(
|
||||
navigator,
|
||||
it.book
|
||||
)
|
||||
)
|
||||
},
|
||||
isDeleteEnabled = !state.isRefreshing,
|
||||
|
|
@ -266,7 +258,9 @@ fun HistoryScreen(
|
|||
snackbarState = snackbarState,
|
||||
context = context,
|
||||
refreshList = {
|
||||
libraryViewModel.onEvent(LibraryEvent.OnLoadList)
|
||||
libraryViewModel.onEvent(
|
||||
LibraryEvent.OnLoadList
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
@ -276,10 +270,11 @@ fun HistoryScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<GroupedHistory>()
|
||||
|
||||
history
|
||||
|
|
@ -278,20 +351,3 @@ class HistoryViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ import kotlinx.coroutines.launch
|
|||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.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<Book>
|
||||
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) {
|
||||
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,7 +206,11 @@ 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)
|
||||
}
|
||||
|
|
@ -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,7 +307,9 @@ fun LibraryScreen(
|
|||
|
||||
LaunchedEffect(state.books) {
|
||||
categorizedBooks.clear()
|
||||
categorizedBooks.addAll(state.books.filter { it.first.category == category }
|
||||
categorizedBooks.addAll(
|
||||
state.books
|
||||
.filter { it.first.category == category }
|
||||
.sortedWith(
|
||||
compareByDescending<Pair<Book, Selected>> { it.first.lastOpened }
|
||||
.thenBy { it.first.title }
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Book>, 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.surfaceVariant
|
||||
)
|
||||
),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
) {
|
||||
tabItems.forEachIndexed { index, tabItem ->
|
||||
Tab(
|
||||
modifier = Modifier,
|
||||
selected = pagerState.currentPage == index,
|
||||
onClick = {
|
||||
viewModel.onEvent(LibraryEvent.OnScrollToPage(index, pagerState))
|
||||
},
|
||||
selectedContentColor = MaterialTheme.colorScheme.primary,
|
||||
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
tabItem.first,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = tabItem.second.toString(),
|
||||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(MaterialTheme.elevation(6.dp))
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ScrollableTabRow(
|
||||
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
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<Book>) : 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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
||||
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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.toMutableList()
|
||||
editedList[indexOfBook] = editedList[indexOfBook].copy(
|
||||
second = event.select ?: !editedList[indexOfBook].second
|
||||
val editedList = _state.value.books.map {
|
||||
if (it.first.id == event.book.first.id) {
|
||||
it.copy(
|
||||
second = event.select ?: !it.second
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
_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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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!!
|
||||
val mainState by mainViewModel.state.collectAsState()
|
||||
val fontFamily = remember(mainState.fontFamily) {
|
||||
Constants.FONTS.find {
|
||||
it.id == mainState.fontFamily
|
||||
} ?: Constants.FONTS[0]
|
||||
val fontStyle = when (mainViewModel.isItalic.collectAsState().value!!) {
|
||||
false -> FontStyle.Normal
|
||||
true -> FontStyle.Italic
|
||||
}
|
||||
val fontSize = mainViewModel.fontSize.collectAsState().value!!
|
||||
val lineHeight = mainViewModel.lineHeight.collectAsState().value!!
|
||||
val paragraphHeight = mainViewModel.paragraphHeight.collectAsState().value!!
|
||||
val paragraphIndentation = mainViewModel.paragraphIndentation.collectAsState().value!!
|
||||
val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!!
|
||||
val fontColor = mainViewModel.fontColor.collectAsState().value!!
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
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()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
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,
|
||||
Argument(
|
||||
"book",
|
||||
state.book.id
|
||||
true
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!!
|
||||
val state by mainViewModel.state.collectAsState()
|
||||
|
||||
val fontFamily = remember(state.fontFamily) {
|
||||
Constants.FONTS.find {
|
||||
it.id == state.fontFamily
|
||||
} ?: Constants.FONTS[0]
|
||||
val fontStyle = mainViewModel.isItalic.collectAsState().value!!
|
||||
val fontSize = mainViewModel.fontSize.collectAsState().value!!
|
||||
val lineHeight = mainViewModel.lineHeight.collectAsState().value!!
|
||||
val paragraphHeight = mainViewModel.paragraphHeight.collectAsState().value!!
|
||||
val paragraphIndentation = mainViewModel.paragraphIndentation.collectAsState().value!!
|
||||
val backgroundColor = mainViewModel.backgroundColor.collectAsState().value!!
|
||||
val fontColor = mainViewModel.fontColor.collectAsState().value!!
|
||||
}
|
||||
|
||||
val scrimColor = if (currentPage == 1) Color.Transparent
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
),
|
||||
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 -> {
|
||||
is ReaderEvent.OnGoBack -> {
|
||||
viewModelScope.launch {
|
||||
val insetsController = WindowCompat.getInsetsController(
|
||||
event.context.window,
|
||||
event.context.window.decorView
|
||||
)
|
||||
|
||||
val firstVisibleItemIndex = event.listState.firstVisibleItemIndex
|
||||
val firstVisibleItemOffset = event.listState.firstVisibleItemScrollOffset
|
||||
val lastVisibleItemIndex = event.listState.layoutInfo
|
||||
.visibleItemsInfo.last().index
|
||||
|
||||
val progress = if (firstVisibleItemIndex > 0) {
|
||||
if (lastVisibleItemIndex >= (event.listState.layoutInfo.totalItemsCount - 1)) {
|
||||
1f
|
||||
} else {
|
||||
(firstVisibleItemIndex.toFloat() / (_state.value.book.text.lastIndex)
|
||||
.toFloat())
|
||||
}
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
book = it.book.copy(
|
||||
progress = progress,
|
||||
scrollIndex = firstVisibleItemIndex,
|
||||
scrollOffset = firstVisibleItemOffset
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
updateBooks.execute(
|
||||
listOf(_state.value.book)
|
||||
)
|
||||
event.navigator.putArgument(
|
||||
Argument("book", _state.value.book)
|
||||
)
|
||||
event.refreshList(_state.value.book)
|
||||
|
||||
insetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||
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,13 +230,17 @@ 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 -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
currentPage = if (it.showSettingsBottomSheet) it.currentPage else 0,
|
||||
|
|
@ -213,6 +248,7 @@ class ReaderViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is ReaderEvent.OnScrollToSettingsPage -> {
|
||||
viewModelScope.launch {
|
||||
|
|
@ -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()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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!!
|
||||
val state by mainViewModel.state.collectAsState()
|
||||
val fontFamily = remember(state.fontFamily) {
|
||||
Constants.FONTS.find {
|
||||
it.id == state.fontFamily
|
||||
} ?: Constants.FONTS[0]
|
||||
val fontStyle = mainViewModel.isItalic.collectAsState().value!!
|
||||
val fontSize = mainViewModel.fontSize.collectAsState().value!!
|
||||
val lineHeight = mainViewModel.lineHeight.collectAsState().value!!
|
||||
val paragraphHeight = mainViewModel.paragraphHeight.collectAsState().value!!
|
||||
val paragraphIndentation = mainViewModel.paragraphIndentation.collectAsState().value!!
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
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!!)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ua.acclorite.book_story.ui
|
||||
package ua.acclorite.book_story.presentation.ui
|
||||
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ua.acclorite.book_story.ui
|
||||
package ua.acclorite.book_story.presentation.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue