1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

CU-2a1ah8b_Theme-selector (#2178)

App | Feature | Add possibility to change night mode
This commit is contained in:
Mikhail 2022-04-12 17:08:08 +03:00 committed by GitHub
parent 096811f539
commit 9ff3233747
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 703 additions and 10 deletions

View file

@ -13,6 +13,7 @@ import com.anytypeio.anytype.di.feature.sets.viewer.ViewerCardSizeSelectModule
import com.anytypeio.anytype.di.feature.sets.viewer.ViewerImagePreviewSelectModule
import com.anytypeio.anytype.di.feature.settings.AboutAppModule
import com.anytypeio.anytype.di.feature.settings.AccountAndDataModule
import com.anytypeio.anytype.di.feature.settings.AppearanceModule
import com.anytypeio.anytype.di.feature.settings.LogoutWarningModule
import com.anytypeio.anytype.di.feature.settings.MainSettingsModule
import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectModule
@ -669,6 +670,10 @@ class ComponentManager(private val main: MainComponent) {
main.accountAndDataComponent().module(AccountAndDataModule).build()
}
val appearanceComponent = Component {
main.appearanceComponent().module(AppearanceModule).build()
}
val logoutWarningComponent = Component {
main.logoutWarningComponent().module(LogoutWarningModule).build()
}

View file

@ -11,10 +11,13 @@ import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.config.FlavourConfigProvider
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.device.PathProvider
import com.anytypeio.anytype.domain.theme.GetTheme
import com.anytypeio.anytype.domain.wallpaper.ObserveWallpaper
import com.anytypeio.anytype.domain.wallpaper.RestoreWallpaper
import com.anytypeio.anytype.presentation.main.MainViewModelFactory
import com.anytypeio.anytype.ui.main.MainActivity
import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicator
import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicatorImpl
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@ -80,6 +83,18 @@ object MainEntryModule {
repo: UserSettingsRepository
): RestoreWallpaper = RestoreWallpaper(repo)
@JvmStatic
@PerScreen
@Provides
fun provideSetupThemeUseCase(
repo: UserSettingsRepository
): GetTheme = GetTheme(repo)
@JvmStatic
@PerScreen
@Provides
fun provideThemeApplicator(): ThemeApplicator = ThemeApplicatorImpl()
@JvmStatic
@PerScreen
@Provides

View file

@ -0,0 +1,57 @@
package com.anytypeio.anytype.di.feature.settings
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.theme.GetTheme
import com.anytypeio.anytype.domain.theme.SetTheme
import com.anytypeio.anytype.ui.settings.AppearanceFragment
import com.anytypeio.anytype.ui_settings.appearance.AppearanceViewModel
import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicator
import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicatorImpl
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [AppearanceModule::class])
@PerScreen
interface AppearanceSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: AppearanceModule): Builder
fun build(): AppearanceSubComponent
}
fun inject(fragment: AppearanceFragment)
}
@Module
object AppearanceModule {
@JvmStatic
@Provides
@PerScreen
fun provideViewModelFactory(
getTheme: GetTheme,
setTheme: SetTheme,
themeApplicator: ThemeApplicator
): AppearanceViewModel.Factory = AppearanceViewModel.Factory(
getTheme,
setTheme,
themeApplicator
)
@JvmStatic
@PerScreen
@Provides
fun provideThemeApplicator(): ThemeApplicator = ThemeApplicatorImpl()
@JvmStatic
@Provides
@PerScreen
fun provideGetThemeUseCase(repo: UserSettingsRepository): GetTheme = GetTheme(repo)
@JvmStatic
@Provides
@PerScreen
fun provideSetThemeUseCase(repo: UserSettingsRepository): SetTheme = SetTheme(repo)
}

View file

