From 37daf12cd0a3ee38307cd141bfd754e6c96f0294 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Dec 2022 21:48:16 +0100 Subject: [PATCH] Move logout and report bug to Setting screen --- .gitignore | 1 + app/build.gradle.kts | 1 + .../java/io/element/android/x/Navigation.kt | 28 +++---- features/logout/.gitignore | 1 + features/logout/build.gradle.kts | 27 ++++++ features/logout/consumer-rules.pro | 0 features/logout/proguard-rules.pro | 21 +++++ .../logout/ExampleInstrumentedTest.kt | 22 +++++ features/logout/src/main/AndroidManifest.xml | 4 + .../android/x/features/logout/LogoutScreen.kt | 84 +++++++++++++++++++ .../x/features/logout/LogoutViewModel.kt | 30 +++++++ .../x/features/logout/LogoutViewState.kt | 9 ++ .../x/features/logout/ExampleUnitTest.kt | 16 ++++ features/preferences/build.gradle.kts | 1 + .../features/preferences/PreferencesScreen.kt | 20 +++-- .../rageshake/bugreport/BugReportScreen.kt | 2 - .../rageshake/crash/CrashDataStore.kt | 2 +- .../crash/VectorUncaughtExceptionHandler.kt | 2 +- .../rageshake/logs/VectorFileLogger.kt | 10 +-- .../RageshakePreferenceCategory.kt | 50 +++++++---- .../features/rageshake/rageshake/RageShake.kt | 3 +- .../rageshake/rageshake/RageshakeDataStore.kt | 2 +- .../rageshake/reporter/BugReporter.kt | 35 ++++---- .../x/features/roomlist/RoomListScreen.kt | 25 ------ .../x/features/roomlist/RoomListViewModel.kt | 15 +--- .../roomlist/components/RoomListTopBar.kt | 37 -------- .../roomlist/model/RoomListViewState.kt | 1 - .../android/x/core/file/compressFile.kt | 2 +- .../android/x/designsystem/ColorUtil.kt | 14 ++++ .../components/dialogs/ConfirmationDialog.kt | 1 - .../preferences/PreferenceCategory.kt | 8 +- .../components/preferences/PreferenceSlide.kt | 45 ++++++---- .../preferences/PreferenceSwitch.kt | 29 +++++-- .../components/preferences/PreferenceText.kt | 24 ++++-- .../preferences/components/PreferenceIcon.kt | 31 +++++++ settings.gradle.kts | 1 + 36 files changed, 429 insertions(+), 175 deletions(-) create mode 100644 features/logout/.gitignore create mode 100644 features/logout/build.gradle.kts create mode 100644 features/logout/consumer-rules.pro create mode 100644 features/logout/proguard-rules.pro create mode 100644 features/logout/src/androidTest/java/io/element/android/x/features/logout/ExampleInstrumentedTest.kt create mode 100644 features/logout/src/main/AndroidManifest.xml create mode 100644 features/logout/src/main/java/io/element/android/x/features/logout/LogoutScreen.kt create mode 100644 features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewModel.kt create mode 100644 features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewState.kt create mode 100644 features/logout/src/test/java/io/element/android/x/features/logout/ExampleUnitTest.kt create mode 100644 libraries/designsystem/src/main/java/io/element/android/x/designsystem/ColorUtil.kt create mode 100644 libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/components/PreferenceIcon.kt diff --git a/.gitignore b/.gitignore index cf226dd935..f6e1ef5b8b 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,4 @@ lint/generated/ lint/outputs/ lint/tmp/ # lint/reports/ +/.idea/deploymentTargetDropDown.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 35f7f83250..271d9c1d53 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -138,6 +138,7 @@ dependencies { implementation(project(":libraries:core")) implementation(project(":features:onboarding")) implementation(project(":features:login")) + implementation(project(":features:logout")) implementation(project(":features:roomlist")) implementation(project(":features:messages")) implementation(project(":features:rageshake")) diff --git a/app/src/main/java/io/element/android/x/Navigation.kt b/app/src/main/java/io/element/android/x/Navigation.kt index 6d0ec42774..22e3c0d53c 100644 --- a/app/src/main/java/io/element/android/x/Navigation.kt +++ b/app/src/main/java/io/element/android/x/Navigation.kt @@ -71,7 +71,6 @@ fun ChangeServerScreenNavigation(navigator: DestinationsNavigator) { @Destination @Composable fun RoomListScreenNavigation(navigator: DestinationsNavigator) { - val sessionComponentsOwner = LocalContext.current.bindings().sessionComponentsOwner() RoomListScreen( onRoomClicked = { roomId: RoomId -> navigator.navigate(MessagesScreenNavigationDestination(roomId = roomId.value)) @@ -79,18 +78,6 @@ fun RoomListScreenNavigation(navigator: DestinationsNavigator) { onOpenSettings = { navigator.navigate(PreferencesScreenNavigationDestination()) }, - onSuccessLogout = { - sessionComponentsOwner.releaseActiveSession() - navigator.navigate(OnBoardingScreenNavigationDestination) { - popUpTo(RoomListScreenNavigationDestination) { - inclusive = true - } - } - }, - // Tmp entry point - onOpenRageShake = { - navigator.navigate(BugReportScreenNavigationDestination) - } ) } @@ -111,8 +98,19 @@ fun BugReportScreenNavigation(navigator: DestinationsNavigator) { @Destination @Composable fun PreferencesScreenNavigation(navigator: DestinationsNavigator) { + val sessionComponentsOwner = LocalContext.current.bindings().sessionComponentsOwner() PreferencesScreen( - onBackPressed = navigator::navigateUp + onBackPressed = navigator::navigateUp, + onOpenRageShake = { + navigator.navigate(BugReportScreenNavigationDestination) + }, + onSuccessLogout = { + sessionComponentsOwner.releaseActiveSession() + navigator.navigate(OnBoardingScreenNavigationDestination) { + popUpTo(RoomListScreenNavigationDestination) { + inclusive = true + } + } + }, ) } - diff --git a/features/logout/.gitignore b/features/logout/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/logout/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/logout/build.gradle.kts b/features/logout/build.gradle.kts new file mode 100644 index 0000000000..3287e95820 --- /dev/null +++ b/features/logout/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.ksp) + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.x.features.logout" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(project(":anvilannotations")) + anvil(project(":anvilcodegen")) + implementation(project(":libraries:di")) + implementation(project(":libraries:core")) + implementation(project(":libraries:matrix")) + implementation(project(":libraries:designsystem")) + implementation(project(":libraries:elementresources")) + implementation(libs.mavericks.compose) + ksp(libs.showkase.processor) + testImplementation(libs.test.junit) + androidTestImplementation(libs.test.junitext) +} diff --git a/features/logout/consumer-rules.pro b/features/logout/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/features/logout/proguard-rules.pro b/features/logout/proguard-rules.pro new file mode 100644 index 0000000000..ff59496d81 --- /dev/null +++ b/features/logout/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/features/logout/src/androidTest/java/io/element/android/x/features/logout/ExampleInstrumentedTest.kt b/features/logout/src/androidTest/java/io/element/android/x/features/logout/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..fc04d1e36c --- /dev/null +++ b/features/logout/src/androidTest/java/io/element/android/x/features/logout/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package io.element.android.x.features.logout + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.element.android.x.features.login.test", appContext.packageName) + } +} diff --git a/features/logout/src/main/AndroidManifest.xml b/features/logout/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e100076157 --- /dev/null +++ b/features/logout/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutScreen.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutScreen.kt new file mode 100644 index 0000000000..26f42f549f --- /dev/null +++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutScreen.kt @@ -0,0 +1,84 @@ +package io.element.android.x.features.logout + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Logout +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.compose.collectAsState +import com.airbnb.mvrx.compose.mavericksViewModel +import io.element.android.x.designsystem.ElementXTheme +import io.element.android.x.designsystem.components.ProgressDialog +import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.x.designsystem.components.preferences.PreferenceCategory +import io.element.android.x.designsystem.components.preferences.PreferenceText +import io.element.android.x.element.resources.R as ElementR + +@Composable +fun LogoutPreference( + viewModel: LogoutViewModel = mavericksViewModel(), + onSuccessLogout: () -> Unit = { }, +) { + val state: LogoutViewState by viewModel.collectAsState() + if (state.logoutAction is Success) { + onSuccessLogout() + return + } + + val openDialog = remember { mutableStateOf(false) } + + LogoutPreferenceContent( + onClick = { + openDialog.value = true + } + ) + + // Log out confirmation dialog + if (openDialog.value) { + ConfirmationDialog( + title = stringResource(id = ElementR.string.action_sign_out), + content = stringResource(id = ElementR.string.action_sign_out_confirmation_simple), + submitText = stringResource(id = ElementR.string.action_sign_out), + onCancelClicked = { + openDialog.value = false + }, + onSubmitClicked = { + openDialog.value = false + viewModel.logout() + }, + onDismiss = { + openDialog.value = false + } + ) + } + + if (state.logoutAction is Loading) { + ProgressDialog(text = "Login out...") + } +} + +@Composable +fun LogoutPreferenceContent( + onClick: () -> Unit = {}, +) { + PreferenceCategory(title = stringResource(id = ElementR.string.settings_general_title)) { + PreferenceText( + title = stringResource(id = ElementR.string.action_sign_out), + icon = Icons.Default.Logout, + onClick = onClick + ) + } +} + +@Composable +@Preview +fun LogoutContentPreview() { + ElementXTheme(darkTheme = false) { + LogoutPreference() + } +} diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewModel.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewModel.kt new file mode 100644 index 0000000000..d986b694ab --- /dev/null +++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewModel.kt @@ -0,0 +1,30 @@ +package io.element.android.x.features.logout + +import com.airbnb.mvrx.MavericksViewModel +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.x.anvilannotations.ContributesViewModel +import io.element.android.x.core.di.daggerMavericksViewModelFactory +import io.element.android.x.di.SessionScope +import io.element.android.x.matrix.MatrixClient +import kotlinx.coroutines.launch + +@ContributesViewModel(SessionScope::class) +class LogoutViewModel @AssistedInject constructor( + private val client: MatrixClient, + @Assisted initialState: LogoutViewState +) : MavericksViewModel(initialState) { + + companion object : MavericksViewModelFactory by daggerMavericksViewModelFactory() + + fun logout() { + viewModelScope.launch { + suspend { + client.logout() + }.execute { + copy(logoutAction = it) + } + } + } +} diff --git a/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewState.kt b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewState.kt new file mode 100644 index 0000000000..7642b7b463 --- /dev/null +++ b/features/logout/src/main/java/io/element/android/x/features/logout/LogoutViewState.kt @@ -0,0 +1,9 @@ +package io.element.android.x.features.logout + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.Uninitialized + +data class LogoutViewState( + val logoutAction: Async = Uninitialized, +) : MavericksState diff --git a/features/logout/src/test/java/io/element/android/x/features/logout/ExampleUnitTest.kt b/features/logout/src/test/java/io/element/android/x/features/logout/ExampleUnitTest.kt new file mode 100644 index 0000000000..6c4175276c --- /dev/null +++ b/features/logout/src/test/java/io/element/android/x/features/logout/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package io.element.android.x.features.logout + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/features/preferences/build.gradle.kts b/features/preferences/build.gradle.kts index d5234773fa..67c0bba642 100644 --- a/features/preferences/build.gradle.kts +++ b/features/preferences/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation(project(":libraries:di")) implementation(project(":libraries:core")) implementation(project(":features:rageshake")) + implementation(project(":features:logout")) implementation(project(":libraries:designsystem")) implementation(project(":libraries:elementresources")) implementation(libs.mavericks.compose) diff --git a/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesScreen.kt b/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesScreen.kt index e535a01e1a..5cca1b4ec0 100644 --- a/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesScreen.kt +++ b/features/preferences/src/main/java/io/element/android/x/features/preferences/PreferencesScreen.kt @@ -1,37 +1,43 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.element.android.x.features.preferences -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import io.element.android.x.designsystem.components.preferences.PreferenceScreen -import io.element.android.x.features.rageshake.preferences.RageshakePreferenceCategory import io.element.android.x.element.resources.R as ElementR +import io.element.android.x.features.logout.LogoutPreference +import io.element.android.x.features.rageshake.preferences.RageshakePreferences @Composable fun PreferencesScreen( onBackPressed: () -> Unit = {}, + onOpenRageShake: () -> Unit = {}, + onSuccessLogout: () -> Unit = {}, ) { // TODO Hierarchy! - // TODO Move logout here // Include pref from other modules - PreferencesContent(onBackPressed = onBackPressed) + PreferencesContent( + onBackPressed = onBackPressed, + onOpenRageShake = onOpenRageShake, + onSuccessLogout = onSuccessLogout, + ) } @Composable fun PreferencesContent( modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, + onOpenRageShake: () -> Unit = {}, + onSuccessLogout: () -> Unit = {}, ) { PreferenceScreen( modifier = modifier, onBackPressed = onBackPressed, title = stringResource(id = ElementR.string.settings) ) { - RageshakePreferenceCategory() + LogoutPreference(onSuccessLogout = onSuccessLogout) + RageshakePreferences(onOpenRageShake = onOpenRageShake) } } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportScreen.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportScreen.kt index 94006fac8e..347b58505f 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportScreen.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/bugreport/BugReportScreen.kt @@ -4,7 +4,6 @@ package io.element.android.x.features.rageshake.bugreport import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding @@ -14,7 +13,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button -import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/CrashDataStore.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/CrashDataStore.kt index b97515d21c..690330c268 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/CrashDataStore.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/CrashDataStore.kt @@ -9,10 +9,10 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import io.element.android.x.core.bool.orFalse import io.element.android.x.di.ApplicationContext +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking -import javax.inject.Inject private val Context.dataStore: DataStore by preferencesDataStore(name = "elementx_crash") diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/VectorUncaughtExceptionHandler.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/VectorUncaughtExceptionHandler.kt index ba904c75fd..d34d530c14 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/VectorUncaughtExceptionHandler.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/crash/VectorUncaughtExceptionHandler.kt @@ -3,9 +3,9 @@ package io.element.android.x.features.rageshake.crash import android.content.Context import android.os.Build import io.element.android.x.core.data.tryOrNull -import timber.log.Timber import java.io.PrintWriter import java.io.StringWriter +import timber.log.Timber class VectorUncaughtExceptionHandler( context: Context diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/VectorFileLogger.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/VectorFileLogger.kt index 560c1cbdd2..c99f7a3681 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/VectorFileLogger.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/logs/VectorFileLogger.kt @@ -3,17 +3,17 @@ package io.element.android.x.features.rageshake.logs import android.content.Context import android.util.Log import io.element.android.x.core.data.tryOrNull -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import timber.log.Timber import java.io.File import java.io.PrintWriter import java.io.StringWriter import java.util.logging.FileHandler import java.util.logging.Level import java.util.logging.Logger +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import timber.log.Timber /** * Will be planted in Timber. diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferenceCategory.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferenceCategory.kt index 1a4cf1fa7d..868397d1a4 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferenceCategory.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/preferences/RageshakePreferenceCategory.kt @@ -1,7 +1,11 @@ package io.element.android.x.features.rageshake.preferences +import androidx.compose.foundation.layout.Column +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.BugReport import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.airbnb.mvrx.compose.collectAsState @@ -10,44 +14,58 @@ import io.element.android.x.designsystem.components.preferences.PreferenceCatego import io.element.android.x.designsystem.components.preferences.PreferenceSlide import io.element.android.x.designsystem.components.preferences.PreferenceSwitch import io.element.android.x.designsystem.components.preferences.PreferenceText +import io.element.android.x.element.resources.R as ElementR import io.element.android.x.features.rageshake.detection.RageshakeDetectionViewModel import io.element.android.x.features.rageshake.detection.RageshakeDetectionViewState -import io.element.android.x.element.resources.R as ElementR @Composable -fun RageshakePreferenceCategory() { - RageshakePreferenceContent() +fun RageshakePreferences( + onOpenRageShake: () -> Unit = {}, +) { + RageshakePreferencesContent( + onOpenRageShake = onOpenRageShake, + ) } @Composable -fun RageshakePreferenceContent( - viewModel: RageshakeDetectionViewModel = mavericksViewModel() +fun RageshakePreferencesContent( + modifier: Modifier = Modifier, + viewModel: RageshakeDetectionViewModel = mavericksViewModel(), + onOpenRageShake: () -> Unit = {}, ) { val state: RageshakeDetectionViewState by viewModel.collectAsState() - PreferenceCategory(title = stringResource(id = ElementR.string.settings_rageshake)) { - if (state.isSupported) { - PreferenceSwitch( - title = stringResource(id = ElementR.string.send_bug_report_rage_shake), - isChecked = state.isEnabled, - onCheckedChange = viewModel::onEnableClicked + Column(modifier = modifier) { + PreferenceCategory(title = stringResource(id = ElementR.string.send_bug_report)) { + PreferenceText( + title = stringResource(id = ElementR.string.send_bug_report), + icon = Icons.Default.BugReport, + onClick = onOpenRageShake ) - if (state.isEnabled) { + } + PreferenceCategory(title = stringResource(id = ElementR.string.settings_rageshake)) { + if (state.isSupported) { + PreferenceSwitch( + title = stringResource(id = ElementR.string.send_bug_report_rage_shake), + isChecked = state.isEnabled, + onCheckedChange = viewModel::onEnableClicked + ) PreferenceSlide( title = stringResource(id = ElementR.string.settings_rageshake_detection_threshold), // summary = stringResource(id = ElementR.string.settings_rageshake_detection_threshold_summary), value = state.sensitivity, + enabled = state.isEnabled, steps = 3 /* 5 possible values - steps are in ]0, 1[ */, onValueChange = viewModel::onSensitivityChange ) + } else { + PreferenceText(title = "Rageshaking is not supported by your device") } - } else { - PreferenceText(title = "Rageshaking is not supported by your device") } } } @Composable @Preview -fun RageshakePreferenceCategoryPreview() { - RageshakePreferenceCategory() +fun RageshakePreferencePreview() { + RageshakePreferences() } diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageShake.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageShake.kt index dac268b1c8..2520dd1241 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageShake.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageShake.kt @@ -41,7 +41,8 @@ class RageShake @Inject constructor( } /** - * sensitivity will be {0, O.25, 0.5, 0.75, 1} and converted to [ShakeDetector.SENSITIVITY_LIGHT (=11), ShakeDetector.SENSITIVITY_HARD (=15)] + * sensitivity will be {0, O.25, 0.5, 0.75, 1} and converted to + * [ShakeDetector.SENSITIVITY_LIGHT (=11), ShakeDetector.SENSITIVITY_HARD (=15)]. */ fun setSensitivity(sensitivity: Float) { shakeDetector?.setSensitivity( diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageshakeDataStore.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageshakeDataStore.kt index a98fdf860f..27b8655bea 100644 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageshakeDataStore.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/rageshake/RageshakeDataStore.kt @@ -9,9 +9,9 @@ import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.preferencesDataStore import io.element.android.x.core.bool.orTrue import io.element.android.x.di.ApplicationContext +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import javax.inject.Inject private val Context.dataStore: DataStore by preferencesDataStore(name = "elementx_rageshake") diff --git a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporter.kt b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporter.kt index 2319266ced..b64e36ae60 100755 --- a/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporter.kt +++ b/features/rageshake/src/main/java/io/element/android/x/features/rageshake/reporter/BugReporter.kt @@ -10,6 +10,12 @@ import io.element.android.x.features.rageshake.R import io.element.android.x.features.rageshake.crash.CrashDataStore import io.element.android.x.features.rageshake.logs.VectorFileLogger import io.element.android.x.features.rageshake.screenshot.ScreenshotHolder +import java.io.File +import java.io.IOException +import java.io.OutputStreamWriter +import java.net.HttpURLConnection +import java.util.Locale +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first @@ -24,12 +30,6 @@ import okhttp3.Response import org.json.JSONException import org.json.JSONObject import timber.log.Timber -import java.io.File -import java.io.IOException -import java.io.OutputStreamWriter -import java.net.HttpURLConnection -import java.util.Locale -import javax.inject.Inject /** * BugReporter creates and sends the bug reports. @@ -121,6 +121,7 @@ class BugReporter @Inject constructor( /** * Send a bug report. * + * @param coroutineScope The coroutine scope * @param reportType The report type (bug, suggestion, feedback) * @param withDevicesLogs true to include the device log * @param withCrashLogs true to include the crash logs @@ -241,14 +242,14 @@ class BugReporter @Inject constructor( .addFormDataPart("device", Build.MODEL.trim()) // .addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff()) .addFormDataPart("multi_window", inMultiWindowMode.toOnOff()) - //.addFormDataPart( + // .addFormDataPart( // "os", Build.VERSION.RELEASE + " (API " + sdkIntProvider.get() + ") " + // Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME - //) + // ) .addFormDataPart("locale", Locale.getDefault().toString()) - //.addFormDataPart("app_language", vectorLocale.applicationLocale.toString()) - //.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString()) - //.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context)) + // .addFormDataPart("app_language", vectorLocale.applicationLocale.toString()) + // .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString()) + // .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context)) .addFormDataPart("server_version", serverVersion) .apply { customFields?.forEach { (name, value) -> @@ -442,19 +443,19 @@ class BugReporter @Inject constructor( } */ - //private fun logOtherInfo() { + // private fun logOtherInfo() { // Timber.i("SyncThread state: " + activeSessionHolder.getSafeActiveSession()?.syncService()?.getSyncState()) - //} + // } - //private fun logDbInfo() { + // private fun logDbInfo() { // val dbInfo = matrix.debugService().getDbUsageInfo() // Timber.i(dbInfo) - //} + // } - //private fun logProcessInfo() { + // private fun logProcessInfo() { // val pInfo = processInfo.getInfo() // Timber.i(pInfo) - //} + // } private fun rageShakeAppNameForReport(reportType: ReportType): String { // As per https://github.com/matrix-org/rageshake diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt index c3cee0456d..9b05b2915a 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt @@ -21,13 +21,10 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Velocity -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel import io.element.android.x.core.compose.LogCompositions import io.element.android.x.designsystem.ElementXTheme -import io.element.android.x.designsystem.components.ProgressDialog import io.element.android.x.designsystem.components.avatar.AvatarData import io.element.android.x.features.roomlist.components.RoomListTopBar import io.element.android.x.features.roomlist.components.RoomSummaryRow @@ -42,17 +39,10 @@ import kotlinx.collections.immutable.toImmutableList @Composable fun RoomListScreen( viewModel: RoomListViewModel = mavericksViewModel(), - onSuccessLogout: () -> Unit = { }, onRoomClicked: (RoomId) -> Unit = { }, - onOpenRageShake: () -> Unit = { }, onOpenSettings: () -> Unit = { }, ) { - val logoutAction by viewModel.collectAsState(RoomListViewState::logoutAction) val filter by viewModel.collectAsState(RoomListViewState::filter) - if (logoutAction is Success) { - onSuccessLogout() - return - } LogCompositions(tag = "RoomListScreen", msg = "Root") val roomSummaries by viewModel.collectAsState(RoomListViewState::rooms) val matrixUser by viewModel.collectAsState(RoomListViewState::user) @@ -60,10 +50,7 @@ fun RoomListScreen( roomSummaries = roomSummaries().orEmpty().toImmutableList(), matrixUser = matrixUser(), onRoomClicked = onRoomClicked, - onLogoutClicked = viewModel::logout, onOpenSettings = onOpenSettings, - onOpenRageShake = onOpenRageShake, - isLoginOut = logoutAction is Loading, filter = filter, onFilterChanged = viewModel::filterRoom, onScrollOver = viewModel::updateVisibleRange @@ -75,13 +62,10 @@ fun RoomListContent( roomSummaries: ImmutableList, matrixUser: MatrixUser?, filter: String, - isLoginOut: Boolean, modifier: Modifier = Modifier, onRoomClicked: (RoomId) -> Unit = {}, onFilterChanged: (String) -> Unit = {}, - onLogoutClicked: () -> Unit = {}, onOpenSettings: () -> Unit = {}, - onOpenRageShake: () -> Unit = { }, onScrollOver: (IntRange) -> Unit = {}, ) { fun onRoomClicked(room: RoomListRoomSummary) { @@ -118,9 +102,7 @@ fun RoomListContent( matrixUser = matrixUser, filter = filter, onFilterChanged = onFilterChanged, - onLogoutClicked = onLogoutClicked, onOpenSettings = onOpenSettings, - onOpenRageShake = onOpenRageShake, scrollBehavior = scrollBehavior ) }, @@ -142,9 +124,6 @@ fun RoomListContent( } } ) - if (isLoginOut) { - ProgressDialog(text = "Login out...") - } } private fun RoomListRoomSummary.contentType() = isPlaceholder @@ -161,10 +140,8 @@ fun PreviewableRoomListContent() { roomSummaries = stubbedRoomSummaries(), matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")), onRoomClicked = {}, - onLogoutClicked = {}, filter = "filter", onFilterChanged = {}, - isLoginOut = false, onScrollOver = {} ) } @@ -178,10 +155,8 @@ fun PreviewableDarkRoomListContent() { roomSummaries = stubbedRoomSummaries(), matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")), onRoomClicked = {}, - onLogoutClicked = {}, filter = "filter", onFilterChanged = {}, - isLoginOut = true, onScrollOver = {} ) } diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt index 2d284c1a80..0a4cd714fe 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt @@ -20,7 +20,6 @@ import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.room.RoomSummary import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn @@ -33,8 +32,7 @@ private const val extendedRangeSize = 40 class RoomListViewModel @AssistedInject constructor( private val client: MatrixClient, @Assisted initialState: RoomListViewState -) : - MavericksViewModel(initialState) { +) : MavericksViewModel(initialState) { companion object : MavericksViewModelFactory by daggerMavericksViewModelFactory() @@ -44,17 +42,6 @@ class RoomListViewModel @AssistedInject constructor( handleInit() } - fun logout() { - viewModelScope.launch { - suspend { - delay(2000) - client.logout() - }.execute { - copy(logoutAction = it) - } - } - } - fun filterRoom(filter: String) { setState { copy( diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt index 0dc45a8061..6d6cd3d7fc 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt @@ -7,9 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.ContentAlpha import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Logout import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.ExperimentalMaterial3Api @@ -34,25 +32,20 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import io.element.android.x.core.compose.LogCompositions import io.element.android.x.core.compose.textFieldState import io.element.android.x.designsystem.components.avatar.Avatar -import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog import io.element.android.x.features.roomlist.model.MatrixUser -import io.element.android.x.element.resources.R as ElementR @Composable fun RoomListTopBar( matrixUser: MatrixUser?, filter: String, onFilterChanged: (String) -> Unit, - onLogoutClicked: () -> Unit, onOpenSettings: () -> Unit, - onOpenRageShake: () -> Unit, scrollBehavior: TopAppBarScrollBehavior ) { LogCompositions(tag = "RoomListScreen", msg = "TopBar") @@ -77,9 +70,7 @@ fun RoomListTopBar( } else { DefaultRoomListTopBar( matrixUser = matrixUser, - onLogoutClicked = onLogoutClicked, onOpenSettings = onOpenSettings, - onOpenRageShake = onOpenRageShake, onSearchClicked = { searchWidgetStateIsOpened = true }, @@ -168,13 +159,10 @@ fun SearchRoomListTopBar( @Composable private fun DefaultRoomListTopBar( matrixUser: MatrixUser?, - onLogoutClicked: () -> Unit, - onOpenRageShake: () -> Unit, onOpenSettings: () -> Unit, onSearchClicked: () -> Unit, scrollBehavior: TopAppBarScrollBehavior ) { - val openDialog = remember { mutableStateOf(false) } MediumTopAppBar( modifier = Modifier .nestedScroll(scrollBehavior.nestedScrollConnection), @@ -197,16 +185,6 @@ private fun DefaultRoomListTopBar( ) { Icon(Icons.Default.Search, contentDescription = "search") } - IconButton( - onClick = onOpenRageShake - ) { - Icon(Icons.Default.BugReport, contentDescription = stringResource(id = ElementR.string.send_bug_report)) - } - IconButton( - onClick = { openDialog.value = true } - ) { - Icon(Icons.Default.Logout, contentDescription = "logout") - } IconButton( onClick = onOpenSettings ) { @@ -215,19 +193,4 @@ private fun DefaultRoomListTopBar( }, scrollBehavior = scrollBehavior, ) - // Log out confirmation dialog - if (openDialog.value) { - ConfirmationDialog( - title = "Log out", - content = "Do you confirm you want to log out?", - submitText = "Log out", - onSubmitClicked = { - openDialog.value = false - onLogoutClicked() - }, - onDismiss = { - openDialog.value = false - } - ) - } } diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt index 86414c4a0e..01797a06fe 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt @@ -11,6 +11,5 @@ data class RoomListViewState( val rooms: Async> = Uninitialized, val filter: String = "", val canLoadMore: Boolean = false, - val logoutAction: Async = Uninitialized, val roomsById: Map = emptyMap() ) : MavericksState diff --git a/libraries/core/src/main/java/io/element/android/x/core/file/compressFile.kt b/libraries/core/src/main/java/io/element/android/x/core/file/compressFile.kt index 2842c43aff..41f65df367 100644 --- a/libraries/core/src/main/java/io/element/android/x/core/file/compressFile.kt +++ b/libraries/core/src/main/java/io/element/android/x/core/file/compressFile.kt @@ -1,8 +1,8 @@ package io.element.android.x.core.file -import timber.log.Timber import java.io.File import java.util.zip.GZIPOutputStream +import timber.log.Timber /** * GZip a file. diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/ColorUtil.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/ColorUtil.kt new file mode 100644 index 0000000000..41cf0ac2ad --- /dev/null +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/ColorUtil.kt @@ -0,0 +1,14 @@ +package io.element.android.x.designsystem + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +@Composable +fun Boolean.toEnabledColor(): Color { + return if (this) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.secondary + } +} diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ConfirmationDialog.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ConfirmationDialog.kt index fcddc3af70..49008b63ca 100644 --- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ConfirmationDialog.kt +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/dialogs/ConfirmationDialog.kt @@ -13,7 +13,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp - import io.element.android.x.element.resources.R as ElementR @Composable diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceCategory.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceCategory.kt index a65b917ecd..c976486cd5 100644 --- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceCategory.kt +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceCategory.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,9 +21,14 @@ fun PreferenceCategory( Column( modifier = modifier .fillMaxWidth() - .padding(horizontal = 16.dp) ) { + Divider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.secondary, + thickness = 1.dp + ) Text( + modifier = Modifier.padding(top = 8.dp, start = 56.dp), style = MaterialTheme.typography.titleSmall, text = title ) diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSlide.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSlide.kt index bb03bdb697..13b7d6af58 100644 --- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSlide.kt +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSlide.kt @@ -3,6 +3,7 @@ package io.element.android.x.designsystem.components.preferences import androidx.annotation.FloatRange import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme @@ -11,7 +12,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview +import io.element.android.x.designsystem.components.preferences.components.PreferenceIcon +import io.element.android.x.designsystem.toEnabledColor @Composable fun PreferenceSlide( @@ -19,6 +23,8 @@ fun PreferenceSlide( @FloatRange(0.0, 1.0) value: Float, modifier: Modifier = Modifier, + icon: ImageVector? = null, + enabled: Boolean = true, summary: String? = null, steps: Int = 0, onValueChange: (Float) -> Unit = {}, @@ -29,27 +35,32 @@ fun PreferenceSlide( .defaultMinSize(minHeight = preferenceMinHeight), contentAlignment = Alignment.CenterStart ) { - Column( - modifier = modifier - .fillMaxWidth(), - ) { - Text( - modifier = Modifier.fillMaxWidth(), - style = MaterialTheme.typography.bodyLarge, - text = title - ) - summary?.let { + Row(modifier = Modifier.fillMaxWidth()) { + PreferenceIcon(icon = icon) + Column( + modifier = Modifier.weight(1f), + ) { Text( modifier = Modifier.fillMaxWidth(), - style = MaterialTheme.typography.bodyMedium, - text = summary + style = MaterialTheme.typography.bodyLarge, + color = enabled.toEnabledColor(), + text = title + ) + summary?.let { + Text( + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyMedium, + color = enabled.toEnabledColor(), + text = summary + ) + } + Slider( + value = value, + steps = steps, + onValueChange = onValueChange, + enabled = enabled, ) } - Slider( - value = value, - steps = steps, - onValueChange = onValueChange - ) } } } diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSwitch.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSwitch.kt index 5cc821a875..62f3aa6201 100644 --- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSwitch.kt +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceSwitch.kt @@ -1,42 +1,58 @@ package io.element.android.x.designsystem.components.preferences +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Announcement import androidx.compose.material3.Checkbox import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview +import io.element.android.x.designsystem.components.preferences.components.PreferenceIcon +import io.element.android.x.designsystem.toEnabledColor @Composable fun PreferenceSwitch( title: String, isChecked: Boolean, modifier: Modifier = Modifier, + enabled: Boolean = true, + icon: ImageVector? = null, onCheckedChange: (Boolean) -> Unit = {}, ) { Box( modifier = modifier .fillMaxWidth() - .defaultMinSize(minHeight = preferenceMinHeight), + .defaultMinSize(minHeight = preferenceMinHeight) + .clickable { onCheckedChange(!isChecked) }, contentAlignment = Alignment.CenterStart ) { Row( - modifier = modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { + PreferenceIcon( + icon = icon, + enabled = enabled + ) Text( - modifier = modifier - .weight(1f), + modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge, + color = enabled.toEnabledColor(), text = title ) - Checkbox(checked = isChecked, onCheckedChange = onCheckedChange) + Checkbox( + checked = isChecked, + enabled = enabled, + onCheckedChange = onCheckedChange + ) } } } @@ -46,6 +62,7 @@ fun PreferenceSwitch( fun PreferenceSwitchPreview() { PreferenceSwitch( title = "Switch", + icon = Icons.Default.Announcement, isChecked = true ) } diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceText.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceText.kt index 099022146d..76091786e7 100644 --- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceText.kt +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/PreferenceText.kt @@ -2,20 +2,26 @@ package io.element.android.x.designsystem.components.preferences import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.BugReport import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview +import io.element.android.x.designsystem.components.preferences.components.PreferenceIcon @Composable fun PreferenceText( title: String, // TODO subtitle modifier: Modifier = Modifier, + icon: ImageVector? = null, onClick: () -> Unit = {}, ) { Box( @@ -25,12 +31,17 @@ fun PreferenceText( .clickable { onClick() }, contentAlignment = Alignment.Center ) { - Text( - modifier = Modifier - .fillMaxWidth(), - style = MaterialTheme.typography.bodyLarge, - text = title - ) + Row( + modifier = Modifier.fillMaxWidth() + ) { + PreferenceIcon(icon = icon) + Text( + modifier = Modifier + .weight(1f), + style = MaterialTheme.typography.bodyLarge, + text = title + ) + } } } @@ -39,5 +50,6 @@ fun PreferenceText( fun PreferenceTextPreview() { PreferenceText( title = "Title", + icon = Icons.Default.BugReport, ) } diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/components/PreferenceIcon.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/components/PreferenceIcon.kt new file mode 100644 index 0000000000..1f9848c808 --- /dev/null +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/preferences/components/PreferenceIcon.kt @@ -0,0 +1,31 @@ +package io.element.android.x.designsystem.components.preferences.components + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import io.element.android.x.designsystem.toEnabledColor + +@Composable +fun PreferenceIcon( + icon: ImageVector?, + modifier: Modifier = Modifier, + enabled: Boolean = true +) { + if (icon != null) { + Icon( + imageVector = icon, + contentDescription = "", + tint = enabled.toEnabledColor(), + modifier = modifier + .padding(start = 8.dp) + .width(48.dp), + ) + } else { + Spacer(modifier = modifier.width(56.dp)) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 096b474a4d..a3fd4ca34e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,6 +25,7 @@ include(":libraries:textcomposer") include(":libraries:elementresources") include(":features:onboarding") include(":features:login") +include(":features:logout") include(":features:roomlist") include(":features:messages") include(":features:rageshake")