Merge branch 'release/25.03.1' into main
This commit is contained in:
commit
bd33d507e3
675 changed files with 2857 additions and 2499 deletions
17
.github/workflows/blocked.yml
vendored
Normal file
17
.github/workflows/blocked.yml
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
name: Prevent blocked
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, labeled, unlabeled]
|
||||||
|
jobs:
|
||||||
|
prevent-blocked:
|
||||||
|
name: Prevent blocked
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
steps:
|
||||||
|
- name: Add notice
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'X-Blocked')
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
core.setFailed("PR has been labeled with X-Blocked; it cannot be merged.");
|
||||||
2
.github/workflows/quality.yml
vendored
2
.github/workflows/quality.yml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
- name: Run code quality check suite
|
- name: Run code quality check suite
|
||||||
run: ./tools/check/check_code_quality.sh
|
run: ./tools/check/check_code_quality.sh
|
||||||
|
|
||||||
checkScreesnhot:
|
checkScreenshot:
|
||||||
name: Search for invalid screenshot files
|
name: Search for invalid screenshot files
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
||||||
77
CHANGES.md
77
CHANGES.md
|
|
@ -1,3 +1,80 @@
|
||||||
|
Changes in Element X v25.03.0
|
||||||
|
=============================
|
||||||
|
|
||||||
|
<!-- Release notes generated using configuration in .github/release.yml at v25.03.0 -->
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### ✨ Features
|
||||||
|
* Create `SyncOrchestrator` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4176
|
||||||
|
* feature(crypto): verification violation handling and block sending by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/4126
|
||||||
|
* Update Matrix Room API and allow media swipe on pinned event only. by @bmarty in https://github.com/element-hq/element-x-android/pull/4274
|
||||||
|
* Feature : join room by address by @ganfra in https://github.com/element-hq/element-x-android/pull/4302
|
||||||
|
### 🙌 Improvements
|
||||||
|
* Change : Room Preview by @ganfra in https://github.com/element-hq/element-x-android/pull/4250
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* SyncOrchestrator: restore the initial sync step by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4242
|
||||||
|
* When an emoji is used as the 'initial' for an avatar, use the whole emoji by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4277
|
||||||
|
* Try avoiding trailing punctuation inside linkified URLs by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4214
|
||||||
|
* Preload account urls by @bmarty in https://github.com/element-hq/element-x-android/pull/4301
|
||||||
|
* Fix issues due to multiple ntfy applications with the same name. by @bmarty in https://github.com/element-hq/element-x-android/pull/4312
|
||||||
|
* Use `Settings.System.DEFAULT_RINGTONE_URI` for ringing notifications by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4310
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings - New translations to turkish by @ElementBot in https://github.com/element-hq/element-x-android/pull/4253
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4298
|
||||||
|
### 🧱 Build
|
||||||
|
* Fix nightly reports by @bmarty in https://github.com/element-hq/element-x-android/pull/4235
|
||||||
|
* Fix nightly reports - next step by @bmarty in https://github.com/element-hq/element-x-android/pull/4239
|
||||||
|
* Prepare application for being configurable by @bmarty in https://github.com/element-hq/element-x-android/pull/4285
|
||||||
|
* runQualityChecks task shouldn't fail fast by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4309
|
||||||
|
* Get library ComposablePreviewScanner from maven and update to the latest version by @bmarty in https://github.com/element-hq/element-x-android/pull/4327
|
||||||
|
### Dependency upgrades
|
||||||
|
* Update dependency com.posthog:posthog-android to v3.11.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4230
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.78 by @renovate in https://github.com/element-hq/element-x-android/pull/4234
|
||||||
|
* Update dependency org.maplibre.gl:android-sdk to v11.8.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4245
|
||||||
|
* fix(deps): update dependency org.jetbrains.kotlinx:kotlinx-datetime to v0.6.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4258
|
||||||
|
* fix(deps): update dependency io.sentry:sentry-android to v8.2.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4262
|
||||||
|
* fix(deps): update telephoto to v0.15.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4270
|
||||||
|
* fix(deps): update dependency com.google.firebase:firebase-bom to v33.9.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4249
|
||||||
|
* chore(deps): update danger/danger-js action to v12.3.4 by @renovate in https://github.com/element-hq/element-x-android/pull/4259
|
||||||
|
* fix(deps): update android.gradle.plugin to v8.8.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4263
|
||||||
|
* chore(deps): update plugin dependencycheck to v12.1.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4272
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25 by @renovate in https://github.com/element-hq/element-x-android/pull/4273
|
||||||
|
* fix(deps): update dependency androidx.compose:compose-bom to v2025.02.00 by @renovate in https://github.com/element-hq/element-x-android/pull/4261
|
||||||
|
* fix(deps): update kotlin to v2.1.10-1.0.30 by @renovate in https://github.com/element-hq/element-x-android/pull/4265
|
||||||
|
* fix(deps): update dependency io.github.zxing-cpp:android to v2.3.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4282
|
||||||
|
* fix(deps): update firebaseappdistribution to v5.1.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4246
|
||||||
|
* fix(deps): update dependencyanalysis to v2.8.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4251
|
||||||
|
* fix(deps): update dependency com.google.accompanist:accompanist-permissions to v0.37.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4283
|
||||||
|
* fix(deps): update dependency com.google.accompanist:accompanist-permissions to v0.37.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4287
|
||||||
|
* fix(deps): update dependencyanalysis to v2.10.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4288
|
||||||
|
* fix(deps): update dependencyanalysis to v2.10.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4295
|
||||||
|
* Upgrade SDK version to 25.02.26 by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4305
|
||||||
|
* fix(deps): update kotlinpoet to v2.1.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4304
|
||||||
|
* Update compound by @bmarty in https://github.com/element-hq/element-x-android/pull/4319
|
||||||
|
* fix(deps): update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4324
|
||||||
|
* fix(deps): update activity to v1.10.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4321
|
||||||
|
* fix(deps): update dependency androidx.exifinterface:exifinterface to v1.4.0 - autoclosed by @renovate in https://github.com/element-hq/element-x-android/pull/4325
|
||||||
|
* fix(deps): update dependency androidx.constraintlayout:constraintlayout to v2.2.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4322
|
||||||
|
* fix(deps): update dependency io.sentry:sentry-android to v8.3.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4316
|
||||||
|
* fix(deps): update dependency com.posthog:posthog-android to v3.11.3 by @renovate in https://github.com/element-hq/element-x-android/pull/4313
|
||||||
|
* fix(deps): update dependency com.android.tools:desugar_jdk_libs to v2.1.5 by @renovate in https://github.com/element-hq/element-x-android/pull/4299
|
||||||
|
* chore(deps): update plugin detekt to v1.23.8 by @renovate in https://github.com/element-hq/element-x-android/pull/4292
|
||||||
|
### Others
|
||||||
|
* Update incoming call notification content to "📹 Incoming call" by @bmarty in https://github.com/element-hq/element-x-android/pull/4231
|
||||||
|
* Display a bottom sheet to let user confirm the DM creation by @bmarty in https://github.com/element-hq/element-x-android/pull/4233
|
||||||
|
* Open chat links in regular browser tabs by @cbs228 in https://github.com/element-hq/element-x-android/pull/4198
|
||||||
|
* Theme override by @bmarty in https://github.com/element-hq/element-x-android/pull/4226
|
||||||
|
* Allow user certificate in production builds. by @bmarty in https://github.com/element-hq/element-x-android/pull/4275
|
||||||
|
* Replace Material icons with Compound icons wherever it's possible by @bmarty in https://github.com/element-hq/element-x-android/pull/4323
|
||||||
|
|
||||||
|
## New Contributors
|
||||||
|
* @cbs228 made their first contribution in https://github.com/element-hq/element-x-android/pull/4198
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.02.0...v25.03.0
|
||||||
|
|
||||||
|
Changes in Element X v25.02.0 (2025-02-04)
|
||||||
|
==========================================
|
||||||
|
|
||||||
<!-- Release notes generated using configuration in .github/release.yml at v25.02.0 -->
|
<!-- Release notes generated using configuration in .github/release.yml at v25.02.0 -->
|
||||||
|
|
||||||
## What's Changed
|
## What's Changed
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(DelicateCoilApi::class)
|
||||||
|
|
||||||
package io.element.android.appnav
|
package io.element.android.appnav
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import coil.Coil
|
import coil3.SingletonImageLoader
|
||||||
|
import coil3.annotation.DelicateCoilApi
|
||||||
import com.bumble.appyx.core.composable.Children
|
import com.bumble.appyx.core.composable.Children
|
||||||
import com.bumble.appyx.core.lifecycle.subscribe
|
import com.bumble.appyx.core.lifecycle.subscribe
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
|
@ -69,7 +72,7 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor(
|
||||||
super.onBuilt()
|
super.onBuilt()
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
onCreate = {
|
onCreate = {
|
||||||
Coil.setImageLoader(imageLoaderHolder.get(inputs.matrixClient))
|
SingletonImageLoader.setUnsafe(imageLoaderHolder.get(inputs.matrixClient))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,34 +10,33 @@ package io.element.android.appnav
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LoggedInEventProcessor @Inject constructor(
|
class LoggedInEventProcessor @Inject constructor(
|
||||||
private val snackbarDispatcher: SnackbarDispatcher,
|
private val snackbarDispatcher: SnackbarDispatcher,
|
||||||
roomMembershipObserver: RoomMembershipObserver,
|
private val roomMembershipObserver: RoomMembershipObserver,
|
||||||
) {
|
) {
|
||||||
private var observingJob: Job? = null
|
private var observingJob: Job? = null
|
||||||
|
|
||||||
private val displayLeftRoomMessage = roomMembershipObserver.updates
|
|
||||||
.map { !it.isUserInRoom }
|
|
||||||
|
|
||||||
fun observeEvents(coroutineScope: CoroutineScope) {
|
fun observeEvents(coroutineScope: CoroutineScope) {
|
||||||
observingJob = coroutineScope.launch {
|
observingJob = roomMembershipObserver.updates
|
||||||
displayLeftRoomMessage
|
.filter { !it.isUserInRoom }
|
||||||
.filter { it }
|
.onEach {
|
||||||
.onEach {
|
when (it.change) {
|
||||||
displayMessage(CommonStrings.common_current_user_left_room)
|
MembershipChange.LEFT -> displayMessage(CommonStrings.common_current_user_left_room)
|
||||||
|
MembershipChange.INVITATION_REJECTED -> displayMessage(CommonStrings.common_current_user_rejected_invite)
|
||||||
|
MembershipChange.KNOCK_RETRACTED -> displayMessage(CommonStrings.common_current_user_canceled_knock)
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
}
|
||||||
}
|
.launchIn(coroutineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopObserving() {
|
fun stopObserving() {
|
||||||
|
|
|
||||||
|
|
@ -262,10 +262,6 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
plugins<Callback>().forEach { it.onOpenBugReport() }
|
plugins<Callback>().forEach { it.onOpenBugReport() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRoomDirectorySearchClick() {
|
|
||||||
backstack.push(NavTarget.RoomDirectorySearch)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLogoutForNativeSlidingSyncMigrationNeeded() {
|
override fun onLogoutForNativeSlidingSyncMigrationNeeded() {
|
||||||
backstack.push(NavTarget.LogoutForNativeSlidingSyncMigrationNeeded)
|
backstack.push(NavTarget.LogoutForNativeSlidingSyncMigrationNeeded)
|
||||||
}
|
}
|
||||||
|
|
@ -360,6 +356,10 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) {
|
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) {
|
||||||
backstack.replace(NavTarget.Room(roomIdOrAlias = roomIdOrAlias, serverNames = serverNames))
|
backstack.replace(NavTarget.Room(roomIdOrAlias = roomIdOrAlias, serverNames = serverNames))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOpenRoomDirectory() {
|
||||||
|
backstack.push(NavTarget.RoomDirectorySearch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createRoomEntryPoint
|
createRoomEntryPoint
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(DelicateCoilApi::class)
|
||||||
|
|
||||||
package io.element.android.appnav
|
package io.element.android.appnav
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import coil.Coil
|
import coil3.SingletonImageLoader
|
||||||
|
import coil3.annotation.DelicateCoilApi
|
||||||
import com.bumble.appyx.core.lifecycle.subscribe
|
import com.bumble.appyx.core.lifecycle.subscribe
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
|
|
@ -55,7 +58,7 @@ class NotLoggedInFlowNode @AssistedInject constructor(
|
||||||
super.onBuilt()
|
super.onBuilt()
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
onCreate = {
|
onCreate = {
|
||||||
Coil.setImageLoader(notLoggedInImageLoaderFactory)
|
SingletonImageLoader.setUnsafe(notLoggedInImageLoaderFactory.newImageLoader())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202503010.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202503010.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Event cache / Join room by address.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -33,6 +33,9 @@ data class WidgetMessage(
|
||||||
@SerialName("im.vector.hangup")
|
@SerialName("im.vector.hangup")
|
||||||
HangUp,
|
HangUp,
|
||||||
|
|
||||||
|
@SerialName("io.element.close")
|
||||||
|
Close,
|
||||||
|
|
||||||
@SerialName("send_event")
|
@SerialName("send_event")
|
||||||
SendEvent,
|
SendEvent,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
val parsedMessage = parseMessage(it)
|
val parsedMessage = parseMessage(it)
|
||||||
if (parsedMessage?.direction == WidgetMessage.Direction.FromWidget) {
|
if (parsedMessage?.direction == WidgetMessage.Direction.FromWidget) {
|
||||||
if (parsedMessage.action == WidgetMessage.Action.HangUp) {
|
if (parsedMessage.action == WidgetMessage.Action.Close) {
|
||||||
close(callWidgetDriver.value, navigator)
|
close(callWidgetDriver.value, navigator)
|
||||||
} else if (parsedMessage.action == WidgetMessage.Action.SendEvent) {
|
} else if (parsedMessage.action == WidgetMessage.Action.SendEvent) {
|
||||||
// This event is received when a member joins the call, the first one will be the current one
|
// This event is received when a member joins the call, the first one will be the current one
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="call_foreground_service_channel_title_android">"Pågående samtale"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"Trykk for å gå tilbake til samtalen"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ Samtale pågår"</string>
|
||||||
<string name="screen_incoming_call_subtitle_android">"Innkommende Element-anrop"</string>
|
<string name="screen_incoming_call_subtitle_android">"Innkommende Element-anrop"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ package io.element.android.features.call.notifications
|
||||||
|
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import coil.ImageLoader
|
import coil3.ImageLoader
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ class CallScreenPresenterTest {
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `present - a received hang up message closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) {
|
fun `present - a received close message closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
|
|
@ -191,7 +191,7 @@ class CallScreenPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
||||||
|
|
||||||
messageInterceptor.givenInterceptedMessage("""{"action":"im.vector.hangup","api":"fromWidget","widgetId":"1","requestId":"1"}""")
|
messageInterceptor.givenInterceptedMessage("""{"action":"io.element.close","api":"fromWidget","widgetId":"1","requestId":"1"}""")
|
||||||
|
|
||||||
// Let background coroutines run
|
// Let background coroutines run
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,6 @@ interface CreateRoomEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>)
|
fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>)
|
||||||
|
fun onOpenRoomDirectory()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,19 @@ interface CreateRoomNavigator : Plugin {
|
||||||
fun onCreateNewRoom()
|
fun onCreateNewRoom()
|
||||||
fun onShowJoinRoomByAddress()
|
fun onShowJoinRoomByAddress()
|
||||||
fun onDismissJoinRoomByAddress()
|
fun onDismissJoinRoomByAddress()
|
||||||
|
fun onOpenRoomDirectory()
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultCreateRoomNavigator(
|
class DefaultCreateRoomNavigator(
|
||||||
private val backstack: BackStack<NavTarget>,
|
private val backstack: BackStack<NavTarget>,
|
||||||
private val overlay: Overlay<NavTarget>,
|
private val overlay: Overlay<NavTarget>,
|
||||||
private val openRoom: (RoomIdOrAlias, List<String>) -> Unit,
|
private val openRoom: (RoomIdOrAlias, List<String>) -> Unit,
|
||||||
|
private val openRoomDirectory: () -> Unit,
|
||||||
) : CreateRoomNavigator {
|
) : CreateRoomNavigator {
|
||||||
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) = openRoom(roomIdOrAlias, serverNames)
|
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) = openRoom(roomIdOrAlias, serverNames)
|
||||||
|
|
||||||
|
override fun onOpenRoomDirectory() = openRoomDirectory()
|
||||||
|
|
||||||
override fun onCreateNewRoom() {
|
override fun onCreateNewRoom() {
|
||||||
backstack.push(NavTarget.NewRoom)
|
backstack.push(NavTarget.NewRoom)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,9 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||||
overlay = overlay,
|
overlay = overlay,
|
||||||
openRoom = { roomIdOrAlias, viaServers ->
|
openRoom = { roomIdOrAlias, viaServers ->
|
||||||
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomIdOrAlias, viaServers) }
|
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomIdOrAlias, viaServers) }
|
||||||
|
},
|
||||||
|
openRoomDirectory = {
|
||||||
|
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoomDirectory() }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||||
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||||
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
||||||
|
|
@ -175,6 +176,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
isEncrypted = config.roomVisibility is RoomVisibilityState.Private,
|
isEncrypted = config.roomVisibility is RoomVisibilityState.Private,
|
||||||
isDirect = false,
|
isDirect = false,
|
||||||
visibility = RoomVisibility.Private,
|
visibility = RoomVisibility.Private,
|
||||||
|
historyVisibilityOverride = RoomHistoryVisibility.Invited,
|
||||||
preset = RoomPreset.PRIVATE_CHAT,
|
preset = RoomPreset.PRIVATE_CHAT,
|
||||||
invite = config.invites.map { it.userId },
|
invite = config.invites.map { it.userId },
|
||||||
avatar = avatarUrl,
|
avatar = avatarUrl,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,8 @@ class CreateRoomRootNode @AssistedInject constructor(
|
||||||
navigator.onOpenRoom(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList())
|
navigator.onOpenRoom(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList())
|
||||||
},
|
},
|
||||||
onJoinByAddressClick = navigator::onShowJoinRoomByAddress,
|
onJoinByAddressClick = navigator::onShowJoinRoomByAddress,
|
||||||
onInviteFriendsClick = { invitePeople(activity) }
|
onInviteFriendsClick = { invitePeople(activity) },
|
||||||
|
onRoomDirectorySearchClick = navigator::onOpenRoomDirectory
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
|
@ -20,6 +22,8 @@ import io.element.android.features.createroom.impl.userlist.UserListPresenterArg
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.usersearch.api.UserRepository
|
import io.element.android.libraries.usersearch.api.UserRepository
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -31,6 +35,7 @@ class CreateRoomRootPresenter @Inject constructor(
|
||||||
userListDataStore: UserListDataStore,
|
userListDataStore: UserListDataStore,
|
||||||
private val startDMAction: StartDMAction,
|
private val startDMAction: StartDMAction,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val featureFlagService: FeatureFlagService,
|
||||||
) : Presenter<CreateRoomRootState> {
|
) : Presenter<CreateRoomRootState> {
|
||||||
private val presenter = presenterFactory.create(
|
private val presenter = presenterFactory.create(
|
||||||
UserListPresenterArgs(
|
UserListPresenterArgs(
|
||||||
|
|
@ -47,6 +52,8 @@ class CreateRoomRootPresenter @Inject constructor(
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
|
val isRoomDirectorySearchEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch).collectAsState(initial = false)
|
||||||
|
|
||||||
fun handleEvents(event: CreateRoomRootEvents) {
|
fun handleEvents(event: CreateRoomRootEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch {
|
is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch {
|
||||||
|
|
@ -64,6 +71,7 @@ class CreateRoomRootPresenter @Inject constructor(
|
||||||
applicationName = buildMeta.applicationName,
|
applicationName = buildMeta.applicationName,
|
||||||
userListState = userListState,
|
userListState = userListState,
|
||||||
startDmAction = startDmActionState.value,
|
startDmAction = startDmActionState.value,
|
||||||
|
isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled,
|
||||||
eventSink = ::handleEvents,
|
eventSink = ::handleEvents,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@ data class CreateRoomRootState(
|
||||||
val applicationName: String,
|
val applicationName: String,
|
||||||
val userListState: UserListState,
|
val userListState: UserListState,
|
||||||
val startDmAction: AsyncAction<RoomId>,
|
val startDmAction: AsyncAction<RoomId>,
|
||||||
|
val isRoomDirectorySearchEnabled: Boolean,
|
||||||
val eventSink: (CreateRoomRootEvents) -> Unit,
|
val eventSink: (CreateRoomRootEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||||
aCreateRoomRootState(
|
aCreateRoomRootState(
|
||||||
startDmAction = ConfirmingStartDmWithMatrixUser(aMatrixUser()),
|
startDmAction = ConfirmingStartDmWithMatrixUser(aMatrixUser()),
|
||||||
),
|
),
|
||||||
|
aCreateRoomRootState(
|
||||||
|
isRoomDirectorySearchEnabled = true,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,10 +63,12 @@ fun aCreateRoomRootState(
|
||||||
applicationName: String = "Element X Preview",
|
applicationName: String = "Element X Preview",
|
||||||
userListState: UserListState = aUserListState(),
|
userListState: UserListState = aUserListState(),
|
||||||
startDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
startDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||||
|
isRoomDirectorySearchEnabled: Boolean = false,
|
||||||
eventSink: (CreateRoomRootEvents) -> Unit = {},
|
eventSink: (CreateRoomRootEvents) -> Unit = {},
|
||||||
) = CreateRoomRootState(
|
) = CreateRoomRootState(
|
||||||
applicationName = applicationName,
|
applicationName = applicationName,
|
||||||
userListState = userListState,
|
userListState = userListState,
|
||||||
startDmAction = startDmAction,
|
startDmAction = startDmAction,
|
||||||
|
isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ fun CreateRoomRootView(
|
||||||
onOpenDM: (RoomId) -> Unit,
|
onOpenDM: (RoomId) -> Unit,
|
||||||
onInviteFriendsClick: () -> Unit,
|
onInviteFriendsClick: () -> Unit,
|
||||||
onJoinByAddressClick: () -> Unit,
|
onJoinByAddressClick: () -> Unit,
|
||||||
|
onRoomDirectorySearchClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -91,6 +92,7 @@ fun CreateRoomRootView(
|
||||||
onNewRoomClick = onNewRoomClick,
|
onNewRoomClick = onNewRoomClick,
|
||||||
onInvitePeopleClick = onInviteFriendsClick,
|
onInvitePeopleClick = onInviteFriendsClick,
|
||||||
onJoinByAddressClick = onJoinByAddressClick,
|
onJoinByAddressClick = onJoinByAddressClick,
|
||||||
|
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
|
||||||
onDmClick = onOpenDM,
|
onDmClick = onOpenDM,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +158,7 @@ private fun CreateRoomActionButtonsList(
|
||||||
onNewRoomClick: () -> Unit,
|
onNewRoomClick: () -> Unit,
|
||||||
onInvitePeopleClick: () -> Unit,
|
onInvitePeopleClick: () -> Unit,
|
||||||
onJoinByAddressClick: () -> Unit,
|
onJoinByAddressClick: () -> Unit,
|
||||||
|
onRoomDirectorySearchClick: () -> Unit,
|
||||||
onDmClick: (RoomId) -> Unit,
|
onDmClick: (RoomId) -> Unit,
|
||||||
) {
|
) {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
|
|
@ -166,6 +169,15 @@ private fun CreateRoomActionButtonsList(
|
||||||
onClick = onNewRoomClick,
|
onClick = onNewRoomClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (state.isRoomDirectorySearchEnabled) {
|
||||||
|
item {
|
||||||
|
CreateRoomActionButton(
|
||||||
|
iconRes = CompoundDrawables.ic_compound_list_bulleted,
|
||||||
|
text = stringResource(id = R.string.screen_room_directory_search_title),
|
||||||
|
onClick = onRoomDirectorySearchClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
item {
|
item {
|
||||||
CreateRoomActionButton(
|
CreateRoomActionButton(
|
||||||
iconRes = CompoundDrawables.ic_compound_share_android,
|
iconRes = CompoundDrawables.ic_compound_share_android,
|
||||||
|
|
@ -242,5 +254,6 @@ internal fun CreateRoomRootViewPreview(@PreviewParameter(CreateRoomRootStateProv
|
||||||
onOpenDM = {},
|
onOpenDM = {},
|
||||||
onJoinByAddressClick = {},
|
onJoinByAddressClick = {},
|
||||||
onInviteFriendsClick = {},
|
onInviteFriendsClick = {},
|
||||||
|
onRoomDirectorySearchClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,10 @@ To můžete kdykoli změnit v nastavení místnosti."</string>
|
||||||
<string name="screen_create_room_title">"Vytvořit místnost"</string>
|
<string name="screen_create_room_title">"Vytvořit místnost"</string>
|
||||||
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
|
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"Při pokusu o zahájení chatu došlo k chybě"</string>
|
<string name="screen_start_chat_error_starting_chat">"Při pokusu o zahájení chatu došlo k chybě"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_action">"Vstoupit do místnosti pomocí adresy"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_invalid_address">"Neplatná adresa"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_placeholder">"Zadejte…"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_room_found">"Odpovídající místnost nalezena"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_room_not_found">"Místnost nebyla nalezena"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_supporting_text">"např. #room-name:matrix.org"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,10 @@ Sa võid seda jututoa seadistustest alati muuta."</string>
|
||||||
<string name="screen_create_room_title">"Loo jututuba"</string>
|
<string name="screen_create_room_title">"Loo jututuba"</string>
|
||||||
<string name="screen_create_room_topic_label">"Teema (kui soovid lisada)"</string>
|
<string name="screen_create_room_topic_label">"Teema (kui soovid lisada)"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"Vestluse alustamisel tekkis viga"</string>
|
<string name="screen_start_chat_error_starting_chat">"Vestluse alustamisel tekkis viga"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_action">"Liitu jututoaga aadressi alusel"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_invalid_address">"See pole kehtiv aadress"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_placeholder">"Sisene…"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_room_found">"Leidsime vastava jututoa"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_room_not_found">"Jututuba ei leidu"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_supporting_text">"nt. #jututoa-nimi:matrix.org"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,10 @@ Vous pouvez modifier cela à tout moment dans les paramètres du salon."</string
|
||||||
<string name="screen_create_room_title">"Créer un salon"</string>
|
<string name="screen_create_room_title">"Créer un salon"</string>
|
||||||
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
|
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"Une erreur s’est produite lors de la tentative de création de la discussion"</string>
|
<string name="screen_start_chat_error_starting_chat">"Une erreur s’est produite lors de la tentative de création de la discussion"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_action">"Saisir une adresse de salon"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_invalid_address">"Ce n’est pas une adresse valide"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_placeholder">"Saisir…"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_room_found">"Ce salon existe"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_room_not_found">"Salon non trouvé"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_supporting_text">"ex: #nom-du-salon:matrix.org"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,10 @@ Môžete to kedykoľvek zmeniť v nastaveniach miestnosti."</string>
|
||||||
<string name="screen_create_room_title">"Vytvoriť miestnosť"</string>
|
<string name="screen_create_room_title">"Vytvoriť miestnosť"</string>
|
||||||
<string name="screen_create_room_topic_label">"Téma (voliteľné)"</string>
|
<string name="screen_create_room_topic_label">"Téma (voliteľné)"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"Pri pokuse o spustenie konverzácie sa vyskytla chyba"</string>
|
<string name="screen_start_chat_error_starting_chat">"Pri pokuse o spustenie konverzácie sa vyskytla chyba"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_action">"Pripojte sa do miestnosti podľa adresy"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_invalid_address">"Neplatná adresa"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_placeholder">"Zadajte…"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_room_found">"Nájdená zodpovedajúca miestnosť"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_room_not_found">"Miestnosť sa nenašla"</string>
|
||||||
|
<string name="screen_start_chat_join_room_by_address_supporting_text">"napr. #nazov-miestnosti:matrix.org"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ Du kan ändra detta när som helst i rumsinställningarna."</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Vem som helst kan be om att gå med i rummet men en administratör eller en moderator måste acceptera begäran"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_description">"Vem som helst kan be om att gå med i rummet men en administratör eller en moderator måste acceptera begäran"</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Be om att gå med"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_title">"Be om att gå med"</string>
|
||||||
<string name="screen_create_room_room_address_section_footer">"För att detta rum ska vara synligt i den allmänna rumskatalogen behöver du en rumsadress."</string>
|
<string name="screen_create_room_room_address_section_footer">"För att detta rum ska vara synligt i den allmänna rumskatalogen behöver du en rumsadress."</string>
|
||||||
|
<string name="screen_create_room_room_address_section_title">"Rumsadress"</string>
|
||||||
<string name="screen_create_room_room_name_label">"Rumsnamn"</string>
|
<string name="screen_create_room_room_name_label">"Rumsnamn"</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Rumssynlighet"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Rumssynlighet"</string>
|
||||||
<string name="screen_create_room_title">"Skapa ett rum"</string>
|
<string name="screen_create_room_title">"Skapa ett rum"</string>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ You can change this anytime in room settings."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Room visibility"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Room visibility"</string>
|
||||||
<string name="screen_create_room_title">"Create a room"</string>
|
<string name="screen_create_room_title">"Create a room"</string>
|
||||||
<string name="screen_create_room_topic_label">"Topic (optional)"</string>
|
<string name="screen_create_room_topic_label">"Topic (optional)"</string>
|
||||||
|
<string name="screen_room_directory_search_title">"Room directory"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Join room by address"</string>
|
<string name="screen_start_chat_join_room_by_address_action">"Join room by address"</string>
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Not a valid address"</string>
|
<string name="screen_start_chat_join_room_by_address_invalid_address">"Not a valid address"</string>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ class FakeCreateRoomNavigator(
|
||||||
private val createNewRoomLambda: () -> Unit = {},
|
private val createNewRoomLambda: () -> Unit = {},
|
||||||
private val showJoinRoomByAddressLambda: () -> Unit = {},
|
private val showJoinRoomByAddressLambda: () -> Unit = {},
|
||||||
private val dismissJoinRoomByAddressLambda: () -> Unit = {},
|
private val dismissJoinRoomByAddressLambda: () -> Unit = {},
|
||||||
) : CreateRoomNavigator {
|
private val openRoomDirectoryLambda: () -> Unit = {},
|
||||||
|
) : CreateRoomNavigator {
|
||||||
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) {
|
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) {
|
||||||
openRoomLambda(roomIdOrAlias, serverNames)
|
openRoomLambda(roomIdOrAlias, serverNames)
|
||||||
}
|
}
|
||||||
|
|
@ -31,4 +32,8 @@ class FakeCreateRoomNavigator(
|
||||||
override fun onDismissJoinRoomByAddress() {
|
override fun onDismissJoinRoomByAddress() {
|
||||||
dismissJoinRoomByAddressLambda()
|
dismissJoinRoomByAddressLambda()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOpenRoomDirectory() {
|
||||||
|
openRoomDirectoryLambda()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import io.element.android.features.createroom.impl.userlist.FakeUserListPresente
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||||
import io.element.android.features.createroom.test.FakeStartDMAction
|
import io.element.android.features.createroom.test.FakeStartDMAction
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
@ -163,14 +165,34 @@ class CreateRoomRootPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - room directory search`() = runTest {
|
||||||
|
val presenter = createCreateRoomRootPresenter(isRoomDirectorySearchEnabled = true)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
skipItems(1)
|
||||||
|
awaitItem().let { state ->
|
||||||
|
assertThat(state.isRoomDirectorySearchEnabled).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createCreateRoomRootPresenter(
|
private fun createCreateRoomRootPresenter(
|
||||||
startDMAction: StartDMAction = FakeStartDMAction(),
|
startDMAction: StartDMAction = FakeStartDMAction(),
|
||||||
|
isRoomDirectorySearchEnabled: Boolean = false,
|
||||||
): CreateRoomRootPresenter {
|
): CreateRoomRootPresenter {
|
||||||
|
val featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(
|
||||||
|
FeatureFlags.RoomDirectorySearch.key to isRoomDirectorySearchEnabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
return CreateRoomRootPresenter(
|
return CreateRoomRootPresenter(
|
||||||
presenterFactory = FakeUserListPresenterFactory(FakeUserListPresenter()),
|
presenterFactory = FakeUserListPresenterFactory(FakeUserListPresenter()),
|
||||||
userRepository = FakeUserRepository(),
|
userRepository = FakeUserRepository(),
|
||||||
userListDataStore = UserListDataStore(),
|
userListDataStore = UserListDataStore(),
|
||||||
startDMAction = startDMAction,
|
startDMAction = startDMAction,
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
buildMeta = aBuildMeta(),
|
buildMeta = aBuildMeta(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,21 @@ class CreateRoomRootViewTest {
|
||||||
rule.clickOn(R.string.screen_start_chat_join_room_by_address_action)
|
rule.clickOn(R.string.screen_start_chat_join_room_by_address_action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on room directory invokes the expected callback`() {
|
||||||
|
val eventsRecorder = EventsRecorder<CreateRoomRootEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnce {
|
||||||
|
rule.setCreateRoomRootView(
|
||||||
|
aCreateRoomRootState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
isRoomDirectorySearchEnabled = true
|
||||||
|
),
|
||||||
|
onRoomDirectorySearchClick = it
|
||||||
|
)
|
||||||
|
rule.clickOn(R.string.screen_room_directory_search_title)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreateRoomRootView(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreateRoomRootView(
|
||||||
|
|
@ -125,6 +140,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreat
|
||||||
onOpenDM: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
onOpenDM: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onInviteFriendsClick: () -> Unit = EnsureNeverCalled(),
|
onInviteFriendsClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onJoinRoomByAddressClick: () -> Unit = EnsureNeverCalled(),
|
onJoinRoomByAddressClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
onRoomDirectorySearchClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
CreateRoomRootView(
|
CreateRoomRootView(
|
||||||
|
|
@ -133,7 +149,8 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreat
|
||||||
onNewRoomClick = onNewRoomClick,
|
onNewRoomClick = onNewRoomClick,
|
||||||
onOpenDM = onOpenDM,
|
onOpenDM = onOpenDM,
|
||||||
onInviteFriendsClick = onInviteFriendsClick,
|
onInviteFriendsClick = onInviteFriendsClick,
|
||||||
onJoinByAddressClick = onJoinRoomByAddressClick
|
onJoinByAddressClick = onJoinRoomByAddressClick,
|
||||||
|
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
features/ftue/impl/src/main/res/values-nb/translations.xml
Normal file
11
features/ftue/impl/src/main/res/values-nb/translations.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_notification_optin_subtitle">"Du kan endre innstillingene dine senere."</string>
|
||||||
|
<string name="screen_notification_optin_title">"Tillat varslinger og gå aldri glipp av en melding"</string>
|
||||||
|
<string name="screen_welcome_bullet_1">"Samtaler, avstemninger, søk og mer vil bli lagt til senere i år."</string>
|
||||||
|
<string name="screen_welcome_bullet_2">"Meldingshistorikk for krypterte rom er ikke tilgjengelig ennå."</string>
|
||||||
|
<string name="screen_welcome_bullet_3">"Vi vil gjerne høre fra deg, så la oss få vite hva du synes via innstillingssiden."</string>
|
||||||
|
<string name="screen_welcome_button">"Kom igjen!"</string>
|
||||||
|
<string name="screen_welcome_subtitle">"Her er hva du trenger å vite:"</string>
|
||||||
|
<string name="screen_welcome_title">"Velkommen til %1$s!"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -8,6 +8,6 @@
|
||||||
package io.element.android.features.invite.api.response
|
package io.element.android.features.invite.api.response
|
||||||
|
|
||||||
interface AcceptDeclineInviteEvents {
|
interface AcceptDeclineInviteEvents {
|
||||||
data class AcceptInvite(val invite: InviteData) : AcceptDeclineInviteEvents
|
data class AcceptInvite(val invite: InviteData?) : AcceptDeclineInviteEvents
|
||||||
data class DeclineInvite(val invite: InviteData) : AcceptDeclineInviteEvents
|
data class DeclineInvite(val invite: InviteData?, val blockUser: Boolean = false) : AcceptDeclineInviteEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.invite.api.response
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
||||||
open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDeclineInviteState> {
|
open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDeclineInviteState> {
|
||||||
override val values: Sequence<AcceptDeclineInviteState>
|
override val values: Sequence<AcceptDeclineInviteState>
|
||||||
|
|
@ -17,12 +18,20 @@ open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDec
|
||||||
anAcceptDeclineInviteState(),
|
anAcceptDeclineInviteState(),
|
||||||
anAcceptDeclineInviteState(
|
anAcceptDeclineInviteState(
|
||||||
declineAction = ConfirmingDeclineInvite(
|
declineAction = ConfirmingDeclineInvite(
|
||||||
InviteData(RoomId("!room:matrix.org"), isDm = true, roomName = "Alice")
|
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice", senderId = UserId("@alice:matrix.org")),
|
||||||
|
blockUser = false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
anAcceptDeclineInviteState(
|
anAcceptDeclineInviteState(
|
||||||
declineAction = ConfirmingDeclineInvite(
|
declineAction = ConfirmingDeclineInvite(
|
||||||
InviteData(RoomId("!room:matrix.org"), isDm = false, roomName = "Some room")
|
InviteData(roomId = RoomId("!room:matrix.org"), isDm = false, roomName = "Some room", senderId = UserId("@alice:matrix.org")),
|
||||||
|
blockUser = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
anAcceptDeclineInviteState(
|
||||||
|
declineAction = ConfirmingDeclineInvite(
|
||||||
|
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice", senderId = UserId("@alice:matrix.org")),
|
||||||
|
blockUser = true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
anAcceptDeclineInviteState(
|
anAcceptDeclineInviteState(
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,5 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
data class ConfirmingDeclineInvite(
|
data class ConfirmingDeclineInvite(
|
||||||
val inviteData: InviteData,
|
val inviteData: InviteData,
|
||||||
|
val blockUser: Boolean,
|
||||||
) : AsyncAction.Confirming
|
) : AsyncAction.Confirming
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@
|
||||||
package io.element.android.features.invite.api.response
|
package io.element.android.features.invite.api.response
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
||||||
data class InviteData(
|
data class InviteData(
|
||||||
|
val senderId: UserId,
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val roomName: String,
|
val roomName: String,
|
||||||
val isDm: Boolean,
|
val isDm: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
||||||
|
import io.element.android.features.invite.api.response.InviteData
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
|
|
@ -43,15 +44,34 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
fun handleEvents(event: AcceptDeclineInviteEvents) {
|
fun handleEvents(event: AcceptDeclineInviteEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is AcceptDeclineInviteEvents.AcceptInvite -> {
|
is AcceptDeclineInviteEvents.AcceptInvite -> {
|
||||||
localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction)
|
val inviteData = event.invite
|
||||||
|
if (inviteData == null) {
|
||||||
|
acceptedAction.value = AsyncAction.Failure(InvalidDataException())
|
||||||
|
} else {
|
||||||
|
localCoroutineScope.acceptInvite(inviteData.roomId, acceptedAction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is AcceptDeclineInviteEvents.DeclineInvite -> {
|
is AcceptDeclineInviteEvents.DeclineInvite -> {
|
||||||
declinedAction.value = ConfirmingDeclineInvite(event.invite)
|
val inviteData = event.invite
|
||||||
|
if (inviteData == null) {
|
||||||
|
declinedAction.value = AsyncAction.Failure(InvalidDataException())
|
||||||
|
} else {
|
||||||
|
declinedAction.value = ConfirmingDeclineInvite(inviteData, event.blockUser)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite -> {
|
is InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite -> {
|
||||||
localCoroutineScope.declineInvite(event.roomId, declinedAction)
|
when (val declinedActionValue = declinedAction.value) {
|
||||||
|
is ConfirmingDeclineInvite -> {
|
||||||
|
localCoroutineScope.declineInvite(
|
||||||
|
inviteData = declinedActionValue.inviteData,
|
||||||
|
declinedAction = declinedAction,
|
||||||
|
blockUser = declinedActionValue.blockUser,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is InternalAcceptDeclineInviteEvents.CancelDeclineInvite -> {
|
is InternalAcceptDeclineInviteEvents.CancelDeclineInvite -> {
|
||||||
|
|
@ -92,13 +112,20 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<AsyncAction<RoomId>>) = launch {
|
private fun CoroutineScope.declineInvite(
|
||||||
|
inviteData: InviteData,
|
||||||
|
blockUser: Boolean,
|
||||||
|
declinedAction: MutableState<AsyncAction<RoomId>>,
|
||||||
|
) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
client.getPendingRoom(roomId)?.use {
|
client.getPendingRoom(inviteData.roomId)?.use {
|
||||||
it.leave().getOrThrow()
|
it.leave().getOrThrow()
|
||||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
|
||||||
}
|
}
|
||||||
roomId
|
if (blockUser) {
|
||||||
|
client.ignoreUser(inviteData.senderId).getOrThrow()
|
||||||
|
}
|
||||||
|
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, inviteData.roomId)
|
||||||
|
inviteData.roomId
|
||||||
}.runCatchingUpdatingState(declinedAction)
|
}.runCatchingUpdatingState(declinedAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,9 @@ fun AcceptDeclineInviteView(
|
||||||
if (confirming is ConfirmingDeclineInvite) {
|
if (confirming is ConfirmingDeclineInvite) {
|
||||||
DeclineConfirmationDialog(
|
DeclineConfirmationDialog(
|
||||||
invite = confirming.inviteData,
|
invite = confirming.inviteData,
|
||||||
|
blockUser = confirming.blockUser,
|
||||||
onConfirmClick = {
|
onConfirmClick = {
|
||||||
state.eventSink(InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite(confirming.inviteData.roomId))
|
state.eventSink(InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite)
|
||||||
},
|
},
|
||||||
onDismissClick = {
|
onDismissClick = {
|
||||||
state.eventSink(InternalAcceptDeclineInviteEvents.CancelDeclineInvite)
|
state.eventSink(InternalAcceptDeclineInviteEvents.CancelDeclineInvite)
|
||||||
|
|
@ -66,29 +67,35 @@ fun AcceptDeclineInviteView(
|
||||||
@Composable
|
@Composable
|
||||||
private fun DeclineConfirmationDialog(
|
private fun DeclineConfirmationDialog(
|
||||||
invite: InviteData,
|
invite: InviteData,
|
||||||
|
blockUser: Boolean,
|
||||||
onConfirmClick: () -> Unit,
|
onConfirmClick: () -> Unit,
|
||||||
onDismissClick: () -> Unit,
|
onDismissClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val contentResource = if (invite.isDm) {
|
val senderId = invite.senderId.value
|
||||||
R.string.screen_invites_decline_direct_chat_message
|
val content = when {
|
||||||
} else {
|
blockUser -> stringResource(R.string.screen_join_room_decline_and_block_alert_message, senderId)
|
||||||
R.string.screen_invites_decline_chat_message
|
invite.isDm -> stringResource(R.string.screen_invites_decline_direct_chat_message, invite.roomName)
|
||||||
|
else -> stringResource(R.string.screen_invites_decline_chat_message, invite.roomName)
|
||||||
}
|
}
|
||||||
|
val title = when {
|
||||||
val titleResource = if (invite.isDm) {
|
blockUser -> stringResource(R.string.screen_join_room_decline_and_block_alert_title)
|
||||||
R.string.screen_invites_decline_direct_chat_title
|
invite.isDm -> stringResource(R.string.screen_invites_decline_direct_chat_title)
|
||||||
} else {
|
else -> stringResource(R.string.screen_invites_decline_chat_title)
|
||||||
R.string.screen_invites_decline_chat_title
|
}
|
||||||
|
val submitText = if (blockUser) {
|
||||||
|
stringResource(R.string.screen_join_room_decline_and_block_alert_confirmation)
|
||||||
|
} else {
|
||||||
|
stringResource(CommonStrings.action_decline)
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
content = stringResource(contentResource, invite.roomName),
|
content = content,
|
||||||
title = stringResource(titleResource),
|
title = title,
|
||||||
submitText = stringResource(CommonStrings.action_decline),
|
submitText = submitText,
|
||||||
cancelText = stringResource(CommonStrings.action_cancel),
|
cancelText = stringResource(CommonStrings.action_cancel),
|
||||||
onSubmitClick = onConfirmClick,
|
onSubmitClick = onConfirmClick,
|
||||||
|
destructiveSubmit = blockUser,
|
||||||
onDismiss = onDismissClick,
|
onDismiss = onDismissClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,9 @@
|
||||||
package io.element.android.features.invite.impl.response
|
package io.element.android.features.invite.impl.response
|
||||||
|
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
|
|
||||||
sealed interface InternalAcceptDeclineInviteEvents : AcceptDeclineInviteEvents {
|
sealed interface InternalAcceptDeclineInviteEvents : AcceptDeclineInviteEvents {
|
||||||
data class ConfirmDeclineInvite(val roomId: RoomId) : InternalAcceptDeclineInviteEvents
|
data object ConfirmDeclineInvite : InternalAcceptDeclineInviteEvents
|
||||||
data object CancelDeclineInvite : InternalAcceptDeclineInviteEvents
|
data object CancelDeclineInvite : InternalAcceptDeclineInviteEvents
|
||||||
data object DismissAcceptError : InternalAcceptDeclineInviteEvents
|
data object DismissAcceptError : InternalAcceptDeclineInviteEvents
|
||||||
data object DismissDeclineError : InternalAcceptDeclineInviteEvents
|
data object DismissDeclineError : InternalAcceptDeclineInviteEvents
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invite.impl.response
|
||||||
|
|
||||||
|
class InvalidDataException : Exception()
|
||||||
|
|
@ -6,4 +6,8 @@
|
||||||
<string name="screen_invites_decline_direct_chat_title">"Decline chat"</string>
|
<string name="screen_invites_decline_direct_chat_title">"Decline chat"</string>
|
||||||
<string name="screen_invites_empty_list">"No Invites"</string>
|
<string name="screen_invites_empty_list">"No Invites"</string>
|
||||||
<string name="screen_invites_invited_you">"%1$s (%2$s) invited you"</string>
|
<string name="screen_invites_invited_you">"%1$s (%2$s) invited you"</string>
|
||||||
|
<string name="screen_join_room_decline_and_block_alert_confirmation">"Yes, decline & block"</string>
|
||||||
|
<string name="screen_join_room_decline_and_block_alert_message">"Are you sure you want to decline the invite to join this room? This will also prevent %1$s from contacting you or inviting you to rooms."</string>
|
||||||
|
<string name="screen_join_room_decline_and_block_alert_title">"Decline invite & block"</string>
|
||||||
|
<string name="screen_join_room_decline_and_block_button_title">"Decline and block"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,12 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeRoomPreview
|
import io.element.android.libraries.matrix.test.room.FakeRoomPreview
|
||||||
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||||
|
|
@ -61,7 +63,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData))
|
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, false))
|
||||||
state.eventSink(
|
state.eventSink(
|
||||||
InternalAcceptDeclineInviteEvents.CancelDeclineInvite
|
InternalAcceptDeclineInviteEvents.CancelDeclineInvite
|
||||||
)
|
)
|
||||||
|
|
@ -91,9 +93,9 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData))
|
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, false))
|
||||||
state.eventSink(
|
state.eventSink(
|
||||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite(inviteData.roomId)
|
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||||
|
|
@ -139,9 +141,9 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData))
|
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, false))
|
||||||
state.eventSink(
|
state.eventSink(
|
||||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite(inviteData.roomId)
|
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||||
|
|
@ -156,6 +158,80 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - declining invite with block success flow`() = runTest {
|
||||||
|
val clearMembershipNotificationForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ ->
|
||||||
|
Result.success(Unit)
|
||||||
|
}
|
||||||
|
val fakeNotificationCleaner = FakeNotificationCleaner(
|
||||||
|
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda
|
||||||
|
)
|
||||||
|
val declineInviteSuccess = lambdaRecorder { -> Result.success(Unit) }
|
||||||
|
val ignoreUserSuccess = lambdaRecorder { _: UserId -> Result.success(Unit) }
|
||||||
|
val client = FakeMatrixClient(
|
||||||
|
getRoomPreviewResult = { _, _ ->
|
||||||
|
Result.success(FakeRoomPreview(declineInviteResult = declineInviteSuccess))
|
||||||
|
},
|
||||||
|
ignoreUserResult = ignoreUserSuccess
|
||||||
|
)
|
||||||
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
|
client = client,
|
||||||
|
notificationCleaner = fakeNotificationCleaner,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val inviteData = anInviteData()
|
||||||
|
awaitItem().also { state ->
|
||||||
|
state.eventSink(
|
||||||
|
AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, true))
|
||||||
|
state.eventSink(
|
||||||
|
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.declineAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||||
|
}
|
||||||
|
cancelAndConsumeRemainingEvents()
|
||||||
|
}
|
||||||
|
declineInviteSuccess.assertions().isCalledOnce()
|
||||||
|
ignoreUserSuccess.assertions().isCalledOnce().with(value(A_USER_ID))
|
||||||
|
clearMembershipNotificationForRoomLambda.assertions()
|
||||||
|
.isCalledOnce()
|
||||||
|
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - declining invite with block error flow`() = runTest {
|
||||||
|
val declineInviteFailure = lambdaRecorder { ->
|
||||||
|
Result.failure<Unit>(RuntimeException("Failed to leave room"))
|
||||||
|
}
|
||||||
|
val client = FakeMatrixClient(
|
||||||
|
getRoomPreviewResult = { _, _ ->
|
||||||
|
Result.success(FakeRoomPreview(declineInviteResult = declineInviteFailure))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val presenter = createAcceptDeclineInvitePresenter(client = client)
|
||||||
|
presenter.test {
|
||||||
|
val inviteData = anInviteData()
|
||||||
|
awaitItem().also { state ->
|
||||||
|
state.eventSink(
|
||||||
|
AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, true))
|
||||||
|
state.eventSink(
|
||||||
|
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - accepting invite error flow`() = runTest {
|
fun `present - accepting invite error flow`() = runTest {
|
||||||
val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
|
|
@ -237,12 +313,14 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
private fun anInviteData(
|
private fun anInviteData(
|
||||||
roomId: RoomId = A_ROOM_ID,
|
roomId: RoomId = A_ROOM_ID,
|
||||||
name: String = A_ROOM_NAME,
|
name: String = A_ROOM_NAME,
|
||||||
isDm: Boolean = false
|
isDm: Boolean = false,
|
||||||
|
senderId: UserId = A_USER_ID,
|
||||||
): InviteData {
|
): InviteData {
|
||||||
return InviteData(
|
return InviteData(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
roomName = name,
|
roomName = name,
|
||||||
isDm = isDm
|
isDm = isDm,
|
||||||
|
senderId = senderId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@ sealed interface JoinRoomEvents {
|
||||||
data class UpdateKnockMessage(val message: String) : JoinRoomEvents
|
data class UpdateKnockMessage(val message: String) : JoinRoomEvents
|
||||||
data object ClearActionStates : JoinRoomEvents
|
data object ClearActionStates : JoinRoomEvents
|
||||||
data object AcceptInvite : JoinRoomEvents
|
data object AcceptInvite : JoinRoomEvents
|
||||||
data object DeclineInvite : JoinRoomEvents
|
data class DeclineInvite(val blockUser: Boolean) : JoinRoomEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,15 +152,15 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
||||||
is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, knockMessage)
|
is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, knockMessage)
|
||||||
JoinRoomEvents.AcceptInvite -> {
|
JoinRoomEvents.AcceptInvite -> {
|
||||||
val inviteData = contentState.toInviteData() ?: return
|
val inviteData = contentState.toInviteData()
|
||||||
acceptDeclineInviteState.eventSink(
|
acceptDeclineInviteState.eventSink(
|
||||||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
JoinRoomEvents.DeclineInvite -> {
|
is JoinRoomEvents.DeclineInvite -> {
|
||||||
val inviteData = contentState.toInviteData() ?: return
|
val inviteData = contentState.toInviteData()
|
||||||
acceptDeclineInviteState.eventSink(
|
acceptDeclineInviteState.eventSink(
|
||||||
AcceptDeclineInviteEvents.DeclineInvite(inviteData)
|
AcceptDeclineInviteEvents.DeclineInvite(invite = inviteData, blockUser = event.blockUser)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is JoinRoomEvents.CancelKnock -> coroutineScope.cancelKnockRoom(event.requiresConfirmation, cancelKnockAction)
|
is JoinRoomEvents.CancelKnock -> coroutineScope.cancelKnockRoom(event.requiresConfirmation, cancelKnockAction)
|
||||||
|
|
@ -314,12 +314,19 @@ private fun JoinRule?.toJoinAuthorisationStatus(): JoinAuthorisationStatus {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
internal fun ContentState.toInviteData(): InviteData? {
|
internal fun ContentState.toInviteData(): InviteData? {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is ContentState.Loaded -> InviteData(
|
is ContentState.Loaded -> {
|
||||||
roomId = roomId,
|
if (joinAuthorisationStatus is JoinAuthorisationStatus.IsInvited && joinAuthorisationStatus.inviteSender != null) {
|
||||||
// Note: name should not be null at this point, but use Id just in case...
|
InviteData(
|
||||||
roomName = name ?: roomId.value,
|
roomId = roomId,
|
||||||
isDm = isDm
|
// Note: name should not be null at this point, but use Id just in case...
|
||||||
)
|
roomName = name ?: roomId.value,
|
||||||
|
senderId = joinAuthorisationStatus.inviteSender.userId,
|
||||||
|
isDm = isDm
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
|
|
@ -105,8 +106,8 @@ fun JoinRoomView(
|
||||||
onAcceptInvite = {
|
onAcceptInvite = {
|
||||||
state.eventSink(JoinRoomEvents.AcceptInvite)
|
state.eventSink(JoinRoomEvents.AcceptInvite)
|
||||||
},
|
},
|
||||||
onDeclineInvite = {
|
onDeclineInvite = { blockUser ->
|
||||||
state.eventSink(JoinRoomEvents.DeclineInvite)
|
state.eventSink(JoinRoomEvents.DeclineInvite(blockUser))
|
||||||
},
|
},
|
||||||
onJoinRoom = {
|
onJoinRoom = {
|
||||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||||
|
|
@ -183,7 +184,7 @@ fun JoinRoomView(
|
||||||
private fun JoinRoomFooter(
|
private fun JoinRoomFooter(
|
||||||
joinAuthorisationStatus: JoinAuthorisationStatus,
|
joinAuthorisationStatus: JoinAuthorisationStatus,
|
||||||
onAcceptInvite: () -> Unit,
|
onAcceptInvite: () -> Unit,
|
||||||
onDeclineInvite: () -> Unit,
|
onDeclineInvite: (Boolean) -> Unit,
|
||||||
onJoinRoom: () -> Unit,
|
onJoinRoom: () -> Unit,
|
||||||
onKnockRoom: () -> Unit,
|
onKnockRoom: () -> Unit,
|
||||||
onCancelKnock: () -> Unit,
|
onCancelKnock: () -> Unit,
|
||||||
|
|
@ -193,23 +194,32 @@ private fun JoinRoomFooter(
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
) {
|
) {
|
||||||
when (joinAuthorisationStatus) {
|
when (joinAuthorisationStatus) {
|
||||||
is JoinAuthorisationStatus.IsInvited -> {
|
is JoinAuthorisationStatus.IsInvited -> {
|
||||||
ButtonRowMolecule(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
Column {
|
||||||
OutlinedButton(
|
ButtonRowMolecule(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
||||||
text = stringResource(CommonStrings.action_decline),
|
OutlinedButton(
|
||||||
onClick = onDeclineInvite,
|
text = stringResource(CommonStrings.action_decline),
|
||||||
modifier = Modifier.weight(1f),
|
onClick = { onDeclineInvite(false) },
|
||||||
size = ButtonSize.LargeLowPadding,
|
modifier = Modifier.weight(1f),
|
||||||
)
|
size = ButtonSize.LargeLowPadding,
|
||||||
Button(
|
)
|
||||||
text = stringResource(CommonStrings.action_accept),
|
Button(
|
||||||
onClick = onAcceptInvite,
|
text = stringResource(CommonStrings.action_accept),
|
||||||
modifier = Modifier.weight(1f),
|
onClick = onAcceptInvite,
|
||||||
size = ButtonSize.LargeLowPadding,
|
modifier = Modifier.weight(1f),
|
||||||
|
size = ButtonSize.LargeLowPadding,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
TextButton(
|
||||||
|
text = stringResource(R.string.screen_join_room_decline_and_block_button_title),
|
||||||
|
onClick = { onDeclineInvite(true) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
destructive = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -372,12 +382,19 @@ private fun JoinRoomContent(
|
||||||
IsKnockedLoadedContent()
|
IsKnockedLoadedContent()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
DefaultLoadedContent(
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender
|
||||||
contentState = contentState,
|
if (inviteSender != null) {
|
||||||
knockMessage = knockMessage,
|
InviteSenderView(inviteSender = inviteSender)
|
||||||
onKnockMessageUpdate = onKnockMessageUpdate
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
)
|
}
|
||||||
|
DefaultLoadedContent(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
contentState = contentState,
|
||||||
|
knockMessage = knockMessage,
|
||||||
|
onKnockMessageUpdate = onKnockMessageUpdate
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -440,8 +457,8 @@ private fun IncompleteContent(
|
||||||
private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
|
private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
|
||||||
BoxWithConstraints(
|
BoxWithConstraints(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
IconTitleSubtitleMolecule(
|
IconTitleSubtitleMolecule(
|
||||||
|
|
@ -487,10 +504,6 @@ private fun DefaultLoadedContent(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender
|
|
||||||
if (inviteSender != null) {
|
|
||||||
InviteSenderView(inviteSender = inviteSender)
|
|
||||||
}
|
|
||||||
RoomPreviewDescriptionAtom(contentState.topic ?: "")
|
RoomPreviewDescriptionAtom(contentState.topic ?: "")
|
||||||
if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ja, avbryt"</string>
|
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ja, avbryt"</string>
|
||||||
<string name="screen_join_room_cancel_knock_alert_description">"Är du säker på att du vill avbryta din begäran om att gå med i det här rummet?"</string>
|
<string name="screen_join_room_cancel_knock_alert_description">"Är du säker på att du vill avbryta din begäran om att gå med i det här rummet?"</string>
|
||||||
<string name="screen_join_room_cancel_knock_alert_title">"Avbryt begäran om att gå med"</string>
|
<string name="screen_join_room_cancel_knock_alert_title">"Avbryt begäran om att gå med"</string>
|
||||||
|
<string name="screen_join_room_forget_action">"Glöm det här rummet"</string>
|
||||||
|
<string name="screen_join_room_invite_required_message">"Du behöver en inbjudan för att gå med i detta rum"</string>
|
||||||
<string name="screen_join_room_join_action">"Gå med i rummet"</string>
|
<string name="screen_join_room_join_action">"Gå med i rummet"</string>
|
||||||
<string name="screen_join_room_knock_action">"Knacka för att gå med"</string>
|
<string name="screen_join_room_knock_action">"Knacka för att gå med"</string>
|
||||||
<string name="screen_join_room_knock_message_description">"Meddelande (valfritt)"</string>
|
<string name="screen_join_room_knock_message_description">"Meddelande (valfritt)"</string>
|
||||||
|
|
|
||||||
|
|
@ -167,9 +167,9 @@ class JoinRoomPresenterTest {
|
||||||
|
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
state.eventSink(JoinRoomEvents.AcceptInvite)
|
state.eventSink(JoinRoomEvents.AcceptInvite)
|
||||||
state.eventSink(JoinRoomEvents.DeclineInvite)
|
state.eventSink(JoinRoomEvents.DeclineInvite(false))
|
||||||
|
|
||||||
val inviteData = state.contentState.toInviteData()!!
|
val inviteData = state.contentState.toInviteData()
|
||||||
|
|
||||||
assert(eventSinkRecorder)
|
assert(eventSinkRecorder)
|
||||||
.isCalledExactly(2)
|
.isCalledExactly(2)
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ class JoinRoomViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Accept invitation IsInvited room emits the expected Event`() {
|
fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
rule.setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
|
|
@ -152,7 +152,7 @@ class JoinRoomViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Decline invitation on IsInvited room emits the expected Event`() {
|
fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
rule.setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
|
|
@ -161,7 +161,20 @@ class JoinRoomViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_decline)
|
rule.clickOn(CommonStrings.action_decline)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite)
|
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
|
rule.setJoinRoomView(
|
||||||
|
aJoinRoomState(
|
||||||
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(null)),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rule.clickOn(R.string.screen_join_room_decline_and_block_button_title)
|
||||||
|
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -22,9 +23,10 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImagePainter
|
import coil3.Extras
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil3.compose.AsyncImagePainter
|
||||||
import coil.request.ImageRequest
|
import coil3.compose.rememberAsyncImagePainter
|
||||||
|
import coil3.request.ImageRequest
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.location.api.internal.StaticMapPlaceholder
|
import io.element.android.features.location.api.internal.StaticMapPlaceholder
|
||||||
import io.element.android.features.location.api.internal.StaticMapUrlBuilder
|
import io.element.android.features.location.api.internal.StaticMapUrlBuilder
|
||||||
|
|
@ -33,7 +35,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a static map image downloaded via a third party service's static maps API.
|
* Shows a static map image downloaded via a third party service's static maps API.
|
||||||
|
|
@ -75,14 +76,15 @@ fun StaticMapView(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.size(width = constraints.maxWidth, height = constraints.maxHeight)
|
.size(width = constraints.maxWidth, height = constraints.maxHeight)
|
||||||
.setParameter("retry_hash", retryHash, memoryCacheKey = null)
|
.apply {
|
||||||
|
extras.set(Extras.Key("retry_hash"), retryHash).build()
|
||||||
|
}
|
||||||
.build()
|
.build()
|
||||||
}.apply {
|
|
||||||
Timber.d("Static map image request: ${this?.data}")
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (painter.state is AsyncImagePainter.State.Success) {
|
val collectedState = painter.state.collectAsState()
|
||||||
|
if (collectedState.value is AsyncImagePainter.State.Success) {
|
||||||
Image(
|
Image(
|
||||||
painter = painter,
|
painter = painter,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
|
|
@ -100,7 +102,7 @@ fun StaticMapView(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
StaticMapPlaceholder(
|
StaticMapPlaceholder(
|
||||||
showProgress = painter.state is AsyncImagePainter.State.Loading,
|
showProgress = collectedState.value.isLoading(),
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
width = maxWidth,
|
width = maxWidth,
|
||||||
height = maxHeight,
|
height = maxHeight,
|
||||||
|
|
@ -110,6 +112,11 @@ fun StaticMapView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun AsyncImagePainter.State.isLoading(): Boolean {
|
||||||
|
return this is AsyncImagePainter.State.Empty ||
|
||||||
|
this is AsyncImagePainter.State.Loading
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun StaticMapViewPreview() = ElementPreview {
|
internal fun StaticMapViewPreview() = ElementPreview {
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,17 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.compound.theme.ElementTheme
|
|
||||||
import io.element.android.features.lockscreen.impl.R
|
import io.element.android.features.lockscreen.impl.R
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceDivider
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceDivider
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListItemStyle
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LockScreenSettingsView(
|
fun LockScreenSettingsView(
|
||||||
|
|
@ -35,15 +36,19 @@ fun LockScreenSettingsView(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
PreferenceCategory(showTopDivider = false) {
|
PreferenceCategory(showTopDivider = false) {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = stringResource(id = R.string.screen_app_lock_settings_change_pin),
|
headlineContent = {
|
||||||
onClick = onChangePinClick
|
Text(stringResource(id = R.string.screen_app_lock_settings_change_pin))
|
||||||
|
},
|
||||||
|
onClick = onChangePinClick,
|
||||||
)
|
)
|
||||||
PreferenceDivider()
|
PreferenceDivider()
|
||||||
if (state.showRemovePinOption) {
|
if (state.showRemovePinOption) {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = stringResource(id = R.string.screen_app_lock_settings_remove_pin),
|
headlineContent = {
|
||||||
tintColor = ElementTheme.colors.textCriticalPrimary,
|
Text(stringResource(id = R.string.screen_app_lock_settings_remove_pin))
|
||||||
|
},
|
||||||
|
style = ListItemStyle.Destructive,
|
||||||
onClick = {
|
onClick = {
|
||||||
state.eventSink(LockScreenSettingsEvents.OnRemovePin)
|
state.eventSink(LockScreenSettingsEvents.OnRemovePin)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_account_provider_change">"Bytt kontotilbyder"</string>
|
<string name="screen_account_provider_change">"Bytt kontotilbyder"</string>
|
||||||
<string name="screen_account_provider_form_hint">"Hjemmeserveradresse"</string>
|
<string name="screen_account_provider_form_hint">"Hjemmeserveradresse"</string>
|
||||||
|
<string name="screen_account_provider_form_notice">"Skriv inn et søkeord eller en domeneadresse."</string>
|
||||||
<string name="screen_account_provider_form_subtitle">"Søk etter et selskap, fellesskap eller privat server."</string>
|
<string name="screen_account_provider_form_subtitle">"Søk etter et selskap, fellesskap eller privat server."</string>
|
||||||
<string name="screen_account_provider_form_title">"Finn en kontoleverandør"</string>
|
<string name="screen_account_provider_form_title">"Finn en kontoleverandør"</string>
|
||||||
<string name="screen_account_provider_signin_subtitle">"Det er her samtalene dine vil ligge - akkurat som du ville brukt en e-postleverandør til å oppbevare e-postene dine."</string>
|
<string name="screen_account_provider_signin_subtitle">"Det er her samtalene dine vil ligge - akkurat som du ville brukt en e-postleverandør til å oppbevare e-postene dine."</string>
|
||||||
<string name="screen_account_provider_signin_title">"Du er i ferd med å logge inn på %s"</string>
|
<string name="screen_account_provider_signin_title">"Du er i ferd med å logge inn på %s"</string>
|
||||||
<string name="screen_account_provider_signup_subtitle">"Det er her samtalene dine vil ligge - akkurat som du ville brukt en e-postleverandør til å oppbevare e-postene dine."</string>
|
<string name="screen_account_provider_signup_subtitle">"Det er her samtalene dine vil ligge - akkurat som du ville brukt en e-postleverandør til å oppbevare e-postene dine."</string>
|
||||||
<string name="screen_account_provider_signup_title">"Du er i ferd med å opprette en konto på %s"</string>
|
<string name="screen_account_provider_signup_title">"Du er i ferd med å opprette en konto på %s"</string>
|
||||||
|
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org er en stor, gratis server på det offentlige Matrix-nettverket for sikker, desentralisert kommunikasjon, drevet av Matrix.org Foundation."</string>
|
||||||
<string name="screen_change_account_provider_other">"Annet"</string>
|
<string name="screen_change_account_provider_other">"Annet"</string>
|
||||||
<string name="screen_change_account_provider_subtitle">"Bruk en annen kontotilbyder, for eksempel din egen private server eller en arbeidskonto."</string>
|
<string name="screen_change_account_provider_subtitle">"Bruk en annen kontotilbyder, for eksempel din egen private server eller en arbeidskonto."</string>
|
||||||
<string name="screen_change_account_provider_title">"Bytt kontotilbyder"</string>
|
<string name="screen_change_account_provider_title">"Bytt kontotilbyder"</string>
|
||||||
|
|
@ -16,6 +18,7 @@
|
||||||
<string name="screen_change_server_form_header">"URL til hjemmeserver"</string>
|
<string name="screen_change_server_form_header">"URL til hjemmeserver"</string>
|
||||||
<string name="screen_change_server_form_notice">"Du kan bare koble til en eksisterende server som støtter sliding sync. Administrator for din hjemmeserver må konfigurere det. %1$s"</string>
|
<string name="screen_change_server_form_notice">"Du kan bare koble til en eksisterende server som støtter sliding sync. Administrator for din hjemmeserver må konfigurere det. %1$s"</string>
|
||||||
<string name="screen_change_server_subtitle">"Hva er adressen til serveren din?"</string>
|
<string name="screen_change_server_subtitle">"Hva er adressen til serveren din?"</string>
|
||||||
|
<string name="screen_change_server_title">"Velg din server"</string>
|
||||||
<string name="screen_create_account_title">"Opprett konto"</string>
|
<string name="screen_create_account_title">"Opprett konto"</string>
|
||||||
<string name="screen_login_error_deactivated_account">"Denne kontoen er deaktivert."</string>
|
<string name="screen_login_error_deactivated_account">"Denne kontoen er deaktivert."</string>
|
||||||
<string name="screen_login_error_invalid_credentials">"Feil brukernavn og/eller passord"</string>
|
<string name="screen_login_error_invalid_credentials">"Feil brukernavn og/eller passord"</string>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.R
|
import io.element.android.features.messages.impl.R
|
||||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil3.compose.AsyncImagePainter
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.ATimelineItemEventRow
|
import io.element.android.features.messages.impl.timeline.components.ATimelineItemEventRow
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil3.compose.AsyncImagePainter
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContentProvider
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContentProvider
|
||||||
import io.element.android.features.messages.impl.timeline.protection.ProtectedView
|
import io.element.android.features.messages.impl.timeline.protection.ProtectedView
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil3.compose.AsyncImagePainter
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.timeline.components.REACTION_IMAGE_ASPECT_RATIO
|
import io.element.android.features.messages.impl.timeline.components.REACTION_IMAGE_ASPECT_RATIO
|
||||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,24 @@
|
||||||
<string name="screen_room_attachment_source_camera_video">"Ta opp video"</string>
|
<string name="screen_room_attachment_source_camera_video">"Ta opp video"</string>
|
||||||
<string name="screen_room_attachment_source_files">"Vedlegg"</string>
|
<string name="screen_room_attachment_source_files">"Vedlegg"</string>
|
||||||
<string name="screen_room_attachment_source_gallery">"Foto- og videobibliotek"</string>
|
<string name="screen_room_attachment_source_gallery">"Foto- og videobibliotek"</string>
|
||||||
|
<string name="screen_room_attachment_source_location">"Lokasjon"</string>
|
||||||
|
<string name="screen_room_attachment_source_poll">"Avstemning"</string>
|
||||||
|
<string name="screen_room_attachment_text_formatting">"Tekstformatering"</string>
|
||||||
<string name="screen_room_encrypted_history_banner">"Meldingshistorikken er for øyeblikket ikke tilgjengelig."</string>
|
<string name="screen_room_encrypted_history_banner">"Meldingshistorikken er for øyeblikket ikke tilgjengelig."</string>
|
||||||
<string name="screen_room_encrypted_history_banner_unverified">"Meldingshistorikk er ikke tilgjengelig i dette rommet. Bekreft denne enheten for å se meldingshistorikken din."</string>
|
<string name="screen_room_encrypted_history_banner_unverified">"Meldingshistorikk er ikke tilgjengelig i dette rommet. Bekreft denne enheten for å se meldingshistorikken din."</string>
|
||||||
<string name="screen_room_invite_again_alert_message">"Vil du invitere dem tilbake?"</string>
|
<string name="screen_room_invite_again_alert_message">"Vil du invitere dem tilbake?"</string>
|
||||||
<string name="screen_room_invite_again_alert_title">"Du er alene i denne chatten"</string>
|
<string name="screen_room_invite_again_alert_title">"Du er alene i denne chatten"</string>
|
||||||
|
<string name="screen_room_mentions_at_room_title">"Alle"</string>
|
||||||
<string name="screen_room_retry_send_menu_send_again_action">"Send igjen"</string>
|
<string name="screen_room_retry_send_menu_send_again_action">"Send igjen"</string>
|
||||||
<string name="screen_room_retry_send_menu_title">"Meldingen din ble ikke sendt"</string>
|
<string name="screen_room_retry_send_menu_title">"Meldingen din ble ikke sendt"</string>
|
||||||
|
<string name="screen_room_timeline_add_reaction">"Legg til emoji"</string>
|
||||||
<string name="screen_room_timeline_beginning_of_room">"Dette er begynnelsen på %1$s."</string>
|
<string name="screen_room_timeline_beginning_of_room">"Dette er begynnelsen på %1$s."</string>
|
||||||
<string name="screen_room_timeline_beginning_of_room_no_name">"Dette er begynnelsen på denne samtalen."</string>
|
<string name="screen_room_timeline_beginning_of_room_no_name">"Dette er begynnelsen på denne samtalen."</string>
|
||||||
|
<string name="screen_room_timeline_less_reactions">"Vis mindre"</string>
|
||||||
|
<string name="screen_room_timeline_message_copied">"Melding kopiert"</string>
|
||||||
|
<string name="screen_room_timeline_no_permission_to_post">"Du har ikke tillatelse til å legge ut innlegg i dette rommet"</string>
|
||||||
|
<string name="screen_room_timeline_reactions_show_less">"Vis mindre"</string>
|
||||||
|
<string name="screen_room_timeline_reactions_show_more">"Vis mer"</string>
|
||||||
<string name="screen_room_timeline_read_marker_title">"Ny"</string>
|
<string name="screen_room_timeline_read_marker_title">"Ny"</string>
|
||||||
<plurals name="screen_room_timeline_state_changes">
|
<plurals name="screen_room_timeline_state_changes">
|
||||||
<item quantity="one">"%1$d romendring"</item>
|
<item quantity="one">"%1$d romendring"</item>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
<string name="screen_onboarding_sign_in_manually">"Logg på manuelt"</string>
|
<string name="screen_onboarding_sign_in_manually">"Logg på manuelt"</string>
|
||||||
<string name="screen_onboarding_sign_in_with_qr_code">"Logg inn med QR-kode"</string>
|
<string name="screen_onboarding_sign_in_with_qr_code">"Logg inn med QR-kode"</string>
|
||||||
<string name="screen_onboarding_sign_up">"Opprett konto"</string>
|
<string name="screen_onboarding_sign_up">"Opprett konto"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Velkommen til den raskeste %1$s noensinne. Superladet for hastighet og enkelhet."</string>
|
||||||
<string name="screen_onboarding_welcome_subtitle">"Velkommen til %1$s. Supercharged, for hastighet og enkelhet."</string>
|
<string name="screen_onboarding_welcome_subtitle">"Velkommen til %1$s. Supercharged, for hastighet og enkelhet."</string>
|
||||||
<string name="screen_onboarding_welcome_title">"Vær i ditt rette element"</string>
|
<string name="screen_onboarding_welcome_title">"Vær i ditt rette element"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_create_poll_add_option_btn">"Legg til alternativ"</string>
|
||||||
|
<string name="screen_create_poll_anonymous_desc">"Vis resultater bare etter at avstemningen er avsluttet"</string>
|
||||||
|
<string name="screen_create_poll_anonymous_headline">"Skjul stemmer"</string>
|
||||||
|
<string name="screen_create_poll_answer_hint">"Alternativ %1$d"</string>
|
||||||
<string name="screen_create_poll_cancel_confirmation_content_android">"Endringene dine er ikke lagret. Er du sikker på at du vil gå tilbake?"</string>
|
<string name="screen_create_poll_cancel_confirmation_content_android">"Endringene dine er ikke lagret. Er du sikker på at du vil gå tilbake?"</string>
|
||||||
|
<string name="screen_create_poll_question_desc">"Spørsmål eller emne"</string>
|
||||||
<string name="screen_create_poll_question_hint">"Hva handler avstemningen om?"</string>
|
<string name="screen_create_poll_question_hint">"Hva handler avstemningen om?"</string>
|
||||||
<string name="screen_create_poll_title">"Opprett avstemning"</string>
|
<string name="screen_create_poll_title">"Opprett avstemning"</string>
|
||||||
<string name="screen_edit_poll_delete_confirmation">"Er du sikker på at du vil slette denne avstemningen?"</string>
|
<string name="screen_edit_poll_delete_confirmation">"Er du sikker på at du vil slette denne avstemningen?"</string>
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ dependencies {
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
api(projects.features.preferences.api)
|
api(projects.features.preferences.api)
|
||||||
|
|
||||||
|
implementation(platform(libs.network.okhttp.bom))
|
||||||
|
implementation(libs.network.okhttp)
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
testImplementation(libs.molecule.runtime)
|
testImplementation(libs.molecule.runtime)
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,10 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -31,13 +32,17 @@ fun AboutView(
|
||||||
title = stringResource(id = CommonStrings.common_about)
|
title = stringResource(id = CommonStrings.common_about)
|
||||||
) {
|
) {
|
||||||
state.elementLegals.forEach { elementLegal ->
|
state.elementLegals.forEach { elementLegal ->
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = stringResource(id = elementLegal.titleRes),
|
headlineContent = {
|
||||||
|
Text(stringResource(id = elementLegal.titleRes))
|
||||||
|
},
|
||||||
onClick = { onElementLegalClick(elementLegal) }
|
onClick = { onElementLegalClick(elementLegal) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = stringResource(id = CommonStrings.common_open_source_licenses),
|
headlineContent = {
|
||||||
|
Text(stringResource(id = CommonStrings.common_open_source_licenses))
|
||||||
|
},
|
||||||
onClick = onOpenSourceLicensesClick,
|
onClick = onOpenSourceLicensesClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,29 @@
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.developer
|
package io.element.android.features.preferences.impl.developer
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.progressSemantics
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView
|
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView
|
||||||
|
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceTextField
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceTextField
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.featureflag.ui.FeatureListView
|
import io.element.android.libraries.featureflag.ui.FeatureListView
|
||||||
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -57,13 +63,15 @@ fun DeveloperSettingsView(
|
||||||
selectedOption = state.tracingLogLevel.dataOrNull(),
|
selectedOption = state.tracingLogLevel.dataOrNull(),
|
||||||
options = LogLevelItem.entries.toPersistentList(),
|
options = LogLevelItem.entries.toPersistentList(),
|
||||||
onSelectOption = { logLevel ->
|
onSelectOption = { logLevel ->
|
||||||
state.eventSink(DeveloperSettingsEvents.SetTracingLogLevel(logLevel))
|
state.eventSink(DeveloperSettingsEvents.SetTracingLogLevel(logLevel))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PreferenceCategory(title = "Showkase") {
|
PreferenceCategory(title = "Showkase") {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = "Open Showkase browser",
|
headlineContent = {
|
||||||
|
Text("Open Showkase browser")
|
||||||
|
},
|
||||||
onClick = onOpenShowkase
|
onClick = onOpenShowkase
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -71,17 +79,31 @@ fun DeveloperSettingsView(
|
||||||
state = state.rageshakeState,
|
state = state.rageshakeState,
|
||||||
)
|
)
|
||||||
PreferenceCategory(title = "Crash", showTopDivider = false) {
|
PreferenceCategory(title = "Crash", showTopDivider = false) {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = "Crash the app 💥",
|
headlineContent = {
|
||||||
|
Text("Crash the app 💥")
|
||||||
|
},
|
||||||
onClick = { error("This crash is a test.") }
|
onClick = { error("This crash is a test.") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val cache = state.cacheSize
|
val cache = state.cacheSize
|
||||||
PreferenceCategory(title = "Cache", showTopDivider = false) {
|
PreferenceCategory(title = "Cache", showTopDivider = false) {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = "Clear cache",
|
headlineContent = {
|
||||||
currentValue = cache.dataOrNull(),
|
Text("Clear cache")
|
||||||
loadingCurrentValue = state.cacheSize.isLoading() || state.clearCacheAction.isLoading(),
|
},
|
||||||
|
trailingContent = if (state.cacheSize.isLoading() || state.clearCacheAction.isLoading()) {
|
||||||
|
ListItemContent.Custom {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.progressSemantics()
|
||||||
|
.size(20.dp),
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ListItemContent.Text(cache.dataOrNull().orEmpty())
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
if (state.clearCacheAction.isLoading().not()) {
|
if (state.clearCacheAction.isLoading().not()) {
|
||||||
state.eventSink(DeveloperSettingsEvents.ClearCache)
|
state.eventSink(DeveloperSettingsEvents.ClearCache)
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||||
|
|
@ -109,13 +109,19 @@ private fun NotificationSettingsContentView(
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val systemSettings: NotificationSettingsState.AppSettings = state.appSettings
|
val systemSettings: NotificationSettingsState.AppSettings = state.appSettings
|
||||||
if (systemSettings.appNotificationsEnabled && !systemSettings.systemNotificationsEnabled) {
|
if (systemSettings.appNotificationsEnabled && !systemSettings.systemNotificationsEnabled) {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
icon = CompoundIcons.NotificationsOffSolid(),
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.NotificationsOffSolid())),
|
||||||
title = stringResource(id = R.string.screen_notification_settings_system_notifications_turned_off),
|
headlineContent = {
|
||||||
subtitle = stringResource(
|
Text(stringResource(id = R.string.screen_notification_settings_system_notifications_turned_off))
|
||||||
id = R.string.screen_notification_settings_system_notifications_action_required,
|
},
|
||||||
stringResource(id = R.string.screen_notification_settings_system_notifications_action_required_content_link)
|
supportingContent = {
|
||||||
),
|
Text(
|
||||||
|
stringResource(
|
||||||
|
id = R.string.screen_notification_settings_system_notifications_action_required,
|
||||||
|
stringResource(id = R.string.screen_notification_settings_system_notifications_action_required_content_link)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
context.startNotificationSettingsIntent()
|
context.startNotificationSettingsIntent()
|
||||||
}
|
}
|
||||||
|
|
@ -131,10 +137,14 @@ private fun NotificationSettingsContentView(
|
||||||
if (systemSettings.appNotificationsEnabled) {
|
if (systemSettings.appNotificationsEnabled) {
|
||||||
if (!state.fullScreenIntentPermissionsState.permissionGranted) {
|
if (!state.fullScreenIntentPermissionsState.permissionGranted) {
|
||||||
PreferenceCategory {
|
PreferenceCategory {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
icon = CompoundIcons.VoiceCallSolid(),
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.VoiceCallSolid())),
|
||||||
title = stringResource(id = R.string.full_screen_intent_banner_title),
|
headlineContent = {
|
||||||
subtitle = stringResource(R.string.full_screen_intent_banner_message),
|
Text(stringResource(id = R.string.full_screen_intent_banner_title))
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(stringResource(R.string.full_screen_intent_banner_message))
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
state.fullScreenIntentPermissionsState.openFullScreenIntentSettings()
|
state.fullScreenIntentPermissionsState.openFullScreenIntentSettings()
|
||||||
}
|
}
|
||||||
|
|
@ -142,15 +152,22 @@ private fun NotificationSettingsContentView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_notification_section_title)) {
|
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_notification_section_title)) {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = stringResource(id = R.string.screen_notification_settings_group_chats),
|
headlineContent = {
|
||||||
subtitle = getTitleForRoomNotificationMode(mode = matrixSettings.defaultGroupNotificationMode),
|
Text(stringResource(id = R.string.screen_notification_settings_group_chats))
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(getTitleForRoomNotificationMode(mode = matrixSettings.defaultGroupNotificationMode))
|
||||||
|
},
|
||||||
onClick = onGroupChatsClick
|
onClick = onGroupChatsClick
|
||||||
)
|
)
|
||||||
|
ListItem(
|
||||||
PreferenceText(
|
headlineContent = {
|
||||||
title = stringResource(id = R.string.screen_notification_settings_direct_chats),
|
Text(stringResource(id = R.string.screen_notification_settings_direct_chats))
|
||||||
subtitle = getTitleForRoomNotificationMode(mode = matrixSettings.defaultOneToOneNotificationMode),
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(getTitleForRoomNotificationMode(mode = matrixSettings.defaultOneToOneNotificationMode))
|
||||||
|
},
|
||||||
onClick = onDirectChatsClick
|
onClick = onDirectChatsClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -180,9 +197,10 @@ private fun NotificationSettingsContentView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PreferenceCategory(title = stringResource(id = R.string.troubleshoot_notifications_entry_point_section)) {
|
PreferenceCategory(title = stringResource(id = R.string.troubleshoot_notifications_entry_point_section)) {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
modifier = Modifier,
|
headlineContent = {
|
||||||
title = stringResource(id = R.string.troubleshoot_notifications_entry_point_title),
|
Text(stringResource(id = R.string.troubleshoot_notifications_entry_point_title))
|
||||||
|
},
|
||||||
onClick = onTroubleshootNotificationsClick
|
onClick = onTroubleshootNotificationsClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,10 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:OptIn(ExperimentalCoilApi::class)
|
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.tasks
|
package io.element.android.features.preferences.impl.tasks
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import coil.Coil
|
import coil3.SingletonImageLoader
|
||||||
import coil.annotation.ExperimentalCoilApi
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.ftue.api.state.FtueService
|
import io.element.android.features.ftue.api.state.FtueService
|
||||||
import io.element.android.features.preferences.impl.DefaultCacheService
|
import io.element.android.features.preferences.impl.DefaultCacheService
|
||||||
|
|
@ -43,7 +40,7 @@ class DefaultClearCacheUseCase @Inject constructor(
|
||||||
// Clear Matrix cache
|
// Clear Matrix cache
|
||||||
matrixClient.clearCache()
|
matrixClient.clearCache()
|
||||||
// Clear Coil cache
|
// Clear Coil cache
|
||||||
Coil.imageLoader(context).let {
|
SingletonImageLoader.get(context).let {
|
||||||
it.diskCache?.clear()
|
it.diskCache?.clear()
|
||||||
it.memoryCache?.clear()
|
it.memoryCache?.clear()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,38 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_advanced_settings_choose_distributor_dialog_title_android">"Velg hvordan du vil motta varsler"</string>
|
<string name="screen_advanced_settings_choose_distributor_dialog_title_android">"Velg hvordan du vil motta varsler"</string>
|
||||||
<string name="screen_advanced_settings_developer_mode">"Utviklermodus"</string>
|
<string name="screen_advanced_settings_developer_mode">"Utviklermodus"</string>
|
||||||
|
<string name="screen_advanced_settings_developer_mode_description">"Aktiver for å få tilgang til funksjoner og funksjonalitet for utviklere."</string>
|
||||||
|
<string name="screen_advanced_settings_rich_text_editor_description">"Deaktiver rik tekstredigering for å skrive Markdown manuelt."</string>
|
||||||
<string name="screen_blocked_users_unblock_alert_action">"Fjern blokkering"</string>
|
<string name="screen_blocked_users_unblock_alert_action">"Fjern blokkering"</string>
|
||||||
<string name="screen_blocked_users_unblock_alert_description">"Du vil kunne se alle meldingene fra dem igjen."</string>
|
<string name="screen_blocked_users_unblock_alert_description">"Du vil kunne se alle meldingene fra dem igjen."</string>
|
||||||
<string name="screen_blocked_users_unblock_alert_title">"Fjern blokkering av bruker"</string>
|
<string name="screen_blocked_users_unblock_alert_title">"Fjern blokkering av bruker"</string>
|
||||||
<string name="screen_edit_profile_display_name">"Visningsnavn"</string>
|
<string name="screen_edit_profile_display_name">"Visningsnavn"</string>
|
||||||
<string name="screen_edit_profile_display_name_placeholder">"Ditt visningsnavn"</string>
|
<string name="screen_edit_profile_display_name_placeholder">"Ditt visningsnavn"</string>
|
||||||
|
<string name="screen_edit_profile_error">"Det oppstod en ukjent feil, og informasjonen kunne ikke endres."</string>
|
||||||
<string name="screen_edit_profile_error_title">"Kan ikke oppdatere profilen"</string>
|
<string name="screen_edit_profile_error_title">"Kan ikke oppdatere profilen"</string>
|
||||||
<string name="screen_edit_profile_title">"Rediger profil"</string>
|
<string name="screen_edit_profile_title">"Rediger profil"</string>
|
||||||
<string name="screen_edit_profile_updating_details">"Oppdaterer profilen…"</string>
|
<string name="screen_edit_profile_updating_details">"Oppdaterer profilen…"</string>
|
||||||
|
<string name="screen_notification_settings_additional_settings_section_title">"Ytterligere innstillinger"</string>
|
||||||
|
<string name="screen_notification_settings_calls_label">"Lyd- og videosamtaler"</string>
|
||||||
|
<string name="screen_notification_settings_configuration_mismatch">"Uoverensstemmelse i konfigurasjonen"</string>
|
||||||
|
<string name="screen_notification_settings_configuration_mismatch_description">"Vi har forenklet varslingsinnstillingene for å gjøre det lettere å finne alternativene. Noen av de egendefinerte innstillingene du har valgt tidligere, vises ikke her, men de er fortsatt aktive.
|
||||||
|
|
||||||
|
Hvis du fortsetter, kan noen av innstillingene dine endres."</string>
|
||||||
|
<string name="screen_notification_settings_direct_chats">"Direkte chatter"</string>
|
||||||
|
<string name="screen_notification_settings_edit_custom_settings_section_title">"Egendefinert innstilling per chat"</string>
|
||||||
|
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Det oppstod en feil under oppdatering av varslingsinnstillingen."</string>
|
||||||
|
<string name="screen_notification_settings_edit_mode_all_messages">"Alle meldinger"</string>
|
||||||
|
<string name="screen_notification_settings_edit_mode_mentions_and_keywords">"Bare omtaler og nøkkelord"</string>
|
||||||
|
<string name="screen_notification_settings_edit_screen_direct_section_header">"På direkte chatter, varsle meg for"</string>
|
||||||
|
<string name="screen_notification_settings_edit_screen_group_section_header">"I gruppechatter, varsle meg om"</string>
|
||||||
|
<string name="screen_notification_settings_enable_notifications">"Aktiver varsler på denne enheten"</string>
|
||||||
|
<string name="screen_notification_settings_failed_fixing_configuration">"Konfigurasjonen er ikke korrigert, prøv igjen."</string>
|
||||||
|
<string name="screen_notification_settings_group_chats">"Gruppechatter"</string>
|
||||||
|
<string name="screen_notification_settings_mode_all">"Alle"</string>
|
||||||
|
<string name="screen_notification_settings_notification_section_title">"Varsle meg om"</string>
|
||||||
|
<string name="screen_notification_settings_room_mention_label">"Gi meg varsel på @room"</string>
|
||||||
|
<string name="screen_notification_settings_system_notifications_action_required">"For å motta varsler, vennligst endre %1$s."</string>
|
||||||
|
<string name="screen_notification_settings_system_notifications_action_required_content_link">"systeminnstillinger"</string>
|
||||||
|
<string name="screen_notification_settings_system_notifications_turned_off">"Systemvarsler er slått av"</string>
|
||||||
|
<string name="screen_notification_settings_title">"Varslinger"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -40,9 +41,9 @@ class BlockedUsersPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state with blocked users`() = runTest {
|
fun `present - initial state with blocked users`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient(
|
||||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||||
}
|
)
|
||||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -56,9 +57,10 @@ class BlockedUsersPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - blocked users list updates with new emissions`() = runTest {
|
fun `present - blocked users list updates with new emissions`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
val matrixClient = FakeMatrixClient(
|
||||||
}
|
ignoredUsersFlow = ignoredUsersFlow
|
||||||
|
)
|
||||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -66,7 +68,7 @@ class BlockedUsersPresenterTest {
|
||||||
with(awaitItem()) {
|
with(awaitItem()) {
|
||||||
assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID)))
|
assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID)))
|
||||||
}
|
}
|
||||||
matrixClient.ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2)
|
ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
with(awaitItem()) {
|
with(awaitItem()) {
|
||||||
assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID), MatrixUser(A_USER_ID_2)))
|
assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID), MatrixUser(A_USER_ID_2)))
|
||||||
|
|
@ -77,8 +79,9 @@ class BlockedUsersPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - blocked users list with data`() = runTest {
|
fun `present - blocked users list with data`() = runTest {
|
||||||
val alice = MatrixUser(A_USER_ID, displayName = "Alice", avatarUrl = "aliceAvatar")
|
val alice = MatrixUser(A_USER_ID, displayName = "Alice", avatarUrl = "aliceAvatar")
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient(
|
||||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2)
|
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID, A_USER_ID_2))
|
||||||
|
).apply {
|
||||||
givenGetProfileResult(A_USER_ID, Result.success(alice))
|
givenGetProfileResult(A_USER_ID, Result.success(alice))
|
||||||
givenGetProfileResult(A_USER_ID_2, Result.failure(AN_EXCEPTION))
|
givenGetProfileResult(A_USER_ID_2, Result.failure(AN_EXCEPTION))
|
||||||
}
|
}
|
||||||
|
|
@ -103,9 +106,9 @@ class BlockedUsersPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - unblock user`() = runTest {
|
fun `present - unblock user`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient(
|
||||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||||
}
|
)
|
||||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -125,10 +128,10 @@ class BlockedUsersPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - unblock user handles failure`() = runTest {
|
fun `present - unblock user handles failure`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient(
|
||||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
unIgnoreUserResult = { Result.failure(IllegalStateException("User not banned")) },
|
||||||
givenUnignoreUserResult(Result.failure(IllegalStateException("User not banned")))
|
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||||
}
|
)
|
||||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -147,10 +150,10 @@ class BlockedUsersPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - unblock user then cancel`() = runTest {
|
fun `present - unblock user then cancel`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient().apply {
|
val matrixClient = FakeMatrixClient(
|
||||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
unIgnoreUserResult = { Result.failure(IllegalStateException("User not banned")) },
|
||||||
givenUnignoreUserResult(Result.failure(IllegalStateException("User not banned")))
|
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||||
}
|
)
|
||||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,10 @@ import io.element.android.features.rageshake.api.R
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSlide
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSlide
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -52,7 +53,11 @@ fun RageshakePreferencesView(
|
||||||
onValueChange = ::onSensitivityChanged
|
onValueChange = ::onSensitivityChanged
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
PreferenceText(title = "Rageshaking is not supported by your device")
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text("Rageshaking is not supported by your device")
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import io.element.android.features.rageshake.impl.R
|
import io.element.android.features.rageshake.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
|
|
@ -38,12 +38,13 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
|
||||||
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
|
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
|
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -96,8 +97,10 @@ fun BugReportView(
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
PreferenceDivider()
|
PreferenceDivider()
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = stringResource(id = R.string.screen_bug_report_view_logs),
|
headlineContent = {
|
||||||
|
Text(stringResource(id = R.string.screen_bug_report_view_logs))
|
||||||
|
},
|
||||||
enabled = isFormEnabled,
|
enabled = isFormEnabled,
|
||||||
onClick = onViewLogs,
|
onClick = onViewLogs,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_bug_report_attach_screenshot">"Legg ved skjermbilde"</string>
|
<string name="screen_bug_report_attach_screenshot">"Legg ved skjermbilde"</string>
|
||||||
<string name="screen_bug_report_contact_me">"Du kan kontakte meg hvis du har noen oppfølgingsspørsmål."</string>
|
<string name="screen_bug_report_contact_me">"Du kan kontakte meg hvis du har noen oppfølgingsspørsmål."</string>
|
||||||
|
<string name="screen_bug_report_contact_me_title">"Kontakt meg"</string>
|
||||||
<string name="screen_bug_report_edit_screenshot">"Rediger skjermbilde"</string>
|
<string name="screen_bug_report_edit_screenshot">"Rediger skjermbilde"</string>
|
||||||
<string name="screen_bug_report_editor_description">"Vennligst beskriv problemet. Hva har du gjort? Hva forventet du skulle skje? Hva som faktisk skjedde. Vær så detaljert som mulig."</string>
|
<string name="screen_bug_report_editor_description">"Vennligst beskriv problemet. Hva har du gjort? Hva forventet du skulle skje? Hva som faktisk skjedde. Vær så detaljert som mulig."</string>
|
||||||
<string name="screen_bug_report_editor_placeholder">"Beskriv problemet…"</string>
|
<string name="screen_bug_report_editor_placeholder">"Beskriv problemet…"</string>
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ import io.element.android.libraries.designsystem.components.button.MainActionBut
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
||||||
|
|
@ -481,10 +480,14 @@ private fun TopicSection(
|
||||||
showTopDivider = false,
|
showTopDivider = false,
|
||||||
) {
|
) {
|
||||||
if (roomTopic is RoomTopicState.CanAddTopic) {
|
if (roomTopic is RoomTopicState.CanAddTopic) {
|
||||||
PreferenceText(
|
ListItem(
|
||||||
title = stringResource(R.string.screen_room_details_add_topic_title),
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Plus())),
|
||||||
icon = CompoundIcons.Plus(),
|
headlineContent = {
|
||||||
onClick = { onActionClick(RoomDetailsAction.AddTopic) },
|
Text(stringResource(id = R.string.screen_room_details_add_topic_title))
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onActionClick(RoomDetailsAction.AddTopic)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else if (roomTopic is RoomTopicState.ExistingTopic) {
|
} else if (roomTopic is RoomTopicState.ExistingTopic) {
|
||||||
ClickableLinkText(
|
ClickableLinkText(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Det oppstod en feil under oppdatering av varslingsinnstillingen."</string>
|
||||||
|
<string name="screen_room_change_permissions_everyone">"Alle"</string>
|
||||||
<string name="screen_room_change_role_section_administrators">"Administratorer"</string>
|
<string name="screen_room_change_role_section_administrators">"Administratorer"</string>
|
||||||
<string name="screen_room_change_role_section_users">"Medlemmer"</string>
|
<string name="screen_room_change_role_section_users">"Medlemmer"</string>
|
||||||
<string name="screen_room_change_role_unsaved_changes_description">"Du har endringer som ikke er lagret."</string>
|
<string name="screen_room_change_role_unsaved_changes_description">"Du har endringer som ikke er lagret."</string>
|
||||||
|
|
@ -15,11 +17,15 @@
|
||||||
<string name="screen_room_details_edition_error_title">"Kan ikke oppdatere rommet"</string>
|
<string name="screen_room_details_edition_error_title">"Kan ikke oppdatere rommet"</string>
|
||||||
<string name="screen_room_details_encryption_enabled_subtitle">"Meldingene er krypterte. Det er bare du og mottakerne som har de unike nøklene til å låse dem opp."</string>
|
<string name="screen_room_details_encryption_enabled_subtitle">"Meldingene er krypterte. Det er bare du og mottakerne som har de unike nøklene til å låse dem opp."</string>
|
||||||
<string name="screen_room_details_encryption_enabled_title">"Meldingskryptering aktivert"</string>
|
<string name="screen_room_details_encryption_enabled_title">"Meldingskryptering aktivert"</string>
|
||||||
|
<string name="screen_room_details_error_loading_notification_settings">"Det oppstod en feil ved lasting av varslingsinnstillinger."</string>
|
||||||
|
<string name="screen_room_details_error_muting">"Mislyktes i å dempe dette rommet, prøv igjen."</string>
|
||||||
|
<string name="screen_room_details_error_unmuting">"Mislyktes i å oppheve dempingen av dette rommet, prøv igjen."</string>
|
||||||
<string name="screen_room_details_invite_people_title">"Inviter folk"</string>
|
<string name="screen_room_details_invite_people_title">"Inviter folk"</string>
|
||||||
<string name="screen_room_details_leave_conversation_title">"Forlat samtalen"</string>
|
<string name="screen_room_details_leave_conversation_title">"Forlat samtalen"</string>
|
||||||
<string name="screen_room_details_leave_room_title">"Forlat rommet"</string>
|
<string name="screen_room_details_leave_room_title">"Forlat rommet"</string>
|
||||||
<string name="screen_room_details_notification_mode_custom">"Tilpasset"</string>
|
<string name="screen_room_details_notification_mode_custom">"Tilpasset"</string>
|
||||||
<string name="screen_room_details_notification_mode_default">"Standard"</string>
|
<string name="screen_room_details_notification_mode_default">"Standard"</string>
|
||||||
|
<string name="screen_room_details_notification_title">"Varslinger"</string>
|
||||||
<string name="screen_room_details_room_name_label">"Romnavn"</string>
|
<string name="screen_room_details_room_name_label">"Romnavn"</string>
|
||||||
<string name="screen_room_details_security_title">"Sikkerhet"</string>
|
<string name="screen_room_details_security_title">"Sikkerhet"</string>
|
||||||
<string name="screen_room_details_share_room_title">"Del rom"</string>
|
<string name="screen_room_details_share_room_title">"Del rom"</string>
|
||||||
|
|
@ -59,6 +65,11 @@
|
||||||
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"globale innstillinger"</string>
|
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"globale innstillinger"</string>
|
||||||
<string name="screen_room_notification_settings_default_setting_title">"Standard innstilling"</string>
|
<string name="screen_room_notification_settings_default_setting_title">"Standard innstilling"</string>
|
||||||
<string name="screen_room_notification_settings_edit_remove_setting">"Fjern egendefinert innstilling"</string>
|
<string name="screen_room_notification_settings_edit_remove_setting">"Fjern egendefinert innstilling"</string>
|
||||||
|
<string name="screen_room_notification_settings_error_loading_settings">"Det oppstod en feil ved innlasting av varslingsinnstillinger."</string>
|
||||||
|
<string name="screen_room_notification_settings_error_restoring_default">"Gjenoppretting av standardmodus mislyktes, prøv igjen."</string>
|
||||||
|
<string name="screen_room_notification_settings_error_setting_mode">"Innstilling av modus mislyktes, prøv igjen."</string>
|
||||||
|
<string name="screen_room_notification_settings_mode_all_messages">"Alle meldinger"</string>
|
||||||
|
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Bare omtaler og nøkkelord"</string>
|
||||||
<string name="screen_room_notification_settings_room_custom_settings_title">"I dette rommet, varsle meg om"</string>
|
<string name="screen_room_notification_settings_room_custom_settings_title">"I dette rommet, varsle meg om"</string>
|
||||||
<string name="screen_room_roles_and_permissions_admins">"Administratorer"</string>
|
<string name="screen_room_roles_and_permissions_admins">"Administratorer"</string>
|
||||||
<string name="screen_room_roles_and_permissions_change_my_role">"Endre rollen min"</string>
|
<string name="screen_room_roles_and_permissions_change_my_role">"Endre rollen min"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_edit_room_address_room_address_section_footer">"Du behöver en rumsadress för att göra den synlig i katalogen."</string>
|
||||||
|
<string name="screen_edit_room_address_title">"Rumsadress"</string>
|
||||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Ett fel uppstod vid uppdatering av aviseringsinställningen."</string>
|
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Ett fel uppstod vid uppdatering av aviseringsinställningen."</string>
|
||||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Din hemserver stöder inte det här alternativet i krypterade rum, du kanske inte aviseras i vissa rum."</string>
|
<string name="screen_notification_settings_mentions_only_disclaimer">"Din hemserver stöder inte det här alternativet i krypterade rum, du kanske inte aviseras i vissa rum."</string>
|
||||||
<string name="screen_polls_history_title">"Omröstningar"</string>
|
<string name="screen_polls_history_title">"Omröstningar"</string>
|
||||||
|
|
@ -132,10 +134,18 @@ Vi rekommenderar inte att aktivera kryptering för rum som vem som helst kan hit
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Användare kan bara gå med om de är inbjudna"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Användare kan bara gå med om de är inbjudna"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Endast inbjudan"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Endast inbjudan"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_section_header">"Tillgång till rum"</string>
|
<string name="screen_security_and_privacy_room_access_section_header">"Tillgång till rum"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_space_members_option_description">"Utrymmen stöds för närvarande inte"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Utrymmesmedlemmar"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_address_section_header">"Rumsadress"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Tillåt att detta rum hittas genom att söka i den offentliga rumskatalogen på %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Vem som helst"</string>
|
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Vem som helst"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_section_header">"Vem kan läsa historik"</string>
|
<string name="screen_security_and_privacy_room_history_section_header">"Vem kan läsa historik"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Endast medlemmar sedan de bjöds in"</string>
|
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Endast medlemmar sedan de bjöds in"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Endast medlemmar sedan det här alternativet har valts"</string>
|
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Endast medlemmar sedan det här alternativet har valts"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_publishing_section_footer">"Rumsadresser är sätt att hitta och komma åt rum. Detta säkerställer också att du enkelt kan dela ditt rum med andra.
|
||||||
|
Du kan välja att publicera ditt rum i din hemservers offentliga rumskatalog."</string>
|
||||||
|
<string name="screen_security_and_privacy_room_visibility_section_footer">"Rumsadresser är sätt att hitta och komma åt rum. Detta säkerställer också att du enkelt kan dela ditt rum med andra.
|
||||||
|
Adressen krävs också för att rummet ska synas i den allmänna rumskatalogen på %1$s."</string>
|
||||||
<string name="screen_security_and_privacy_room_visibility_section_header">"Rumssynlighet"</string>
|
<string name="screen_security_and_privacy_room_visibility_section_header">"Rumssynlighet"</string>
|
||||||
<string name="screen_security_and_privacy_title">"Säkerhet och sekretess"</string>
|
<string name="screen_security_and_privacy_title">"Säkerhet och sekretess"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ interface RoomListEntryPoint : FeatureEntryPoint {
|
||||||
fun onSessionConfirmRecoveryKeyClick()
|
fun onSessionConfirmRecoveryKeyClick()
|
||||||
fun onRoomSettingsClick(roomId: RoomId)
|
fun onRoomSettingsClick(roomId: RoomId)
|
||||||
fun onReportBugClick()
|
fun onReportBugClick()
|
||||||
fun onRoomDirectorySearchClick()
|
|
||||||
fun onLogoutForNativeSlidingSyncMigrationNeeded()
|
fun onLogoutForNativeSlidingSyncMigrationNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,10 +82,6 @@ class RoomListNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRoomDirectorySearchClick() {
|
|
||||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomDirectorySearchClick() }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
|
|
@ -100,7 +96,6 @@ class RoomListNode @AssistedInject constructor(
|
||||||
onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick,
|
onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick,
|
||||||
onRoomSettingsClick = this::onRoomSettingsClick,
|
onRoomSettingsClick = this::onRoomSettingsClick,
|
||||||
onMenuActionClick = { onMenuActionClick(activity, it) },
|
onMenuActionClick = { onMenuActionClick(activity, it) },
|
||||||
onRoomDirectorySearchClick = this::onRoomDirectorySearchClick,
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
acceptDeclineInviteView.Render(
|
acceptDeclineInviteView.Render(
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||||
import io.element.android.libraries.matrix.api.sync.isOnline
|
import io.element.android.libraries.matrix.api.sync.isOnline
|
||||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||||
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|
@ -89,6 +90,7 @@ class RoomListPresenter @Inject constructor(
|
||||||
private val fullScreenIntentPermissionsPresenter: Presenter<FullScreenIntentPermissionsState>,
|
private val fullScreenIntentPermissionsPresenter: Presenter<FullScreenIntentPermissionsState>,
|
||||||
private val notificationCleaner: NotificationCleaner,
|
private val notificationCleaner: NotificationCleaner,
|
||||||
private val logoutPresenter: Presenter<DirectLogoutState>,
|
private val logoutPresenter: Presenter<DirectLogoutState>,
|
||||||
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
private val encryptionService: EncryptionService = client.encryptionService()
|
private val encryptionService: EncryptionService = client.encryptionService()
|
||||||
|
|
||||||
|
|
@ -245,7 +247,8 @@ class RoomListPresenter @Inject constructor(
|
||||||
isFavorite = event.roomListRoomSummary.isFavorite,
|
isFavorite = event.roomListRoomSummary.isFavorite,
|
||||||
markAsUnreadFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.MarkAsUnread),
|
markAsUnreadFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.MarkAsUnread),
|
||||||
hasNewContent = event.roomListRoomSummary.hasNewContent,
|
hasNewContent = event.roomListRoomSummary.hasNewContent,
|
||||||
eventCacheFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.EventCache),
|
eventCacheFeatureFlagEnabled = appPreferencesStore.isDeveloperModeEnabledFlow().first() &&
|
||||||
|
featureFlagService.isFeatureEnabled(FeatureFlags.EventCache),
|
||||||
)
|
)
|
||||||
contextMenuState.value = initialState
|
contextMenuState.value = initialState
|
||||||
|
|
||||||
|
|
@ -329,9 +332,12 @@ class RoomListPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
internal fun RoomListRoomSummary.toInviteData() = InviteData(
|
internal fun RoomListRoomSummary.toInviteData(): InviteData? {
|
||||||
roomId = roomId,
|
if (inviteSender == null) return null
|
||||||
// Note: `name` should not be null at this point, but just in case, fallback to the roomId
|
return InviteData(
|
||||||
roomName = name ?: roomId.value,
|
roomId = roomId,
|
||||||
isDm = isDm,
|
roomName = name ?: roomId.value,
|
||||||
)
|
isDm = isDm,
|
||||||
|
senderId = inviteSender.userId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ fun RoomListView(
|
||||||
onCreateRoomClick: () -> Unit,
|
onCreateRoomClick: () -> Unit,
|
||||||
onRoomSettingsClick: (roomId: RoomId) -> Unit,
|
onRoomSettingsClick: (roomId: RoomId) -> Unit,
|
||||||
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
||||||
onRoomDirectorySearchClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
acceptDeclineInviteView: @Composable () -> Unit,
|
acceptDeclineInviteView: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -83,7 +82,6 @@ fun RoomListView(
|
||||||
state = state.searchState,
|
state = state.searchState,
|
||||||
eventSink = state.eventSink,
|
eventSink = state.eventSink,
|
||||||
onRoomClick = onRoomClick,
|
onRoomClick = onRoomClick,
|
||||||
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.padding(top = topPadding)
|
.padding(top = topPadding)
|
||||||
|
|
@ -177,7 +175,6 @@ internal fun RoomListViewPreview(@PreviewParameter(RoomListStateProvider::class)
|
||||||
onCreateRoomClick = {},
|
onCreateRoomClick = {},
|
||||||
onRoomSettingsClick = {},
|
onRoomSettingsClick = {},
|
||||||
onMenuActionClick = {},
|
onMenuActionClick = {},
|
||||||
onRoomDirectorySearchClick = {},
|
|
||||||
acceptDeclineInviteView = {},
|
acceptDeclineInviteView = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,11 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomListSearchPresenter @Inject constructor(
|
class RoomListSearchPresenter @Inject constructor(
|
||||||
private val dataSource: RoomListSearchDataSource,
|
private val dataSource: RoomListSearchDataSource,
|
||||||
private val featureFlagService: FeatureFlagService,
|
|
||||||
) : Presenter<RoomListSearchState> {
|
) : Presenter<RoomListSearchState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomListSearchState {
|
override fun present(): RoomListSearchState {
|
||||||
|
|
@ -57,14 +54,12 @@ class RoomListSearchPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isRoomDirectorySearchEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch).collectAsState(initial = false)
|
|
||||||
val searchResults by dataSource.roomSummaries.collectAsState(initial = persistentListOf())
|
val searchResults by dataSource.roomSummaries.collectAsState(initial = persistentListOf())
|
||||||
|
|
||||||
return RoomListSearchState(
|
return RoomListSearchState(
|
||||||
isSearchActive = isSearchActive,
|
isSearchActive = isSearchActive,
|
||||||
query = searchQuery,
|
query = searchQuery,
|
||||||
results = searchResults,
|
results = searchResults,
|
||||||
isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled,
|
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,5 @@ data class RoomListSearchState(
|
||||||
val isSearchActive: Boolean,
|
val isSearchActive: Boolean,
|
||||||
val query: String,
|
val query: String,
|
||||||
val results: ImmutableList<RoomListRoomSummary>,
|
val results: ImmutableList<RoomListRoomSummary>,
|
||||||
val isRoomDirectorySearchEnabled: Boolean,
|
|
||||||
val eventSink: (RoomListSearchEvents) -> Unit
|
val eventSink: (RoomListSearchEvents) -> Unit
|
||||||
) {
|
)
|
||||||
val displayRoomDirectorySearch = query.isEmpty() && isRoomDirectorySearchEnabled
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ class RoomListSearchStateProvider : PreviewParameterProvider<RoomListSearchState
|
||||||
override val values: Sequence<RoomListSearchState>
|
override val values: Sequence<RoomListSearchState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomListSearchState(),
|
aRoomListSearchState(),
|
||||||
aRoomListSearchState(isRoomDirectorySearchEnabled = true),
|
|
||||||
aRoomListSearchState(
|
aRoomListSearchState(
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
query = "Test",
|
query = "Test",
|
||||||
|
|
@ -30,12 +29,10 @@ fun aRoomListSearchState(
|
||||||
isSearchActive: Boolean = false,
|
isSearchActive: Boolean = false,
|
||||||
query: String = "",
|
query: String = "",
|
||||||
results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
|
results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
|
||||||
isRoomDirectorySearchEnabled: Boolean = false,
|
|
||||||
eventSink: (RoomListSearchEvents) -> Unit = { },
|
eventSink: (RoomListSearchEvents) -> Unit = { },
|
||||||
) = RoomListSearchState(
|
) = RoomListSearchState(
|
||||||
isSearchActive = isSearchActive,
|
isSearchActive = isSearchActive,
|
||||||
query = query,
|
query = query,
|
||||||
results = results,
|
results = results,
|
||||||
isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled,
|
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,9 @@ import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
|
@ -26,7 +23,6 @@ import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
|
@ -38,22 +34,18 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.roomlist.impl.R
|
|
||||||
import io.element.android.features.roomlist.impl.RoomListEvents
|
import io.element.android.features.roomlist.impl.RoomListEvents
|
||||||
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
||||||
import io.element.android.features.roomlist.impl.contentType
|
import io.element.android.features.roomlist.impl.contentType
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.button.SuperButton
|
|
||||||
import io.element.android.libraries.designsystem.modifiers.applyIf
|
import io.element.android.libraries.designsystem.modifiers.applyIf
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.FilledTextField
|
import io.element.android.libraries.designsystem.theme.components.FilledTextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.utils.copy
|
import io.element.android.libraries.designsystem.utils.copy
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -64,7 +56,6 @@ internal fun RoomListSearchView(
|
||||||
state: RoomListSearchState,
|
state: RoomListSearchState,
|
||||||
eventSink: (RoomListEvents) -> Unit,
|
eventSink: (RoomListEvents) -> Unit,
|
||||||
onRoomClick: (RoomId) -> Unit,
|
onRoomClick: (RoomId) -> Unit,
|
||||||
onRoomDirectorySearchClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BackHandler(enabled = state.isSearchActive) {
|
BackHandler(enabled = state.isSearchActive) {
|
||||||
|
|
@ -91,7 +82,6 @@ internal fun RoomListSearchView(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomClick = onRoomClick,
|
onRoomClick = onRoomClick,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +94,6 @@ private fun RoomListSearchContent(
|
||||||
state: RoomListSearchState,
|
state: RoomListSearchState,
|
||||||
eventSink: (RoomListEvents) -> Unit,
|
eventSink: (RoomListEvents) -> Unit,
|
||||||
onRoomClick: (RoomId) -> Unit,
|
onRoomClick: (RoomId) -> Unit,
|
||||||
onRoomDirectorySearchClick: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val borderColor = MaterialTheme.colorScheme.tertiary
|
val borderColor = MaterialTheme.colorScheme.tertiary
|
||||||
val strokeWidth = 1.dp
|
val strokeWidth = 1.dp
|
||||||
|
|
@ -175,14 +164,6 @@ private fun RoomListSearchContent(
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.consumeWindowInsets(padding)
|
.consumeWindowInsets(padding)
|
||||||
) {
|
) {
|
||||||
if (state.displayRoomDirectorySearch) {
|
|
||||||
RoomDirectorySearchButton(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 24.dp, horizontal = 16.dp),
|
|
||||||
onClick = onRoomDirectorySearchClick
|
|
||||||
)
|
|
||||||
}
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
) {
|
) {
|
||||||
|
|
@ -201,31 +182,6 @@ private fun RoomListSearchContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RoomDirectorySearchButton(
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
SuperButton(
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = modifier,
|
|
||||||
buttonSize = ButtonSize.Large,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = CompoundIcons.ListBulleted(),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.screen_roomlist_room_directory_button_title),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomListSearchContentPreview(@PreviewParameter(RoomListSearchStateProvider::class) state: RoomListSearchState) = ElementPreview {
|
internal fun RoomListSearchContentPreview(@PreviewParameter(RoomListSearchStateProvider::class) state: RoomListSearchState) = ElementPreview {
|
||||||
|
|
@ -233,6 +189,5 @@ internal fun RoomListSearchContentPreview(@PreviewParameter(RoomListSearchStateP
|
||||||
state = state,
|
state = state,
|
||||||
onRoomClick = {},
|
onRoomClick = {},
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
onRoomDirectorySearchClick = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
<string name="screen_invites_decline_direct_chat_title">"Avslå chat"</string>
|
<string name="screen_invites_decline_direct_chat_title">"Avslå chat"</string>
|
||||||
<string name="screen_invites_empty_list">"Ingen invitasjoner"</string>
|
<string name="screen_invites_empty_list">"Ingen invitasjoner"</string>
|
||||||
<string name="screen_invites_invited_you">"%1$s(%2$s) inviterte deg"</string>
|
<string name="screen_invites_invited_you">"%1$s(%2$s) inviterte deg"</string>
|
||||||
|
<string name="screen_migration_message">"Dette er en engangsprosess, takk for at du venter."</string>
|
||||||
|
<string name="screen_migration_title">"Setter opp kontoen din."</string>
|
||||||
<string name="screen_roomlist_a11y_create_message">"Opprett en ny samtale eller et nytt rom"</string>
|
<string name="screen_roomlist_a11y_create_message">"Opprett en ny samtale eller et nytt rom"</string>
|
||||||
<string name="screen_roomlist_empty_message">"Kom i gang med å sende meldinger til noen."</string>
|
<string name="screen_roomlist_empty_message">"Kom i gang med å sende meldinger til noen."</string>
|
||||||
<string name="screen_roomlist_empty_title">"Ingen chatter ennå."</string>
|
<string name="screen_roomlist_empty_title">"Ingen chatter ennå."</string>
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,9 @@ import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||||
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||||
|
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||||
import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner
|
import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner
|
||||||
|
|
@ -312,6 +314,35 @@ class RoomListPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - show context menu with view source on`() = runTest {
|
||||||
|
val presenter = createRoomListPresenter(
|
||||||
|
appPreferencesStore = InMemoryAppPreferencesStore(
|
||||||
|
isDeveloperModeEnabled = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val summary = createRoomListRoomSummary()
|
||||||
|
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.contextMenu)
|
||||||
|
.isEqualTo(
|
||||||
|
RoomListState.ContextMenu.Shown(
|
||||||
|
roomId = summary.roomId,
|
||||||
|
roomName = summary.name,
|
||||||
|
isDm = false,
|
||||||
|
isFavorite = false,
|
||||||
|
markAsUnreadFeatureFlagEnabled = true,
|
||||||
|
// true here.
|
||||||
|
eventCacheFeatureFlagEnabled = true,
|
||||||
|
hasNewContent = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - hide context menu`() = runTest {
|
fun `present - hide context menu`() = runTest {
|
||||||
val room = FakeMatrixRoom()
|
val room = FakeMatrixRoom()
|
||||||
|
|
@ -643,6 +674,7 @@ class RoomListPresenterTest {
|
||||||
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
||||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
|
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
|
||||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||||
|
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||||
) = RoomListPresenter(
|
) = RoomListPresenter(
|
||||||
client = client,
|
client = client,
|
||||||
syncService = syncService,
|
syncService = syncService,
|
||||||
|
|
@ -672,6 +704,7 @@ class RoomListPresenterTest {
|
||||||
fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() },
|
fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() },
|
||||||
notificationCleaner = notificationCleaner,
|
notificationCleaner = notificationCleaner,
|
||||||
logoutPresenter = { aDirectLogoutState() },
|
logoutPresenter = { aDirectLogoutState() },
|
||||||
|
appPreferencesStore = appPreferencesStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
|
||||||
onCreateRoomClick: () -> Unit = EnsureNeverCalled(),
|
onCreateRoomClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onMenuActionClick: (RoomListMenuAction) -> Unit = EnsureNeverCalledWithParam(),
|
onMenuActionClick: (RoomListMenuAction) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onRoomDirectorySearchClick: () -> Unit = EnsureNeverCalled(),
|
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
RoomListView(
|
RoomListView(
|
||||||
|
|
@ -252,7 +251,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
|
||||||
onCreateRoomClick = onCreateRoomClick,
|
onCreateRoomClick = onCreateRoomClick,
|
||||||
onRoomSettingsClick = onRoomSettingsClick,
|
onRoomSettingsClick = onRoomSettingsClick,
|
||||||
onMenuActionClick = onMenuActionClick,
|
onMenuActionClick = onMenuActionClick,
|
||||||
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
|
|
||||||
acceptDeclineInviteView = { },
|
acceptDeclineInviteView = { },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory
|
import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
|
|
@ -118,26 +115,10 @@ class RoomListSearchPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - room directory search`() = runTest {
|
|
||||||
val featureFlagService = FakeFeatureFlagService()
|
|
||||||
featureFlagService.setFeatureEnabled(FeatureFlags.RoomDirectorySearch, true)
|
|
||||||
val presenter = createRoomListSearchPresenter(featureFlagService = featureFlagService)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
|
||||||
awaitItem().let { state ->
|
|
||||||
assertThat(state.isRoomDirectorySearchEnabled).isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TestScope.createRoomListSearchPresenter(
|
fun TestScope.createRoomListSearchPresenter(
|
||||||
roomListService: RoomListService = FakeRoomListService(),
|
roomListService: RoomListService = FakeRoomListService(),
|
||||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
|
||||||
): RoomListSearchPresenter {
|
): RoomListSearchPresenter {
|
||||||
return RoomListSearchPresenter(
|
return RoomListSearchPresenter(
|
||||||
dataSource = RoomListSearchDataSource(
|
dataSource = RoomListSearchDataSource(
|
||||||
|
|
@ -148,6 +129,5 @@ fun TestScope.createRoomListSearchPresenter(
|
||||||
),
|
),
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
),
|
),
|
||||||
featureFlagService = featureFlagService,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.roomlist.impl.search
|
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import io.element.android.features.roomlist.impl.R
|
|
||||||
import io.element.android.features.roomlist.impl.RoomListEvents
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
|
||||||
import io.element.android.tests.testutils.clickOn
|
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class RoomListSearchViewTest {
|
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `clicking on 'Browse all rooms' invokes the expected callback`() {
|
|
||||||
val eventsRecorder = EventsRecorder<RoomListSearchEvents>(expectEvents = false)
|
|
||||||
ensureCalledOnce {
|
|
||||||
rule.setRoomListSearchView(
|
|
||||||
aRoomListSearchState(
|
|
||||||
isSearchActive = true,
|
|
||||||
isRoomDirectorySearchEnabled = true,
|
|
||||||
eventSink = eventsRecorder,
|
|
||||||
),
|
|
||||||
onRoomDirectorySearchClick = it,
|
|
||||||
)
|
|
||||||
rule.clickOn(R.string.screen_roomlist_room_directory_button_title)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomListSearchView(
|
|
||||||
state: RoomListSearchState,
|
|
||||||
eventSink: (RoomListEvents) -> Unit = EventsRecorder(expectEvents = false),
|
|
||||||
onRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
|
||||||
onRoomDirectorySearchClick: () -> Unit = EnsureNeverCalled(),
|
|
||||||
) {
|
|
||||||
setContent {
|
|
||||||
RoomListSearchView(
|
|
||||||
state = state,
|
|
||||||
eventSink = eventSink,
|
|
||||||
onRoomClick = onRoomClick,
|
|
||||||
onRoomDirectorySearchClick = onRoomDirectorySearchClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_chat_backup_key_backup_action_enable">"Slå på sikkerhetskopiering"</string>
|
||||||
|
<string name="screen_chat_backup_key_backup_description">"Lagre din kryptografiske identitet og meldingsnøkler sikkert på serveren. Dette gjør at du kan se meldingshistorikken din på alle nye enheter. %1$s."</string>
|
||||||
|
<string name="screen_chat_backup_recovery_action_change">"Endre gjenopprettingsnøkkel"</string>
|
||||||
<string name="screen_chat_backup_recovery_action_confirm">"Skriv inn gjenopprettingsnøkkel"</string>
|
<string name="screen_chat_backup_recovery_action_confirm">"Skriv inn gjenopprettingsnøkkel"</string>
|
||||||
|
<string name="screen_chat_backup_recovery_action_setup_description">"Få tilgang til de krypterte meldingene dine hvis du mister alle enhetene dine eller blir logget ut av %1$s overalt."</string>
|
||||||
<string name="screen_encryption_reset_action_continue_reset">"Fortsett tilbakestillingen"</string>
|
<string name="screen_encryption_reset_action_continue_reset">"Fortsett tilbakestillingen"</string>
|
||||||
<string name="screen_encryption_reset_bullet_1">"Dine kontodetaljer, kontakter, innstillinger og chatteliste vil bli beholdt"</string>
|
<string name="screen_encryption_reset_bullet_1">"Dine kontodetaljer, kontakter, innstillinger og chatteliste vil bli beholdt"</string>
|
||||||
<string name="screen_encryption_reset_bullet_2">"Du mister all meldingshistorikk som bare er lagret på serveren"</string>
|
<string name="screen_encryption_reset_bullet_2">"Du mister all meldingshistorikk som bare er lagret på serveren"</string>
|
||||||
|
|
@ -8,5 +12,6 @@
|
||||||
<string name="screen_encryption_reset_footer">"Tilbakestill identiteten din bare hvis du ikke har tilgang til en annen pålogget enhet og du har mistet gjenopprettingsnøkkelen."</string>
|
<string name="screen_encryption_reset_footer">"Tilbakestill identiteten din bare hvis du ikke har tilgang til en annen pålogget enhet og du har mistet gjenopprettingsnøkkelen."</string>
|
||||||
<string name="screen_encryption_reset_title">"Kan du ikke bekrefte? Du må tilbakestille identiteten din."</string>
|
<string name="screen_encryption_reset_title">"Kan du ikke bekrefte? Du må tilbakestille identiteten din."</string>
|
||||||
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Slå av"</string>
|
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Slå av"</string>
|
||||||
|
<string name="screen_key_backup_disable_description_point_1">"Du vil ikke ha kryptert meldingshistorikk på nye enheter"</string>
|
||||||
<string name="screen_recovery_key_confirm_title">"Skriv inn gjenopprettingsnøkkelen din"</string>
|
<string name="screen_recovery_key_confirm_title">"Skriv inn gjenopprettingsnøkkelen din"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_signed_out_reason_1">"Du har endret passordet ditt i en annen sesjon"</string>
|
||||||
|
<string name="screen_signed_out_reason_2">"Du har slettet sesjonen fra en annen sesjon"</string>
|
||||||
|
<string name="screen_signed_out_reason_3">"Serveradministratoren har ugyldiggjort tilgangen din"</string>
|
||||||
|
<string name="screen_signed_out_subtitle">"Du kan ha blitt logget ut av en av årsakene som er oppført nedenfor. Logg på igjen for å fortsette å bruke %s."</string>
|
||||||
<string name="screen_signed_out_title">"Du er logget ut"</string>
|
<string name="screen_signed_out_title">"Du er logget ut"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,11 @@ import io.element.android.tests.testutils.lambda.any
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -169,7 +173,8 @@ class UserProfilePresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest {
|
fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest {
|
||||||
val client = createFakeMatrixClient()
|
val ignoredUsersFlow = MutableStateFlow(persistentListOf<UserId>())
|
||||||
|
val client = createFakeMatrixClient(ignoredUsersFlow = ignoredUsersFlow)
|
||||||
val presenter = createUserProfilePresenter(
|
val presenter = createUserProfilePresenter(
|
||||||
client = client,
|
client = client,
|
||||||
userId = A_USER_ID
|
userId = A_USER_ID
|
||||||
|
|
@ -178,20 +183,21 @@ class UserProfilePresenterTest {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
|
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
|
||||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||||
client.emitIgnoreUserList(listOf(A_USER_ID))
|
ignoredUsersFlow.emit(persistentListOf(A_USER_ID))
|
||||||
assertThat(awaitItem().isBlocked.dataOrNull()).isTrue()
|
assertThat(awaitItem().isBlocked.dataOrNull()).isTrue()
|
||||||
|
|
||||||
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
|
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
|
||||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||||
client.emitIgnoreUserList(listOf())
|
ignoredUsersFlow.emit(persistentListOf())
|
||||||
assertThat(awaitItem().isBlocked.dataOrNull()).isFalse()
|
assertThat(awaitItem().isBlocked.dataOrNull()).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - BlockUser with error`() = runTest {
|
fun `present - BlockUser with error`() = runTest {
|
||||||
val matrixClient = createFakeMatrixClient()
|
val matrixClient = createFakeMatrixClient(
|
||||||
matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE))
|
ignoreUserResult = { Result.failure(A_THROWABLE) }
|
||||||
|
)
|
||||||
val presenter = createUserProfilePresenter(client = matrixClient)
|
val presenter = createUserProfilePresenter(client = matrixClient)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
|
|
@ -207,8 +213,9 @@ class UserProfilePresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - UnblockUser with error`() = runTest {
|
fun `present - UnblockUser with error`() = runTest {
|
||||||
val matrixClient = createFakeMatrixClient()
|
val matrixClient = createFakeMatrixClient(
|
||||||
matrixClient.givenUnignoreUserResult(Result.failure(A_THROWABLE))
|
unIgnoreUserResult = { Result.failure(A_THROWABLE) }
|
||||||
|
)
|
||||||
val presenter = createUserProfilePresenter(client = matrixClient)
|
val presenter = createUserProfilePresenter(client = matrixClient)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
|
|
@ -374,10 +381,16 @@ class UserProfilePresenterTest {
|
||||||
|
|
||||||
private fun createFakeMatrixClient(
|
private fun createFakeMatrixClient(
|
||||||
isUserVerified: Boolean = false,
|
isUserVerified: Boolean = false,
|
||||||
|
ignoreUserResult: (UserId) -> Result<Unit> = { Result.success(Unit) },
|
||||||
|
unIgnoreUserResult: (UserId) -> Result<Unit> = { Result.success(Unit) },
|
||||||
|
ignoredUsersFlow: StateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf())
|
||||||
) = FakeMatrixClient(
|
) = FakeMatrixClient(
|
||||||
encryptionService = FakeEncryptionService(
|
encryptionService = FakeEncryptionService(
|
||||||
isUserVerifiedResult = { Result.success(isUserVerified) }
|
isUserVerifiedResult = { Result.success(isUserVerified) }
|
||||||
),
|
),
|
||||||
|
ignoreUserResult = ignoreUserResult,
|
||||||
|
unIgnoreUserResult = unIgnoreUserResult,
|
||||||
|
ignoredUsersFlow = ignoredUsersFlow
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createUserProfilePresenter(
|
private fun createUserProfilePresenter(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
android_gradle_plugin = "8.8.1"
|
android_gradle_plugin = "8.8.1"
|
||||||
kotlin = "2.1.10"
|
kotlin = "2.1.10"
|
||||||
kotlinpoet = "2.1.0"
|
kotlinpoet = "2.1.0"
|
||||||
ksp = "2.1.10-1.0.30"
|
ksp = "2.1.10-1.0.31"
|
||||||
firebaseAppDistribution = "5.1.1"
|
firebaseAppDistribution = "5.1.1"
|
||||||
|
|
||||||
# AndroidX
|
# AndroidX
|
||||||
|
|
@ -42,7 +42,7 @@ datetime = "0.6.2"
|
||||||
serialization_json = "1.8.0"
|
serialization_json = "1.8.0"
|
||||||
|
|
||||||
#other
|
#other
|
||||||
coil = "2.7.0"
|
coil = "3.1.0"
|
||||||
showkase = "1.0.3"
|
showkase = "1.0.3"
|
||||||
appyx = "1.6.0"
|
appyx = "1.6.0"
|
||||||
sqldelight = "2.0.2"
|
sqldelight = "2.0.2"
|
||||||
|
|
@ -77,7 +77,7 @@ kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", ve
|
||||||
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
||||||
gms_google_services = "com.google.gms:google-services:4.4.2"
|
gms_google_services = "com.google.gms:google-services:4.4.2"
|
||||||
# https://firebase.google.com/docs/android/setup#available-libraries
|
# https://firebase.google.com/docs/android/setup#available-libraries
|
||||||
google_firebase_bom = "com.google.firebase:firebase-bom:33.9.0"
|
google_firebase_bom = "com.google.firebase:firebase-bom:33.10.0"
|
||||||
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
||||||
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
||||||
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||||
|
|
@ -149,7 +149,7 @@ test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" }
|
||||||
test_arch_core = "androidx.arch.core:core-testing:2.2.0"
|
test_arch_core = "androidx.arch.core:core-testing:2.2.0"
|
||||||
test_junit = "junit:junit:4.13.2"
|
test_junit = "junit:junit:4.13.2"
|
||||||
test_runner = "androidx.test:runner:1.6.2"
|
test_runner = "androidx.test:runner:1.6.2"
|
||||||
test_mockk = "io.mockk:mockk:1.13.16"
|
test_mockk = "io.mockk:mockk:1.13.17"
|
||||||
test_konsist = "com.lemonappdev:konsist:0.17.3"
|
test_konsist = "com.lemonappdev:konsist:0.17.3"
|
||||||
test_turbine = "app.cash.turbine:turbine:1.2.0"
|
test_turbine = "app.cash.turbine:turbine:1.2.0"
|
||||||
test_truth = "com.google.truth:truth:1.4.4"
|
test_truth = "com.google.truth:truth:1.4.4"
|
||||||
|
|
@ -159,21 +159,22 @@ test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "
|
||||||
test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.5.1"
|
test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.5.1"
|
||||||
|
|
||||||
# Others
|
# Others
|
||||||
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
|
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
||||||
coil_compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
coil_network_okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
|
||||||
coil_gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" }
|
coil_compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||||
coil_test = { module = "io.coil-kt:coil-test", version.ref = "coil" }
|
coil_gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" }
|
||||||
|
coil_test = { module = "io.coil-kt.coil3:coil-test", version.ref = "coil" }
|
||||||
compound = { module = "io.element.android:compound-android", version = "25.2.26" }
|
compound = { module = "io.element.android:compound-android", version = "25.2.26" }
|
||||||
datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" }
|
datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" }
|
||||||
serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_json" }
|
serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_json" }
|
||||||
kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8"
|
kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8"
|
||||||
showkase = { module = "com.airbnb.android:showkase", version.ref = "showkase" }
|
showkase = { module = "com.airbnb.android:showkase", version.ref = "showkase" }
|
||||||
showkase_processor = { module = "com.airbnb.android:showkase-processor", version.ref = "showkase" }
|
showkase_processor = { module = "com.airbnb.android:showkase-processor", version.ref = "showkase" }
|
||||||
jsoup = "org.jsoup:jsoup:1.18.3"
|
jsoup = "org.jsoup:jsoup:1.19.1"
|
||||||
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
||||||
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
|
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
|
||||||
timber = "com.jakewharton.timber:timber:5.0.1"
|
timber = "com.jakewharton.timber:timber:5.0.1"
|
||||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.2.26"
|
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.3.6"
|
||||||
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
||||||
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
|
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
|
||||||
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||||
|
|
@ -187,7 +188,7 @@ vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0"
|
||||||
telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" }
|
telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" }
|
||||||
telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" }
|
telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" }
|
||||||
statemachine = "com.freeletics.flowredux:compose:1.2.2"
|
statemachine = "com.freeletics.flowredux:compose:1.2.2"
|
||||||
maplibre = "org.maplibre.gl:android-sdk:11.8.1"
|
maplibre = "org.maplibre.gl:android-sdk:11.8.2"
|
||||||
maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2"
|
maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2"
|
||||||
maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2"
|
maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2"
|
||||||
opusencoder = "io.element.android:opusencoder:1.1.0"
|
opusencoder = "io.element.android:opusencoder:1.1.0"
|
||||||
|
|
@ -233,7 +234,7 @@ kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
||||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||||
anvil = { id = "dev.zacsweers.anvil", version.ref = "anvil" }
|
anvil = { id = "dev.zacsweers.anvil", version.ref = "anvil" }
|
||||||
detekt = "io.gitlab.arturbosch.detekt:1.23.8"
|
detekt = "io.gitlab.arturbosch.detekt:1.23.8"
|
||||||
ktlint = "org.jlleitschuh.gradle.ktlint:12.1.2"
|
ktlint = "org.jlleitschuh.gradle.ktlint:12.2.0"
|
||||||
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
|
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
|
||||||
dependencycheck = "org.owasp.dependencycheck:12.1.0"
|
dependencycheck = "org.owasp.dependencycheck:12.1.0"
|
||||||
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
|
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,21 @@ fun Boolean.toSecondaryEnabledColor(): Color {
|
||||||
ElementTheme.colors.textDisabled
|
ElementTheme.colors.textDisabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Boolean.toIconEnabledColor(): Color {
|
||||||
|
return if (this) {
|
||||||
|
ElementTheme.colors.iconPrimary
|
||||||
|
} else {
|
||||||
|
ElementTheme.colors.iconDisabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Boolean.toIconSecondaryEnabledColor(): Color {
|
||||||
|
return if (this) {
|
||||||
|
ElementTheme.colors.iconSecondary
|
||||||
|
} else {
|
||||||
|
ElementTheme.colors.iconDisabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ package io.element.android.libraries.designsystem.components
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.BitmapDrawable
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import androidx.annotation.FloatRange
|
import androidx.annotation.FloatRange
|
||||||
|
|
@ -85,9 +84,10 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.isSpecified
|
import androidx.compose.ui.unit.isSpecified
|
||||||
import androidx.compose.ui.unit.toOffset
|
import androidx.compose.ui.unit.toOffset
|
||||||
import androidx.compose.ui.unit.toSize
|
import androidx.compose.ui.unit.toSize
|
||||||
import coil.imageLoader
|
import coil3.SingletonImageLoader
|
||||||
import coil.request.DefaultRequestOptions
|
import coil3.request.ImageRequest
|
||||||
import coil.request.ImageRequest
|
import coil3.request.allowHardware
|
||||||
|
import coil3.toBitmap
|
||||||
import com.airbnb.android.showkase.annotation.ShowkaseComposable
|
import com.airbnb.android.showkase.annotation.ShowkaseComposable
|
||||||
import com.vanniktech.blurhash.BlurHash
|
import com.vanniktech.blurhash.BlurHash
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
|
@ -328,7 +328,7 @@ fun Modifier.avatarBloom(
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
.data(avatarData)
|
.data(avatarData)
|
||||||
// Allow cache and default dispatchers
|
// Allow cache and default dispatchers
|
||||||
.defaults(DefaultRequestOptions())
|
.defaults(ImageRequest.Defaults())
|
||||||
// Needed to be able to read pixels from the Bitmap for the hash
|
// Needed to be able to read pixels from the Bitmap for the hash
|
||||||
.allowHardware(false)
|
.allowHardware(false)
|
||||||
// Reduce size so it loads faster for large avatars
|
// Reduce size so it loads faster for large avatars
|
||||||
|
|
@ -340,9 +340,11 @@ fun Modifier.avatarBloom(
|
||||||
var blurHash by rememberSaveable(avatarData) { mutableStateOf<String?>(null) }
|
var blurHash by rememberSaveable(avatarData) { mutableStateOf<String?>(null) }
|
||||||
LaunchedEffect(avatarData) {
|
LaunchedEffect(avatarData) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val drawable =
|
val bitmap = SingletonImageLoader.get(context)
|
||||||
context.imageLoader.execute(painterRequest).drawable ?: return@withContext
|
.execute(painterRequest)
|
||||||
val bitmap = (drawable as? BitmapDrawable)?.bitmap ?: return@withContext
|
.image
|
||||||
|
?.toBitmap()
|
||||||
|
?: return@withContext
|
||||||
blurHash = BlurHash.encode(
|
blurHash = BlurHash.encode(
|
||||||
bitmap = bitmap,
|
bitmap = bitmap,
|
||||||
componentX = BloomDefaults.HASH_COMPONENTS,
|
componentX = BloomDefaults.HASH_COMPONENTS,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
|
@ -26,10 +28,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil3.compose.AsyncImagePainter
|
||||||
import coil.compose.SubcomposeAsyncImage
|
import coil3.compose.SubcomposeAsyncImage
|
||||||
import coil.compose.SubcomposeAsyncImageContent
|
import coil3.compose.SubcomposeAsyncImageContent
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
|
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
|
||||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||||
|
|
@ -89,7 +91,8 @@ private fun ImageAvatar(
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
when (val state = painter.state) {
|
val collectedState by painter.state.collectAsState()
|
||||||
|
when (val state = collectedState) {
|
||||||
is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent()
|
is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent()
|
||||||
is AsyncImagePainter.State.Error -> {
|
is AsyncImagePainter.State.Error -> {
|
||||||
SideEffect {
|
SideEffect {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BlurHashAsyncImage(
|
fun BlurHashAsyncImage(
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,6 @@ internal fun PreferenceCategoryPreview() = ElementThemedPreview {
|
||||||
PreferenceCategory(
|
PreferenceCategory(
|
||||||
title = "Category title",
|
title = "Category title",
|
||||||
) {
|
) {
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
)
|
|
||||||
PreferenceSwitch(
|
PreferenceSwitch(
|
||||||
title = "Switch",
|
title = "Switch",
|
||||||
icon = CompoundIcons.Threads(),
|
icon = CompoundIcons.Threads(),
|
||||||
|
|
|
||||||
|
|
@ -97,11 +97,6 @@ internal fun PreferencePagePreview() = ElementPreview {
|
||||||
PreferenceCategory(
|
PreferenceCategory(
|
||||||
title = "Category title",
|
title = "Category title",
|
||||||
) {
|
) {
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
subtitle = "Some other text",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
)
|
|
||||||
PreferenceDivider()
|
PreferenceDivider()
|
||||||
PreferenceSwitch(
|
PreferenceSwitch(
|
||||||
title = "Switch",
|
title = "Switch",
|
||||||
|
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.designsystem.components.preferences
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.progressSemantics
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import io.element.android.compound.theme.ElementTheme
|
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
|
||||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
|
||||||
import io.element.android.libraries.designsystem.components.preferences.components.preferenceIcon
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
|
||||||
import io.element.android.libraries.designsystem.toEnabledColor
|
|
||||||
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PreferenceText(
|
|
||||||
title: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
enabled: Boolean = true,
|
|
||||||
subtitle: String? = null,
|
|
||||||
subtitleAnnotated: AnnotatedString? = null,
|
|
||||||
currentValue: String? = null,
|
|
||||||
loadingCurrentValue: Boolean = false,
|
|
||||||
icon: ImageVector? = null,
|
|
||||||
@DrawableRes iconResourceId: Int? = null,
|
|
||||||
showIconAreaIfNoIcon: Boolean = false,
|
|
||||||
showIconBadge: Boolean = false,
|
|
||||||
showEndBadge: Boolean = false,
|
|
||||||
tintColor: Color? = null,
|
|
||||||
onClick: () -> Unit = {},
|
|
||||||
) {
|
|
||||||
ListItem(
|
|
||||||
modifier = modifier,
|
|
||||||
enabled = enabled,
|
|
||||||
onClick = onClick,
|
|
||||||
leadingContent = preferenceIcon(
|
|
||||||
icon = icon,
|
|
||||||
iconResourceId = iconResourceId,
|
|
||||||
showIconBadge = showIconBadge,
|
|
||||||
enabled = enabled,
|
|
||||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
|
||||||
tintColor = tintColor,
|
|
||||||
),
|
|
||||||
headlineContent = {
|
|
||||||
Text(
|
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
|
||||||
text = title,
|
|
||||||
color = tintColor ?: enabled.toEnabledColor(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
supportingContent = if (subtitle != null) {
|
|
||||||
{
|
|
||||||
Text(
|
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
|
||||||
text = subtitle,
|
|
||||||
color = tintColor ?: enabled.toSecondaryEnabledColor(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
subtitleAnnotated?.let {
|
|
||||||
{
|
|
||||||
Text(
|
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
|
||||||
text = it,
|
|
||||||
color = tintColor ?: enabled.toSecondaryEnabledColor(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
trailingContent = if (currentValue != null || loadingCurrentValue || showEndBadge) {
|
|
||||||
ListItemContent.Custom {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
if (currentValue != null) {
|
|
||||||
Text(
|
|
||||||
text = currentValue,
|
|
||||||
style = ElementTheme.typography.fontBodyXsMedium,
|
|
||||||
color = enabled.toSecondaryEnabledColor(),
|
|
||||||
)
|
|
||||||
} else if (loadingCurrentValue) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.progressSemantics()
|
|
||||||
.size(20.dp),
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (showEndBadge) {
|
|
||||||
val endBadgeStartPadding = if (currentValue != null || loadingCurrentValue) 16.dp else 0.dp
|
|
||||||
RedIndicatorAtom(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = endBadgeStartPadding)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(group = PreviewGroup.Preferences)
|
|
||||||
@Composable
|
|
||||||
internal fun PreferenceTextLightPreview() = ElementPreviewLight {
|
|
||||||
ContentToPreview(showEndBadge = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(group = PreviewGroup.Preferences)
|
|
||||||
@Composable
|
|
||||||
internal fun PreferenceTextDarkPreview() = ElementPreviewDark {
|
|
||||||
ContentToPreview(showEndBadge = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(group = PreviewGroup.Preferences)
|
|
||||||
@Composable
|
|
||||||
internal fun PreferenceTextWithEndBadgeLightPreview() = ElementPreviewLight {
|
|
||||||
ContentToPreview(showEndBadge = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(group = PreviewGroup.Preferences)
|
|
||||||
@Composable
|
|
||||||
internal fun PreferenceTextWithEndBadgeDarkPreview() = ElementPreviewDark {
|
|
||||||
ContentToPreview(showEndBadge = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExcludeFromCoverage
|
|
||||||
@Composable
|
|
||||||
private fun ContentToPreview(showEndBadge: Boolean) {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
|
||||||
) {
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
subtitle = "Some content",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
subtitle = "Some content",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
currentValue = "123",
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
subtitle = "Some content",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
currentValue = "123",
|
|
||||||
enabled = false,
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
subtitle = "Some content",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
loadingCurrentValue = true,
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
currentValue = "123",
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title",
|
|
||||||
icon = CompoundIcons.ChatProblem(),
|
|
||||||
loadingCurrentValue = true,
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title no icon with icon area",
|
|
||||||
showIconAreaIfNoIcon = true,
|
|
||||||
loadingCurrentValue = true,
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
PreferenceText(
|
|
||||||
title = "Title no icon",
|
|
||||||
loadingCurrentValue = true,
|
|
||||||
showEndBadge = showEndBadge,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -25,7 +25,7 @@ import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
import io.element.android.libraries.designsystem.toIconSecondaryEnabledColor
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun preferenceIcon(
|
fun preferenceIcon(
|
||||||
|
|
@ -68,7 +68,7 @@ private fun PreferenceIcon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
resourceId = iconResourceId,
|
resourceId = iconResourceId,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = tintColor ?: enabled.toSecondaryEnabledColor(),
|
tint = tintColor ?: enabled.toIconSecondaryEnabledColor(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp),
|
.size(24.dp),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,14 @@ package io.element.android.libraries.designsystem.preview
|
||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import coil3.annotation.ExperimentalCoilApi
|
||||||
|
import coil3.compose.AsyncImagePreviewHandler
|
||||||
|
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoilApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("ModifierMissing")
|
@Suppress("ModifierMissing")
|
||||||
fun ElementPreview(
|
fun ElementPreview(
|
||||||
|
|
@ -19,12 +24,14 @@ fun ElementPreview(
|
||||||
showBackground: Boolean = true,
|
showBackground: Boolean = true,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
ElementTheme(darkTheme = darkTheme) {
|
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides AsyncImagePreviewHandler { null }) {
|
||||||
if (showBackground) {
|
ElementTheme(darkTheme = darkTheme) {
|
||||||
// If we have a proper contentColor applied we need a Surface instead of a Box
|
if (showBackground) {
|
||||||
Surface(content = content)
|
// If we have a proper contentColor applied we need a Surface instead of a Box
|
||||||
} else {
|
Surface(content = content)
|
||||||
content()
|
} else {
|
||||||
|
content()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -193,14 +193,14 @@ sealed interface ListItemStyle {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun leadingIconColor() = when (this) {
|
fun leadingIconColor() = when (this) {
|
||||||
Default -> ListItemDefaultColors.icon
|
Default -> ListItemDefaultColors.leadingIcon
|
||||||
Primary -> ElementTheme.colors.iconPrimary
|
Primary -> ElementTheme.colors.iconPrimary
|
||||||
Destructive -> ElementTheme.colors.iconCriticalPrimary
|
Destructive -> ElementTheme.colors.iconCriticalPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun trailingIconColor() = when (this) {
|
fun trailingIconColor() = when (this) {
|
||||||
Default -> ListItemDefaultColors.icon
|
Default -> ListItemDefaultColors.trailingIcon
|
||||||
Primary -> ElementTheme.colors.iconPrimary
|
Primary -> ElementTheme.colors.iconPrimary
|
||||||
Destructive -> ElementTheme.colors.iconCriticalPrimary
|
Destructive -> ElementTheme.colors.iconCriticalPrimary
|
||||||
}
|
}
|
||||||
|
|
@ -210,15 +210,16 @@ object ListItemDefaultColors {
|
||||||
val headline: Color @Composable get() = ElementTheme.colors.textPrimary
|
val headline: Color @Composable get() = ElementTheme.colors.textPrimary
|
||||||
val headlineDisabled: Color @Composable get() = ElementTheme.colors.textDisabled
|
val headlineDisabled: Color @Composable get() = ElementTheme.colors.textDisabled
|
||||||
val supportingText: Color @Composable get() = ElementTheme.materialColors.onSurfaceVariant
|
val supportingText: Color @Composable get() = ElementTheme.materialColors.onSurfaceVariant
|
||||||
val icon: Color @Composable get() = ElementTheme.colors.iconTertiary
|
val leadingIcon: Color @Composable get() = ElementTheme.colors.iconSecondary
|
||||||
|
val trailingIcon: Color @Composable get() = ElementTheme.colors.iconPrimary
|
||||||
val iconDisabled: Color @Composable get() = ElementTheme.colors.iconDisabled
|
val iconDisabled: Color @Composable get() = ElementTheme.colors.iconDisabled
|
||||||
|
|
||||||
val colors: ListItemColors
|
val colors: ListItemColors
|
||||||
@Composable get() = ListItemDefaults.colors(
|
@Composable get() = ListItemDefaults.colors(
|
||||||
headlineColor = headline,
|
headlineColor = headline,
|
||||||
supportingColor = supportingText,
|
supportingColor = supportingText,
|
||||||
leadingIconColor = icon,
|
leadingIconColor = leadingIcon,
|
||||||
trailingIconColor = icon,
|
trailingIconColor = trailingIcon,
|
||||||
disabledHeadlineColor = headlineDisabled,
|
disabledHeadlineColor = headlineDisabled,
|
||||||
disabledLeadingIconColor = iconDisabled,
|
disabledLeadingIconColor = iconDisabled,
|
||||||
disabledTrailingIconColor = iconDisabled,
|
disabledTrailingIconColor = iconDisabled,
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ enum class FeatureFlags(
|
||||||
key = "feature.event_cache",
|
key = "feature.event_cache",
|
||||||
title = "Use SDK Event cache",
|
title = "Use SDK Event cache",
|
||||||
description = "Warning: you must kill and restart the app for the change to take effect.",
|
description = "Warning: you must kill and restart the app for the change to take effect.",
|
||||||
defaultValue = { false },
|
defaultValue = { true },
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue