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 e0616cce..1defb39f 100644 Binary files a/app/src/main/res/drawable/browse_screen_outlined.png and b/app/src/main/res/drawable/browse_screen_outlined.png differ 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