From 43da347ae01106b43cebb699c15d113b7e572c7c Mon Sep 17 00:00:00 2001 From: acclorite Date: Wed, 24 Apr 2024 18:42:32 +0300 Subject: [PATCH] 0.9.9 - Open source licenses & Credits screens in About + Bug fixes(No check for updates in Release Build) + Side Padding setting in ReaderScreen. --- app/build.gradle.kts | 19 +- app/proguard-rules.pro | 14 +- .../java/ua/acclorite/book_story/Activity.kt | 16 ++ .../data/remote/dto/ReleaseResponse.kt | 10 +- .../data/repository/BookRepositoryImpl.kt | 10 +- .../book_story/domain/model/Credit.kt | 12 + .../domain/repository/BookRepository.kt | 4 +- .../domain/use_case/GetAllSettings.kt | 4 +- .../book_story/domain/util/Constants.kt | 78 ++++++ .../domain/util/DataStoreConstants.kt | 1 + .../book_story/presentation/data/MainEvent.kt | 1 + .../{MainSettingsState.kt => MainState.kt} | 28 ++- .../presentation/data/MainViewModel.kt | 19 +- .../presentation/data/Navigation.kt | 4 + .../presentation/screens/about/AboutScreen.kt | 19 ++ .../screens/about/components/AboutItem.kt | 4 +- .../about/components/AboutUpdateDialog.kt | 2 +- .../screens/about/data/AboutViewModel.kt | 2 +- .../about/nested/credits/CreditsScreen.kt | 107 ++++++++ .../nested/credits/components/CreditItem.kt | 81 ++++++ .../nested/license_info/LicenseInfoScreen.kt | 232 ++++++++++++++++++ .../license_info/data/LicenseInfoEvent.kt | 13 + .../license_info/data/LicenseInfoState.kt | 9 + .../license_info/data/LicenseInfoViewModel.kt | 74 ++++++ .../about/nested/licenses/LicensesScreen.kt | 114 +++++++++ .../nested/licenses/components/LicenseItem.kt | 92 +++++++ .../nested/licenses/data/LicensesState.kt | 9 + .../nested/licenses/data/LicensesViewModel.kt | 44 ++++ .../screens/reader/ReaderScreen.kt | 52 ++-- .../ReaderSettingsBottomSheet.kt | 17 +- .../screens/reader/data/ReaderViewModel.kt | 54 +++- .../nested/appearance/AppearanceSettings.kt | 4 +- .../AppearanceSettingsThemeSwitcher.kt | 4 +- .../nested/general/GeneralSettings.kt | 4 +- .../settings/nested/reader/ReaderSettings.kt | 17 +- .../presentation/screens/start/StartScreen.kt | 4 +- .../start/components/StartAppearanceScreen.kt | 4 +- .../screens/start/components/StartSettings.kt | 4 +- .../res/drawable/browse_screen_outlined.png | Bin 10620 -> 20625 bytes app/src/main/res/values-uk/strings.xml | 9 + app/src/main/res/values/strings.xml | 11 +- gradle.properties | 22 +- 42 files changed, 1116 insertions(+), 112 deletions(-) create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/model/Credit.kt rename app/src/main/java/ua/acclorite/book_story/presentation/data/{MainSettingsState.kt => MainState.kt} (90%) create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/credits/CreditsScreen.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/credits/components/CreditItem.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/LicenseInfoScreen.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoEvent.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoState.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoViewModel.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/LicensesScreen.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/components/LicenseItem.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/data/LicensesState.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/data/LicensesViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 07c5bf31..1b9d4d6c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,7 +16,7 @@ android { minSdk = 26 targetSdk = 34 versionCode = 1 - versionName = "0.9.8" + versionName = "0.9.9" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -52,13 +52,24 @@ android { packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" - excludes += "/META-INF/LICENSE.md" - excludes += "/META-INF/LICENSE-notice.md" excludes += "/META-INF/gradle/incremental.annotation.processors" } } } +aboutLibraries { + registerAndroidTasks = true + outputFileName = "aboutlibraries.json" + offlineMode = false + fetchRemoteLicense = true + fetchRemoteFunding = true + includePlatform = true + duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE + duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.SIMPLE + prettyPrint = false + filterVariants = arrayOf("debug", "release") +} + dependencies { // Default @@ -137,5 +148,5 @@ dependencies { // About open source libraries implementation("com.mikepenz:aboutlibraries-core:latest.release") - implementation("com.mikepenz:aboutlibraries-compose:latest.release") + implementation("com.mikepenz:aboutlibraries-compose-m3:latest.release") } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 8030281d..b9504bba 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,14 +1,7 @@ # Proguard-rules -dontwarn org.xmlpull.v1.** --dontwarn org.kxml2.io.** --dontwarn android.content.res.** --dontwarn org.slf4j.impl.StaticLoggerBinder -dontwarn com.gemalto.jp2.JP2Decoder -dontwarn com.google.auto.service.AutoService --dontwarn javax.annotation.processing.AbstractProcessor --dontwarn javax.annotation.processing.Filer --dontwarn javax.annotation.processing.Messager --dontwarn javax.annotation.processing.ProcessingEnvironment -dontwarn javax.lang.model.SourceVersion -dontwarn javax.lang.model.element.AnnotationMirror -dontwarn javax.lang.model.element.AnnotationValue @@ -38,6 +31,11 @@ -dontwarn javax.lang.model.util.SimpleTypeVisitor8 -dontwarn javax.lang.model.util.Types -dontwarn javax.tools.Diagnostic$Kind +-dontwarn org.kxml2.io.** +-keep class com.mikepenz.aboutlibraries.** { *; } -keep class org.xmlpull.** { *; } --keepclassmembers class org.xmlpull.** { *; } \ No newline at end of file +-keepclassmembers class org.xmlpull.** { *; } + +# Debug rule +#-keepnames class ** { *; } diff --git a/app/src/main/java/ua/acclorite/book_story/Activity.kt b/app/src/main/java/ua/acclorite/book_story/Activity.kt index 3b54b4d0..f96ec417 100644 --- a/app/src/main/java/ua/acclorite/book_story/Activity.kt +++ b/app/src/main/java/ua/acclorite/book_story/Activity.kt @@ -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() diff --git a/app/src/main/java/ua/acclorite/book_story/data/remote/dto/ReleaseResponse.kt b/app/src/main/java/ua/acclorite/book_story/data/remote/dto/ReleaseResponse.kt index 9af2515c..008539c8 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/remote/dto/ReleaseResponse.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/remote/dto/ReleaseResponse.kt @@ -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 ) diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt index 0f742b50..b8b22e77 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt @@ -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() + override suspend fun getAllSettings(scope: CoroutineScope): MainState { + val result = CompletableDeferred() 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) { diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/Credit.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/Credit.kt new file mode 100644 index 00000000..ea783d69 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/Credit.kt @@ -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, + val website: String +) diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt index 88de0407..8cf71987 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt @@ -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>> diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetAllSettings.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetAllSettings.kt index 3d8ad810..ab8a124e 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetAllSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetAllSettings.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt index 13adb7e2..d47c932d 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt @@ -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( diff --git a/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt index 717e483c..458424e4 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt @@ -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") } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt index 2217ea19..50bd5c42 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainSettingsState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt similarity index 90% rename from app/src/main/java/ua/acclorite/book_story/presentation/data/MainSettingsState.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt index fe688c05..875f040e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainSettingsState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt @@ -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 { - override fun createFromParcel(parcel: Parcel): MainSettingsState { - return MainSettingsState(parcel) + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MainState { + return MainState(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } /** - * Initializes [MainSettingsState] by given [Map]. + * Initializes [MainState] by given [Map]. */ - fun initialize(data: Map): MainSettingsState { + fun initialize(data: Map): 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 ) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt index 889688f5..e5664e58 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt @@ -44,8 +44,8 @@ class MainViewModel @Inject constructor( private val isSettingsReady = MutableStateFlow(false) private val isViewModelReady = MutableStateFlow(false) - private val _state: MutableStateFlow = MutableStateFlow( - stateHandle[Constants.MAIN_STATE] ?: MainSettingsState() + private val _state: MutableStateFlow = 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) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt index 6799720d..2c7afc11 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/Navigation.kt @@ -77,6 +77,10 @@ enum class Screen { READER_SETTINGS, ABOUT, + LICENSES, + LICENSES_INFO, + CREDITS, + HELP, START } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/AboutScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/AboutScreen.kt index 1577cfb2..07997b5b 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/AboutScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/AboutScreen.kt @@ -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), diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutItem.kt index 9d64e2a4..b8a7af7e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutItem.kt @@ -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 { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutUpdateDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutUpdateDialog.kt index 53446568..6e7cf5ca 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutUpdateDialog.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/components/AboutUpdateDialog.kt @@ -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 diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutViewModel.kt index 21b58e14..0a032ac1 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/data/AboutViewModel.kt @@ -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) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/credits/CreditsScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/credits/CreditsScreen.kt new file mode 100644 index 00000000..945f6533 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/credits/CreditsScreen.kt @@ -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() + } + ) + ) + } + } + } + } +} + + + + + + + diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/credits/components/CreditItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/credits/components/CreditItem.kt new file mode 100644 index 00000000..179ae583 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/credits/components/CreditItem.kt @@ -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) + ) + } + } + } + } +} + + + + + + + diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/LicenseInfoScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/LicenseInfoScreen.kt new file mode 100644 index 00000000..9f542e07 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/LicenseInfoScreen.kt @@ -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, + 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) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoEvent.kt new file mode 100644 index 00000000..f6267366 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoEvent.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoState.kt new file mode 100644 index 00000000..1961e2ab --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoState.kt @@ -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, +) \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoViewModel.kt new file mode 100644 index 00000000..423f94be --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/license_info/data/LicenseInfoViewModel.kt @@ -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 + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/LicensesScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/LicensesScreen.kt new file mode 100644 index 00000000..47090254 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/LicensesScreen.kt @@ -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, + 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) + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/components/LicenseItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/components/LicenseItem.kt new file mode 100644 index 00000000..4fa5fd40 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/components/LicenseItem.kt @@ -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) + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/data/LicensesState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/data/LicensesState.kt new file mode 100644 index 00000000..7508a4f3 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/data/LicensesState.kt @@ -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 = emptyList() +) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/data/LicensesViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/data/LicensesViewModel.kt new file mode 100644 index 00000000..af3e799f --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/data/LicensesViewModel.kt @@ -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 + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt index d90bb21e..3e4921b7 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt @@ -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, - mainState: State, + mainState: State, listState: LazyListState, loading: State, 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 ) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt index eab6691f..ae4e9a47 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/settings_bottom_sheet/ReaderSettingsBottomSheet.kt @@ -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, + mainState: State, 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", diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt index 31f1cbda..bba9718a 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt @@ -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) }, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt index c9987f48..be774a6e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/AppearanceSettings.kt @@ -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, + state: State, navigator: Navigator, onMainEvent: (MainEvent) -> Unit ) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt index af1f723d..86475bd3 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/theme_switcher/AppearanceSettingsThemeSwitcher.kt @@ -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, + state: State, onMainEvent: (MainEvent) -> Unit, verticalPadding: Dp = 8.dp ) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt index 8012747f..2a4b3772 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/general/GeneralSettings.kt @@ -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, + state: State, navigator: Navigator, onSettingsEvent: (SettingsEvent) -> Unit, onMainEvent: (MainEvent) -> Unit diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt index 523815ee..2519f3c6 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt @@ -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, + state: State, 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", diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/StartScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/StartScreen.kt index a2c21f30..0c6e43ca 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/StartScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/StartScreen.kt @@ -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, - mainState: State, + mainState: State, storagePermissionState: PermissionState, notificationsPermissionState: PermissionState, onEvent: (StartEvent) -> Unit, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartAppearanceScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartAppearanceScreen.kt index 5a1da06b..76bc98e9 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartAppearanceScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartAppearanceScreen.kt @@ -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, + mainState: State, onMainEvent: (MainEvent) -> Unit, themeContrastTheme: State ) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartSettings.kt index c9aeccb2..c66a3025 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartSettings.kt @@ -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, - mainState: State, + mainState: State, onEvent: (StartEvent) -> Unit, onMainEvent: (MainEvent) -> Unit, storagePermissionState: PermissionState, diff --git a/app/src/main/res/drawable/browse_screen_outlined.png b/app/src/main/res/drawable/browse_screen_outlined.png index e0616ccee783fbf94bfb6b85bcc6abee90378fdb..1defb39f7a222ada009038c02e5da192857b9337 100644 GIT binary patch literal 20625 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 literal 10620 zcmeHt_d6S2^gn9GCTh>3Nm_d=YKI^s_N-BR#hz6wsMV?{u{W_*>{45)5v$awRkNj9 zqe{ou`}4#1`$v2~_j&Gkp1aPu_dNHU`#R^D5gbYj-~bR25z*@EXd;P-NN$=WM3lE~ zs=-RZcQ+M#pq53Tv9C*Dh=ac~QAkLLsJoA6fRh8tS=86xHSbu7<7N;QO2;yQh=^|R zzf3&pTm6`b=oXQYzKIqTrGL}^zv+KZ;Q!APm^o&6bHn++KqTCl=oaZu65^}Be|}#l zog82Mx?})+kN$Ba_3bo-l7=xDd;XP4lYHBkB4fX_=EE85LfPnqor~!gt+f_f{yU(> z?)#r5M7h&tsM!zcgdw~`@l*}>+~V5$Obj^4Y~(i9>BSoboU;>!oE(vy(%1}RR^0`ky$&1_wMCMCxGh@G^s7L+yBKMbteRhtBQ!qNnqi!WZ7E`RNBvqlHfjs3eXn65=XsxQ>wd!RqMq?im z0IAIGb4}3@vnC{n&btgmEu2_-c~KX=g>l5pUkaqH6)78UB$Z?kta z4UIYkHGx zy3$aq2x7F2VOF-K2pt7s$hldVMSkTZYcZq zfR{!qlD&wt^c%%X&P4w7)sBMRqJ6u#&I&2fjd`)s0qF4 zzkaj$+~?GQa4{}pvsbi_v$N7HLp{~{2W4!9|9tzgpkvlj z+C!JY?N>rG{ty0%*VDAs{$1$|!rRRHhgxdfsh6$tgEGLeIoDkEAC!0sWq!+Aed%0G zj~(Putb0%&bj5g5{Vc$(VlwT)27o!>dKPwTcQDff>0W?;JCT^8bY{NpM!9+A98+FgOZxYYV;jBBHfl5{8T9QGvZ>So6h9%{qt+o3zZ<}dW zTaFZM_@9%XC*yg^rk8`ga>CJ~@wICPJp&?7Hr&hd?#x48(}=dA!wc}Vg(1uR^W&G4 z4W|0_R=leE^1p9+oYUAGDjm+Zst`*E5OB{FEBdlV5M`+&8A<#Vox1b;N*#pv^VOs3 zLpg@Iw1=lkHm=m?3a8#f#c4;?(@+%lpHW`z3`l;&>ut|KXP(99%poaEy1@;i~u)~w*6#2)cb%)u8NG_QRV zpmACay$(yFw#=)BdJx+bn=`lS6rek1gwm`6dsxzIKfy&r*;D0zjG1`MwA0Y#btWjr z``=2s*MCKGh*G`9#o|>-SAUB!1pXjXk&81vQ`k81Z7P&&+)kg{E+I~|j9iOV0M5Oh zu*?20!Kq=aIBfbHx_USVJ5`rKz1vsM4bi{7Wif5;W;f-I8ah#s7J133YD0Y+>bAUk zXCpdc#cerBkMU<$P=z};hy7b>H>=U>&B30kIee`{H?zCd`_RkY4<{f4Yw|o={VK~e z;>*AHa`akW^2FBH$jz01);)RR8kQt{voAmqwo89O9m(HZb0%ifX#qIoVS)D!=lZLX)CJK^iXU#SBPUC{s>6(Z-Xq{mV$HbZKxjs718bk ze=C}%c-=;xZlsKNOFK#2KfOH}%F_$7)s^av)p9YJi9g*)S$>>L2$HXBjuw_4xQik; zu%OWUohF>m9dF1NDr0cTbUP=6Z=oQcs7S}2Wx=!+9p!4gCh_cqO6a1vE3;&clRWE> z2V11$wqaG5JK_E{9fT7l770k0E_q!kxqq&hwL=qYIJU12v8Ji_p+cBeb4DiLTx89O z8Ugg(+(eeD_wa37-IK2B*RN511#cfAV)sR=Ubjk>Oe>fK3vSu|dnNt%Mf1Oo`hHDTIx0T}ek+zrhLW1v5{r1W8zU|OL-oe=5*_pb;deL^q1Dwv!hxX5nmxv< z@ODE-pnBpRj;G}{|5W4HwM_7QNud}EyHODOttK#b6oG(S=Y+_zD9h3NRa+;Dm$}*` zfX=?^X|aeOy`o&eA3X2Klo2eF+>~QL&n+7ne3s5RCt(Rve}fI`QEQ%%NE-NV5_nr* z5uNMi=(6p-8V}4UE2F?4B0vqVPqvs&9>C$98QisJ@Ygcxtn1wabypOvAn*3ma=wQqihIv9l2A+G?1@PM z)s9q-LnrGyGj^luTR{-Ja*$)py?a)hpKS0x_2c+Vfm%u?s08t8)P zmpH-K@&5{hKh@#ZCmX$^=mcghq@=gTuNB7?CM>x-jB`gk^DU~A z{b5X_0J9#``@-%wmd&X@d%?+FX>+I4C|9Esx~bbzB{&yGp)qWV(c?~dVy)S!!y9V& z;YDQEfLk>icjao-Qac^SlYaU?kqpV9mKjAJ6MdZn#W`k_ZHhSEY$7A8I$2FQoNC=H zmWVhGIe9ts<2W#5f(WP#>-1Yn9O?$$R^a#~5eSpC-SMxs%GGZsrdsNLWaPvA#_{tD z;jUB(wsFy_RhFgh_uvrvH=DI|4qfnj^U9i?rc1SjS-@^)JMoSuLTmeaIVh6hWsIZm zUxUtv{pU<__}PMF`w{7J?&m$fFpf7SrazgndSETJy|#KtGIv~0=tHdI+nCxku362W zl5GQ+r$+Y!K^I>efqF+&ta+dK_2s@$259dZc`;ph-5>Ue_N4VG(#Y9@}a+kD;=c`NPiAKN9S@hT%iU1)AV8 zxz)#)@sGpb%@Inn)o_{3*0fp}MQTyW-zss1ugR>Gv~i{6U#D&sW@=z$V=8HnXDS(f z2CVHr{{0e(`-n=h3_Ht>D;d31hyCH`mdZx94+_B^%tl8pWj7}L$0DTp*4`DuLvppajk^a#b#=dFzW);YG7z>#!pdd| zHS7Xh0gY-5Zbj8_R&))Ljg-%3re+cL>0q{TMT&9A#{RHdnfnGu#dGW!MW%;A5*6vO z+vHVqWxerm4RDZ3RH>qV$)uWmy_k%SE-84I)56IxjnW34si?#7VCFMR-407X30} zD-m8YFD-wsJE<^>{>ZM}MD8~s1DK5Iw_Kd*o{A1mOaKS+MC~&H)+k&jOGD5tCF{R{ z;Uo_k-jx`Ma0mT1fGlZRV8u-MNAUXk^FhXQ&iDS|2zx#_7VON|tIzk|>teF`^> zDp|PmqwNOdL`(I}<=tqNzj|0eiX;~|-;x|)!f2+b;;Rkhl^bgs8X#g|{RlS8ruhO4 z7~q&~2fU)rKc)A`XueqWjM#qA!}@1Ef*rpa)bt?^*u~l%SVq91rfhN-@u4cgPWHnd zR<)dUtugA0_Sy8{Kn5Xi^6P1**I+7ZMO^T8_aeuy?^L8nWM1-1K(W1%qRb&NCut_=ls|e9GzX|s(v~@JDt@fbbk0upKnaRnBvOK5#Wr53 zCc)I>iroT}x&jkL?27dxh@K-{B)E|JLYs2Mb=En#`2cGhRjdq=75G$%>vbkfwlDrI z^10xW6SeaPl6O@T-%;AWkYB*cd@T{#exAEX7a8%n;t4g>$e2mSD@tRg+Ul5Qri;** zlAD@!z9hvSMzClGjUy$_c(zO)Mj7 z#0sJ_p5QKYAbpR%4Ocp`+3`a1$!3yA9N;*Ce2-99>yWnxIt|H(VVhAyaVAEjB0t>H zGsso~iziw`3nfSUZ`AdPBtA54H=q8xu54n>LLJ+(8PAF10~3jm=t}RPmi9t0*wkT6 zCow!U8Sp7y`0h+9WNCj9)5)C&I%Vg2k)u!cr)116x0zISSsiOCpCDPV87*Q$AobQZ zvX$qQy?P-@-IO3Xw-K!fv7qr5E$+fK&7m=2BzKK=QA@sHQ=n=dNzJ|Jf-b}^*hU{4 zXaAPWx=9e9*SVmx;ugcW66tk?ale(@e0oi?Lu~|TF8Lh8PP$^N&9bLn)`9~8 zqw9B#T|~8wD(Srz(;*FL?ab@K(fmr9R6KPzzIV zZE>Z z4fqsy!~|m2{%@`w1=8TZxn!e*!5=YH{F#!c_O&x67>fF2Q1-2BXM`|!7AB0P?NCii zmjKE1ryq#&1{=pKjn~E3OX5H@^)$)smQCyS<3NEl$sYSEEt57N$9c)BH%ira+{A&@ z`Kk||X!KFQ4Iksbwh`@(aUTdDr-@Bl<}e8sl~&AiwB3yVg~6t1fKC~_&Yqh5+ke9S~m2|P`@-dQ$gDP?}sU&Vvd5+nAjBB7%;0L2h z>aPKclN}t(`FhPUsG*db3mr9bVe&F&s$@4HRNku~?_>XlbRm{S>`E=|%H&E>=AKnq zDC9SzC2LRkoD0$3g_R1-#bbULfY1Q@+p392Cmhapv9{zh<>f?p%E!n+QjxA`Itd}-goCS!NO6%2V!~5`t;14`-T}opY zh6H0bA+H&I5xQ927Frnl;3mcpbf&^Ih*6+y1C`!(Zxt7kD01}N%;5wx>k@S#cSDyp zfgZ2PEA=Z@0rI<=bt@ChIu#pmg{Wj#1vpzd#4XH&+-Wdpu+rGM&Eh{` zDZ0LHj4u6vKiNDa00f*ctV;Gl!Ns3RR*{6ptA88x7c*-7oX2;eodoF`P?G`$0-|Gp zT@+^2@zs`>atv4AIj~oKY7_`JhG-3Npg>))(0QH}bxa?y<{73>?LAnU{!#7C^V?(< zV#W6qRC?jZ!D35tS6L6s_4V~DNs2+oIQKMMMH1|SG_D4)QGWAaGX`6#Vhfs?J|;j0Qx=}%*P3tHQ+OjV60|Iy915|kDQJD zyHuuj*LgNIl_(#dq5i%}PYzH3(6iM{N$ao&1v_1I-f^4tL(O*syU4rGN(n^{6&Vgz zVr6R|GqpD#lnM9DbCiblY*tf%1I2==%8hp=EZixN90gf+78iC{h1ln}>Eyq_$AM(u zAF(1T0cMA=X)8L{3dC+?q!SA|ykAk{!14g~p&od3xA!|cx;H*Zw8XVFu?1dE_%|r` z>AwxJs!4XvG>I)d<%G%qly*?O^KqqX0ac={VTs+Pwz`n>%NB>M&a=Aqjk;ON|1^8i z92n1dn_YjpCU%gnieP;C!4aTLpJD@_zM|l|phN$S^$n^d_%-P$_SDek%c9laf3Hsj z&C5{g>6f{R#0qWimKg1kHa&YZ%uWlt3TREys@m1v};o(P?4C^ zwgfOHg*QAJXcfL z#U3a^%U5exQM54lH(2!djCq!_Q=(+n;$YzTimNsa( zU{v!!8dkPvgQ0EThvU*NaX0qwWTLFxtY<3c2FK6;v43~TU}d6Dk?~iuuOFfR zeW#3J0)Z9d7YnGj84AEJsmc*DSjTg5XGz<4ZUy9qmmYYAI-SLXaTImTj8v9JYG+-MtWdguj zy*`z4qvH^x`47dMY>U3}6x@29r%hsIH2;mv&E6-;I;!2&DjNh!g*z3&i)|vR$I9kX zKe(S%zX!{)T(_nAXQ&u`m*+P7*emkJ!!gJI-$-xPuF)}~TD~Z~=3ZiAt5JhEC*o3Q zXW$RNIa6hTy0Lcud*KA|&j?K?yjrIde|vnoJA}S(#jYgwS<-j`hT1URg1O*7s_o?7 z6|b6T?gc>hiE%1{nu~s|C%t0T8(NxG4x}ves{8)S(;ZQF9Q(IT2qr0@0w~!L6Th)m zcf}_aCJBB&5th#aR)bR2#ZsCucd@coYR6%y$%`=3(hr3Iv+1}Y9Nswmx-E&_(iXSj zrTGn1BTvI*KBE$}lO&{3?WjUpSQ?vZ&5u&{e(7i9JB0a!H9=7cni&m`F|esxe>SA! z8@XSc4CP~7g0mP3gro6xpX+`lDP$wHy7@F;A+*-XHS3ei-t!`3m*xmCmI6Mh0A*7z zwfST>+{pnaW2RkYa+ek5ngI)Tg`qyus`~X48OOO9tOq@I1tPUPZUglzUpg@s zjN)wF#z8}E=GC7c8D*I22tDV6JaagdDOQSR3z7N{j~dG`c^$x0I-Aaceh`_OV{Qh$ zL60U&6ii+N5WiBFKhuNNxH^(yfR^HY_8JAAnRl|)T9<8d46KTXNd+U*WlbTR6GIwq zzW9dpEsaJlh~*mdctd&a17-$vKe>UVRX3jx}6xebk&kP6#G--Z@{=9rBWk(trBnQUrS%Xe;^2TLHkv99STIb(rDQNphPibCca zkUd}D)cY7MyK{rmmdg0QUw-_TaA?rO_-(5A^;6ZNq4XPQmqf0LYqi}o3jTL$WqBHc z_GQm1C?ZPxzc8>)WzFXp0t7woPu^ek*b$PI7H3EM1n1S)a{^}T%^ar#$GC9PLarCY zFNa?HmPu|S-9khcxHBn>2orI*29m2uPSv2hcl+)#EolaYWc8EGxea_&yCjWek=`_H z8Y0alP!{!lk=|sna0)fgMe#%;IHAniT*N6#_JsJo&6L?J>1{@fU60)FyqBdvL?N3}|h1nJdSkZ8YTtY4_HFuU8v%qzKyWzE#oBD2~MNN(ioB2RHedd7lc6*cA#{JwM zG?5Xe`o@*y_RU$Q(n4gtJ)lakdz?cji}YSL@#O`deEjZjT3PwLUFo|6?bcMRg#3qn zbQMQ#QGay7Z_8@d}r2 zCLI^$`t!4O-OQ=DCnUyh$eGELKdS8}x$|PgvNgx>Oaab~>!}|M2DvRPZCw>FU1Z2N zt{d$|Ct;B2NtYSC`N-hU)&v9TBgrKn34`ux)=0KUMT2hc3(tMkxf&n!)$Y>d$~ALp ziZ@*k4Wy7HNuK>%lm>Y#x6_wiX`g<`;!jV@S3UK=8sz7ca{u%@HTi+d|{*MzWb)S}19+DN#m51(l5yWy1 z;-WMU?WR4i&P$g+(cwM^g|mtJxiiTe_=!2LGEUA5ZUZ_-Sg`Ygtt!!Mx4)Wb8K}RF zPN~zW6m^2$aS`c>@6xo3uMGqkXB)w8InGXO=^r}UP-ll_U*YZI*aF=j9kviR1PI9- z&TvZXez@&%rew3P7m6YupRxzwJ(|#Uvf`2yY;K5=^XMPRI3`a*yB)X>gq#HFF zv39J259aT$Zr=`_ZQG@kc^^sL&@S=33vGSX7tkgHt+vkdIf-sJ#@T-U@$m$cXgx6B zaN-a;fH1)KQOz-JFg~+YB#;==$3s`w)6H_MZOG~OEgxo^xrIDby7Ps}N}%z3RLANc zuseRp((>fR^7;%%hMb{pw5QOj62H3V8xv|ns@t6Scedd6fh=;?=@PBfRlYa71A`T!iS4_{q0ysiQH_4d;=u5I>edD0q zGcg~QBS#2)TrBy2u57Hn^tPSU4FRpZpKPb|w^nBxfE+Swz`?0Cc!U?{c1b=C#P zo%h9aSv{pL7M}Je*R7>@g+T*0UBOJQ0e#ozNrq?z0{8D z?eXMEum~QuJv$msf--EU%cu7$ho4do^@`rvU#YL^<`aFhuD?FbKDm&@`t)gTZ_!fJ zovn9oMkt>=e36fgz4V_CI(xELN-|V{-N`!pW1e@9nc=ya>>QLs#EGG?!Sa1a$l6Mh z)3bA2swYJ-9ba#2eUiA3P#P!sqoUg*IG&!3m5SZ)k&+aj_-{{{F7gKDTRM%2dcRG6 zjbz={S=?f}jPkPZBIv^eVp(7cP>hJ1hu0 zIkMHLI!Z+t?oGw{u9M7-N|GNvS|@sh`DDl7;??;$oIxc9otBl#&MwljnYnz`xR6R^ z(CNKtjQD?nBIDW>CNqP`d+@mF{I7rL#P@Ya=HVYt+{HUWrIcM&$LmiHm^vIy8I=Va zJFSUS>S2}bj|t2Ie-yI@Tw^p0ZJA5n3;nbv* zd61+tIwHDoqR@C&nO&q)Q4e@HSqh26QWK}wT;}J^Qzy&Jut!$@-TE|rdo2C^zBYM3#)reC!FyYo*)|cAJ!+S4a9{C{ zB10da=C&F*0OIpLG`Oqy&x*m{$BJnSa``P+%r*2xtRB@0pGDL-tsk4ZySpF!whFhh yvNHYI%l+-EoWsJ=_Oac?Pd&p20@0!0{}KPQML7~EZRP(9fz#E3Yc{Cc$NnEd=u?;g diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index b9ea46ee..1b7bf4d8 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -206,6 +206,7 @@ Колір шрифта Перевіряти на оновлення Перевіряти на новішу версію застосунку при запуску + Бокові відступи Червоний @@ -289,9 +290,16 @@ Повідомити про помилку Сторінка проєкту Підтримати мою роботу + Ліцензії з відкритим кодом + Заслуги Слава Україні! 🇺🇦 Героям Слава! 🐉 + + Дизайн + Іконки + Керування новими оновленнями + Будь ласка, зверніть увагу, що на такий виділений текст @@ -464,6 +472,7 @@ Необхідно Скинути початковий посібник Вийти з редагування назви + Відкрити в браузері \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 02bef385..38be0b01 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - 0.9.8 + 0.9.9 Book\'s Story @@ -225,6 +225,7 @@ Font color Check for updates Check for newer app version on startup + Side padding Red @@ -308,9 +309,16 @@ Report a bug Project\'s page Support my work + Open source licenses + Credits Slava Ukraini! 🇺🇦 Heroyam Slava! 🐉 + + Design + Icons + New updates managing + Please note, that such highlighted text @@ -484,5 +492,6 @@ Required Reset start guide Exit title editing + Open in web \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 2e7e7ed8..38388a5a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,24 +1,6 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -org.gradle.configuration-cache=true \ No newline at end of file +org.gradle.configuration-cache=true +android.enableR8.fullMode=false \ No newline at end of file