@ -5,6 +5,7 @@ import com.anytypeio.anytype.di.feature.*
import com.anytypeio.anytype.di.feature.auth.DeletedAccountSubcomponent
import com.anytypeio.anytype.di.feature.settings.AboutAppSubComponent
import com.anytypeio.anytype.di.feature.settings.AccountAndDataSubComponent
import com.anytypeio.anytype.di.feature.settings.AppearanceSubComponent
import com.anytypeio.anytype.di.feature.settings.LogoutWarningSubComponent
import com.anytypeio.anytype.di.feature.settings.MainSettingsSubComponent
import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectSubComponent
@ -59,6 +60,7 @@ interface MainComponent {
fun aboutAppComponent() : AboutAppSubComponent.Builder
fun accountAndDataComponent() : AccountAndDataSubComponent.Builder
fun appearanceComponent() : AppearanceSubComponent.Builder
fun debugSettingsBuilder(): DebugSettingsSubComponent.Builder
fun keychainPhraseComponentBuilder(): KeychainPhraseSubComponent.Builder
fun otherSettingsComponentBuilder(): OtherSettingsSubComponent.Builder

View file

@ -16,8 +16,11 @@ import androidx.navigation.findNavController
import com.anytypeio.anytype.BuildConfig
import com.anytypeio.anytype.R
import com.anytypeio.anytype.app.DefaultAppActionManager
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.core_models.Wallpaper
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.theme.GetTheme
import com.anytypeio.anytype.navigation.Navigator
import com.anytypeio.anytype.presentation.editor.cover.CoverGradient
import com.anytypeio.anytype.presentation.main.MainViewModel
@ -26,7 +29,10 @@ import com.anytypeio.anytype.presentation.main.MainViewModelFactory
import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.wallpaper.WallpaperColor
import com.anytypeio.anytype.ui.editor.CreateObjectFragment
import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicator
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import javax.inject.Inject
class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Provider {
@ -41,12 +47,19 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
@Inject
lateinit var factory: MainViewModelFactory
val container : FragmentContainerView get() = findViewById(R.id.fragment)
@Inject
lateinit var getTheme: GetTheme
@Inject
lateinit var themeApplicator: ThemeApplicator
val container: FragmentContainerView get() = findViewById(R.id.fragment)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupWindowInsets()
inject()
setupTheme()
super.onCreate(savedInstanceState)
if (savedInstanceState != null) vm.onRestore()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -77,6 +90,19 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
}
}
private fun setupTheme() {
runBlocking {
getTheme(BaseUseCase.None).proceed(
success = {
setTheme(it)
},
failure = {
Timber.e(it, "Error while setting current app theme")
},
)
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null && intent.action == Intent.ACTION_VIEW) {
@ -94,6 +120,10 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
}
}
private fun setTheme(themeMode: ThemeMode) {
themeApplicator.apply(themeMode)
}
private fun setWallpaper(wallpaper: Wallpaper) {
when (wallpaper) {
is Wallpaper.Gradient -> {

View file

@ -56,7 +56,7 @@ class AboutAppFragment : BaseBottomSheetComposeFragment() {
}
}
private fun getVersionText() : String {
private fun getVersionText(): String {
val version = BuildConfig.VERSION_NAME
return if (version.isNotEmpty()) {
if (BuildConfig.DEBUG)
@ -117,5 +117,15 @@ val typography = Typography(
fontFamily = fonts,
fontWeight = FontWeight.SemiBold,
fontSize = 17.sp
),
body2 = TextStyle(
fontFamily = fonts,
fontWeight = FontWeight.Normal,
fontSize = 13.sp,
),
caption = TextStyle(
fontFamily = fonts,
fontWeight = FontWeight.Normal,
fontSize = 11.sp
)
)

View file

@ -5,15 +5,25 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.ui_settings.appearance.AppearanceScreen
import com.anytypeio.anytype.ui_settings.appearance.AppearanceViewModel
import javax.inject.Inject
class AppearanceFragment : BaseBottomSheetComposeFragment() {
@Inject
lateinit var factory: AppearanceViewModel.Factory
private val vm by viewModels<AppearanceViewModel> { factory }
private val onWallpaperClicked = {
findNavController().navigate(R.id.wallpaperSetScreen)
}
@ -28,13 +38,22 @@ class AppearanceFragment : BaseBottomSheetComposeFragment() {
setContent {
MaterialTheme(typography = typography) {
AppearanceScreen(
onWallpaperClicked = onWallpaperClicked
onWallpaperClicked = onWallpaperClicked,
light = { vm.onLight() },
dark = { vm.onDark() },
system = { vm.onSystem() },
selectedMode = vm.selectedTheme.collectAsState().value
)
}
}
}
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
override fun injectDependencies() {
componentManager().appearanceComponent.get().inject(this)
}
override fun releaseDependencies() {
componentManager().appearanceComponent.release()
}
}

View file

@ -0,0 +1,7 @@
package com.anytypeio.anytype.core_models
sealed class ThemeMode {
object System : ThemeMode()
object Light : ThemeMode()
object Night : ThemeMode()
}

View file

@ -105,6 +105,7 @@
<color name="default_tag_background">#FEF3C5</color>
<color name="dark_amber">#E89D00</color>
<color name="amber25">#FFEE94</color>
<color name="default_filter_tag_background_color">#F3F2EC</color>
<color name="default_filter_tag_text_color">#6C6A5F</color>

View file

@ -1,10 +1,13 @@
package com.anytypeio.anytype.data.auth.repo
import com.anytypeio.anytype.core_models.Wallpaper
import com.anytypeio.anytype.core_models.ThemeMode
interface UserSettingsCache {
suspend fun setDefaultObjectType(type: String, name: String)
suspend fun getDefaultObjectType(): Pair<String?, String?>
suspend fun setWallpaper(wallpaper: Wallpaper)
suspend fun getWallpaper() : Wallpaper
suspend fun setThemeMode(mode: ThemeMode)
suspend fun getThemeMode(): ThemeMode
}

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.data.auth.repo
import com.anytypeio.anytype.core_models.Wallpaper
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.domain.config.UserSettingsRepository
class UserSettingsDataRepository(private val cache: UserSettingsCache) : UserSettingsRepository {
@ -16,4 +17,9 @@ class UserSettingsDataRepository(private val cache: UserSettingsCache) : UserSet
}
override suspend fun getDefaultObjectType(): Pair<String?, String?> = cache.getDefaultObjectType()
override suspend fun setThemeMode(mode: ThemeMode) {
cache.setThemeMode(mode)
}
override suspend fun getThemeMode(): ThemeMode = cache.getThemeMode()
}

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.domain.config
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.core_models.Wallpaper
interface UserSettingsRepository {
@ -7,4 +8,6 @@ interface UserSettingsRepository {
suspend fun getWallpaper(): Wallpaper
suspend fun setDefaultObjectType(type: String, name: String)
suspend fun getDefaultObjectType(): Pair<String?, String?>
suspend fun setThemeMode(mode: ThemeMode)
suspend fun getThemeMode(): ThemeMode
}

View file

@ -0,0 +1,14 @@
package com.anytypeio.anytype.domain.theme
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.config.UserSettingsRepository
class GetTheme(
private val repo: UserSettingsRepository
) : BaseUseCase<ThemeMode, BaseUseCase.None>() {
override suspend fun run(params: None) = safe {
repo.getThemeMode()
}
}

View file

@ -0,0 +1,13 @@
package com.anytypeio.anytype.domain.theme
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.config.UserSettingsRepository
class SetTheme(
private val repo: UserSettingsRepository
) : BaseUseCase<Unit, ThemeMode>() {
override suspend fun run(params: ThemeMode) = safe {
repo.setThemeMode(params)
}
}

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.persistence.repo
import android.content.SharedPreferences
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.core_models.Wallpaper
import com.anytypeio.anytype.data.auth.repo.UserSettingsCache
@ -78,6 +79,48 @@ class DefaultUserSettingsCache(private val prefs: SharedPreferences) : UserSetti
}
}
override suspend fun setThemeMode(mode: ThemeMode) {
prefs
.edit()
.putInt(THEME_KEY, mode.toInt())
.apply()
}
override suspend fun getThemeMode(): ThemeMode {
return if (prefs.contains(THEME_KEY)) {
prefs.getInt(THEME_KEY, -1).toTheme()
} else {
ThemeMode.System
}
}
private fun ThemeMode.toInt() = when (this) {
ThemeMode.Light -> {
THEME_TYPE_LIGHT
}
ThemeMode.Night -> {
THEME_TYPE_NIGHT
}
ThemeMode.System -> {
THEME_TYPE_SYSTEM
}
}
private fun Int.toTheme() = when (this) {
THEME_TYPE_LIGHT -> {
ThemeMode.Light
}
THEME_TYPE_NIGHT -> {
ThemeMode.Night
}
THEME_TYPE_SYSTEM -> {
ThemeMode.System
}
else -> {
throw IllegalStateException("Illegal theme key!")
}
}
companion object {
const val DEFAULT_OBJECT_TYPE_ID_KEY = "prefs.user_settings.default_object_type.id"
const val DEFAULT_OBJECT_TYPE_NAME_KEY = "prefs.user_settings.default_object_type.name"
@ -88,5 +131,10 @@ class DefaultUserSettingsCache(private val prefs: SharedPreferences) : UserSetti
const val WALLPAPER_TYPE_KEY = "prefs.user_settings.wallpaper_type"
const val WALLPAPER_VALUE_KEY = "prefs.user_settings.wallpaper_value"
const val THEME_KEY = "prefs.user_settings.theme_mode"
const val THEME_TYPE_SYSTEM = 1
const val THEME_TYPE_LIGHT = 2
const val THEME_TYPE_NIGHT = 3
}
}

View file

@ -32,6 +32,7 @@ dependencies {
implementation project(':domain')
implementation project(':core-ui')
implementation project(':analytics')
implementation project(':core-models')
def applicationDependencies = rootProject.ext.mainApplication
def unitTestDependencies = rootProject.ext.unitTesting
@ -39,6 +40,7 @@ dependencies {
implementation applicationDependencies.lifecycleViewModel
implementation applicationDependencies.lifecycleRuntime
implementation applicationDependencies.appcompat
implementation applicationDependencies.compose
implementation applicationDependencies.composeFoundation
implementation applicationDependencies.composeMaterial

View file

@ -1,26 +1,73 @@
package com.anytypeio.anytype.ui_settings.appearance
import androidx.annotation.StringRes
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement.SpaceEvenly
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
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.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.foundation.Option
import com.anytypeio.anytype.core_ui.foundation.Toolbar
import com.anytypeio.anytype.ui_settings.R
private val buttonSize = 60.dp
private const val firstQuarterFactor = 0.5f
private const val thirdQuartersFactor = 1.5f
@Composable
fun AppearanceScreen(
onWallpaperClicked: () -> Unit
onWallpaperClicked: () -> Unit,
light: () -> Unit,
dark: () -> Unit,
system: () -> Unit,
selectedMode: ThemeMode
) {
Column {
Box(Modifier.padding(vertical = 6.dp).align(Alignment.CenterHorizontally)) {
Box(
Modifier
.padding(vertical = 6.dp)
.align(Alignment.CenterHorizontally)
) {
Dragger()
}
Toolbar(stringResource(R.string.appearance))
@ -30,6 +77,304 @@ fun AppearanceScreen(
onClick = onWallpaperClicked
)
Divider(paddingStart = 60.dp)
Box(Modifier.height(54.dp))
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = 18.dp, bottom = 12.dp),
textAlign = TextAlign.Center,
text = stringResource(R.string.mode),
style = MaterialTheme.typography.body2,
color = colorResource(com.anytypeio.anytype.core_ui.R.color.text_secondary),
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 20.dp),
horizontalArrangement = SpaceEvenly
) {
LightModeButton(
onClick = light,
selectedMode == ThemeMode.Light
)
DarkModeButton(
onClick = dark,
selectedMode == ThemeMode.Night
)
SystemModeButton(
onClick = system,
selectedMode == ThemeMode.System
)
}
Divider(paddingStart = 20.dp)
Box(Modifier.height(48.dp))
}
}
}
@Composable
fun LightModeButton(
onClick: () -> Unit = {},
isSelected: Boolean
) {
ButtonColumn(onClick = onClick) {
SelectionBox(isSelected = isSelected) {
Text(
text = "Aa",
style = MaterialTheme.typography.h1,
modifier = Modifier
.size(buttonSize)
.background(
color = Color.White,
shape = RoundedCornerShape(14.dp)
)
.border(
width = 1.dp,
color = colorResource(id = R.color.shape_primary),
shape = RoundedCornerShape(14.dp)
)
.wrapContentSize(align = Alignment.Center)
)
}
ModeNameText(id = R.string.light)
}
}
@Composable
fun DarkModeButton(
onClick: () -> Unit = {},
isSelected: Boolean
) {
ButtonColumn(onClick = onClick) {
SelectionBox(isSelected = isSelected) {
Text(
text = "Aa",
style = MaterialTheme.typography.h1,
color = colorResource(id = R.color.text_white),
modifier = Modifier
.size(buttonSize)
.background(
color = Color.Black,
shape = RoundedCornerShape(14.dp)
)
.wrapContentSize(align = Alignment.Center)
)
}
ModeNameText(id = R.string.dark)
}
}
@Composable
fun SystemModeButton(
onClick: () -> Unit = {},
isSelected: Boolean
) {
ButtonColumn(onClick = onClick) {
SelectionBox(isSelected = isSelected) {
val cornersRadius = with(LocalDensity.current) { 14.dp.toPx() }
val greyCornerRadius = with(LocalDensity.current) { 15.dp.toPx() }
val greyBorderSize = with(LocalDensity.current) { 1.dp.toPx() }
val greyColor = colorResource(id = R.color.shape_primary)
Canvas(
modifier = Modifier.size(buttonSize)
) {
val rect = Rect(Offset.Zero, size)
drawWholeViewGreyRoundedRectangle(this, rect, greyColor, greyCornerRadius)
drawFirstQuarterWhiteRoundedRectangle(this, rect, cornersRadius, greyBorderSize)
drawSecondQuarterWhiteRectangle(this, rect, greyBorderSize)
drawThirdQuarterBlackRectangle(this, rect)
drawFourthQuarterBlackRoundedRectangle(this, rect, cornersRadius)
}
val annotatedString = buildAnnotatedString {
withStyle(style = SpanStyle(Color.Black)) {
append("A")
}
withStyle(style = SpanStyle(Color.White)) {
append("a")
}
}
Text(
text = annotatedString,
style = MaterialTheme.typography.h1,
modifier = Modifier
.size(buttonSize)
.wrapContentSize(align = Alignment.Center)
)
}
ModeNameText(id = R.string.system)
}
}
@Composable
fun SelectionBox(
isSelected: Boolean,
content: @Composable BoxScope.() -> Unit
) {
Box(
modifier = Modifier
.let {
if (isSelected) it.border(
width = 2.dp,
color = colorResource(id = R.color.amber25),
shape = RoundedCornerShape(18.dp)
) else {
it
}
}
.padding(4.dp),
content = content
)
}
@Composable
fun ButtonColumn(
onClick: () -> Unit,
content: @Composable ColumnScope.() -> Unit
) {
Column(
modifier = Modifier
.wrapContentWidth(Alignment.CenterHorizontally)
.clickable(onClick = onClick),
horizontalAlignment = Alignment.CenterHorizontally,
content = content
)
}
@Composable
fun ModeNameText(
@StringRes id: Int
) {
Text(
text = stringResource(id = id),
style = MaterialTheme.typography.caption,
color = colorResource(com.anytypeio.anytype.core_ui.R.color.text_secondary),
modifier = Modifier.padding(top = 4.dp)
)
}
@Preview()
@Composable
fun ComposablePreview() {
AppearanceScreen({}, {}, {}, {}, ThemeMode.Light)
}
fun drawThirdQuarterBlackRectangle(
drawScope: DrawScope,
rect: Rect
) {
with(drawScope) {
val path = Path().apply {
moveTo(rect.topCenter.x, rect.topCenter.y)
lineTo(rect.topCenter.x * thirdQuartersFactor, rect.topCenter.y)
lineTo(rect.bottomCenter.x * thirdQuartersFactor, rect.bottomCenter.y)
lineTo(rect.bottomCenter.x, rect.bottomCenter.y)
close()
}
drawPathIntoCanvas(this, path, Color.Black)
}
}
fun drawFourthQuarterBlackRoundedRectangle(
drawScope: DrawScope,
rect: Rect,
cornersRadius: Float
) {
with(drawScope) {
val path = Path().apply {
moveTo(rect.topCenter.x * thirdQuartersFactor - cornersRadius, rect.topCenter.y)
lineTo(rect.topRight)
lineTo(rect.bottomRight)
lineTo(rect.bottomCenter.x * thirdQuartersFactor - cornersRadius, rect.bottomCenter.y)
close()
}
drawPathIntoCanvas(this, path, Color.Black, PathEffect.cornerPathEffect(cornersRadius))
}
}
fun drawSecondQuarterWhiteRectangle(
drawScope: DrawScope,
rect: Rect,
greyBorderSize: Float
) {
with(drawScope) {
val path = Path().apply {
moveTo(rect.topCenter.x, rect.topCenter.y + greyBorderSize)
lineTo(rect.topCenter.x * firstQuarterFactor, rect.topCenter.y + greyBorderSize)
lineTo(rect.bottomCenter.x * firstQuarterFactor, rect.bottomCenter.y - greyBorderSize)
lineTo(rect.bottomCenter.x, rect.bottomCenter.y - greyBorderSize)
close()
}
drawPathIntoCanvas(this, path, Color.White, null)
}
}
fun drawFirstQuarterWhiteRoundedRectangle(
drawScope: DrawScope,
rect: Rect,
cornersRadius: Float,
greyBorderSize: Float
) {
with(drawScope) {
val path = Path().apply {
moveTo(
rect.topCenter.x * firstQuarterFactor + cornersRadius,
rect.topCenter.y + greyBorderSize
)
lineTo(rect.topLeft.x + greyBorderSize, rect.topLeft.y + greyBorderSize)
lineTo(rect.bottomLeft.x + greyBorderSize, rect.bottomLeft.y - greyBorderSize)
lineTo(
rect.bottomCenter.x * firstQuarterFactor + cornersRadius,
rect.bottomCenter.y - greyBorderSize
)
close()
}
drawPathIntoCanvas(this, path, Color.White, PathEffect.cornerPathEffect(cornersRadius))
}
}
fun drawWholeViewGreyRoundedRectangle(
drawScope: DrawScope,
rect: Rect,
greyColor: Color,
greyCornerRadius: Float
) {
with(drawScope) {
val path = Path().apply {
moveTo(rect.topLeft.x, rect.topLeft.y)
lineTo(rect.topRight.x, rect.topRight.y)
lineTo(rect.bottomRight.x, rect.bottomRight.y)
lineTo(rect.bottomLeft.x, rect.bottomLeft.y)
close()
}
drawPathIntoCanvas(this, path, greyColor, PathEffect.cornerPathEffect(greyCornerRadius))
}
}
fun drawPathIntoCanvas(
drawScope: DrawScope,
path: Path,
toColor: Color,
toPathEffect: PathEffect? = null
) {
with(drawScope) {
drawIntoCanvas { canvas ->
canvas.drawOutline(
outline = Outline.Generic(path),
paint = Paint().apply {
color = toColor
pathEffect = toPathEffect
}
)
}
}
}
private fun Path.lineTo(offset: Offset) = lineTo(offset.x, offset.y)

View file

@ -0,0 +1,82 @@
package com.anytypeio.anytype.ui_settings.appearance
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.theme.GetTheme
import com.anytypeio.anytype.domain.theme.SetTheme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
class AppearanceViewModel(
private val getTheme: GetTheme,
private val setTheme: SetTheme,
private val themeApplicator: ThemeApplicator,
) : ViewModel() {
val selectedTheme = MutableStateFlow<ThemeMode>(ThemeMode.System)
init {
viewModelScope.launch {
getTheme(BaseUseCase.None).proceed(
success = {
proceedWithUpdatingSelectedTheme(it)
},
failure = {
Timber.e(it, "Error while getting current app theme")
})
}
}
private fun saveTheme(mode: ThemeMode) {
viewModelScope.launch {
setTheme(params = mode).proceed(
success = {
proceedWithUpdatingTheme(mode)
},
failure = {
Timber.e(it, "Error while setting current app theme")
}
)
}
}
fun onLight() {
saveTheme(ThemeMode.Light)
}
fun onDark() {
saveTheme(ThemeMode.Night)
}
fun onSystem() {
saveTheme(ThemeMode.System)
}
private fun proceedWithUpdatingTheme(themeMode: ThemeMode) {
themeApplicator.apply(themeMode)
proceedWithUpdatingSelectedTheme(themeMode)
}
private fun proceedWithUpdatingSelectedTheme(themeMode: ThemeMode) {
selectedTheme.value = themeMode
}
class Factory(
private val getTheme: GetTheme,
private val setTheme: SetTheme,
private val themeApplicator: ThemeApplicator,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return AppearanceViewModel(
getTheme = getTheme,
setTheme = setTheme,
themeApplicator = themeApplicator
) as T
}
}
}

View file

@ -0,0 +1,26 @@
package com.anytypeio.anytype.ui_settings.appearance
import androidx.appcompat.app.AppCompatDelegate
import com.anytypeio.anytype.core_models.ThemeMode
interface ThemeApplicator {
fun apply(theme: ThemeMode)
}
class ThemeApplicatorImpl: ThemeApplicator {
override fun apply(theme: ThemeMode) {
when(theme) {
ThemeMode.Light -> apply(AppCompatDelegate.MODE_NIGHT_NO)
ThemeMode.Night -> apply(AppCompatDelegate.MODE_NIGHT_YES)
ThemeMode.System -> apply(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
private fun apply(mode: Int) {
if (AppCompatDelegate.getDefaultNightMode() != mode) {
AppCompatDelegate.setDefaultNightMode(mode)
}
}
}

View file

@ -14,11 +14,16 @@
<string name="reset_account">Reset account</string>
<string name="delete_account">Delete account</string>
<string name="data">Data</string>
<string name="mode">Mode</string>
<string name="log_out">Logout</string>
<string name="pin_code">Pin code</string>
<string name="access">Access</string>
<string name="wallpaper">Wallpaper</string>
<string name="light">Light</string>
<string name="dark">Dark</string>
<string name="system">System</string>
<string name="have_you_back_up_your_keychain">Have you backed up your keychain phrase?</string>
<string name="you_will_need_to_sign_in">You will need it to sign in. Keep it in a safe place. If you lose it, you can no longer access your account.</string>