Move logout and report bug to Setting screen
This commit is contained in:
committed by
Benoit Marty
parent
0568ea7c28
commit
37daf12cd0
1
.gitignore
vendored
1
.gitignore
vendored
@@ -82,3 +82,4 @@ lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
/.idea/deploymentTargetDropDown.xml
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -71,7 +71,6 @@ fun ChangeServerScreenNavigation(navigator: DestinationsNavigator) {
|
||||
@Destination
|
||||
@Composable
|
||||
fun RoomListScreenNavigation(navigator: DestinationsNavigator) {
|
||||
val sessionComponentsOwner = LocalContext.current.bindings<AppBindings>().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<AppBindings>().sessionComponentsOwner()
|
||||
PreferencesScreen(
|
||||
onBackPressed = navigator::navigateUp
|
||||
onBackPressed = navigator::navigateUp,
|
||||
onOpenRageShake = {
|
||||
navigator.navigate(BugReportScreenNavigationDestination)
|
||||
},
|
||||
onSuccessLogout = {
|
||||
sessionComponentsOwner.releaseActiveSession()
|
||||
navigator.navigate(OnBoardingScreenNavigationDestination) {
|
||||
popUpTo(RoomListScreenNavigationDestination) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
1
features/logout/.gitignore
vendored
Normal file
1
features/logout/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
27
features/logout/build.gradle.kts
Normal file
27
features/logout/build.gradle.kts
Normal file
@@ -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)
|
||||
}
|
||||
0
features/logout/consumer-rules.pro
Normal file
0
features/logout/consumer-rules.pro
Normal file
21
features/logout/proguard-rules.pro
vendored
Normal file
21
features/logout/proguard-rules.pro
vendored
Normal file
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
4
features/logout/src/main/AndroidManifest.xml
Normal file
4
features/logout/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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<LogoutViewState>(initialState) {
|
||||
|
||||
companion object : MavericksViewModelFactory<LogoutViewModel, LogoutViewState> by daggerMavericksViewModelFactory()
|
||||
|
||||
fun logout() {
|
||||
viewModelScope.launch {
|
||||
suspend {
|
||||
client.logout()
|
||||
}.execute {
|
||||
copy(logoutAction = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Unit> = Uninitialized,
|
||||
) : MavericksState
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Preferences> by preferencesDataStore(name = "elementx_crash")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,20 +14,34 @@ 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()
|
||||
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
|
||||
)
|
||||
}
|
||||
PreferenceCategory(title = stringResource(id = ElementR.string.settings_rageshake)) {
|
||||
if (state.isSupported) {
|
||||
PreferenceSwitch(
|
||||
@@ -31,23 +49,23 @@ fun RageshakePreferenceContent(
|
||||
isChecked = state.isEnabled,
|
||||
onCheckedChange = viewModel::onEnableClicked
|
||||
)
|
||||
if (state.isEnabled) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun RageshakePreferenceCategoryPreview() {
|
||||
RageshakePreferenceCategory()
|
||||
fun RageshakePreferencePreview() {
|
||||
RageshakePreferences()
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<Preferences> by preferencesDataStore(name = "elementx_rageshake")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<RoomListRoomSummary>,
|
||||
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 = {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<RoomListViewState>(initialState) {
|
||||
) : MavericksViewModel<RoomListViewState>(initialState) {
|
||||
|
||||
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> 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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,5 @@ data class RoomListViewState(
|
||||
val rooms: Async<List<RoomListRoomSummary>> = Uninitialized,
|
||||
val filter: String = "",
|
||||
val canLoadMore: Boolean = false,
|
||||
val logoutAction: Async<Unit> = Uninitialized,
|
||||
val roomsById: Map<RoomId, RoomListRoomSummary> = emptyMap()
|
||||
) : MavericksState
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,30 +35,35 @@ fun PreferenceSlide(
|
||||
.defaultMinSize(minHeight = preferenceMinHeight),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
PreferenceIcon(icon = icon)
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
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
|
||||
onValueChange = onValueChange,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = false)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,19 +31,25 @@ fun PreferenceText(
|
||||
.clickable { onClick() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
PreferenceIcon(icon = icon)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
.weight(1f),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
text = title
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = false)
|
||||
fun PreferenceTextPreview() {
|
||||
PreferenceText(
|
||||
title = "Title",
|
||||
icon = Icons.Default.BugReport,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user