ColorPresets + Room Migration 4 → 5.
This commit is contained in:
parent
945634c653
commit
5b8ddb4dfb
41 changed files with 1694 additions and 140 deletions
|
|
@ -165,4 +165,7 @@ dependencies {
|
||||||
// About open source libraries
|
// About open source libraries
|
||||||
implementation("com.mikepenz:aboutlibraries-core:latest.release")
|
implementation("com.mikepenz:aboutlibraries-core:latest.release")
|
||||||
implementation("com.mikepenz:aboutlibraries-compose-m3:latest.release")
|
implementation("com.mikepenz:aboutlibraries-compose-m3:latest.release")
|
||||||
|
|
||||||
|
// Drag & Drop
|
||||||
|
implementation("sh.calvin.reorderable:reorderable:2.2.0")
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 5,
|
||||||
|
"identityHash": "b91d8ccd2316f8f11c90b0118512208b",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "BookEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `author` TEXT, `description` TEXT, `textPath` TEXT NOT NULL, `filePath` TEXT NOT NULL, `scrollIndex` INTEGER NOT NULL, `scrollOffset` INTEGER NOT NULL, `progress` REAL NOT NULL, `image` TEXT, `category` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "author",
|
||||||
|
"columnName": "author",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "textPath",
|
||||||
|
"columnName": "textPath",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "filePath",
|
||||||
|
"columnName": "filePath",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "scrollIndex",
|
||||||
|
"columnName": "scrollIndex",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "scrollOffset",
|
||||||
|
"columnName": "scrollOffset",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "progress",
|
||||||
|
"columnName": "progress",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "image",
|
||||||
|
"columnName": "image",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "category",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "HistoryEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bookId` INTEGER NOT NULL, `time` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookId",
|
||||||
|
"columnName": "bookId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "time",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "ColorPresetEntity",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `backgroundColor` INTEGER NOT NULL, `fontColor` INTEGER NOT NULL, `isSelected` INTEGER NOT NULL, `order` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "backgroundColor",
|
||||||
|
"columnName": "backgroundColor",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "fontColor",
|
||||||
|
"columnName": "fontColor",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isSelected",
|
||||||
|
"columnName": "isSelected",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "order",
|
||||||
|
"columnName": "order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b91d8ccd2316f8f11c90b0118512208b')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ import ua.acclorite.book_story.presentation.screens.library.LibraryScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.ReaderScreenRoot
|
import ua.acclorite.book_story.presentation.screens.reader.ReaderScreenRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.SettingsScreenRoot
|
import ua.acclorite.book_story.presentation.screens.settings.SettingsScreenRoot
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettingsRoot
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettingsRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettingsRoot
|
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettingsRoot
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettingsRoot
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.ReaderSettingsRoot
|
||||||
|
|
@ -53,11 +54,12 @@ import java.lang.reflect.Field
|
||||||
@SuppressLint("DiscouragedPrivateApi")
|
@SuppressLint("DiscouragedPrivateApi")
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class Activity : AppCompatActivity() {
|
class Activity : AppCompatActivity() {
|
||||||
// Initializing all viewModels on app startup
|
// Initializing all required viewModels on app startup
|
||||||
private val mainViewModel: MainViewModel by viewModels()
|
private val mainViewModel: MainViewModel by viewModels()
|
||||||
private val libraryViewModel: LibraryViewModel by viewModels()
|
private val libraryViewModel: LibraryViewModel by viewModels()
|
||||||
private val historyViewModel: HistoryViewModel by viewModels()
|
private val historyViewModel: HistoryViewModel by viewModels()
|
||||||
private val browseViewModel: BrowseViewModel by viewModels()
|
private val browseViewModel: BrowseViewModel by viewModels()
|
||||||
|
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -72,7 +74,7 @@ class Activity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializing all variables
|
// Initializing all variables
|
||||||
mainViewModel.init(libraryViewModel)
|
mainViewModel.init(libraryViewModel, settingsViewModel)
|
||||||
|
|
||||||
// Splash screen
|
// Splash screen
|
||||||
installSplashScreen().apply {
|
installSplashScreen().apply {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ object AppModule {
|
||||||
)
|
)
|
||||||
.addMigrations(
|
.addMigrations(
|
||||||
DatabaseHelper.MIGRATION_2_3, // creates LanguageHistoryEntity table(if does not exist)
|
DatabaseHelper.MIGRATION_2_3, // creates LanguageHistoryEntity table(if does not exist)
|
||||||
|
DatabaseHelper.MIGRATION_4_5, // creates ColorPresetEntity table(if does not exist)
|
||||||
)
|
)
|
||||||
.allowMainThreadQueries()
|
.allowMainThreadQueries()
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import ua.acclorite.book_story.data.local.notification.UpdatesNotificationServic
|
||||||
import ua.acclorite.book_story.data.local.notification.UpdatesNotificationServiceImpl
|
import ua.acclorite.book_story.data.local.notification.UpdatesNotificationServiceImpl
|
||||||
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
||||||
import ua.acclorite.book_story.data.mapper.book.BookMapperImpl
|
import ua.acclorite.book_story.data.mapper.book.BookMapperImpl
|
||||||
|
import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapper
|
||||||
|
import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapperImpl
|
||||||
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
||||||
import ua.acclorite.book_story.data.mapper.history.HistoryMapperImpl
|
import ua.acclorite.book_story.data.mapper.history.HistoryMapperImpl
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
|
|
@ -47,6 +49,12 @@ abstract class RepositoryModule {
|
||||||
historyMapperImpl: HistoryMapperImpl
|
historyMapperImpl: HistoryMapperImpl
|
||||||
): HistoryMapper
|
): HistoryMapper
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindColorPresetMapper(
|
||||||
|
colorPresetMapperImpl: ColorPresetMapperImpl
|
||||||
|
): ColorPresetMapper
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
abstract fun bindFileParser(
|
abstract fun bindFileParser(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ua.acclorite.book_story.data.local.dto
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class ColorPresetEntity(
|
||||||
|
@PrimaryKey(true)
|
||||||
|
val id: Int? = null,
|
||||||
|
val name: String?,
|
||||||
|
val backgroundColor: Long,
|
||||||
|
val fontColor: Long,
|
||||||
|
val isSelected: Boolean,
|
||||||
|
val order: Int
|
||||||
|
)
|
||||||
|
|
@ -6,7 +6,9 @@ import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
|
import androidx.room.Upsert
|
||||||
import ua.acclorite.book_story.data.local.dto.BookEntity
|
import ua.acclorite.book_story.data.local.dto.BookEntity
|
||||||
|
import ua.acclorite.book_story.data.local.dto.ColorPresetEntity
|
||||||
import ua.acclorite.book_story.data.local.dto.HistoryEntity
|
import ua.acclorite.book_story.data.local.dto.HistoryEntity
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
|
@ -57,4 +59,23 @@ interface BookDao {
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
suspend fun deleteHistory(history: List<HistoryEntity>)
|
suspend fun deleteHistory(history: List<HistoryEntity>)
|
||||||
|
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
suspend fun updateColorPreset(colorPreset: ColorPresetEntity)
|
||||||
|
|
||||||
|
@Query("SELECT `order` FROM colorpresetentity WHERE :id=id")
|
||||||
|
suspend fun getColorPresetOrder(id: Int): Int
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM colorpresetentity")
|
||||||
|
suspend fun getColorPresetsSize(): Int
|
||||||
|
|
||||||
|
@Query("SELECT * FROM colorpresetentity")
|
||||||
|
suspend fun getColorPresets(): List<ColorPresetEntity>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteColorPreset(colorPreset: ColorPresetEntity)
|
||||||
|
|
||||||
|
@Query("DELETE FROM colorpresetentity")
|
||||||
|
suspend fun deleteColorPresets()
|
||||||
}
|
}
|
||||||
|
|
@ -9,18 +9,21 @@ import androidx.room.migration.AutoMigrationSpec
|
||||||
import androidx.room.migration.Migration
|
import androidx.room.migration.Migration
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import ua.acclorite.book_story.data.local.dto.BookEntity
|
import ua.acclorite.book_story.data.local.dto.BookEntity
|
||||||
|
import ua.acclorite.book_story.data.local.dto.ColorPresetEntity
|
||||||
import ua.acclorite.book_story.data.local.dto.HistoryEntity
|
import ua.acclorite.book_story.data.local.dto.HistoryEntity
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
BookEntity::class,
|
BookEntity::class,
|
||||||
HistoryEntity::class
|
HistoryEntity::class,
|
||||||
|
ColorPresetEntity::class,
|
||||||
],
|
],
|
||||||
version = 4,
|
version = 5,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration(1, 2),
|
AutoMigration(1, 2),
|
||||||
AutoMigration(2, 3),
|
AutoMigration(2, 3),
|
||||||
AutoMigration(3, 4, spec = DatabaseHelper.MIGRATION_3_4::class),
|
AutoMigration(3, 4, spec = DatabaseHelper.MIGRATION_3_4::class),
|
||||||
|
AutoMigration(4, 5),
|
||||||
],
|
],
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
)
|
)
|
||||||
|
|
@ -50,4 +53,19 @@ object DatabaseHelper {
|
||||||
@DeleteColumn("BookEntity", "translateWhenOpen")
|
@DeleteColumn("BookEntity", "translateWhenOpen")
|
||||||
@DeleteTable("LanguageHistoryEntity")
|
@DeleteTable("LanguageHistoryEntity")
|
||||||
class MIGRATION_3_4 : AutoMigrationSpec
|
class MIGRATION_3_4 : AutoMigrationSpec
|
||||||
|
|
||||||
|
val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL(
|
||||||
|
"CREATE TABLE IF NOT EXISTS `ColorPresetEntity` (" +
|
||||||
|
"`id` INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||||
|
"`name` TEXT, " +
|
||||||
|
"`backgroundColor` INTEGER NOT NULL, " +
|
||||||
|
"`fontColor` INTEGER NOT NULL, " +
|
||||||
|
"`isSelected` INTEGER NOT NULL, " +
|
||||||
|
"`order` INTEGER NOT NULL" +
|
||||||
|
")"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ua.acclorite.book_story.data.mapper.color_preset
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.data.local.dto.ColorPresetEntity
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
|
||||||
|
interface ColorPresetMapper {
|
||||||
|
suspend fun toColorPresetEntity(colorPreset: ColorPreset, order: Int): ColorPresetEntity
|
||||||
|
|
||||||
|
suspend fun toColorPreset(colorPresetEntity: ColorPresetEntity): ColorPreset
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package ua.acclorite.book_story.data.mapper.color_preset
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import ua.acclorite.book_story.data.local.dto.ColorPresetEntity
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ColorPresetMapperImpl @Inject constructor() : ColorPresetMapper {
|
||||||
|
|
||||||
|
override suspend fun toColorPresetEntity(
|
||||||
|
colorPreset: ColorPreset,
|
||||||
|
order: Int
|
||||||
|
): ColorPresetEntity {
|
||||||
|
return ColorPresetEntity(
|
||||||
|
id = if (colorPreset.id != -1) colorPreset.id
|
||||||
|
else null,
|
||||||
|
name = colorPreset.name,
|
||||||
|
backgroundColor = colorPreset.backgroundColor.value.toLong(),
|
||||||
|
fontColor = colorPreset.fontColor.value.toLong(),
|
||||||
|
isSelected = colorPreset.isSelected,
|
||||||
|
order = order
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun toColorPreset(colorPresetEntity: ColorPresetEntity): ColorPreset {
|
||||||
|
return ColorPreset(
|
||||||
|
id = colorPresetEntity.id!!,
|
||||||
|
name = colorPresetEntity.name,
|
||||||
|
backgroundColor = Color(colorPresetEntity.backgroundColor.toULong()),
|
||||||
|
fontColor = Color(colorPresetEntity.fontColor.toULong()),
|
||||||
|
isSelected = colorPresetEntity.isSelected
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,12 +22,14 @@ import ua.acclorite.book_story.data.local.data_store.DataStore
|
||||||
import ua.acclorite.book_story.data.local.notification.UpdatesNotificationService
|
import ua.acclorite.book_story.data.local.notification.UpdatesNotificationService
|
||||||
import ua.acclorite.book_story.data.local.room.BookDao
|
import ua.acclorite.book_story.data.local.room.BookDao
|
||||||
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
||||||
|
import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapper
|
||||||
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.data.parser.TextParser
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
import ua.acclorite.book_story.data.remote.GithubAPI
|
import ua.acclorite.book_story.data.remote.GithubAPI
|
||||||
import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo
|
import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
|
@ -55,6 +57,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
private val bookMapper: BookMapper,
|
private val bookMapper: BookMapper,
|
||||||
private val historyMapper: HistoryMapper,
|
private val historyMapper: HistoryMapper,
|
||||||
|
private val colorPresetMapper: ColorPresetMapper,
|
||||||
|
|
||||||
private val fileParser: FileParser,
|
private val fileParser: FileParser,
|
||||||
private val textParser: TextParser
|
private val textParser: TextParser
|
||||||
|
|
@ -667,6 +670,50 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun updateColorPreset(colorPreset: ColorPreset) {
|
||||||
|
database.updateColorPreset(
|
||||||
|
colorPresetMapper.toColorPresetEntity(
|
||||||
|
colorPreset,
|
||||||
|
if (colorPreset.id != -1) database.getColorPresetOrder(colorPreset.id)
|
||||||
|
else database.getColorPresetsSize()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun selectColorPreset(colorPreset: ColorPreset) {
|
||||||
|
database.getColorPresets().map {
|
||||||
|
it.copy(
|
||||||
|
isSelected = it.id == colorPreset.id
|
||||||
|
)
|
||||||
|
}.forEach {
|
||||||
|
database.updateColorPreset(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getColorPresets(): List<ColorPreset> {
|
||||||
|
return database.getColorPresets()
|
||||||
|
.sortedBy { it.order }
|
||||||
|
.map { colorPresetMapper.toColorPreset(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun reorderColorPresets(orderedColorPresets: List<ColorPreset>) {
|
||||||
|
database.deleteColorPresets()
|
||||||
|
|
||||||
|
orderedColorPresets.forEachIndexed { index, colorPreset ->
|
||||||
|
database.updateColorPreset(
|
||||||
|
colorPresetMapper.toColorPresetEntity(colorPreset, order = index)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteColorPreset(colorPreset: ColorPreset) {
|
||||||
|
database.deleteColorPreset(
|
||||||
|
colorPresetMapper.toColorPresetEntity(
|
||||||
|
colorPreset, -1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ua.acclorite.book_story.domain.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class ColorPreset(
|
||||||
|
val id: Int,
|
||||||
|
val name: String?,
|
||||||
|
val backgroundColor: Color,
|
||||||
|
val fontColor: Color,
|
||||||
|
val isSelected: Selected
|
||||||
|
)
|
||||||
|
|
@ -5,6 +5,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo
|
import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
|
|
@ -89,5 +90,17 @@ interface BookRepository {
|
||||||
history: List<History>
|
history: List<History>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
suspend fun checkForUpdates(postNotification: Boolean): LatestReleaseInfo?
|
suspend fun checkForUpdates(postNotification: Boolean): LatestReleaseInfo?
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun updateColorPreset(colorPreset: ColorPreset)
|
||||||
|
|
||||||
|
suspend fun selectColorPreset(colorPreset: ColorPreset)
|
||||||
|
|
||||||
|
suspend fun getColorPresets(): List<ColorPreset>
|
||||||
|
|
||||||
|
suspend fun reorderColorPresets(orderedColorPresets: List<ColorPreset>)
|
||||||
|
|
||||||
|
suspend fun deleteColorPreset(colorPreset: ColorPreset)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DeleteColorPreset @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(colorPreset: ColorPreset) {
|
||||||
|
repository.deleteColorPreset(colorPreset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetColorPresets @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(): List<ColorPreset> {
|
||||||
|
return repository.getColorPresets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ReorderColorPresets @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(reorderedColorPresets: List<ColorPreset>) {
|
||||||
|
repository.reorderColorPresets(reorderedColorPresets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SelectColorPreset @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(colorPreset: ColorPreset) {
|
||||||
|
repository.selectColorPreset(colorPreset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdateColorPreset @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(colorPreset: ColorPreset) {
|
||||||
|
repository.updateColorPreset(colorPreset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package ua.acclorite.book_story.domain.util
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
|
@ -10,6 +11,7 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.Badge
|
import ua.acclorite.book_story.domain.model.Badge
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
import ua.acclorite.book_story.domain.model.Credit
|
import ua.acclorite.book_story.domain.model.Credit
|
||||||
import ua.acclorite.book_story.domain.model.FontWithName
|
import ua.acclorite.book_story.domain.model.FontWithName
|
||||||
import ua.acclorite.book_story.domain.model.NavigationItem
|
import ua.acclorite.book_story.domain.model.NavigationItem
|
||||||
|
|
@ -295,4 +297,12 @@ object Constants {
|
||||||
lastOpened = null,
|
lastOpened = null,
|
||||||
category = Category.READING
|
category = Category.READING
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val DEFAULT_COLOR_PRESET = ColorPreset(
|
||||||
|
id = -1,
|
||||||
|
name = null,
|
||||||
|
backgroundColor = Color.DarkGray,
|
||||||
|
fontColor = Color.LightGray,
|
||||||
|
isSelected = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package ua.acclorite.book_story.domain.util
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
import androidx.datastore.preferences.core.longPreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
|
||||||
object DataStoreConstants {
|
object DataStoreConstants {
|
||||||
|
|
@ -13,8 +12,6 @@ object DataStoreConstants {
|
||||||
val PURE_DARK = stringPreferencesKey("pure_dark")
|
val PURE_DARK = stringPreferencesKey("pure_dark")
|
||||||
val THEME_CONTRAST = stringPreferencesKey("theme_contrast")
|
val THEME_CONTRAST = stringPreferencesKey("theme_contrast")
|
||||||
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
|
val SHOW_START_SCREEN = booleanPreferencesKey("guide")
|
||||||
val BACKGROUND_COLOR = longPreferencesKey("background_color")
|
|
||||||
val FONT_COLOR = longPreferencesKey("font_color")
|
|
||||||
val FONT = stringPreferencesKey("font")
|
val FONT = stringPreferencesKey("font")
|
||||||
val IS_ITALIC = booleanPreferencesKey("font_style")
|
val IS_ITALIC = booleanPreferencesKey("font_style")
|
||||||
val FONT_SIZE = intPreferencesKey("font_size")
|
val FONT_SIZE = intPreferencesKey("font_size")
|
||||||
|
|
@ -24,4 +21,5 @@ object DataStoreConstants {
|
||||||
val CHECK_FOR_UPDATES = booleanPreferencesKey("check_for_updates")
|
val CHECK_FOR_UPDATES = booleanPreferencesKey("check_for_updates")
|
||||||
val SIDE_PADDING = intPreferencesKey("side_padding")
|
val SIDE_PADDING = intPreferencesKey("side_padding")
|
||||||
val DOUBLE_CLICK_TRANSLATION = booleanPreferencesKey("double_click_translation")
|
val DOUBLE_CLICK_TRANSLATION = booleanPreferencesKey("double_click_translation")
|
||||||
|
val FAST_COLOR_PRESET_CHANGE = booleanPreferencesKey("fast_color_preset_change")
|
||||||
}
|
}
|
||||||
|
|
@ -20,11 +20,11 @@ fun <T> LazyListScope.customItems(
|
||||||
|
|
||||||
fun <T> LazyListScope.customItemsIndexed(
|
fun <T> LazyListScope.customItemsIndexed(
|
||||||
items: List<T>,
|
items: List<T>,
|
||||||
key: (T) -> Any?,
|
key: (T, Int) -> Any?,
|
||||||
content: @Composable LazyItemScope.(Int, T) -> Unit
|
content: @Composable LazyItemScope.(Int, T) -> Unit
|
||||||
) {
|
) {
|
||||||
for ((index, item) in items.withIndex()) {
|
for ((index, item) in items.withIndex()) {
|
||||||
item(key = key(item)) {
|
item(key = key(item, index)) {
|
||||||
content(index, item)
|
content(index, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,9 @@ sealed class MainEvent {
|
||||||
data class OnChangeLineHeight(val lineHeight: Int) : MainEvent()
|
data class OnChangeLineHeight(val lineHeight: Int) : MainEvent()
|
||||||
data class OnChangeParagraphHeight(val paragraphHeight: Int) : MainEvent()
|
data class OnChangeParagraphHeight(val paragraphHeight: Int) : MainEvent()
|
||||||
data class OnChangeParagraphIndentation(val bool: Boolean) : MainEvent()
|
data class OnChangeParagraphIndentation(val bool: Boolean) : MainEvent()
|
||||||
data class OnChangeBackgroundColor(val color: Long) : MainEvent()
|
|
||||||
data class OnChangeFontColor(val color: Long) : MainEvent()
|
|
||||||
data class OnChangeShowStartScreen(val bool: Boolean) : MainEvent()
|
data class OnChangeShowStartScreen(val bool: Boolean) : MainEvent()
|
||||||
data class OnChangeCheckForUpdates(val bool: Boolean) : MainEvent()
|
data class OnChangeCheckForUpdates(val bool: Boolean) : MainEvent()
|
||||||
data class OnChangeSidePadding(val sidePadding: Int) : MainEvent()
|
data class OnChangeSidePadding(val sidePadding: Int) : MainEvent()
|
||||||
data class OnChangeDoubleClickTranslation(val bool: Boolean) : MainEvent()
|
data class OnChangeDoubleClickTranslation(val bool: Boolean) : MainEvent()
|
||||||
|
data class OnChangeFastColorPresetChange(val bool: Boolean) : MainEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import android.os.Build
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
||||||
|
|
@ -38,12 +37,11 @@ data class MainState(
|
||||||
val lineHeight: Int? = null,
|
val lineHeight: Int? = null,
|
||||||
val paragraphHeight: Int? = null,
|
val paragraphHeight: Int? = null,
|
||||||
val paragraphIndentation: Boolean? = null,
|
val paragraphIndentation: Boolean? = null,
|
||||||
val backgroundColor: Long? = null,
|
|
||||||
val fontColor: Long? = null,
|
|
||||||
val showStartScreen: Boolean? = null,
|
val showStartScreen: Boolean? = null,
|
||||||
val checkForUpdates: Boolean? = null,
|
val checkForUpdates: Boolean? = null,
|
||||||
val sidePadding: Int? = null,
|
val sidePadding: Int? = null,
|
||||||
val doubleClickTranslation: Boolean? = null
|
val doubleClickTranslation: Boolean? = null,
|
||||||
|
val fastColorPresetChange: Boolean? = null,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,12 +73,6 @@ data class MainState(
|
||||||
val showStartScreen: Boolean = data[SHOW_START_SCREEN.name] as? Boolean
|
val showStartScreen: Boolean = data[SHOW_START_SCREEN.name] as? Boolean
|
||||||
?: true
|
?: true
|
||||||
|
|
||||||
val backgroundColor: Long = data[BACKGROUND_COLOR.name] as? Long
|
|
||||||
?: Color.DarkGray.value.toLong()
|
|
||||||
|
|
||||||
val fontColor: Long = data[FONT_COLOR.name] as? Long
|
|
||||||
?: Color.LightGray.value.toLong()
|
|
||||||
|
|
||||||
val fontFamily: String = data[FONT.name] as? String
|
val fontFamily: String = data[FONT.name] as? String
|
||||||
?: Constants.FONTS[0].id
|
?: Constants.FONTS[0].id
|
||||||
|
|
||||||
|
|
@ -109,6 +101,10 @@ data class MainState(
|
||||||
data[DOUBLE_CLICK_TRANSLATION.name] as? Boolean
|
data[DOUBLE_CLICK_TRANSLATION.name] as? Boolean
|
||||||
?: false
|
?: false
|
||||||
|
|
||||||
|
val fastColorPresetChange: Boolean =
|
||||||
|
data[FAST_COLOR_PRESET_CHANGE.name] as? Boolean
|
||||||
|
?: true
|
||||||
|
|
||||||
return MainState(
|
return MainState(
|
||||||
language = language,
|
language = language,
|
||||||
theme = theme.toTheme(),
|
theme = theme.toTheme(),
|
||||||
|
|
@ -116,8 +112,6 @@ data class MainState(
|
||||||
pureDark = pureDark.toPureDark(),
|
pureDark = pureDark.toPureDark(),
|
||||||
themeContrast = themeContrast.toThemeContrast(),
|
themeContrast = themeContrast.toThemeContrast(),
|
||||||
showStartScreen = showStartScreen,
|
showStartScreen = showStartScreen,
|
||||||
backgroundColor = backgroundColor,
|
|
||||||
fontColor = fontColor,
|
|
||||||
fontFamily = fontFamily,
|
fontFamily = fontFamily,
|
||||||
isItalic = isItalic,
|
isItalic = isItalic,
|
||||||
fontSize = fontSize,
|
fontSize = fontSize,
|
||||||
|
|
@ -126,7 +120,8 @@ data class MainState(
|
||||||
paragraphIndentation = paragraphIndentation,
|
paragraphIndentation = paragraphIndentation,
|
||||||
checkForUpdates = checkForUpdates,
|
checkForUpdates = checkForUpdates,
|
||||||
sidePadding = sidePadding,
|
sidePadding = sidePadding,
|
||||||
doubleClickTranslation = doubleClickTranslation
|
doubleClickTranslation = doubleClickTranslation,
|
||||||
|
fastColorPresetChange = fastColorPresetChange
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import ua.acclorite.book_story.domain.use_case.SetDatastore
|
||||||
import ua.acclorite.book_story.domain.util.Constants
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
import ua.acclorite.book_story.domain.util.DataStoreConstants
|
||||||
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsViewModel
|
||||||
import ua.acclorite.book_story.presentation.ui.toDarkTheme
|
import ua.acclorite.book_story.presentation.ui.toDarkTheme
|
||||||
import ua.acclorite.book_story.presentation.ui.toPureDark
|
import ua.acclorite.book_story.presentation.ui.toPureDark
|
||||||
import ua.acclorite.book_story.presentation.ui.toTheme
|
import ua.acclorite.book_story.presentation.ui.toTheme
|
||||||
|
|
@ -173,28 +174,6 @@ class MainViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainEvent.OnChangeBackgroundColor -> {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
setDatastore.execute(DataStoreConstants.BACKGROUND_COLOR, event.color)
|
|
||||||
updateStateWithSavedHandle {
|
|
||||||
it.copy(
|
|
||||||
backgroundColor = event.color
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is MainEvent.OnChangeFontColor -> {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
setDatastore.execute(DataStoreConstants.FONT_COLOR, event.color)
|
|
||||||
updateStateWithSavedHandle {
|
|
||||||
it.copy(
|
|
||||||
fontColor = event.color
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is MainEvent.OnChangeShowStartScreen -> {
|
is MainEvent.OnChangeShowStartScreen -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
setDatastore.execute(DataStoreConstants.SHOW_START_SCREEN, event.bool)
|
setDatastore.execute(DataStoreConstants.SHOW_START_SCREEN, event.bool)
|
||||||
|
|
@ -238,11 +217,23 @@ class MainViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeFastColorPresetChange -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(DataStoreConstants.FAST_COLOR_PRESET_CHANGE, event.bool)
|
||||||
|
updateStateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
fastColorPresetChange = event.bool
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(
|
fun init(
|
||||||
libraryViewModel: LibraryViewModel
|
libraryViewModel: LibraryViewModel,
|
||||||
|
settingsViewModel: SettingsViewModel,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
val settings = getAllSettings.execute(viewModelScope)
|
val settings = getAllSettings.execute(viewModelScope)
|
||||||
|
|
@ -265,7 +256,12 @@ class MainViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
libraryViewModel.isReady.collectLatest { ready ->
|
combine(
|
||||||
|
libraryViewModel.isReady,
|
||||||
|
settingsViewModel.isReady
|
||||||
|
) { (libraryViewModelReady, settingsViewModelReady) ->
|
||||||
|
libraryViewModelReady && settingsViewModelReady
|
||||||
|
}.collectLatest { ready ->
|
||||||
isViewModelReady.update {
|
isViewModelReady.update {
|
||||||
ready
|
ready
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
|
@ -34,7 +35,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
|
|
@ -67,11 +67,15 @@ import ua.acclorite.book_story.presentation.screens.reader.components.ReaderEndI
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderTextParagraph
|
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderTextParagraph
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderBottomBar
|
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderBottomBar
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderTopBar
|
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderTopBar
|
||||||
|
import ua.acclorite.book_story.presentation.screens.reader.components.readerFastColorPresetChange
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.settings_bottom_sheet.ReaderSettingsBottomSheet
|
import ua.acclorite.book_story.presentation.screens.reader.components.settings_bottom_sheet.ReaderSettingsBottomSheet
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.start_item.ReaderStartItem
|
import ua.acclorite.book_story.presentation.screens.reader.components.start_item.ReaderStartItem
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsState
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderScreenRoot(screen: Screen.Reader) {
|
fun ReaderScreenRoot(screen: Screen.Reader) {
|
||||||
|
|
@ -82,9 +86,11 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
|
||||||
val mainViewModel: MainViewModel = hiltViewModel()
|
val mainViewModel: MainViewModel = hiltViewModel()
|
||||||
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
||||||
val historyViewModel: HistoryViewModel = hiltViewModel()
|
val historyViewModel: HistoryViewModel = hiltViewModel()
|
||||||
|
val settingsViewModel: SettingsViewModel = hiltViewModel()
|
||||||
|
|
||||||
val state = viewModel.state.collectAsState()
|
val state = viewModel.state.collectAsState()
|
||||||
val mainState = mainViewModel.state.collectAsState()
|
val mainState = mainViewModel.state.collectAsState()
|
||||||
|
val settingsState = settingsViewModel.state.collectAsState()
|
||||||
|
|
||||||
val lazyListState = rememberSaveable(
|
val lazyListState = rememberSaveable(
|
||||||
state.value.listState,
|
state.value.listState,
|
||||||
|
|
@ -137,10 +143,12 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
|
||||||
ReaderScreen(
|
ReaderScreen(
|
||||||
state = state,
|
state = state,
|
||||||
mainState = mainState,
|
mainState = mainState,
|
||||||
|
settingsState = settingsState,
|
||||||
lazyListState = lazyListState,
|
lazyListState = lazyListState,
|
||||||
onNavigate = { navigator.it() },
|
onNavigate = { navigator.it() },
|
||||||
onEvent = viewModel::onEvent,
|
onEvent = viewModel::onEvent,
|
||||||
onMainEvent = mainViewModel::onEvent,
|
onMainEvent = mainViewModel::onEvent,
|
||||||
|
onSettingsEvent = settingsViewModel::onEvent,
|
||||||
onLibraryEvent = libraryViewModel::onEvent,
|
onLibraryEvent = libraryViewModel::onEvent,
|
||||||
onHistoryEvent = historyViewModel::onEvent
|
onHistoryEvent = historyViewModel::onEvent
|
||||||
)
|
)
|
||||||
|
|
@ -151,10 +159,12 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
|
||||||
private fun ReaderScreen(
|
private fun ReaderScreen(
|
||||||
state: State<ReaderState>,
|
state: State<ReaderState>,
|
||||||
mainState: State<MainState>,
|
mainState: State<MainState>,
|
||||||
|
settingsState: State<SettingsState>,
|
||||||
lazyListState: LazyListState,
|
lazyListState: LazyListState,
|
||||||
onNavigate: OnNavigate,
|
onNavigate: OnNavigate,
|
||||||
onEvent: (ReaderEvent) -> Unit,
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
onMainEvent: (MainEvent) -> Unit,
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
|
onSettingsEvent: (SettingsEvent) -> Unit,
|
||||||
onLibraryEvent: (LibraryEvent) -> Unit,
|
onLibraryEvent: (LibraryEvent) -> Unit,
|
||||||
onHistoryEvent: (HistoryEvent) -> Unit,
|
onHistoryEvent: (HistoryEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -186,12 +196,14 @@ private fun ReaderScreen(
|
||||||
it.id == mainState.value.fontFamily
|
it.id == mainState.value.fontFamily
|
||||||
} ?: Constants.FONTS[0]
|
} ?: Constants.FONTS[0]
|
||||||
}
|
}
|
||||||
val fontColor = remember(mainState.value.fontColor) {
|
val backgroundColor = animateColorAsState(
|
||||||
Color(mainState.value.fontColor!!.toULong())
|
settingsState.value.selectedColorPreset.backgroundColor,
|
||||||
}
|
label = ""
|
||||||
val backgroundColor = remember(mainState.value.backgroundColor) {
|
)
|
||||||
Color(mainState.value.backgroundColor!!.toULong())
|
val fontColor = animateColorAsState(
|
||||||
}
|
settingsState.value.selectedColorPreset.fontColor,
|
||||||
|
label = ""
|
||||||
|
)
|
||||||
val lineHeight =
|
val lineHeight =
|
||||||
remember(mainState.value.fontSize, mainState.value.lineHeight) {
|
remember(mainState.value.fontSize, mainState.value.lineHeight) {
|
||||||
(mainState.value.fontSize!! + mainState.value.lineHeight!!).sp
|
(mainState.value.fontSize!! + mainState.value.lineHeight!!).sp
|
||||||
|
|
@ -212,8 +224,10 @@ private fun ReaderScreen(
|
||||||
if (state.value.showSettingsBottomSheet) {
|
if (state.value.showSettingsBottomSheet) {
|
||||||
ReaderSettingsBottomSheet(
|
ReaderSettingsBottomSheet(
|
||||||
mainState = mainState,
|
mainState = mainState,
|
||||||
|
settingsState = settingsState,
|
||||||
onEvent = onEvent,
|
onEvent = onEvent,
|
||||||
onMainEvent = onMainEvent
|
onMainEvent = onMainEvent,
|
||||||
|
onSettingsEvent = onSettingsEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,7 +312,7 @@ private fun ReaderScreen(
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(backgroundColor)
|
.background(backgroundColor.value)
|
||||||
.then(
|
.then(
|
||||||
if (!state.value.loading && toolbarHidden) {
|
if (!state.value.loading && toolbarHidden) {
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -314,6 +328,25 @@ private fun ReaderScreen(
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
.readerFastColorPresetChange(
|
||||||
|
fastColorPresetChangeEnabled = mainState.value.fastColorPresetChange!!,
|
||||||
|
toolbarHidden = toolbarHidden,
|
||||||
|
onSettingsEvent = onSettingsEvent,
|
||||||
|
presetChanged = {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(
|
||||||
|
R.string.color_preset_selected_query,
|
||||||
|
it
|
||||||
|
.asString(context)
|
||||||
|
.trim()
|
||||||
|
),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(paragraphHeight)
|
verticalArrangement = Arrangement.spacedBy(paragraphHeight)
|
||||||
) {
|
) {
|
||||||
|
|
@ -332,7 +365,7 @@ private fun ReaderScreen(
|
||||||
line = line.second,
|
line = line.second,
|
||||||
context = context,
|
context = context,
|
||||||
fontFamily = fontFamily,
|
fontFamily = fontFamily,
|
||||||
fontColor = fontColor,
|
fontColor = fontColor.value,
|
||||||
lineHeight = lineHeight,
|
lineHeight = lineHeight,
|
||||||
fontStyle = fontStyle,
|
fontStyle = fontStyle,
|
||||||
fontSize = mainState.value.fontSize!!.sp,
|
fontSize = mainState.value.fontSize!!.sp,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader Fast Color Preset Change.
|
||||||
|
* Detects horizontal swipe gestures.
|
||||||
|
*
|
||||||
|
* @param fastColorPresetChangeEnabled Whether this is enabled.
|
||||||
|
* @param toolbarHidden Whether toolbar(selection) is hidden.
|
||||||
|
* @param onSettingsEvent [SettingsEvent] callback.
|
||||||
|
* @param presetChanged Callback when Color Preset was changed.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun Modifier.readerFastColorPresetChange(
|
||||||
|
fastColorPresetChangeEnabled: Boolean,
|
||||||
|
toolbarHidden: Boolean,
|
||||||
|
onSettingsEvent: (SettingsEvent) -> Unit,
|
||||||
|
presetChanged: (UIText) -> Unit
|
||||||
|
): Modifier {
|
||||||
|
val offset = remember { mutableFloatStateOf(0f) }
|
||||||
|
return this.then(
|
||||||
|
if (fastColorPresetChangeEnabled && toolbarHidden) {
|
||||||
|
Modifier.pointerInput(Unit) {
|
||||||
|
detectHorizontalDragGestures(
|
||||||
|
onDragStart = { offset.floatValue = 0f },
|
||||||
|
onDragEnd = {
|
||||||
|
when {
|
||||||
|
offset.floatValue > 200 -> {
|
||||||
|
onSettingsEvent(
|
||||||
|
SettingsEvent.OnSelectPreviousPreset {
|
||||||
|
presetChanged(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset.floatValue < -200 -> {
|
||||||
|
onSettingsEvent(
|
||||||
|
SettingsEvent.OnSelectNextPreset {
|
||||||
|
presetChanged(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { _, dragAmount ->
|
||||||
|
offset.floatValue += dragAmount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,8 @@ import ua.acclorite.book_story.presentation.components.CustomBottomSheet
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
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.reader.data.ReaderEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsState
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ColorsSubcategory
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ColorsSubcategory
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.ReaderSettingsCategory
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.ReaderSettingsCategory
|
||||||
|
|
||||||
|
|
@ -33,15 +35,19 @@ import ua.acclorite.book_story.presentation.screens.settings.nested.reader.compo
|
||||||
* Settings bottom sheet. Has General and Colors categories.
|
* Settings bottom sheet. Has General and Colors categories.
|
||||||
*
|
*
|
||||||
* @param mainState [MainState].
|
* @param mainState [MainState].
|
||||||
|
* @param settingsState [SettingsState].
|
||||||
* @param onEvent [ReaderEvent] callback.
|
* @param onEvent [ReaderEvent] callback.
|
||||||
* @param onMainEvent [MainEvent] callback.
|
* @param onMainEvent [MainEvent] callback.
|
||||||
|
* @param onSettingsEvent [SettingsEvent] callback.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderSettingsBottomSheet(
|
fun ReaderSettingsBottomSheet(
|
||||||
mainState: State<MainState>,
|
mainState: State<MainState>,
|
||||||
|
settingsState: State<SettingsState>,
|
||||||
onEvent: (ReaderEvent) -> Unit,
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
onMainEvent: (MainEvent) -> Unit
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
|
onSettingsEvent: (SettingsEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val pagerState = rememberPagerState { 2 }
|
val pagerState = rememberPagerState { 2 }
|
||||||
val currentPage by remember { derivedStateOf { pagerState.currentPage } }
|
val currentPage by remember { derivedStateOf { pagerState.currentPage } }
|
||||||
|
|
@ -57,7 +63,7 @@ fun ReaderSettingsBottomSheet(
|
||||||
)
|
)
|
||||||
|
|
||||||
val height = remember(currentPage) {
|
val height = remember(currentPage) {
|
||||||
if (currentPage == 1) 0.5f else 0.7f
|
if (currentPage == 1) 0.6f else 0.7f
|
||||||
}
|
}
|
||||||
val animatedHeight by animateFloatAsState(
|
val animatedHeight by animateFloatAsState(
|
||||||
targetValue = height,
|
targetValue = height,
|
||||||
|
|
@ -107,8 +113,11 @@ fun ReaderSettingsBottomSheet(
|
||||||
if (page == 1) {
|
if (page == 1) {
|
||||||
ColorsSubcategory(
|
ColorsSubcategory(
|
||||||
state = mainState,
|
state = mainState,
|
||||||
|
settingsState = settingsState,
|
||||||
onMainEvent = onMainEvent,
|
onMainEvent = onMainEvent,
|
||||||
|
onSettingsEvent = onSettingsEvent,
|
||||||
showTitle = false,
|
showTitle = false,
|
||||||
|
backgroundColor = { MaterialTheme.colorScheme.surfaceContainer },
|
||||||
topPadding = 16.dp,
|
topPadding = 16.dp,
|
||||||
bottomPadding = 8.dp + it
|
bottomPadding = 8.dp + it
|
||||||
)
|
)
|
||||||
|
|
@ -117,14 +126,3 @@ fun ReaderSettingsBottomSheet(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.settings.components
|
package ua.acclorite.book_story.presentation.screens.settings.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -9,21 +8,27 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.History
|
import androidx.compose.material.icons.filled.History
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
|
|
@ -33,22 +38,33 @@ import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
*
|
*
|
||||||
* @param modifier Modifier.
|
* @param modifier Modifier.
|
||||||
* @param value Target [Color].
|
* @param value Target [Color].
|
||||||
|
* @param presetId Current Color Preset id(to trigger initial value change).
|
||||||
* @param title Title.
|
* @param title Title.
|
||||||
* @param horizontalPadding Horizontal item padding.
|
* @param horizontalPadding Horizontal item padding.
|
||||||
* @param verticalPadding Vertical item padding.
|
* @param verticalPadding Vertical item padding.
|
||||||
* @param onValueChange Callback when target color changes.
|
* @param onValueChange Callback when target color changes.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ColorPickerWithTitle(
|
fun ColorPickerWithTitle(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
value: Color,
|
value: Color,
|
||||||
|
presetId: Int,
|
||||||
title: String,
|
title: String,
|
||||||
horizontalPadding: Dp = 18.dp,
|
horizontalPadding: Dp = 18.dp,
|
||||||
verticalPadding: Dp = 8.dp,
|
verticalPadding: Dp = 8.dp,
|
||||||
onValueChange: (Color) -> Unit
|
onValueChange: (Color) -> Unit
|
||||||
) {
|
) {
|
||||||
val initialValue = rememberSaveable { value.value.toString() }
|
val initialValue = rememberSaveable(presetId) { value.value.toString() }
|
||||||
var color by remember { mutableStateOf(value) }
|
var color by remember(value) { mutableStateOf(value) }
|
||||||
|
|
||||||
|
LaunchedEffect(color) {
|
||||||
|
snapshotFlow {
|
||||||
|
color
|
||||||
|
}.debounce(50).collectLatest {
|
||||||
|
onValueChange(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier
|
modifier
|
||||||
|
|
@ -66,7 +82,6 @@ fun ColorPickerWithTitle(
|
||||||
title = stringResource(id = R.string.red_color),
|
title = stringResource(id = R.string.red_color),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
color = color.copy(red = it)
|
color = color.copy(red = it)
|
||||||
onValueChange(color)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
RevertibleSlider(
|
RevertibleSlider(
|
||||||
|
|
@ -75,7 +90,6 @@ fun ColorPickerWithTitle(
|
||||||
title = stringResource(id = R.string.green_color),
|
title = stringResource(id = R.string.green_color),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
color = color.copy(green = it)
|
color = color.copy(green = it)
|
||||||
onValueChange(color)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
RevertibleSlider(
|
RevertibleSlider(
|
||||||
|
|
@ -84,7 +98,6 @@ fun ColorPickerWithTitle(
|
||||||
title = stringResource(id = R.string.blue_color),
|
title = stringResource(id = R.string.blue_color),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
color = color.copy(blue = it)
|
color = color.copy(blue = it)
|
||||||
onValueChange(color)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +121,7 @@ private fun RevertibleSlider(
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
SliderWithTitle(
|
SliderWithTitle(
|
||||||
modifier = Modifier.weight(0.85f),
|
modifier = Modifier.weight(1f),
|
||||||
value = value,
|
value = value,
|
||||||
title = title,
|
title = title,
|
||||||
toValue = 255,
|
toValue = 255,
|
||||||
|
|
@ -118,7 +131,8 @@ private fun RevertibleSlider(
|
||||||
horizontalPadding = 0.dp,
|
horizontalPadding = 0.dp,
|
||||||
verticalPadding = 0.dp
|
verticalPadding = 0.dp
|
||||||
)
|
)
|
||||||
Box(modifier = Modifier.weight(0.15f), contentAlignment = Alignment.CenterEnd) {
|
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
modifier = Modifier.size(28.dp),
|
modifier = Modifier.size(28.dp),
|
||||||
icon = Icons.Default.History,
|
icon = Icons.Default.History,
|
||||||
|
|
@ -132,4 +146,3 @@ private fun RevertibleSlider(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@ package ua.acclorite.book_story.presentation.screens.settings.data
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.PermissionState
|
import com.google.accompanist.permissions.PermissionState
|
||||||
|
import ua.acclorite.book_story.domain.util.ID
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class SettingsEvent {
|
sealed class SettingsEvent {
|
||||||
|
|
@ -15,4 +18,27 @@ sealed class SettingsEvent {
|
||||||
val notificationsPermissionState: PermissionState,
|
val notificationsPermissionState: PermissionState,
|
||||||
val onChangeCheckForUpdates: (Boolean) -> Unit
|
val onChangeCheckForUpdates: (Boolean) -> Unit
|
||||||
) : SettingsEvent()
|
) : SettingsEvent()
|
||||||
|
|
||||||
|
data class OnSelectPreviousPreset(
|
||||||
|
val onSelected: suspend (name: UIText) -> Unit
|
||||||
|
) : SettingsEvent()
|
||||||
|
|
||||||
|
data class OnSelectNextPreset(
|
||||||
|
val onSelected: suspend (name: UIText) -> Unit
|
||||||
|
) : SettingsEvent()
|
||||||
|
|
||||||
|
data class OnSelectColorPreset(val id: ID) : SettingsEvent()
|
||||||
|
data class OnDeleteColorPreset(val id: ID) : SettingsEvent()
|
||||||
|
data class OnScrollToColorPreset(val scrollTo: Int) : SettingsEvent()
|
||||||
|
data class OnUpdateColorPresetTitle(val id: ID, val title: String) : SettingsEvent()
|
||||||
|
data class OnShuffleColorPreset(val id: ID) : SettingsEvent()
|
||||||
|
data class OnUpdateColorPresetColor(
|
||||||
|
val id: ID,
|
||||||
|
val backgroundColor: Color?,
|
||||||
|
val fontColor: Color?
|
||||||
|
) : SettingsEvent()
|
||||||
|
|
||||||
|
data object OnAddColorPreset : SettingsEvent()
|
||||||
|
data class OnReorderColorPresets(val from: Int, val to: Int) : SettingsEvent()
|
||||||
|
data object OnConfirmReorderColorPresets : SettingsEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.settings.data
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class SettingsState(
|
||||||
|
val colorPresets: List<ColorPreset> = emptyList(),
|
||||||
|
val selectedColorPreset: ColorPreset = Constants.DEFAULT_COLOR_PRESET,
|
||||||
|
val animateColorPreset: Boolean = false,
|
||||||
|
val colorPresetsListState: LazyListState = LazyListState()
|
||||||
|
)
|
||||||
|
|
@ -9,24 +9,92 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.shouldShowRationale
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.use_case.DeleteColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.use_case.GetColorPresets
|
||||||
|
import ua.acclorite.book_story.domain.use_case.ReorderColorPresets
|
||||||
|
import ua.acclorite.book_story.domain.use_case.SelectColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.use_case.UpdateColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.util.Constants
|
||||||
|
import ua.acclorite.book_story.domain.util.ID
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.presentation.data.launchActivity
|
import ua.acclorite.book_story.presentation.data.launchActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SettingsViewModel @Inject constructor(
|
class SettingsViewModel @Inject constructor(
|
||||||
|
private val getColorPresets: GetColorPresets,
|
||||||
|
private val updateColorPreset: UpdateColorPreset,
|
||||||
|
private val deleteColorPreset: DeleteColorPreset,
|
||||||
|
private val selectColorPreset: SelectColorPreset,
|
||||||
|
private val reorderColorPresets: ReorderColorPresets
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private var notificationsPermissionJob: Job? = null
|
private val _state = MutableStateFlow(SettingsState())
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
private val _isReady = MutableStateFlow(false)
|
||||||
|
val isReady = _isReady.asStateFlow()
|
||||||
|
|
||||||
|
private var notificationsPermissionJob: Job? = null
|
||||||
|
private var selectColorPresetJob: Job? = null
|
||||||
|
private var addColorPresetJob: Job? = null
|
||||||
|
private var deleteColorPresetJob: Job? = null
|
||||||
|
private var updateColorColorPresetJob: Job? = null
|
||||||
|
private var updateTitleColorPresetJob: Job? = null
|
||||||
|
private var shuffleColorPresetJob: Job? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
var colorPresets = getColorPresets.execute()
|
||||||
|
|
||||||
|
if (colorPresets.isEmpty()) {
|
||||||
|
updateColorPreset.execute(Constants.DEFAULT_COLOR_PRESET)
|
||||||
|
onSelectColorPreset(getColorPresets.execute().first())
|
||||||
|
colorPresets = getColorPresets.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
val scrollIndex = colorPresets.indexOfFirst {
|
||||||
|
it.isSelected
|
||||||
|
}
|
||||||
|
if (scrollIndex != -1) {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
|
_state.value.colorPresetsListState.requestScrollToItem(
|
||||||
|
index = scrollIndex
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = selectedColorPreset(colorPresets),
|
||||||
|
colorPresets = colorPresets
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_isReady.update {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("LABEL_NAME_CLASH")
|
||||||
fun onEvent(event: SettingsEvent) {
|
fun onEvent(event: SettingsEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|
||||||
is SettingsEvent.OnGeneralChangeCheckForUpdates -> {
|
is SettingsEvent.OnGeneralChangeCheckForUpdates -> {
|
||||||
if (!event.enable) {
|
if (!event.enable) {
|
||||||
event.onChangeCheckForUpdates(false)
|
event.onChangeCheckForUpdates(false)
|
||||||
|
|
@ -75,6 +143,385 @@ class SettingsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnSelectPreviousPreset -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
selectColorPresetJob = launch {
|
||||||
|
val colorPresets = _state.value.colorPresets
|
||||||
|
val selectedPreset = _state.value.selectedColorPreset
|
||||||
|
|
||||||
|
if (colorPresets.size == 1) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedPresetIndex = colorPresets.indexOf(selectedPreset)
|
||||||
|
if (selectedPresetIndex == -1) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val previousColorPresetIndex = when (selectedPresetIndex) {
|
||||||
|
0 -> {
|
||||||
|
colorPresets.lastIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
selectedPresetIndex - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val previousColorPreset = colorPresets.getOrNull(previousColorPresetIndex)
|
||||||
|
?: return@launch
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
onSelectColorPreset(previousColorPreset)
|
||||||
|
val updatedColorPresets = _state.value.colorPresets.map {
|
||||||
|
it.copy(isSelected = previousColorPreset.id == it.id)
|
||||||
|
}
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = selectedColorPreset(updatedColorPresets),
|
||||||
|
colorPresets = updatedColorPresets
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.onSelected(
|
||||||
|
if ((previousColorPreset.name ?: "").isBlank()) {
|
||||||
|
UIText.StringResource(
|
||||||
|
R.string.color_preset_query,
|
||||||
|
previousColorPreset.id.toString()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
UIText.StringValue(previousColorPreset.name!!)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnSelectNextPreset -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
selectColorPresetJob = launch {
|
||||||
|
val colorPresets = _state.value.colorPresets
|
||||||
|
val selectedPreset = _state.value.selectedColorPreset
|
||||||
|
|
||||||
|
if (colorPresets.size == 1) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedPresetIndex = colorPresets.indexOf(selectedPreset)
|
||||||
|
if (selectedPresetIndex == -1) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextColorPresetIndex = when (selectedPresetIndex) {
|
||||||
|
colorPresets.lastIndex -> {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
selectedPresetIndex + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val nextColorPreset = colorPresets.getOrNull(nextColorPresetIndex)
|
||||||
|
?: return@launch
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
onSelectColorPreset(nextColorPreset)
|
||||||
|
val updatedColorPresets = _state.value.colorPresets.map {
|
||||||
|
it.copy(isSelected = nextColorPreset.id == it.id)
|
||||||
|
}
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = selectedColorPreset(updatedColorPresets),
|
||||||
|
colorPresets = updatedColorPresets
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.onSelected(
|
||||||
|
if ((nextColorPreset.name ?: "").isBlank()) {
|
||||||
|
UIText.StringResource(
|
||||||
|
R.string.color_preset_query,
|
||||||
|
nextColorPreset.id.toString()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
UIText.StringValue(nextColorPreset.name!!)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnSelectColorPreset -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
selectColorPresetJob = launch {
|
||||||
|
val colorPreset = getColorPresetById(event.id) ?: return@launch
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
onSelectColorPreset(colorPreset, animate = true)
|
||||||
|
val colorPresets = _state.value.colorPresets.map {
|
||||||
|
it.copy(isSelected = colorPreset.id == it.id)
|
||||||
|
}
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = selectedColorPreset(colorPresets),
|
||||||
|
colorPresets = colorPresets
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnDeleteColorPreset -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
deleteColorPresetJob = launch {
|
||||||
|
val colorPreset = getColorPresetById(event.id) ?: return@launch
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
val position = _state.value.colorPresets.indexOf(colorPreset)
|
||||||
|
if (position == -1) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextPosition = if (position == _state.value.colorPresets.lastIndex) {
|
||||||
|
position - 1
|
||||||
|
} else {
|
||||||
|
position
|
||||||
|
}
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
deleteColorPreset.execute(colorPreset)
|
||||||
|
onSelectColorPreset(getColorPresets.execute()[nextPosition])
|
||||||
|
val colorPresets = getColorPresets.execute()
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = selectedColorPreset(colorPresets),
|
||||||
|
colorPresets = colorPresets
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onEvent(SettingsEvent.OnScrollToColorPreset(nextPosition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnScrollToColorPreset -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
_state.value.colorPresetsListState.requestScrollToItem(
|
||||||
|
event.scrollTo
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnUpdateColorPresetTitle -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
updateTitleColorPresetJob = launch {
|
||||||
|
val colorPreset = getColorPresetById(event.id) ?: return@launch
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
val updatedColorPreset = colorPreset.copy(
|
||||||
|
name = event.title
|
||||||
|
)
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
updateColorPreset.execute(updatedColorPreset)
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = updatedColorPreset,
|
||||||
|
colorPresets = it.colorPresets.updateColorPreset(
|
||||||
|
updatedColorPreset
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnShuffleColorPreset -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
shuffleColorPresetJob = launch {
|
||||||
|
val colorPreset = getColorPresetById(event.id) ?: return@launch
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
val shuffledColorPreset = colorPreset.copy(
|
||||||
|
backgroundColor = colorPreset.backgroundColor.copy(
|
||||||
|
red = Random.nextFloat(),
|
||||||
|
green = Random.nextFloat(),
|
||||||
|
blue = Random.nextFloat()
|
||||||
|
),
|
||||||
|
fontColor = colorPreset.fontColor.copy(
|
||||||
|
red = Random.nextFloat(),
|
||||||
|
green = Random.nextFloat(),
|
||||||
|
blue = Random.nextFloat()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
updateColorPreset.execute(shuffledColorPreset)
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = shuffledColorPreset,
|
||||||
|
colorPresets = it.colorPresets.updateColorPreset(
|
||||||
|
shuffledColorPreset
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnAddColorPreset -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
addColorPresetJob = launch {
|
||||||
|
yield()
|
||||||
|
|
||||||
|
val newColorPreset = Constants.DEFAULT_COLOR_PRESET
|
||||||
|
updateColorPreset.execute(newColorPreset)
|
||||||
|
|
||||||
|
onSelectColorPreset(getColorPresets.execute().last())
|
||||||
|
val colorPresets = getColorPresets.execute()
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = selectedColorPreset(colorPresets),
|
||||||
|
colorPresets = colorPresets
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onEvent(SettingsEvent.OnScrollToColorPreset(colorPresets.lastIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnUpdateColorPresetColor -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
updateColorColorPresetJob = launch {
|
||||||
|
val colorPreset = getColorPresetById(event.id) ?: return@launch
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
val updatedColorPreset = colorPreset.copy(
|
||||||
|
backgroundColor = event.backgroundColor
|
||||||
|
?: colorPreset.backgroundColor,
|
||||||
|
fontColor = event.fontColor
|
||||||
|
?: colorPreset.fontColor
|
||||||
|
)
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
updateColorPreset.execute(updatedColorPreset)
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
selectedColorPreset = updatedColorPreset,
|
||||||
|
colorPresets = it.colorPresets.updateColorPreset(
|
||||||
|
updatedColorPreset
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnReorderColorPresets -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
launch {
|
||||||
|
val reorderedColorPresets =
|
||||||
|
_state.value.colorPresets.toMutableList().apply {
|
||||||
|
add(event.to, removeAt(event.from))
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
colorPresets = reorderedColorPresets
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SettingsEvent.OnConfirmReorderColorPresets -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cancelColorPresetJobs()
|
||||||
|
launch {
|
||||||
|
reorderColorPresets.execute(_state.value.colorPresets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelColorPresetJobs() {
|
||||||
|
selectColorPresetJob?.cancel()
|
||||||
|
addColorPresetJob?.cancel()
|
||||||
|
updateTitleColorPresetJob?.cancel()
|
||||||
|
shuffleColorPresetJob?.cancel()
|
||||||
|
updateColorColorPresetJob?.cancel()
|
||||||
|
deleteColorPresetJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onSelectColorPreset(colorPreset: ColorPreset, animate: Boolean = false) {
|
||||||
|
selectColorPreset.execute(colorPreset)
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
animateColorPreset = animate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getColorPresetById(id: ID): ColorPreset? {
|
||||||
|
return _state.value.colorPresets.firstOrNull {
|
||||||
|
it.id == id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectedColorPreset(colorPresets: List<ColorPreset>? = null): ColorPreset {
|
||||||
|
val presets = colorPresets ?: _state.value.colorPresets
|
||||||
|
|
||||||
|
if (presets.size == 1) {
|
||||||
|
return presets.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedPreset = presets.firstOrNull { it.isSelected }
|
||||||
|
|
||||||
|
if (selectedPreset == null) {
|
||||||
|
return Constants.DEFAULT_COLOR_PRESET
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedPreset
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ColorPreset>.updateColorPreset(colorPreset: ColorPreset): List<ColorPreset> {
|
||||||
|
if (size == 1) {
|
||||||
|
return listOf(colorPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.map {
|
||||||
|
if (it.isSelected) {
|
||||||
|
colorPreset
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package ua.acclorite.book_story.presentation.screens.settings.nested.appearance
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
|
@ -27,19 +28,26 @@ import ua.acclorite.book_story.presentation.data.LocalNavigator
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
import ua.acclorite.book_story.presentation.data.MainViewModel
|
import ua.acclorite.book_story.presentation.data.MainViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsState
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsViewModel
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.AppearanceSettingsCategory
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.AppearanceSettingsCategory
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppearanceSettingsRoot() {
|
fun AppearanceSettingsRoot() {
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val mainViewModel: MainViewModel = hiltViewModel()
|
val mainViewModel: MainViewModel = hiltViewModel()
|
||||||
|
val settingsViewModel: SettingsViewModel = hiltViewModel()
|
||||||
|
|
||||||
val state = mainViewModel.state.collectAsState()
|
val state = mainViewModel.state.collectAsState()
|
||||||
|
val settingsState = settingsViewModel.state.collectAsState()
|
||||||
|
|
||||||
AppearanceSettings(
|
AppearanceSettings(
|
||||||
state = state,
|
state = state,
|
||||||
|
settingsState = settingsState,
|
||||||
onNavigate = { navigator.it() },
|
onNavigate = { navigator.it() },
|
||||||
onMainEvent = mainViewModel::onEvent
|
onMainEvent = mainViewModel::onEvent,
|
||||||
|
onSettingsEvent = settingsViewModel::onEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,8 +55,10 @@ fun AppearanceSettingsRoot() {
|
||||||
@Composable
|
@Composable
|
||||||
private fun AppearanceSettings(
|
private fun AppearanceSettings(
|
||||||
state: State<MainState>,
|
state: State<MainState>,
|
||||||
|
settingsState: State<SettingsState>,
|
||||||
onNavigate: OnNavigate,
|
onNavigate: OnNavigate,
|
||||||
onMainEvent: (MainEvent) -> Unit
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
|
onSettingsEvent: (SettingsEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
|
val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState()
|
||||||
|
|
||||||
|
|
@ -56,7 +66,8 @@ private fun AppearanceSettings(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollState.first.nestedScrollConnection)
|
.nestedScroll(scrollState.first.nestedScrollConnection)
|
||||||
.windowInsetsPadding(WindowInsets.navigationBars),
|
.windowInsetsPadding(WindowInsets.navigationBars)
|
||||||
|
.imePadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
LargeTopAppBar(
|
LargeTopAppBar(
|
||||||
|
|
@ -82,7 +93,9 @@ private fun AppearanceSettings(
|
||||||
) {
|
) {
|
||||||
AppearanceSettingsCategory(
|
AppearanceSettingsCategory(
|
||||||
state = state,
|
state = state,
|
||||||
onMainEvent = onMainEvent
|
settingsState = settingsState,
|
||||||
|
onMainEvent = onMainEvent,
|
||||||
|
onSettingsEvent = onSettingsEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsState
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ColorsSubcategory
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ColorsSubcategory
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ThemePreferencesSubcategory
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.subcategories.ThemePreferencesSubcategory
|
||||||
|
|
||||||
|
|
@ -20,13 +22,16 @@ import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.c
|
||||||
*
|
*
|
||||||
* @param state [MainState] instance.
|
* @param state [MainState] instance.
|
||||||
* @param onMainEvent [MainEvent] callback.
|
* @param onMainEvent [MainEvent] callback.
|
||||||
|
* @param onSettingsEvent [SettingsEvent] callback.
|
||||||
* @param titleColor Title's color.
|
* @param titleColor Title's color.
|
||||||
* @param topPadding Top padding to be applied.
|
* @param topPadding Top padding to be applied.
|
||||||
* @param bottomPadding Bottom padding to be applied.
|
* @param bottomPadding Bottom padding to be applied.
|
||||||
*/
|
*/
|
||||||
fun LazyListScope.AppearanceSettingsCategory(
|
fun LazyListScope.AppearanceSettingsCategory(
|
||||||
state: State<MainState>,
|
state: State<MainState>,
|
||||||
|
settingsState: State<SettingsState>,
|
||||||
onMainEvent: (MainEvent) -> Unit,
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
|
onSettingsEvent: (SettingsEvent) -> Unit,
|
||||||
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
|
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
|
||||||
topPadding: Dp = 16.dp,
|
topPadding: Dp = 16.dp,
|
||||||
bottomPadding: Dp = 48.dp
|
bottomPadding: Dp = 48.dp
|
||||||
|
|
@ -39,8 +44,10 @@ fun LazyListScope.AppearanceSettingsCategory(
|
||||||
bottomPadding = 0.dp
|
bottomPadding = 0.dp
|
||||||
)
|
)
|
||||||
ColorsSubcategory(
|
ColorsSubcategory(
|
||||||
state,
|
state = state,
|
||||||
|
settingsState = settingsState,
|
||||||
onMainEvent = onMainEvent,
|
onMainEvent = onMainEvent,
|
||||||
|
onSettingsEvent = onSettingsEvent,
|
||||||
titleColor = titleColor,
|
titleColor = titleColor,
|
||||||
topPadding = 22.dp,
|
topPadding = 22.dp,
|
||||||
bottomPadding = bottomPadding
|
bottomPadding = bottomPadding
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,467 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.expandHorizontally
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkHorizontally
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.DeleteOutline
|
||||||
|
import androidx.compose.material.icons.filled.Done
|
||||||
|
import androidx.compose.material.icons.filled.Shuffle
|
||||||
|
import androidx.compose.material.icons.rounded.DragHandle
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import sh.calvin.reorderable.ReorderableCollectionItemScope
|
||||||
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.domain.model.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
|
import ua.acclorite.book_story.presentation.components.CustomIconButton
|
||||||
|
import ua.acclorite.book_story.presentation.components.customItemsIndexed
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsState
|
||||||
|
import ua.acclorite.book_story.presentation.ui.DefaultTransition
|
||||||
|
import ua.acclorite.book_story.presentation.ui.Transitions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color Preset setting.
|
||||||
|
* Lets user create and use custom color preset.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.ColorPresetSetting(
|
||||||
|
state: State<SettingsState>,
|
||||||
|
onEvent: (SettingsEvent) -> Unit,
|
||||||
|
backgroundColor: Color,
|
||||||
|
verticalPadding: Dp = 8.dp,
|
||||||
|
horizontalPadding: Dp = 18.dp
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val reorderableListState = rememberReorderableLazyListState(
|
||||||
|
lazyListState = state.value.colorPresetsListState
|
||||||
|
) { from, to ->
|
||||||
|
onEvent(SettingsEvent.OnReorderColorPresets(from.index, to.index))
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.animateItem()
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = verticalPadding)
|
||||||
|
) {
|
||||||
|
CategoryTitle(
|
||||||
|
title = stringResource(id = R.string.color_preset_option),
|
||||||
|
padding = horizontalPadding
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
state = state.value.colorPresetsListState,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
customItemsIndexed(
|
||||||
|
state.value.colorPresets,
|
||||||
|
key = { item, _ -> item.id }
|
||||||
|
) { index, colorPreset ->
|
||||||
|
ReorderableItem(
|
||||||
|
state = reorderableListState,
|
||||||
|
animateItemModifier = Modifier,
|
||||||
|
key = colorPreset.id
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
if (index == 0) {
|
||||||
|
Spacer(modifier = Modifier.width(horizontalPadding))
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorPresetSettingItem(
|
||||||
|
colorPreset = colorPreset,
|
||||||
|
isSelected = remember(
|
||||||
|
colorPreset.isSelected,
|
||||||
|
state.value.colorPresets.size
|
||||||
|
) {
|
||||||
|
if (state.value.colorPresets.size > 1) {
|
||||||
|
colorPreset.isSelected
|
||||||
|
} else true
|
||||||
|
},
|
||||||
|
enableAnimation = state.value.animateColorPreset,
|
||||||
|
context = context,
|
||||||
|
canDrag = remember(state.value.colorPresets.size) {
|
||||||
|
state.value.colorPresets.size > 1
|
||||||
|
},
|
||||||
|
onDragStopped = {
|
||||||
|
onEvent(SettingsEvent.OnConfirmReorderColorPresets)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onEvent(
|
||||||
|
SettingsEvent.OnSelectColorPreset(
|
||||||
|
id = colorPreset.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (index == state.value.colorPresets.lastIndex) {
|
||||||
|
Spacer(modifier = Modifier.width(horizontalPadding))
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = horizontalPadding)
|
||||||
|
.clip(MaterialTheme.shapes.large)
|
||||||
|
.background(
|
||||||
|
backgroundColor,
|
||||||
|
MaterialTheme.shapes.large
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
|
|
||||||
|
ColorPresetSettingConfigurationItem(
|
||||||
|
selectedColorPreset = state.value.selectedColorPreset,
|
||||||
|
canDelete = state.value.colorPresets.size > 1,
|
||||||
|
onDelete = {
|
||||||
|
onEvent(
|
||||||
|
SettingsEvent.OnDeleteColorPreset(
|
||||||
|
id = state.value.selectedColorPreset.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onTitleChange = {
|
||||||
|
onEvent(
|
||||||
|
SettingsEvent.OnUpdateColorPresetTitle(
|
||||||
|
id = state.value.selectedColorPreset.id,
|
||||||
|
title = it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onShuffle = {
|
||||||
|
onEvent(
|
||||||
|
SettingsEvent.OnShuffleColorPreset(
|
||||||
|
id = state.value.selectedColorPreset.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onAdd = {
|
||||||
|
onEvent(SettingsEvent.OnAddColorPreset)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
ColorPickerWithTitle(
|
||||||
|
value = state.value.selectedColorPreset.backgroundColor,
|
||||||
|
presetId = state.value.selectedColorPreset.id,
|
||||||
|
title = stringResource(id = R.string.background_color_option),
|
||||||
|
onValueChange = {
|
||||||
|
onEvent(
|
||||||
|
SettingsEvent.OnUpdateColorPresetColor(
|
||||||
|
id = state.value.selectedColorPreset.id,
|
||||||
|
backgroundColor = it,
|
||||||
|
fontColor = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ColorPickerWithTitle(
|
||||||
|
value = state.value.selectedColorPreset.fontColor,
|
||||||
|
presetId = state.value.selectedColorPreset.id,
|
||||||
|
title = stringResource(id = R.string.font_color_option),
|
||||||
|
onValueChange = {
|
||||||
|
onEvent(
|
||||||
|
SettingsEvent.OnUpdateColorPresetColor(
|
||||||
|
id = state.value.selectedColorPreset.id,
|
||||||
|
backgroundColor = null,
|
||||||
|
fontColor = it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color Preset Setting Item.
|
||||||
|
* Lets user select color preset and shows little preview of the preset.
|
||||||
|
*
|
||||||
|
* @param colorPreset [ColorPreset] to select.
|
||||||
|
* @param isSelected Whether [ColorPreset] is selected.
|
||||||
|
* @param canDrag Whether user can drag [ColorPreset].
|
||||||
|
* @param enableAnimation Whether there is appearing/disappearing animation.
|
||||||
|
* @param context [Context].
|
||||||
|
* @param onDragStopped Callback when user stops dragging [ColorPreset].
|
||||||
|
* @param onClick OnClick callback.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ReorderableCollectionItemScope.ColorPresetSettingItem(
|
||||||
|
colorPreset: ColorPreset,
|
||||||
|
isSelected: Selected,
|
||||||
|
canDrag: Boolean,
|
||||||
|
enableAnimation: Boolean,
|
||||||
|
context: Context,
|
||||||
|
onDragStopped: () -> Unit,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val title = remember(colorPreset) {
|
||||||
|
if ((colorPreset.name ?: "").isBlank()) {
|
||||||
|
return@remember context.getString(
|
||||||
|
R.string.color_preset_query,
|
||||||
|
colorPreset.id.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
colorPreset.name!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val borderColor = remember(isSelected, colorPreset.fontColor) {
|
||||||
|
if (!isSelected) colorPreset.fontColor.copy(0.3f)
|
||||||
|
else colorPreset.fontColor
|
||||||
|
}
|
||||||
|
val animatedBorderColor = animateColorAsState(
|
||||||
|
borderColor,
|
||||||
|
label = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
val animatedBackgroundColor = animateColorAsState(
|
||||||
|
colorPreset.backgroundColor,
|
||||||
|
label = ""
|
||||||
|
)
|
||||||
|
val animatedFontColor = animateColorAsState(
|
||||||
|
colorPreset.fontColor,
|
||||||
|
label = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.border(
|
||||||
|
width = 2.dp,
|
||||||
|
color = if (enableAnimation) animatedBorderColor.value
|
||||||
|
else borderColor,
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
.padding(2.dp)
|
||||||
|
.background(animatedBackgroundColor.value, CircleShape)
|
||||||
|
.clickable(enabled = !isSelected) {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
CustomAnimatedVisibility(
|
||||||
|
visible = isSelected,
|
||||||
|
enter = if (enableAnimation) expandHorizontally() + fadeIn()
|
||||||
|
else Transitions.NoEnterAnimation,
|
||||||
|
exit = if (enableAnimation) shrinkHorizontally() + fadeOut()
|
||||||
|
else Transitions.NoExitAnimation
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Done,
|
||||||
|
contentDescription = stringResource(id = R.string.selected_content_desc),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.size(18.dp),
|
||||||
|
tint = colorPreset.fontColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
title.trim(),
|
||||||
|
color = animatedFontColor.value,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
CustomAnimatedVisibility(
|
||||||
|
visible = isSelected && canDrag,
|
||||||
|
enter = if (enableAnimation) expandHorizontally() + fadeIn()
|
||||||
|
else Transitions.NoEnterAnimation,
|
||||||
|
exit = if (enableAnimation) shrinkHorizontally() + fadeOut()
|
||||||
|
else Transitions.NoExitAnimation
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.DragHandle,
|
||||||
|
contentDescription = stringResource(id = R.string.drag_content_desc),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
.size(18.dp)
|
||||||
|
.longPressDraggableHandle(
|
||||||
|
onDragStopped = onDragStopped
|
||||||
|
),
|
||||||
|
tint = colorPreset.fontColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color Preset Settings Configuration Item.
|
||||||
|
* Lets user configure Title, Shuffle colors, Delete presets and Add presets.
|
||||||
|
*
|
||||||
|
* @param selectedColorPreset Currently selected [ColorPreset].
|
||||||
|
* @param canDelete Whether this [ColorPreset] can be deleted.
|
||||||
|
* @param onDelete Delete callback.
|
||||||
|
* @param onShuffle Shuffle callback.
|
||||||
|
* @param onTitleChange TitleChange callback.
|
||||||
|
* @param onAdd Add callback.
|
||||||
|
*/
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
@Composable
|
||||||
|
private fun ColorPresetSettingConfigurationItem(
|
||||||
|
selectedColorPreset: ColorPreset,
|
||||||
|
canDelete: Boolean,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
onShuffle: () -> Unit,
|
||||||
|
onTitleChange: (String) -> Unit,
|
||||||
|
onAdd: () -> Unit
|
||||||
|
) {
|
||||||
|
val title = remember(selectedColorPreset.id) {
|
||||||
|
mutableStateOf(selectedColorPreset.name ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(title) {
|
||||||
|
snapshotFlow {
|
||||||
|
title.value
|
||||||
|
}.debounce(50).collectLatest {
|
||||||
|
onTitleChange(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier.padding(horizontal = 18.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
value = title.value,
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
textStyle = TextStyle(
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||||
|
lineHeight = MaterialTheme.typography.titleLarge.lineHeight,
|
||||||
|
fontFamily = MaterialTheme.typography.titleLarge.fontFamily
|
||||||
|
),
|
||||||
|
onValueChange = {
|
||||||
|
if (it.length < 40 || it.length <= title.value.length) {
|
||||||
|
title.value = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
KeyboardCapitalization.Sentences
|
||||||
|
),
|
||||||
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
) { innerText ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
if (title.value.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.color_preset_placeholder),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
innerText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
|
||||||
|
DefaultTransition(visible = canDelete) {
|
||||||
|
CustomIconButton(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
icon = Icons.Default.DeleteOutline,
|
||||||
|
contentDescription = R.string.delete_color_preset_content_desc,
|
||||||
|
disableOnClick = false,
|
||||||
|
enabled = canDelete,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
) {
|
||||||
|
onDelete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomIconButton(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
icon = Icons.Default.Shuffle,
|
||||||
|
contentDescription = R.string.shuffle_color_preset_content_desc,
|
||||||
|
disableOnClick = false,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
) {
|
||||||
|
onShuffle()
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomIconButton(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
icon = Icons.Default.Add,
|
||||||
|
contentDescription = R.string.create_color_preset_content_desc,
|
||||||
|
disableOnClick = false,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
) {
|
||||||
|
onAdd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fast Color Preset Change setting.
|
||||||
|
* If true, user can fast change color presets in Reader with swipes.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.FastColorPresetChangeSetting(
|
||||||
|
state: State<MainState>,
|
||||||
|
onMainEvent: (MainEvent) -> Unit
|
||||||
|
) {
|
||||||
|
SwitchWithTitle(
|
||||||
|
selected = state.value.fastColorPresetChange!!,
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
title = stringResource(id = R.string.fast_color_preset_change_option),
|
||||||
|
description = stringResource(id = R.string.fast_color_preset_change_option_desc),
|
||||||
|
onClick = {
|
||||||
|
onMainEvent(
|
||||||
|
MainEvent.OnChangeFastColorPresetChange(
|
||||||
|
!state.value.fastColorPresetChange!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
|
@ -44,7 +43,7 @@ import ua.acclorite.book_story.domain.util.Constants
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.components.customItems
|
import ua.acclorite.book_story.presentation.components.customItemsIndexed
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
import ua.acclorite.book_story.presentation.ui.Theme
|
import ua.acclorite.book_story.presentation.ui.Theme
|
||||||
|
|
@ -79,13 +78,19 @@ fun LazyItemScope.ThemeSetting(
|
||||||
title = stringResource(id = R.string.app_theme_option),
|
title = stringResource(id = R.string.app_theme_option),
|
||||||
padding = horizontalPadding
|
padding = horizontalPadding
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
LazyRow(
|
LazyRow(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
contentPadding = PaddingValues(horizontal = horizontalPadding)
|
|
||||||
) {
|
) {
|
||||||
customItems(themes, key = { it.first.name }) { themeEntry ->
|
customItemsIndexed(
|
||||||
|
themes,
|
||||||
|
key = { item, _ -> item.first.name }
|
||||||
|
) { index, themeEntry ->
|
||||||
|
if (index == 0) {
|
||||||
|
Spacer(modifier = Modifier.width(horizontalPadding))
|
||||||
|
}
|
||||||
|
|
||||||
ThemeSettingItem(
|
ThemeSettingItem(
|
||||||
theme = themeEntry,
|
theme = themeEntry,
|
||||||
darkTheme = state.value.darkTheme!!.isDark(),
|
darkTheme = state.value.darkTheme!!.isDark(),
|
||||||
|
|
@ -95,6 +100,10 @@ fun LazyItemScope.ThemeSetting(
|
||||||
) {
|
) {
|
||||||
onMainEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
onMainEvent(MainEvent.OnChangeTheme(themeEntry.first.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (index == themes.lastIndex) {
|
||||||
|
Spacer(modifier = Modifier.width(horizontalPadding))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
import ua.acclorite.book_story.presentation.components.CategoryTitle
|
||||||
import ua.acclorite.book_story.presentation.data.MainEvent
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.ColorPickerWithTitle
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsState
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings.ColorPresetSetting
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.components.settings.FastColorPresetChangeSetting
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Colors subcategory.
|
* Colors subcategory.
|
||||||
|
|
@ -27,9 +30,12 @@ import ua.acclorite.book_story.presentation.screens.settings.components.ColorPic
|
||||||
*/
|
*/
|
||||||
fun LazyListScope.ColorsSubcategory(
|
fun LazyListScope.ColorsSubcategory(
|
||||||
state: State<MainState>,
|
state: State<MainState>,
|
||||||
|
settingsState: State<SettingsState>,
|
||||||
onMainEvent: (MainEvent) -> Unit,
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
|
onSettingsEvent: (SettingsEvent) -> Unit,
|
||||||
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
|
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
|
||||||
title: @Composable () -> String = { stringResource(id = R.string.colors_appearance_settings) },
|
title: @Composable () -> String = { stringResource(id = R.string.colors_appearance_settings) },
|
||||||
|
backgroundColor: @Composable () -> Color = { MaterialTheme.colorScheme.surfaceContainerLow },
|
||||||
showTitle: Boolean = true,
|
showTitle: Boolean = true,
|
||||||
topPadding: Dp,
|
topPadding: Dp,
|
||||||
bottomPadding: Dp
|
bottomPadding: Dp
|
||||||
|
|
@ -55,36 +61,20 @@ fun LazyListScope.ColorsSubcategory(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: This will be replaced with ColorPresets
|
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
ColorPresetSetting(
|
||||||
modifier = Modifier.animateItem(),
|
state = settingsState,
|
||||||
value = Color(state.value.backgroundColor!!.toULong()),
|
backgroundColor = backgroundColor.invoke(),
|
||||||
title = stringResource(id = R.string.background_color_option),
|
onEvent = onSettingsEvent
|
||||||
onValueChange = {
|
|
||||||
onMainEvent(
|
|
||||||
MainEvent.OnChangeBackgroundColor(
|
|
||||||
it.value.toLong()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
ColorPickerWithTitle(
|
FastColorPresetChangeSetting(
|
||||||
modifier = Modifier.animateItem(),
|
state = state,
|
||||||
value = Color(state.value.fontColor!!.toULong()),
|
onMainEvent = onMainEvent
|
||||||
title = stringResource(id = R.string.font_color_option),
|
|
||||||
onValueChange = {
|
|
||||||
onMainEvent(
|
|
||||||
MainEvent.OnChangeFontColor(
|
|
||||||
it.value.toLong()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
// ---------
|
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Spacer(
|
Spacer(
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ fun LazyItemScope.DoubleClickTranslationSetting(
|
||||||
SwitchWithTitle(
|
SwitchWithTitle(
|
||||||
selected = state.value.doubleClickTranslation!!,
|
selected = state.value.doubleClickTranslation!!,
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
title = stringResource(id = R.string.translator_double_click_to_translate_option),
|
title = stringResource(id = R.string.double_click_translation_option),
|
||||||
description = stringResource(id = R.string.translator_double_click_to_translate_option_desc),
|
description = stringResource(id = R.string.double_click_translation_option_desc),
|
||||||
onClick = {
|
onClick = {
|
||||||
onMainEvent(
|
onMainEvent(
|
||||||
MainEvent.OnChangeDoubleClickTranslation(
|
MainEvent.OnChangeDoubleClickTranslation(
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,16 @@ object Transitions {
|
||||||
|
|
||||||
val SlidingTransitionIn = fadeIn(tween(350)) +
|
val SlidingTransitionIn = fadeIn(tween(350)) +
|
||||||
slideInHorizontally(tween(350)) { it / 16 }
|
slideInHorizontally(tween(350)) { it / 16 }
|
||||||
|
|
||||||
val BackSlidingTransitionIn = fadeIn(tween(350)) +
|
val BackSlidingTransitionIn = fadeIn(tween(350)) +
|
||||||
slideInHorizontally(tween(350)) { -it / 16 }
|
slideInHorizontally(tween(350)) { -it / 16 }
|
||||||
|
|
||||||
val SlidingTransitionOut = fadeOut(tween(250)) +
|
val SlidingTransitionOut = fadeOut(tween(250)) +
|
||||||
slideOutHorizontally(tween(350)) { -it / 16 }
|
slideOutHorizontally(tween(350)) { -it / 16 }
|
||||||
|
|
||||||
val BackSlidingTransitionOut = fadeOut(tween(250)) +
|
val BackSlidingTransitionOut = fadeOut(tween(250)) +
|
||||||
slideOutHorizontally(tween(350)) { it / 16 }
|
slideOutHorizontally(tween(350)) { it / 16 }
|
||||||
|
|
||||||
|
val NoEnterAnimation = fadeIn(tween(0))
|
||||||
|
val NoExitAnimation = fadeOut(tween(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,8 @@
|
||||||
<string name="read_query">%1$s прочитано</string>
|
<string name="read_query">%1$s прочитано</string>
|
||||||
<string name="file_path_query">Шлях до файлу: %1$s</string>
|
<string name="file_path_query">Шлях до файлу: %1$s</string>
|
||||||
<string name="update_query">Оновлення %1$s</string>
|
<string name="update_query">Оновлення %1$s</string>
|
||||||
|
<string name="color_preset_query">Пресет %1$s</string>
|
||||||
|
<string name="color_preset_selected_query">Вибрано «%1$s»</string>
|
||||||
|
|
||||||
<!-- Action Texts -->
|
<!-- Action Texts -->
|
||||||
<string name="cancel">Відмінити</string>
|
<string name="cancel">Відмінити</string>
|
||||||
|
|
@ -205,18 +207,22 @@
|
||||||
<string name="line_height_option">Висота рядка</string>
|
<string name="line_height_option">Висота рядка</string>
|
||||||
<string name="paragraph_height_option">Висота абзаца</string>
|
<string name="paragraph_height_option">Висота абзаца</string>
|
||||||
<string name="paragraph_indentation_option">Абзацний відступ</string>
|
<string name="paragraph_indentation_option">Абзацний відступ</string>
|
||||||
|
<string name="color_preset_option">Пресет кольорів</string>
|
||||||
<string name="background_color_option">Колір фона</string>
|
<string name="background_color_option">Колір фона</string>
|
||||||
<string name="font_color_option">Колір шрифта</string>
|
<string name="font_color_option">Колір шрифта</string>
|
||||||
<string name="check_for_updates_option">Перевіряти на оновлення</string>
|
<string name="check_for_updates_option">Перевіряти на оновлення</string>
|
||||||
<string name="check_for_updates_option_desc">Перевіряти на новішу версію застосунку при запуску</string>
|
<string name="check_for_updates_option_desc">Перевіряти на новішу версію застосунку при запуску</string>
|
||||||
<string name="side_padding_option">Бокові відступи</string>
|
<string name="side_padding_option">Бокові відступи</string>
|
||||||
<string name="translator_double_click_to_translate_option">Переклад подвійним кліком</string>
|
<string name="double_click_translation_option">Переклад подвійним кліком</string>
|
||||||
<string name="translator_double_click_to_translate_option_desc">Двічі клацніть на абзац, щоб перекласти його</string>
|
<string name="double_click_translation_option_desc">Двічі клацніть на абзац, щоб перекласти його</string>
|
||||||
|
<string name="fast_color_preset_change_option">Швидка зміна пресета кольорів</string>
|
||||||
|
<string name="fast_color_preset_change_option_desc">Проведіть вліво або вправо, щоб змінити пресет кольорів</string>
|
||||||
|
|
||||||
<!-- Colors -->
|
<!-- Color presets -->
|
||||||
<string name="red_color">Червоний</string>
|
<string name="red_color">Червоний</string>
|
||||||
<string name="green_color">Зелений</string>
|
<string name="green_color">Зелений</string>
|
||||||
<string name="blue_color">Синій</string>
|
<string name="blue_color">Синій</string>
|
||||||
|
<string name="color_preset_placeholder">Назвіть пресет…</string>
|
||||||
|
|
||||||
<!-- Dark Theme properties -->
|
<!-- Dark Theme properties -->
|
||||||
<string name="dark_theme_off">Вимкнена</string>
|
<string name="dark_theme_off">Вимкнена</string>
|
||||||
|
|
@ -484,8 +490,12 @@
|
||||||
<string name="reset_start_content_desc">Скинути початковий посібник</string>
|
<string name="reset_start_content_desc">Скинути початковий посібник</string>
|
||||||
<string name="exit_editing_content_desc">Вийти з режиму редагування</string>
|
<string name="exit_editing_content_desc">Вийти з режиму редагування</string>
|
||||||
<string name="open_in_web_content_desc">Відкрити в браузері</string>
|
<string name="open_in_web_content_desc">Відкрити в браузері</string>
|
||||||
|
<string name="delete_color_preset_content_desc">Видалити пресет кольорів</string>
|
||||||
|
<string name="create_color_preset_content_desc">Створити новий пресет кольорів</string>
|
||||||
|
<string name="shuffle_color_preset_content_desc">Перетасувати кольори</string>
|
||||||
<string name="tryzub_content_desc">Тризуб</string>
|
<string name="tryzub_content_desc">Тризуб</string>
|
||||||
<string name="github_project_content_desc">GitHub Проєкт</string>
|
<string name="github_project_content_desc">GitHub Проєкт</string>
|
||||||
<string name="github_profile_content_desc">GitHub Профіль</string>
|
<string name="github_profile_content_desc">GitHub Профіль</string>
|
||||||
|
<string name="drag_content_desc">Тягнути</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -139,6 +139,8 @@
|
||||||
<string name="read_query">%1$s read</string>
|
<string name="read_query">%1$s read</string>
|
||||||
<string name="file_path_query">File path: %1$s</string>
|
<string name="file_path_query">File path: %1$s</string>
|
||||||
<string name="update_query">Update %1$s</string>
|
<string name="update_query">Update %1$s</string>
|
||||||
|
<string name="color_preset_query">Preset %1$s</string>
|
||||||
|
<string name="color_preset_selected_query">Selected «%1$s»</string>
|
||||||
|
|
||||||
<!-- Action Texts -->
|
<!-- Action Texts -->
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
|
|
@ -220,18 +222,22 @@
|
||||||
<string name="line_height_option">Line height</string>
|
<string name="line_height_option">Line height</string>
|
||||||
<string name="paragraph_height_option">Paragraph height</string>
|
<string name="paragraph_height_option">Paragraph height</string>
|
||||||
<string name="paragraph_indentation_option">Paragraph indentation</string>
|
<string name="paragraph_indentation_option">Paragraph indentation</string>
|
||||||
|
<string name="color_preset_option">Color preset</string>
|
||||||
<string name="background_color_option">Background color</string>
|
<string name="background_color_option">Background color</string>
|
||||||
<string name="font_color_option">Font color</string>
|
<string name="font_color_option">Font color</string>
|
||||||
<string name="check_for_updates_option">Check for updates</string>
|
<string name="check_for_updates_option">Check for updates</string>
|
||||||
<string name="check_for_updates_option_desc">Check for newer app version on startup</string>
|
<string name="check_for_updates_option_desc">Check for newer app version on startup</string>
|
||||||
<string name="side_padding_option">Side padding</string>
|
<string name="side_padding_option">Side padding</string>
|
||||||
<string name="translator_double_click_to_translate_option">Double click translation</string>
|
<string name="double_click_translation_option">Double click translation</string>
|
||||||
<string name="translator_double_click_to_translate_option_desc">Double click on paragraph to translate it</string>
|
<string name="double_click_translation_option_desc">Double click on paragraph to translate it</string>
|
||||||
|
<string name="fast_color_preset_change_option">Fast color preset change</string>
|
||||||
|
<string name="fast_color_preset_change_option_desc">Swipe left or right to change color preset</string>
|
||||||
|
|
||||||
<!-- Colors -->
|
<!-- Color presets -->
|
||||||
<string name="red_color">Red</string>
|
<string name="red_color">Red</string>
|
||||||
<string name="green_color">Green</string>
|
<string name="green_color">Green</string>
|
||||||
<string name="blue_color">Blue</string>
|
<string name="blue_color">Blue</string>
|
||||||
|
<string name="color_preset_placeholder">Name a preset…</string>
|
||||||
|
|
||||||
<!-- Dark Theme properties -->
|
<!-- Dark Theme properties -->
|
||||||
<string name="dark_theme_off">Disabled</string>
|
<string name="dark_theme_off">Disabled</string>
|
||||||
|
|
@ -500,10 +506,14 @@
|
||||||
<string name="reset_start_content_desc">Reset start guide</string>
|
<string name="reset_start_content_desc">Reset start guide</string>
|
||||||
<string name="exit_editing_content_desc">Exit editing mode</string>
|
<string name="exit_editing_content_desc">Exit editing mode</string>
|
||||||
<string name="open_in_web_content_desc">Open in web</string>
|
<string name="open_in_web_content_desc">Open in web</string>
|
||||||
|
<string name="delete_color_preset_content_desc">Delete color preset</string>
|
||||||
|
<string name="create_color_preset_content_desc">Create new color preset</string>
|
||||||
|
<string name="shuffle_color_preset_content_desc">Shuffle colors</string>
|
||||||
<string name="x_content_desc" translatable="false">X</string>
|
<string name="x_content_desc" translatable="false">X</string>
|
||||||
<string name="reddit_content_desc" translatable="false">Reddit</string>
|
<string name="reddit_content_desc" translatable="false">Reddit</string>
|
||||||
<string name="tryzub_content_desc">Tryzub</string>
|
<string name="tryzub_content_desc">Tryzub</string>
|
||||||
<string name="github_project_content_desc">GitHub Project</string>
|
<string name="github_project_content_desc">GitHub Project</string>
|
||||||
<string name="github_profile_content_desc">GitHub Profile</string>
|
<string name="github_profile_content_desc">GitHub Profile</string>
|
||||||
|
<string name="drag_content_desc">Drag</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue