adding material you toggle

- meant moving the base preferences to the core module and allowing the activity to recreate itself if the theme congifuration has changed
This commit is contained in:
Adam Brown 2022-09-08 15:08:19 +01:00 committed by Adam Brown
parent f646da9d1e
commit 0c113896c1
35 changed files with 237 additions and 118 deletions

View File

@ -2,8 +2,8 @@ package app.dapk.st
import android.content.Context
import app.dapk.st.core.CoroutineDispatchers
import app.dapk.st.core.Preferences
import app.dapk.st.core.withIoContext
import app.dapk.st.domain.Preferences
internal class SharedPreferencesDelegate(
context: Context,

View File

@ -96,32 +96,35 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver)
val domainModules = DomainModules(matrixModules, trackingModule.errorTracker, workModule, storeModule, context, coroutineDispatchers)
val coreAndroidModule = CoreAndroidModule(intentFactory = object : IntentFactory {
override fun notificationOpenApp(context: Context) = PendingIntent.getActivity(
context,
1000,
home(context)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK),
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
val coreAndroidModule = CoreAndroidModule(
intentFactory = object : IntentFactory {
override fun notificationOpenApp(context: Context) = PendingIntent.getActivity(
context,
1000,
home(context)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK),
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
override fun notificationOpenMessage(context: Context, roomId: RoomId) = PendingIntent.getActivity(
context,
roomId.hashCode(),
messenger(context, roomId)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK),
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
override fun notificationOpenMessage(context: Context, roomId: RoomId) = PendingIntent.getActivity(
context,
roomId.hashCode(),
messenger(context, roomId)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK),
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
override fun home(context: Context) = Intent(context, MainActivity::class.java)
override fun messenger(context: Context, roomId: RoomId) = MessengerActivity.newInstance(context, roomId)
override fun messengerShortcut(context: Context, roomId: RoomId) = MessengerActivity.newShortcutInstance(context, roomId)
override fun messengerAttachments(context: Context, roomId: RoomId, attachments: List<MessageAttachment>) = MessengerActivity.newMessageAttachment(
context,
roomId,
attachments
)
})
override fun home(context: Context) = Intent(context, MainActivity::class.java)
override fun messenger(context: Context, roomId: RoomId) = MessengerActivity.newInstance(context, roomId)
override fun messengerShortcut(context: Context, roomId: RoomId) = MessengerActivity.newShortcutInstance(context, roomId)
override fun messengerAttachments(context: Context, roomId: RoomId, attachments: List<MessageAttachment>) = MessengerActivity.newMessageAttachment(
context,
roomId,
attachments
)
},
unsafeLazy { storeModule.value.preferences }
)
val featureModules = FeatureModules(
storeModule,
@ -187,7 +190,8 @@ internal class FeatureModules internal constructor(
matrixModules.sync,
context.contentResolver,
buildMeta,
coroutineDispatchers
coroutineDispatchers,
coreAndroidModule.themeStore(),
)
}
val profileModule by unsafeLazy { ProfileModule(matrixModules.profile, matrixModules.sync, matrixModules.room, trackingModule.errorTracker) }

View File

@ -0,0 +1,12 @@
package app.dapk.st.core
interface Preferences {
suspend fun store(key: String, value: String)
suspend fun readString(key: String): String?
suspend fun clear()
suspend fun remove(key: String)
}
suspend fun Preferences.readBoolean(key: String) = this.readString(key)?.toBooleanStrict()
suspend fun Preferences.store(key: String, value: Boolean) = this.store(key, value.toString())

View File

@ -1,6 +1,5 @@
package app.dapk.st.design.components
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
@ -19,13 +18,13 @@ private val DARK_COLOURS = darkColorScheme(
onPrimary = Color(0xDDFFFFFF),
)
private val LIGHT_COLOURS = DARK_COLOURS
private val DARK_EXTENDED = createExtended(DARK_COLOURS.primary, DARK_COLOURS.onPrimary)
private val DARK_EXTENDED = ExtendedColors(
selfBubble = DARK_COLOURS.primary,
onSelfBubble = DARK_COLOURS.onPrimary,
private fun createExtended(primary: Color, onPrimary: Color) = ExtendedColors(
selfBubble = primary,
onSelfBubble = onPrimary,
othersBubble = Color(0x20EDEDED),
onOthersBubble = Color(0xFF000000),
onOthersBubble = DARK_COLOURS.onPrimary,
selfBubbleReplyBackground = Color(0x40EAEAEA),
otherBubbleReplyBackground = Color(0x20EAEAEA),
missingImageColors = listOf(
@ -34,7 +33,6 @@ private val DARK_EXTENDED = ExtendedColors(
Color(0xFFf6c8cb) to Color(0xFFda2535),
)
)
private val LIGHT_EXTENDED = DARK_EXTENDED
@Immutable
data class ExtendedColors(
@ -51,21 +49,22 @@ data class ExtendedColors(
}
}
private val LocalExtendedColors = staticCompositionLocalOf { LIGHT_EXTENDED }
private val LocalExtendedColors = staticCompositionLocalOf { DARK_EXTENDED }
@Composable
fun SmallTalkTheme(content: @Composable () -> Unit) {
fun SmallTalkTheme(themeConfig: ThemeConfig, content: @Composable () -> Unit) {
val systemUiController = rememberSystemUiController()
val systemInDarkTheme = isSystemInDarkTheme()
MaterialTheme(
colorScheme = dynamicDarkColorScheme(LocalContext.current)
// colorScheme = if (systemInDarkTheme) DARK_COLOURS else LIGHT_COLOURS,
) {
val colorScheme = if (themeConfig.useDynamicTheme) {
dynamicDarkColorScheme(LocalContext.current)
} else {
DARK_COLOURS
}
MaterialTheme(colorScheme = colorScheme) {
val backgroundColor = MaterialTheme.colorScheme.background
SideEffect {
systemUiController.setSystemBarsColor(backgroundColor)
}
CompositionLocalProvider(LocalExtendedColors provides if (systemInDarkTheme) DARK_EXTENDED else LIGHT_EXTENDED) {
CompositionLocalProvider(LocalExtendedColors provides createExtended(MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.onPrimary)) {
content()
}
}
@ -75,4 +74,8 @@ object SmallTalkTheme {
val extendedColors: ExtendedColors
@Composable
get() = LocalExtendedColors.current
}
}
data class ThemeConfig(
val useDynamicTheme: Boolean,
)

View File

@ -3,5 +3,6 @@ applyAndroidComposeLibraryModule(project)
dependencies {
implementation project(":core")
implementation project(":features:navigator")
implementation project(":design-library")
api project(":domains:android:core")
}

View File

@ -1,5 +1,8 @@
package app.dapk.st.core
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@ -53,4 +56,10 @@ fun LifecycleEffect(onStart: () -> Unit = {}, onStop: () -> Unit = {}) {
lifecycleOwner.value.lifecycle.removeObserver(lifecycleObserver)
}
}
}
}
fun Context.getActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.getActivity()
else -> null
}

View File

@ -1,9 +1,17 @@
package app.dapk.st.core
import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.navigator.IntentFactory
class CoreAndroidModule(private val intentFactory: IntentFactory): ProvidableModule {
class CoreAndroidModule(
private val intentFactory: IntentFactory,
private val preferences: Lazy<Preferences>,
) : ProvidableModule {
fun intentFactory() = intentFactory
private val themeStore by unsafeLazy { ThemeStore(preferences.value) }
fun themeStore() = themeStore
}

View File

@ -6,20 +6,43 @@ import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.design.components.SmallTalkTheme
import app.dapk.st.design.components.ThemeConfig
import app.dapk.st.navigator.navigator
import androidx.activity.compose.setContent as _setContent
abstract class DapkActivity : ComponentActivity(), EffectScope {
private val coreAndroidModule by unsafeLazy { module<CoreAndroidModule>() }
private val themeStore by unsafeLazy { coreAndroidModule.themeStore() }
private val remembers = mutableMapOf<Any, Any>()
protected val navigator by navigator { coreAndroidModule.intentFactory() }
private lateinit var themeConfig: ThemeConfig
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.themeConfig = ThemeConfig(themeStore.isMaterialYouEnabled())
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
}
protected fun setContent(content: @Composable () -> Unit) {
_setContent {
SmallTalkTheme(themeConfig) {
content()
}
}
}
override fun onResume() {
super.onResume()
if (themeConfig.useDynamicTheme != themeStore.isMaterialYouEnabled()) {
recreate()
}
}
@Composable
override fun OnceEffect(key: Any, sideEffect: () -> Unit) {
val triggerSideEffect = remembers.containsKey(key).not()

View File

@ -0,0 +1,26 @@
package app.dapk.st.core
import kotlinx.coroutines.runBlocking
private const val KEY_MATERIAL_YOU_ENABLED = "material_you_enabled"
class ThemeStore(
private val preferences: Preferences
) {
private var _isMaterialYouEnabled: Boolean? = null
fun isMaterialYouEnabled() = _isMaterialYouEnabled ?: blockingInitialRead()
private fun blockingInitialRead(): Boolean {
return runBlocking {
(preferences.readBoolean(KEY_MATERIAL_YOU_ENABLED) ?: false).also { _isMaterialYouEnabled = it }
}
}
suspend fun storeMaterialYouEnabled(isEnabled: Boolean) {
_isMaterialYouEnabled = isEnabled
preferences.store(KEY_MATERIAL_YOU_ENABLED, isEnabled)
}
}

View File

@ -2,10 +2,10 @@ package app.dapk.st.push
import android.content.Context
import app.dapk.st.core.CoroutineDispatchers
import app.dapk.st.core.Preferences
import app.dapk.st.core.ProvidableModule
import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.domain.Preferences
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
import app.dapk.st.firebase.messaging.Messaging
import app.dapk.st.push.messaging.MessagingPushTokenRegistrar

View File

@ -1,5 +1,7 @@
package app.dapk.st.domain
import app.dapk.st.core.Preferences
class ApplicationPreferences(
private val preferences: Preferences,
) {

View File

@ -1,7 +1,8 @@
package app.dapk.st.domain
import app.dapk.st.matrix.common.UserCredentials
import app.dapk.st.core.Preferences
import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.common.UserCredentials
internal class CredentialsPreferences(
private val preferences: Preferences,

View File

@ -1,5 +1,6 @@
package app.dapk.st.domain
import app.dapk.st.core.Preferences
import app.dapk.st.matrix.sync.FilterStore
internal class FilterPreferences(

View File

@ -1,9 +0,0 @@
package app.dapk.st.domain
interface Preferences {
suspend fun store(key: String, value: String)
suspend fun readString(key: String): String?
suspend fun clear()
suspend fun remove(key: String)
}

View File

@ -2,6 +2,7 @@ package app.dapk.st.domain
import app.dapk.db.DapkDb
import app.dapk.st.core.CoroutineDispatchers
import app.dapk.st.core.Preferences
import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.domain.eventlog.EventLogPersistence
@ -22,7 +23,7 @@ import app.dapk.st.matrix.sync.SyncStore
class StoreModule(
private val database: DapkDb,
private val databaseDropper: DatabaseDropper,
private val preferences: Preferences,
val preferences: Preferences,
private val credentialPreferences: Preferences,
private val errorTracker: ErrorTracker,
private val coroutineDispatchers: CoroutineDispatchers,

View File

@ -1,7 +1,6 @@
package app.dapk.st.domain
import app.dapk.st.core.AppLogTag
import app.dapk.st.core.log
import app.dapk.st.core.Preferences
import app.dapk.st.matrix.common.SyncToken
import app.dapk.st.matrix.sync.SyncStore
import app.dapk.st.matrix.sync.SyncStore.SyncKey

View File

@ -1,6 +1,6 @@
package app.dapk.st.domain.profile
import app.dapk.st.domain.Preferences
import app.dapk.st.core.Preferences
import app.dapk.st.matrix.common.AvatarUrl
import app.dapk.st.matrix.common.HomeServerUrl
import app.dapk.st.matrix.common.UserId

View File

@ -1,6 +1,6 @@
package app.dapk.st.domain.push
import app.dapk.st.domain.Preferences
import app.dapk.st.core.Preferences
private const val SELECTION_KEY = "push_token_selection"

View File

@ -20,7 +20,6 @@ import app.dapk.st.profile.ProfileScreen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(homeViewModel: HomeViewModel) {
SmallTalkTheme {
Surface(Modifier.fillMaxSize()) {
LaunchedEffect(true) {
homeViewModel.start()
@ -54,7 +53,6 @@ fun HomeScreen(homeViewModel: HomeViewModel) {
}
}
}
}
}
@Composable

View File

@ -46,11 +46,9 @@ class MessengerActivity : DapkActivity() {
val payload = readPayload<MessagerActivityPayload>()
log(AppLogTag.ERROR_NON_FATAL, payload)
setContent {
SmallTalkTheme {
Surface(Modifier.fillMaxSize()) {
MessengerScreen(RoomId(payload.roomId), payload.attachments, viewModel, navigator)
}
}
}
}
}

View File

@ -5,16 +5,14 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import app.dapk.st.messenger.MessengerModule
import app.dapk.st.design.components.SmallTalkTheme
import app.dapk.st.core.DapkActivity
import app.dapk.st.core.module
import app.dapk.st.core.viewModel
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.messenger.MessengerModule
import kotlinx.parcelize.Parcelize
class RoomSettingsActivity : DapkActivity() {
@ -33,10 +31,8 @@ class RoomSettingsActivity : DapkActivity() {
super.onCreate(savedInstanceState)
val payload = readPayload<RoomSettingsActivityPayload>()
setContent {
SmallTalkTheme {
Surface(Modifier.fillMaxSize()) {
Surface(Modifier.fillMaxSize()) {
// MessengerScreen(RoomId(payload.roomId), payload.attachments, viewModel, navigator)
}
}
}
}

View File

@ -1,7 +1,6 @@
package app.dapk.st.settings
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
@ -9,7 +8,6 @@ import app.dapk.st.core.DapkActivity
import app.dapk.st.core.module
import app.dapk.st.core.resetModules
import app.dapk.st.core.viewModel
import app.dapk.st.design.components.SmallTalkTheme
class SettingsActivity : DapkActivity() {
@ -18,14 +16,12 @@ class SettingsActivity : DapkActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SmallTalkTheme {
Surface(Modifier.fillMaxSize()) {
SettingsScreen(settingsViewModel, onSignOut = {
resetModules()
navigator.navigate.toHome()
finish()
}, navigator)
}
Surface(Modifier.fillMaxSize()) {
SettingsScreen(settingsViewModel, onSignOut = {
resetModules()
navigator.navigate.toHome()
finish()
}, navigator)
}
}
}

View File

@ -1,11 +1,13 @@
package app.dapk.st.settings
import app.dapk.st.core.BuildMeta
import app.dapk.st.core.ThemeStore
import app.dapk.st.push.PushTokenRegistrars
internal class SettingsItemFactory(
private val buildMeta: BuildMeta,
private val pushTokenRegistrars: PushTokenRegistrars,
private val themeStore: ThemeStore,
) {
suspend fun root() = listOf(
@ -13,6 +15,8 @@ internal class SettingsItemFactory(
SettingItem.Text(SettingItem.Id.Encryption, "Encryption"),
SettingItem.Text(SettingItem.Id.EventLog, "Event log"),
SettingItem.Text(SettingItem.Id.PushProvider, "Push provider", pushTokenRegistrars.currentSelection().id),
SettingItem.Header("Theme"),
SettingItem.Toggle(SettingItem.Id.ToggleDynamicTheme, "Enable Material You", state = themeStore.isMaterialYouEnabled()),
SettingItem.Header("Data"),
SettingItem.Text(SettingItem.Id.ClearCache, "Clear cache"),
SettingItem.Header("Account"),
@ -22,4 +26,4 @@ internal class SettingsItemFactory(
SettingItem.Text(SettingItem.Id.Ignored, "Version", buildMeta.versionName),
)
}
}

View File

@ -4,6 +4,7 @@ import android.content.ContentResolver
import app.dapk.st.core.BuildMeta
import app.dapk.st.core.CoroutineDispatchers
import app.dapk.st.core.ProvidableModule
import app.dapk.st.core.ThemeStore
import app.dapk.st.domain.StoreModule
import app.dapk.st.matrix.crypto.CryptoService
import app.dapk.st.matrix.sync.SyncService
@ -18,17 +19,21 @@ class SettingsModule(
private val contentResolver: ContentResolver,
private val buildMeta: BuildMeta,
private val coroutineDispatchers: CoroutineDispatchers,
private val themeStore: ThemeStore,
) : ProvidableModule {
internal fun settingsViewModel() = SettingsViewModel(
storeModule.cacheCleaner(),
contentResolver,
cryptoService,
syncService,
UriFilenameResolver(contentResolver, coroutineDispatchers),
SettingsItemFactory(buildMeta, pushModule.pushTokenRegistrars()),
pushModule.pushTokenRegistrars(),
)
internal fun settingsViewModel(): SettingsViewModel {
return SettingsViewModel(
storeModule.cacheCleaner(),
contentResolver,
cryptoService,
syncService,
UriFilenameResolver(contentResolver, coroutineDispatchers),
SettingsItemFactory(buildMeta, pushModule.pushTokenRegistrars(), themeStore),
pushModule.pushTokenRegistrars(),
themeStore,
)
}
internal fun eventLogViewModel(): EventLoggerViewModel {
return EventLoggerViewModel(storeModule.eventLogStore())

View File

@ -5,6 +5,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.activity.compose.LocalActivityResultRegistryOwner
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
@ -13,11 +14,11 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
@ -37,10 +38,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import app.dapk.st.core.Lce
import app.dapk.st.core.LceWithProgress
import app.dapk.st.core.StartObserving
import app.dapk.st.core.components.CenteredLoading
import app.dapk.st.core.components.Header
import app.dapk.st.core.getActivity
import app.dapk.st.design.components.SettingsTextRow
import app.dapk.st.design.components.Spider
import app.dapk.st.design.components.SpiderPage
@ -154,7 +155,7 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
is ImportResult.Error -> {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
val message = when(val type = result.cause) {
val message = when (val type = result.cause) {
ImportResult.Error.Type.NoKeysFound -> "No keys found in the file"
ImportResult.Error.Type.UnexpectedDecryptionOutput -> "Unable to decrypt file, double check your passphrase"
is ImportResult.Error.Type.Unknown -> "${type.cause::class.java.simpleName}: ${type.cause.message}"
@ -222,6 +223,9 @@ private fun RootSettings(page: Page.Root, onClick: (SettingItem) -> Unit) {
}
is SettingItem.Header -> Header(item.label)
is SettingItem.Toggle -> Toggle(item, onToggle = {
onClick(item)
})
}
}
item { Spacer(Modifier.height(12.dp)) }
@ -238,6 +242,23 @@ private fun RootSettings(page: Page.Root, onClick: (SettingItem) -> Unit) {
}
}
@Composable
private fun Toggle(item: SettingItem.Toggle, onToggle: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, end = 24.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = item.content)
Switch(
checked = item.state,
onCheckedChange = { onToggle() }
)
}
}
@Composable
private fun Encryption(viewModel: SettingsViewModel, page: Page.Security) {
Column {
@ -292,6 +313,9 @@ private fun SettingsViewModel.ObserveEvents(onSignOut: () -> Unit) {
is OpenUrl -> {
context.startActivity(Intent(Intent.ACTION_VIEW).apply { data = it.url.toUri() })
}
RecreateActivity -> {
context.getActivity()?.recreate()
}
}
}
}

View File

@ -44,6 +44,7 @@ internal sealed interface SettingItem {
data class Header(val label: String, override val id: Id = Id.Ignored) : SettingItem
data class Text(override val id: Id, val content: String, val subtitle: String? = null) : SettingItem
data class Toggle(override val id: Id, val content: String, val state: Boolean) : SettingItem
data class AccessToken(override val id: Id, val content: String, val accessToken: String) : SettingItem
enum class Id {
@ -55,6 +56,7 @@ internal sealed interface SettingItem {
Encryption,
PrivacyPolicy,
Ignored,
ToggleDynamicTheme,
}
}
@ -65,5 +67,6 @@ sealed interface SettingsEvent {
object OpenEventLog : SettingsEvent
data class OpenUrl(val url: String) : SettingsEvent
data class CopyToClipboard(val message: String, val content: String) : SettingsEvent
object RecreateActivity : SettingsEvent
}

View File

@ -4,6 +4,7 @@ import android.content.ContentResolver
import android.net.Uri
import androidx.lifecycle.viewModelScope
import app.dapk.st.core.Lce
import app.dapk.st.core.ThemeStore
import app.dapk.st.design.components.SpiderPage
import app.dapk.st.domain.StoreCleaner
import app.dapk.st.matrix.crypto.CryptoService
@ -30,6 +31,7 @@ internal class SettingsViewModel(
private val uriFilenameResolver: UriFilenameResolver,
private val settingsItemFactory: SettingsItemFactory,
private val pushTokenRegistrars: PushTokenRegistrars,
private val themeStore: ThemeStore,
factory: MutableStateFactory<SettingsScreenState> = defaultStateFactory(),
) : DapkViewModel<SettingsScreenState, SettingsEvent>(
initialState = SettingsScreenState(SpiderPage(Page.Routes.root, "Settings", null, Page.Root(Lce.Loading()))),
@ -98,9 +100,17 @@ internal class SettingsViewModel(
Ignored -> {
// do nothing
}
ToggleDynamicTheme -> {
viewModelScope.launch {
themeStore.storeMaterialYouEnabled(!themeStore.isMaterialYouEnabled())
start()
_events.emit(RecreateActivity)
}
}
}
}
fun fetchPushProviders() {
updatePageState<Page.PushProviders> { copy(options = Lce.Loading()) }
viewModelScope.launch {

View File

@ -1,7 +1,6 @@
package app.dapk.st.settings.eventlogger
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
@ -9,7 +8,6 @@ import app.dapk.st.core.DapkActivity
import app.dapk.st.core.module
import app.dapk.st.core.viewModel
import app.dapk.st.settings.SettingsModule
import app.dapk.st.design.components.SmallTalkTheme
class EventLogActivity : DapkActivity() {
@ -18,10 +16,8 @@ class EventLogActivity : DapkActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SmallTalkTheme {
Surface(Modifier.fillMaxSize()) {
EventLogScreen(viewModel)
}
Surface(Modifier.fillMaxSize()) {
EventLogScreen(viewModel)
}
}
}

View File

@ -0,0 +1,12 @@
package app.dapk.st.settings
import app.dapk.st.core.ThemeStore
import io.mockk.every
import io.mockk.mockk
import test.delegateReturn
class FakeThemeStore {
val instance = mockk<ThemeStore>()
fun givenMaterialYouIsEnabled() = every { instance.isMaterialYouEnabled() }.delegateReturn()
}

View File

@ -6,7 +6,6 @@ import app.dapk.st.push.Registrar
import internalfixture.aSettingHeaderItem
import internalfixture.aSettingTextItem
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
@ -14,17 +13,20 @@ import org.junit.Test
import test.delegateReturn
private val A_SELECTION = Registrar("A_SELECTION")
private const val ENABLED_MATERIAL_YOU = true
class SettingsItemFactoryTest {
private val buildMeta = BuildMeta(versionName = "a-version-name", versionCode = 100)
private val fakePushTokenRegistrars = FakePushRegistrars()
private val fakeThemeStore = FakeThemeStore()
private val settingsItemFactory = SettingsItemFactory(buildMeta, fakePushTokenRegistrars.instance)
private val settingsItemFactory = SettingsItemFactory(buildMeta, fakePushTokenRegistrars.instance, fakeThemeStore.instance)
@Test
fun `when creating root items, then is expected`() = runTest {
fakePushTokenRegistrars.givenCurrentSelection().returns(A_SELECTION)
fakeThemeStore.givenMaterialYouIsEnabled().returns(ENABLED_MATERIAL_YOU)
val result = settingsItemFactory.root()
@ -33,6 +35,8 @@ class SettingsItemFactoryTest {
aSettingTextItem(SettingItem.Id.Encryption, "Encryption"),
aSettingTextItem(SettingItem.Id.EventLog, "Event log"),
aSettingTextItem(SettingItem.Id.PushProvider, "Push provider", A_SELECTION.id),
SettingItem.Header("Theme"),
SettingItem.Toggle(SettingItem.Id.ToggleDynamicTheme, "Enable Material You", state = ENABLED_MATERIAL_YOU),
aSettingHeaderItem("Data"),
aSettingTextItem(SettingItem.Id.ClearCache, "Clear cache"),
aSettingHeaderItem("Account"),

View File

@ -40,6 +40,7 @@ internal class SettingsViewModelTest {
private val fakeUriFilenameResolver = FakeUriFilenameResolver()
private val fakePushTokenRegistrars = FakePushRegistrars()
private val fakeSettingsItemFactory = FakeSettingsItemFactory()
private val fakeThemeStore = FakeThemeStore()
private val viewModel = SettingsViewModel(
fakeStoreCleaner,
@ -49,6 +50,7 @@ internal class SettingsViewModelTest {
fakeUriFilenameResolver.instance,
fakeSettingsItemFactory.instance,
fakePushTokenRegistrars.instance,
fakeThemeStore.instance,
runViewModelTest.testMutableStateFactory(),
)

View File

@ -4,14 +4,12 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import app.dapk.st.core.DapkActivity
import app.dapk.st.core.module
import app.dapk.st.core.viewModel
import app.dapk.st.design.components.SmallTalkTheme
class ShareEntryActivity : DapkActivity() {
@ -21,10 +19,8 @@ class ShareEntryActivity : DapkActivity() {
super.onCreate(savedInstanceState)
val urisToShare = intent.readSendUrisOrNull() ?: throw IllegalArgumentException("Expected deeplink uris but they were missing")
setContent {
SmallTalkTheme {
Surface(Modifier.fillMaxSize()) {
ShareEntryScreen(navigator, viewModel)
}
Surface(Modifier.fillMaxSize()) {
ShareEntryScreen(navigator, viewModel)
}
}
viewModel.withUris(urisToShare)

View File

@ -1,11 +1,9 @@
package app.dapk.st.verification
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import app.dapk.st.design.components.SmallTalkTheme
import app.dapk.st.core.DapkActivity
import app.dapk.st.core.module
import app.dapk.st.core.viewModel
@ -17,10 +15,8 @@ class VerificationActivity : DapkActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SmallTalkTheme {
Surface(Modifier.fillMaxSize()) {
VerificationScreen(verificationViewModel)
}
Surface(Modifier.fillMaxSize()) {
VerificationScreen(verificationViewModel)
}
}
}

View File

@ -2,14 +2,12 @@ package app.dapk.st.verification
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@Composable
fun VerificationScreen(viewModel: VerificationViewModel) {
Column {
Text("Verification request")

View File

@ -1,6 +1,6 @@
package test.impl
import app.dapk.st.domain.Preferences
import app.dapk.st.core.Preferences
import test.unit
class InMemoryPreferences : Preferences {