0.9.9 - Open source licenses & Credits screens in About + Bug fixes(No check for updates in Release Build) + Side Padding setting in ReaderScreen.

This commit is contained in:
acclorite 2024-04-24 18:42:32 +03:00
parent df215fcfa3
commit 43da347ae0
42 changed files with 1116 additions and 112 deletions

View file

@ -26,6 +26,10 @@ import ua.acclorite.book_story.presentation.data.NavigationHost
import ua.acclorite.book_story.presentation.data.Screen
import ua.acclorite.book_story.presentation.screens.about.AboutScreenRoot
import ua.acclorite.book_story.presentation.screens.about.data.AboutViewModel
import ua.acclorite.book_story.presentation.screens.about.nested.credits.CreditsScreenRoot
import ua.acclorite.book_story.presentation.screens.about.nested.license_info.LicenseInfoScreenRoot
import ua.acclorite.book_story.presentation.screens.about.nested.license_info.data.LicenseInfoViewModel
import ua.acclorite.book_story.presentation.screens.about.nested.licenses.LicensesScreenRoot
import ua.acclorite.book_story.presentation.screens.book_info.BookInfoScreenRoot
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
import ua.acclorite.book_story.presentation.screens.browse.BrowseScreenRoot
@ -65,6 +69,7 @@ class Activity : AppCompatActivity() {
private val readerViewModel: ReaderViewModel by viewModels()
private val helpViewModel: HelpViewModel by viewModels()
private val aboutViewModel: AboutViewModel by viewModels()
private val licenseInfoViewModel: LicenseInfoViewModel by viewModels()
private val settingsViewModel: SettingsViewModel by viewModels()
private val startViewModel: StartViewModel by viewModels()
@ -192,6 +197,17 @@ class Activity : AppCompatActivity() {
AboutScreenRoot()
}
// Nested about categories
composable(screen = Screen.LICENSES) {
LicensesScreenRoot()
}
composable(screen = Screen.LICENSES_INFO) {
LicenseInfoScreenRoot()
}
composable(screen = Screen.CREDITS) {
CreditsScreenRoot()
}
// Help screen
composable(screen = Screen.HELP) {
HelpScreenRoot()

View file

@ -1,10 +1,14 @@
package ua.acclorite.book_story.data.remote.dto
import androidx.annotation.Keep
import androidx.compose.runtime.Immutable
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@Suppress("PropertyName")
@Keep
@JsonClass(generateAdapter = true)
@Immutable
data class ReleaseResponse(
val name: String,
val tag_name: String
@Json(name = "name") val name: String,
@Json(name = "tag_name") val tagName: String
)

View file

@ -34,7 +34,7 @@ import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.domain.util.CoverImage
import ua.acclorite.book_story.domain.util.Resource
import ua.acclorite.book_story.domain.util.UIText
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import java.io.File
import java.io.FileOutputStream
import java.util.UUID
@ -391,8 +391,8 @@ class BookRepositoryImpl @Inject constructor(
dataStore.putData(key, value)
}
override suspend fun getAllSettings(scope: CoroutineScope): MainSettingsState {
val result = CompletableDeferred<MainSettingsState>()
override suspend fun getAllSettings(scope: CoroutineScope): MainState {
val result = CompletableDeferred<MainState>()
scope.launch {
val keys = dataStore.getAllData()
@ -411,7 +411,7 @@ class BookRepositoryImpl @Inject constructor(
}
jobs?.awaitAll()
result.complete(MainSettingsState.initialize(data))
result.complete(MainState.initialize(data))
}
return result.await()
@ -613,7 +613,7 @@ class BookRepositoryImpl @Inject constructor(
try {
val result = githubAPI.getLatestRelease()
val version = result.tag_name.substringAfterLast("v")
val version = result.tagName.substringAfterLast("v")
val currentVersion = application.getString(R.string.app_version)
if (version != currentVersion && postNotification) {

View file

@ -0,0 +1,12 @@
package ua.acclorite.book_story.domain.model
import androidx.compose.runtime.Immutable
import ua.acclorite.book_story.domain.util.UIText
@Immutable
data class Credit(
val name: String,
val source: String,
val credits: List<UIText>,
val website: String
)

View file

@ -10,7 +10,7 @@ import ua.acclorite.book_story.domain.model.NullableBook
import ua.acclorite.book_story.domain.model.StringWithId
import ua.acclorite.book_story.domain.util.CoverImage
import ua.acclorite.book_story.domain.util.Resource
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import java.io.File
interface BookRepository {
@ -58,7 +58,7 @@ interface BookRepository {
value: T
)
suspend fun getAllSettings(scope: CoroutineScope): MainSettingsState
suspend fun getAllSettings(scope: CoroutineScope): MainState
suspend fun getFilesFromDevice(query: String = ""): Flow<Resource<List<File>>>

View file

@ -2,14 +2,14 @@ package ua.acclorite.book_story.domain.use_case
import kotlinx.coroutines.CoroutineScope
import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import javax.inject.Inject
class GetAllSettings @Inject constructor(
private val repository: BookRepository
) {
suspend fun execute(scope: CoroutineScope): MainSettingsState {
suspend fun execute(scope: CoroutineScope): MainState {
return repository.getAllSettings(scope)
}
}

View file

@ -7,6 +7,7 @@ import androidx.compose.ui.text.font.FontWeight
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.Credit
import ua.acclorite.book_story.domain.model.FontWithName
import ua.acclorite.book_story.presentation.ui.Theme
@ -36,6 +37,83 @@ object Constants {
Pair(Theme.AQUA, UIText.StringResource(R.string.aqua_theme)),
)
// Credits
val CREDITS = listOf(
Credit(
name = "Tachiyomi (Mihon)",
source = "GitHub",
credits = listOf(
UIText.StringResource(R.string.credits_design),
UIText.StringValue("Readme")
),
website = "https://github.com/mihonapp/mihon"
),
Credit(
name = "Libre-Sudoku",
source = "GitHub",
credits = listOf(
UIText.StringValue("Readme")
),
website = "https://github.com/kaajjo/Libre-Sudoku"
),
Credit(
name = "Kitsune",
source = "GitHub",
credits = listOf(
UIText.StringResource(R.string.credits_updates)
),
website = "https://github.com/Drumber/Kitsune"
),
Credit(
name = "Freepik",
source = "Flaticon",
credits = listOf(
UIText.StringResource(R.string.credits_icon)
),
website = "https://www.flaticon.com/authors/freepik"
),
Credit(
name = "Anggara",
source = "Flaticon",
credits = listOf(
UIText.StringResource(R.string.credits_icon)
),
website = "https://www.flaticon.com/authors/anggara"
),
Credit(
name = "Google",
source = "Flaticon",
credits = listOf(
UIText.StringResource(R.string.credits_icon)
),
website = "https://www.flaticon.com/authors/google"
),
Credit(
name = "Creative Stall Premium",
source = "Flaticon",
credits = listOf(
UIText.StringResource(R.string.credits_icon)
),
website = "https://www.flaticon.com/authors/creative-stall-premium"
),
Credit(
name = "Gajah Mada",
source = "Flaticon",
credits = listOf(
UIText.StringResource(R.string.credits_icon)
),
website = "https://www.flaticon.com/authors/gajah-mada"
),
Credit(
name = "mavadee",
source = "Flaticon",
credits = listOf(
UIText.StringResource(R.string.credits_icon)
),
website = "https://www.flaticon.com/authors/mavadee"
),
)
// Fonts for Reader
val FONTS = listOf(
FontWithName(

View file

@ -21,4 +21,5 @@ object DataStoreConstants {
val PARAGRAPH_HEIGHT = intPreferencesKey("paragraph_height")
val PARAGRAPH_INDENTATION = booleanPreferencesKey("paragraph_indentation")
val CHECK_FOR_UPDATES = booleanPreferencesKey("check_for_updates")
val SIDE_PADDING = intPreferencesKey("side_padding")
}

View file

@ -16,4 +16,5 @@ sealed class MainEvent {
data class OnChangeFontColor(val color: Long) : MainEvent()
data class OnChangeShowStartScreen(val bool: Boolean) : MainEvent()
data class OnChangeCheckForUpdates(val bool: Boolean) : MainEvent()
data class OnChangeSidePadding(val sidePadding: Int) : MainEvent()
}

View file

@ -21,7 +21,7 @@ import java.util.Locale
* Main State. All app's settings are here. Wrapped in SavedStateHandle, so it won't reset.
*/
@Immutable
data class MainSettingsState(
data class MainState(
val language: String? = null,
val theme: Theme? = null,
val darkTheme: DarkTheme? = null,
@ -37,6 +37,7 @@ data class MainSettingsState(
val fontColor: Long? = null,
val showStartScreen: Boolean? = null,
val checkForUpdates: Boolean? = null,
val sidePadding: Int? = null,
) : Parcelable {
constructor(parcel: Parcel) : this(
// String
@ -72,6 +73,8 @@ data class MainSettingsState(
// Boolean
checkForUpdates = if (parcel.readByte().toInt() != 0) parcel.readByte()
.toInt() == 1 else null,
// Int
sidePadding = if (parcel.readByte().toInt() != 0) parcel.readInt() else null,
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
@ -114,25 +117,28 @@ data class MainSettingsState(
// Check For Updates
parcel.writeByte((if (checkForUpdates != null) 1 else 0).toByte())
checkForUpdates?.let { parcel.writeByte(if (it) 1 else 0) }
// Side padding
parcel.writeByte((if (sidePadding != null) 1 else 0).toByte())
sidePadding?.let { parcel.writeInt(it) }
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<MainSettingsState> {
override fun createFromParcel(parcel: Parcel): MainSettingsState {
return MainSettingsState(parcel)
companion object CREATOR : Parcelable.Creator<MainState> {
override fun createFromParcel(parcel: Parcel): MainState {
return MainState(parcel)
}
override fun newArray(size: Int): Array<MainSettingsState?> {
override fun newArray(size: Int): Array<MainState?> {
return arrayOfNulls(size)
}
/**
* Initializes [MainSettingsState] by given [Map].
* Initializes [MainState] by given [Map].
*/
fun initialize(data: Map<String, Any>): MainSettingsState {
fun initialize(data: Map<String, Any>): MainState {
DataStoreConstants.apply {
val language: String = data[LANGUAGE.name] as? String ?: if (
@ -186,7 +192,10 @@ data class MainSettingsState(
val checkForUpdates: Boolean = data[CHECK_FOR_UPDATES.name] as? Boolean
?: false
return MainSettingsState(
val sidePadding: Int = data[SIDE_PADDING.name] as? Int
?: 6
return MainState(
language = language,
theme = theme.toTheme(),
darkTheme = darkTheme.toDarkTheme(),
@ -201,7 +210,8 @@ data class MainSettingsState(
lineHeight = lineHeight,
paragraphHeight = paragraphHeight,
paragraphIndentation = paragraphIndentation,
checkForUpdates = checkForUpdates
checkForUpdates = checkForUpdates,
sidePadding = sidePadding
)
}
}

View file

@ -44,8 +44,8 @@ class MainViewModel @Inject constructor(
private val isSettingsReady = MutableStateFlow(false)
private val isViewModelReady = MutableStateFlow(false)
private val _state: MutableStateFlow<MainSettingsState> = MutableStateFlow(
stateHandle[Constants.MAIN_STATE] ?: MainSettingsState()
private val _state: MutableStateFlow<MainState> = MutableStateFlow(
stateHandle[Constants.MAIN_STATE] ?: MainState()
)
val state = _state.asStateFlow()
@ -216,6 +216,17 @@ class MainViewModel @Inject constructor(
}
}
}
is MainEvent.OnChangeSidePadding -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(DataStoreConstants.SIDE_PADDING, event.sidePadding)
updateStateWithSavedHandle {
it.copy(
sidePadding = event.sidePadding
)
}
}
}
}
}
@ -270,10 +281,10 @@ class MainViewModel @Inject constructor(
}
/**
* Updates [MainSettingsState] along with [SavedStateHandle].
* Updates [MainState] along with [SavedStateHandle].
*/
private fun updateStateWithSavedHandle(
function: (MainSettingsState) -> MainSettingsState
function: (MainState) -> MainState
) {
_state.update {
stateHandle[Constants.MAIN_STATE] = function(it)

View file

@ -77,6 +77,10 @@ enum class Screen {
READER_SETTINGS,
ABOUT,
LICENSES,
LICENSES_INFO,
CREDITS,
HELP,
START
}

View file

@ -37,6 +37,7 @@ import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.Navigator
import ua.acclorite.book_story.presentation.data.Screen
import ua.acclorite.book_story.presentation.screens.about.components.AboutItem
import ua.acclorite.book_story.presentation.screens.about.components.AboutUpdateDialog
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
@ -207,6 +208,24 @@ private fun AboutScreen(
}
}
item {
AboutItem(
title = stringResource(id = R.string.licenses_option),
description = null
) {
navigator.navigate(Screen.LICENSES, false)
}
}
item {
AboutItem(
title = stringResource(id = R.string.credits_option),
description = null
) {
navigator.navigate(Screen.CREDITS, false)
}
}
item {
AboutItem(
title = stringResource(id = R.string.support_option),

View file

@ -38,8 +38,8 @@ fun AboutItem(
) {
Text(
title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontSize = 18.sp
)
description?.let {

View file

@ -28,7 +28,7 @@ fun AboutUpdateDialog(
}
CustomDialogWithContent(
title = stringResource(id = R.string.update_query, update.tag_name),
title = stringResource(id = R.string.update_query, update.tagName),
imageVectorIcon = Icons.Default.Update,
description = stringResource(
id = R.string.update_app_description

View file

@ -48,7 +48,7 @@ class AboutViewModel @Inject constructor(
return@launch
}
val version = result.tag_name.substringAfterLast("v")
val version = result.tagName.substringAfterLast("v")
val currentVersion = event.context.getString(R.string.app_version)
if (version == currentVersion) {

View file

@ -0,0 +1,107 @@
package ua.acclorite.book_story.presentation.screens.about.nested.credits
import android.widget.Toast
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
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.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.Navigator
import ua.acclorite.book_story.presentation.screens.about.data.AboutEvent
import ua.acclorite.book_story.presentation.screens.about.data.AboutViewModel
import ua.acclorite.book_story.presentation.screens.about.nested.credits.components.CreditItem
@Composable
fun CreditsScreenRoot() {
val navigator = LocalNavigator.current
val aboutViewModel: AboutViewModel = hiltViewModel()
CreditsScreen(
navigator = navigator,
onAboutNavigateEvent = aboutViewModel::onEvent
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CreditsScreen(
navigator: Navigator,
onAboutNavigateEvent: (AboutEvent.OnNavigateToBrowserPage) -> Unit
) {
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
val context = LocalContext.current
Scaffold(
Modifier
.fillMaxSize()
.nestedScroll(scrollState.first.nestedScrollConnection)
.windowInsetsPadding(WindowInsets.navigationBars),
containerColor = MaterialTheme.colorScheme.surface,
topBar = {
LargeTopAppBar(
title = {
Text(stringResource(id = R.string.credits_option))
},
navigationIcon = {
GoBackButton(navigator = navigator)
},
scrollBehavior = scrollState.first,
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
)
)
}
) { paddingValues ->
LazyColumn(
Modifier
.fillMaxSize()
.padding(top = paddingValues.calculateTopPadding()),
state = scrollState.second
) {
items(Constants.CREDITS, key = { it.name }) {
CreditItem(credit = it) {
onAboutNavigateEvent(
AboutEvent.OnNavigateToBrowserPage(
page = it.website,
context = context,
noAppsFound = {
Toast.makeText(
context,
context.getString(R.string.error_no_browser),
Toast.LENGTH_SHORT
).show()
}
)
)
}
}
}
}
}

View file

@ -0,0 +1,81 @@
package ua.acclorite.book_story.presentation.screens.about.nested.credits.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
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.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.domain.model.Credit
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun CreditItem(
credit: Credit,
horizontalPadding: Dp = 18.dp,
verticalPadding: Dp = 12.dp,
onClick: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { onClick.invoke() }
.padding(vertical = verticalPadding, horizontal = horizontalPadding)
) {
Text(
text = credit.name,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
text = credit.source,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
if (credit.credits.isNotEmpty()) {
Spacer(modifier = Modifier.height(6.dp))
FlowRow(
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
credit.credits.forEach {
Text(
text = it.asString(),
color = MaterialTheme.colorScheme.onPrimary,
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.clip(RoundedCornerShape(100))
.background(MaterialTheme.colorScheme.primary)
.padding(vertical = 4.dp, horizontal = 8.dp)
)
}
}
}
}
}

View file

@ -0,0 +1,232 @@
package ua.acclorite.book_story.presentation.screens.about.nested.license_info
import android.widget.Toast
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Language
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.LinearProgressIndicator
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.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
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.graphics.StrokeCap
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
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.CustomAnimatedVisibility
import ua.acclorite.book_story.presentation.components.CustomIconButton
import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.Navigator
import ua.acclorite.book_story.presentation.screens.about.nested.license_info.data.LicenseInfoEvent
import ua.acclorite.book_story.presentation.screens.about.nested.license_info.data.LicenseInfoState
import ua.acclorite.book_story.presentation.screens.about.nested.license_info.data.LicenseInfoViewModel
@Composable
fun LicenseInfoScreenRoot() {
val navigator = LocalNavigator.current
val context = LocalContext.current
val licenseInfoViewModel: LicenseInfoViewModel = hiltViewModel()
val state = licenseInfoViewModel.state.collectAsState()
LaunchedEffect(Unit) {
licenseInfoViewModel.init(
navigator,
context
)
}
LicenseInfoScreen(
state = state,
onEvent = licenseInfoViewModel::onEvent,
navigator = navigator
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun LicenseInfoScreen(
state: State<LicenseInfoState>,
onEvent: (LicenseInfoEvent) -> Unit,
navigator: Navigator
) {
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
val context = LocalContext.current
if (state.value.license != null) {
val licenses = remember(state.value) {
state.value.license!!.licenses.toList()
}
Scaffold(
Modifier
.fillMaxSize()
.nestedScroll(scrollState.first.nestedScrollConnection)
.windowInsetsPadding(WindowInsets.navigationBars),
containerColor = MaterialTheme.colorScheme.surface,
topBar = {
LargeTopAppBar(
title = {
Text(
state.value.license!!.name,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = {
GoBackButton(navigator = navigator)
},
actions = {
if (state.value.license?.website?.isNotBlank() == true) {
CustomIconButton(
icon = Icons.Outlined.Language,
contentDescription = R.string.open_in_web_content_desc,
disableOnClick = false
) {
var url = state.value.license?.website!!
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "https://$url"
}
onEvent(
LicenseInfoEvent.OnOpenLicensePage(
page = url,
context = context,
noAppsFound = {
Toast.makeText(
context,
context.getString(R.string.error_no_browser),
Toast.LENGTH_SHORT
).show()
}
)
)
}
}
},
scrollBehavior = scrollState.first,
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
)
)
}
) { paddingValues ->
LazyColumn(
Modifier
.fillMaxSize()
.padding(top = paddingValues.calculateTopPadding()),
state = scrollState.second,
contentPadding = PaddingValues(vertical = 12.dp, horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(licenses, key = { it.name }) { license ->
val showed = remember {
mutableStateOf(licenses.size == 1)
}
Column(
modifier = Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
)
) {
Text(
text = license.name,
color = MaterialTheme.colorScheme.onPrimary,
style = MaterialTheme.typography.titleSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.clip(RoundedCornerShape(100))
.clickable(enabled = license.licenseContent?.isNotBlank() == true) {
showed.value = !showed.value
}
.background(MaterialTheme.colorScheme.primary)
.padding(vertical = 6.dp, horizontal = 12.dp)
)
CustomAnimatedVisibility(
visible = showed.value && license.licenseContent?.isNotBlank() == true,
enter = slideInVertically(tween(300)) { -50 } +
fadeIn(tween(300)),
exit = slideOutVertically(tween(200)) { -30 } +
fadeOut(tween(150)),
modifier = Modifier.padding(horizontal = 8.dp)
) {
Column {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = license.licenseContent!!
.lines()
.joinToString(separator = "\n") {
it.trim()
},
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
}
} else {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(id = R.string.loading),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.titleLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(12.dp))
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(0.55f),
strokeCap = StrokeCap.Round,
trackColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.7f)
)
}
}
}

View file

@ -0,0 +1,13 @@
package ua.acclorite.book_story.presentation.screens.about.nested.license_info.data
import android.content.Context
import androidx.compose.runtime.Immutable
@Immutable
sealed class LicenseInfoEvent {
data class OnOpenLicensePage(
val page: String,
val context: Context,
val noAppsFound: () -> Unit
) : LicenseInfoEvent()
}

View file

@ -0,0 +1,9 @@
package ua.acclorite.book_story.presentation.screens.about.nested.license_info.data
import androidx.compose.runtime.Immutable
import com.mikepenz.aboutlibraries.entity.Library
@Immutable
data class LicenseInfoState(
val license: Library? = null,
)

View file

@ -0,0 +1,74 @@
package ua.acclorite.book_story.presentation.screens.about.nested.license_info.data
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.util.withContext
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ua.acclorite.book_story.presentation.data.Navigator
import javax.inject.Inject
@HiltViewModel
class LicenseInfoViewModel @Inject constructor() : ViewModel() {
private val _state = MutableStateFlow(LicenseInfoState())
val state = _state.asStateFlow()
fun onEvent(event: LicenseInfoEvent) {
when (event) {
is LicenseInfoEvent.OnOpenLicensePage -> {
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()
}
}
}
}
fun init(navigator: Navigator, context: Context) {
viewModelScope.launch(Dispatchers.IO) {
_state.update {
it.copy(license = null)
}
val licenseId = navigator.retrieveArgument("license") as? String
if (licenseId == null) {
navigator.navigateBack()
return@launch
}
val license = Libs.Builder().withContext(context).build().libraries.find {
it.uniqueId == licenseId
}
if (license == null) {
navigator.navigateBack()
return@launch
}
_state.update {
it.copy(
license = license
)
}
}
}
}

View file

@ -0,0 +1,114 @@
package ua.acclorite.book_story.presentation.screens.about.nested.licenses
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
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.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
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.CustomAnimatedVisibility
import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.Argument
import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.Navigator
import ua.acclorite.book_story.presentation.data.Screen
import ua.acclorite.book_story.presentation.screens.about.nested.licenses.components.LicenseItem
import ua.acclorite.book_story.presentation.screens.about.nested.licenses.data.LicensesState
import ua.acclorite.book_story.presentation.screens.about.nested.licenses.data.LicensesViewModel
@Composable
fun LicensesScreenRoot() {
val navigator = LocalNavigator.current
val context = LocalContext.current
val licensesViewModel: LicensesViewModel = hiltViewModel()
val state = licensesViewModel.state.collectAsState()
LaunchedEffect(Unit) {
licensesViewModel.init(context)
}
LicensesScreen(
state = state,
navigator = navigator
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun LicensesScreen(
state: State<LicensesState>,
navigator: Navigator
) {
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
Scaffold(
Modifier
.fillMaxSize()
.nestedScroll(scrollState.first.nestedScrollConnection)
.windowInsetsPadding(WindowInsets.navigationBars),
containerColor = MaterialTheme.colorScheme.surface,
topBar = {
LargeTopAppBar(
title = {
Text(stringResource(id = R.string.licenses_option))
},
navigationIcon = {
GoBackButton(navigator = navigator)
},
scrollBehavior = scrollState.first,
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer
)
)
}
) { paddingValues ->
CustomAnimatedVisibility(
visible = state.value.licenses.isNotEmpty(),
enter = fadeIn(tween(300)),
exit = fadeOut(tween(300))
) {
LazyColumn(
Modifier
.fillMaxSize()
.padding(top = paddingValues.calculateTopPadding()),
state = scrollState.second,
contentPadding = PaddingValues(vertical = 12.dp)
) {
items(state.value.licenses, key = { it.uniqueId }) {
LicenseItem(library = it) {
navigator.navigate(
Screen.LICENSES_INFO,
false,
Argument("license", it.uniqueId)
)
}
}
}
}
}
}

View file

@ -0,0 +1,92 @@
package ua.acclorite.book_story.presentation.screens.about.nested.licenses.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
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.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.mikepenz.aboutlibraries.entity.Library
import com.mikepenz.aboutlibraries.ui.compose.m3.util.author
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun LicenseItem(
modifier: Modifier = Modifier,
library: Library,
horizontalPadding: Dp = 18.dp,
verticalPadding: Dp = 12.dp,
onClick: () -> Unit
) {
Column(
modifier = modifier
.fillMaxWidth()
.clickable { onClick.invoke() }
.padding(vertical = verticalPadding, horizontal = horizontalPadding)
) {
Row {
Text(
text = library.name,
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
library.artifactVersion?.let {
Spacer(modifier = Modifier.width(8.dp))
Text(
it,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
}
if (library.author.isNotBlank()) {
Text(
text = library.author,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
if (library.licenses.isNotEmpty()) {
Spacer(modifier = Modifier.height(6.dp))
FlowRow(
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
library.licenses.forEach {
Text(
text = it.name,
color = MaterialTheme.colorScheme.onPrimary,
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.clip(RoundedCornerShape(100))
.background(MaterialTheme.colorScheme.primary)
.padding(vertical = 4.dp, horizontal = 8.dp)
)
}
}
}
}
}

View file

@ -0,0 +1,9 @@
package ua.acclorite.book_story.presentation.screens.about.nested.licenses.data
import androidx.compose.runtime.Immutable
import com.mikepenz.aboutlibraries.entity.Library
@Immutable
data class LicensesState(
val licenses: List<Library> = emptyList()
)

View file

@ -0,0 +1,44 @@
package ua.acclorite.book_story.presentation.screens.about.nested.licenses.data
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.util.withContext
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class LicensesViewModel @Inject constructor(
) : ViewModel() {
private val _state = MutableStateFlow(LicensesState())
val state = _state.asStateFlow()
fun init(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
if (_state.value.licenses.isNotEmpty()) {
return@launch
}
val licenses = Libs
.Builder()
.withContext(context)
.build()
.libraries
.toList()
_state.update {
it.copy(
licenses = licenses
)
}
}
}
}

View file

@ -62,7 +62,7 @@ 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.LocalNavigator
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.data.Navigator
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
@ -127,7 +127,7 @@ fun ReaderScreenRoot() {
@Composable
private fun ReaderScreen(
state: State<ReaderState>,
mainState: State<MainSettingsState>,
mainState: State<MainState>,
listState: LazyListState,
loading: State<Boolean>,
navigator: Navigator,
@ -167,6 +167,29 @@ private fun ReaderScreen(
it.id == mainState.value.fontFamily
} ?: Constants.FONTS[0]
}
val fontColor = remember(mainState.value.fontColor) {
Color(mainState.value.fontColor!!.toULong())
}
val backgroundColor = remember(mainState.value.backgroundColor) {
Color(mainState.value.backgroundColor!!.toULong())
}
val lineHeight =
remember(mainState.value.fontSize, mainState.value.lineHeight) {
(mainState.value.fontSize!! + mainState.value.lineHeight!!).sp
}
val sidePadding = remember(mainState.value.sidePadding) {
(mainState.value.sidePadding!! * 3).dp
}
val paragraphHeight = remember(mainState.value.paragraphHeight) {
(mainState.value.paragraphHeight!! * 3).dp
}
val fontStyle = remember(mainState.value.isItalic) {
when (mainState.value.isItalic!!) {
true -> FontStyle.Italic
false -> FontStyle.Normal
}
}
LaunchedEffect(canScroll) {
if (!canScroll && !state.value.showMenu && !loading.value) {
@ -329,16 +352,6 @@ private fun ReaderScreen(
val text = remember(mainState.value.paragraphIndentation) {
"${if (mainState.value.paragraphIndentation!!) " " else ""}${line.line}"
}
val fontColor = remember(mainState.value.fontColor) {
Color(mainState.value.fontColor!!.toULong())
}
val backgroundColor = remember(mainState.value.backgroundColor) {
Color(mainState.value.backgroundColor!!.toULong())
}
val lineHeight =
remember(mainState.value.fontSize, mainState.value.lineHeight) {
(mainState.value.fontSize!! + mainState.value.lineHeight!!).sp
}
Column(
Modifier
@ -346,23 +359,18 @@ private fun ReaderScreen(
.fillMaxWidth()
.padding(
top = if (index == 0) 18.dp else 0.dp,
start = 18.dp,
end = 18.dp,
start = sidePadding,
end = sidePadding,
bottom = if (index == state.value.book.text.lastIndex) 18.dp
else (mainState.value.paragraphHeight!! * 3).dp
else paragraphHeight
)
) {
Text(
text = text,
color = fontColor,
style = TextStyle(
lineBreak = LineBreak.Paragraph
),
style = TextStyle(lineBreak = LineBreak.Paragraph),
fontFamily = fontFamily.font,
fontStyle = when (mainState.value.isItalic!!) {
true -> FontStyle.Italic
false -> FontStyle.Normal
},
fontStyle = fontStyle,
fontSize = mainState.value.fontSize!!.sp,
lineHeight = lineHeight
)

View file

@ -39,7 +39,7 @@ import ua.acclorite.book_story.domain.model.ButtonItem
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.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
import ua.acclorite.book_story.presentation.screens.settings.components.CheckboxWithTitle
import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle
@ -54,7 +54,7 @@ import ua.acclorite.book_story.presentation.screens.settings.components.SliderWi
)
@Composable
fun ReaderSettingsBottomSheet(
mainState: State<MainSettingsState>,
mainState: State<MainState>,
onEvent: (ReaderEvent) -> Unit,
onMainEvent: (MainEvent) -> Unit
) {
@ -212,6 +212,19 @@ fun ReaderSettingsBottomSheet(
item {
Spacer(modifier = Modifier.height(8.dp))
}
item {
SliderWithTitle(
value = mainState.value.sidePadding!! to "pt",
fromValue = 1,
toValue = 20,
title = stringResource(id = R.string.side_padding_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeSidePadding(it)
)
}
)
}
item {
SliderWithTitle(
value = mainState.value.lineHeight!! to "pt",

View file

@ -111,7 +111,13 @@ class ReaderViewModel @Inject constructor(
updateBooks.execute(
listOf(_state.value.book)
)
event.refreshList(_state.value.book)
event.refreshList(
_state.value.book.copy(
text = emptyList(),
letters = 0,
words = 0
)
)
viewModelScope.launch {
snapshotFlow {
@ -231,7 +237,13 @@ class ReaderViewModel @Inject constructor(
event.navigator.putArgument(
Argument("book", _state.value.book.id)
)
event.refreshList(_state.value.book)
event.refreshList(
_state.value.book.copy(
text = emptyList(),
letters = 0,
words = 0
)
)
insetsController.show(WindowInsetsCompat.Type.systemBars())
event.navigate(event.navigator)
@ -269,7 +281,13 @@ class ReaderViewModel @Inject constructor(
_state.value.book.id
)
)
event.refreshList(_state.value.book)
event.refreshList(
_state.value.book.copy(
text = emptyList(),
letters = 0,
words = 0
)
)
}
}
@ -317,7 +335,13 @@ class ReaderViewModel @Inject constructor(
}
updateBooks.execute(listOf(_state.value.book))
event.onUpdateCategories(_state.value.book)
event.onUpdateCategories(
_state.value.book.copy(
text = emptyList(),
letters = 0,
words = 0
)
)
event.updatePage(
Category.entries.dropLastWhile {
it != Category.ALREADY_READ
@ -396,21 +420,27 @@ class ReaderViewModel @Inject constructor(
return@launch
}
val book = getBooksById.execute(listOf(bookId))
if (_state.value.book.id != bookId) {
val book = getBooksById.execute(listOf(bookId))
if (book.isEmpty()) {
navigator.navigateBack()
return@launch
if (book.isEmpty()) {
navigator.navigateBack()
return@launch
}
_state.update {
ReaderState(book = book.first())
}
} else {
_state.update {
ReaderState(book = it.book)
}
}
viewModelScope.launch {
onEvent(ReaderEvent.OnShowHideMenu(false, context))
}
_state.update {
ReaderState(book = book.first())
}
onEvent(
ReaderEvent.OnLoadText(
refreshList = { refreshList(it) },

View file

@ -34,7 +34,7 @@ import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
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.ColorPickerWithTitle
@ -66,7 +66,7 @@ fun AppearanceSettingsRoot() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AppearanceSettings(
state: State<MainSettingsState>,
state: State<MainState>,
navigator: Navigator,
onMainEvent: (MainEvent) -> Unit
) {

View file

@ -21,7 +21,7 @@ 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.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.ui.Theme
import ua.acclorite.book_story.presentation.ui.isDark
import ua.acclorite.book_story.presentation.ui.isPureDark
@ -32,7 +32,7 @@ import ua.acclorite.book_story.presentation.ui.isPureDark
@Composable
fun AppearanceSettingsThemeSwitcher(
modifier: Modifier = Modifier,
state: State<MainSettingsState>,
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
verticalPadding: Dp = 8.dp
) {

View file

@ -35,7 +35,7 @@ import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
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.CheckboxWithTitle
@ -63,7 +63,7 @@ fun GeneralSettingsRoot() {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
@Composable
private fun GeneralSettings(
state: State<MainSettingsState>,
state: State<MainState>,
navigator: Navigator,
onSettingsEvent: (SettingsEvent) -> Unit,
onMainEvent: (MainEvent) -> Unit

View file

@ -32,7 +32,7 @@ import ua.acclorite.book_story.presentation.components.GoBackButton
import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState
import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
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.CheckboxWithTitle
@ -56,7 +56,7 @@ fun ReaderSettingsRoot() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ReaderSettings(
state: State<MainSettingsState>,
state: State<MainState>,
navigator: Navigator,
onMainEvent: (MainEvent) -> Unit
) {
@ -187,6 +187,19 @@ private fun ReaderSettings(
item {
Spacer(modifier = Modifier.height(8.dp))
}
item {
SliderWithTitle(
value = state.value.sidePadding!! to "pt",
fromValue = 1,
toValue = 20,
title = stringResource(id = R.string.side_padding_option),
onValueChange = {
onMainEvent(
MainEvent.OnChangeSidePadding(it)
)
}
)
}
item {
SliderWithTitle(
value = state.value.lineHeight!! to "pt",

View file

@ -16,7 +16,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.rememberPermissionState
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
@ -69,7 +69,7 @@ fun StartScreenRoot() {
@Composable
private fun StartScreen(
state: State<StartState>,
mainState: State<MainSettingsState>,
mainState: State<MainState>,
storagePermissionState: PermissionState,
notificationsPermissionState: PermissionState,
onEvent: (StartEvent) -> Unit,

View file

@ -13,7 +13,7 @@ import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.theme_switcher.AppearanceSettingsThemeSwitcher
import ua.acclorite.book_story.presentation.ui.BookStoryTheme
@ -29,7 +29,7 @@ import ua.acclorite.book_story.presentation.ui.isPureDark
* Appearance settings.
*/
fun LazyListScope.startAppearanceScreen(
mainState: State<MainSettingsState>,
mainState: State<MainState>,
onMainEvent: (MainEvent) -> Unit,
themeContrastTheme: State<Theme>
) {

View file

@ -35,7 +35,7 @@ import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.ButtonItem
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainSettingsState
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.start.components.permissions.startPermissionsScreen
import ua.acclorite.book_story.presentation.screens.start.data.StartEvent
import ua.acclorite.book_story.presentation.screens.start.data.StartNavigationScreen
@ -49,7 +49,7 @@ import ua.acclorite.book_story.presentation.ui.Theme
@Composable
fun StartSettings(
state: State<StartState>,
mainState: State<MainSettingsState>,
mainState: State<MainState>,
onEvent: (StartEvent) -> Unit,
onMainEvent: (MainEvent) -> Unit,
storagePermissionState: PermissionState,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

@ -206,6 +206,7 @@
<string name="font_color_option">Колір шрифта</string>
<string name="check_for_updates_option">Перевіряти на оновлення</string>
<string name="check_for_updates_option_desc">Перевіряти на новішу версію застосунку при запуску</string>
<string name="side_padding_option">Бокові відступи</string>
<!-- Colors -->
<string name="red_color">Червоний</string>
@ -289,9 +290,16 @@
<string name="report_bug_option">Повідомити про помилку</string>
<string name="project_page_option">Сторінка проєкту</string>
<string name="support_option">Підтримати мою роботу</string>
<string name="licenses_option">Ліцензії з відкритим кодом</string>
<string name="credits_option">Заслуги</string>
<string name="slava_ukraini_option">Слава Україні! 🇺🇦</string>
<string name="slava_ukraini_option_desc">Героям Слава! 🐉</string>
<!-- Credits -->
<string name="credits_design">Дизайн</string>
<string name="credits_icon">Іконки</string>
<string name="credits_updates">Керування новими оновленнями</string>
<!-- Help -->
<string name="help_clickable_note_1">Будь ласка, зверніть увагу, що на</string>
<string name="help_clickable_note_2">такий виділений текст</string>
@ -464,6 +472,7 @@
<string name="required_content_desc">Необхідно</string>
<string name="reset_start_content_desc">Скинути початковий посібник</string>
<string name="hide_edit_title_content_desc">Вийти з редагування назви</string>
<string name="open_in_web_content_desc">Відкрити в браузері</string>
</resources>

View file

@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="InconsistentArrays">
<!-- Basic Strings -->
<string name="app_version" translatable="false">0.9.8</string>
<string name="app_version" translatable="false">0.9.9</string>
<string name="app_name">Book\'s Story</string>
<!-- Notifications -->
@ -225,6 +225,7 @@
<string name="font_color_option">Font color</string>
<string name="check_for_updates_option">Check for updates</string>
<string name="check_for_updates_option_desc">Check for newer app version on startup</string>
<string name="side_padding_option">Side padding</string>
<!-- Colors -->
<string name="red_color">Red</string>
@ -308,9 +309,16 @@
<string name="report_bug_option">Report a bug</string>
<string name="project_page_option">Project\'s page</string>
<string name="support_option">Support my work</string>
<string name="licenses_option">Open source licenses</string>
<string name="credits_option">Credits</string>
<string name="slava_ukraini_option">Slava Ukraini! 🇺🇦</string>
<string name="slava_ukraini_option_desc">Heroyam Slava! 🐉</string>
<!-- Credits -->
<string name="credits_design">Design</string>
<string name="credits_icon">Icons</string>
<string name="credits_updates">New updates managing</string>
<!-- Help -->
<string name="help_clickable_note_1">Please note, that</string>
<string name="help_clickable_note_2">such highlighted text</string>
@ -484,5 +492,6 @@
<string name="required_content_desc">Required</string>
<string name="reset_start_content_desc">Reset start guide</string>
<string name="hide_edit_title_content_desc">Exit title editing</string>
<string name="open_in_web_content_desc">Open in web</string>
</resources>