Merge branch 'develop' into fix
This commit is contained in:
commit
ad63de00df
207 changed files with 2230 additions and 1541 deletions
|
|
@ -9,6 +9,7 @@
|
|||
<locale android:name="el"/>
|
||||
<locale android:name="en"/>
|
||||
<locale android:name="en_US"/>
|
||||
<locale android:name="eo"/>
|
||||
<locale android:name="es"/>
|
||||
<locale android:name="et"/>
|
||||
<locale android:name="eu"/>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
|
|
@ -111,7 +110,7 @@ class RoomFlowNode(
|
|||
data class JoinedRoom(val roomId: RoomId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class Space(val spaceId: RoomId) : NavTarget
|
||||
data class JoinedSpace(val spaceId: RoomId) : NavTarget
|
||||
}
|
||||
|
||||
override fun onBuilt() {
|
||||
|
|
@ -149,30 +148,28 @@ class RoomFlowNode(
|
|||
.withPreviousValue()
|
||||
combine(currentMembershipFlow, isSpaceFlow) { (previousMembership, membership), isSpace ->
|
||||
Timber.d("Room membership: $membership")
|
||||
when (membership) {
|
||||
CurrentUserMembership.JOINED -> {
|
||||
if (isSpace) {
|
||||
backstack.newRoot(NavTarget.Space(spaceId = roomId))
|
||||
} else {
|
||||
backstack.newRoot(NavTarget.JoinedRoom(roomId))
|
||||
}
|
||||
if (membership == CurrentUserMembership.JOINED) {
|
||||
if (isSpace) {
|
||||
backstack.newRoot(NavTarget.JoinedSpace(spaceId = roomId))
|
||||
} else {
|
||||
backstack.newRoot(NavTarget.JoinedRoom(roomId))
|
||||
}
|
||||
else -> {
|
||||
if (membership == CurrentUserMembership.LEFT && previousMembership == CurrentUserMembership.JOINED) {
|
||||
// The user left the room in this device, remove the room from the backstack
|
||||
if (!membershipUpdateFlow.first().isUserInRoom) {
|
||||
navigateUp()
|
||||
}
|
||||
} else {
|
||||
// Was invited or the room is not known, display the join room screen
|
||||
backstack.newRoot(
|
||||
NavTarget.JoinRoom(
|
||||
roomId = roomId,
|
||||
serverNames = serverNames,
|
||||
trigger = inputs.trigger.getOrNull() ?: JoinedRoom.Trigger.Invite,
|
||||
)
|
||||
} else {
|
||||
val leavingFromCurrentDevice =
|
||||
membership == CurrentUserMembership.LEFT &&
|
||||
previousMembership == CurrentUserMembership.JOINED &&
|
||||
membershipUpdateFlow.replayCache.lastOrNull()?.isUserInRoom == false
|
||||
|
||||
if (leavingFromCurrentDevice) {
|
||||
navigateUp()
|
||||
} else {
|
||||
backstack.newRoot(
|
||||
NavTarget.JoinRoom(
|
||||
roomId = roomId,
|
||||
serverNames = serverNames,
|
||||
trigger = inputs.trigger.getOrNull() ?: JoinedRoom.Trigger.Invite,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
|
|
@ -214,7 +211,7 @@ class RoomFlowNode(
|
|||
)
|
||||
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
||||
}
|
||||
is NavTarget.Space -> {
|
||||
is NavTarget.JoinedSpace -> {
|
||||
val spaceCallback = plugins<SpaceEntryPoint.Callback>().single()
|
||||
spaceEntryPoint.nodeBuilder(this, buildContext)
|
||||
.inputs(SpaceEntryPoint.Inputs(roomId = navTarget.spaceId))
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ allprojects {
|
|||
// Fix compilation warning for annotations
|
||||
// See https://youtrack.jetbrains.com/issue/KT-73255/Change-defaulting-rule-for-annotations for more details
|
||||
freeCompilerArgs.add("-Xannotation-default-target=first-only")
|
||||
// Opt-in to context receivers
|
||||
freeCompilerArgs.add("-Xcontext-parameters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_change_permissions_administrators">"Gweinyddwyr yn unig"</string>
|
||||
<string name="screen_room_change_permissions_ban_people">"Gwahardd pobl"</string>
|
||||
<string name="screen_room_change_permissions_delete_messages">"Dileu negeseuon"</string>
|
||||
<string name="screen_room_change_permissions_delete_messages">"Tynnu negeseuon"</string>
|
||||
<string name="screen_room_change_permissions_everyone">"Pawb"</string>
|
||||
<string name="screen_room_change_permissions_invite_people">"Gwahodd pobl a derbyn ceisiadau i ymuno"</string>
|
||||
<string name="screen_room_change_permissions_member_moderation">"Cymedroli aelodau"</string>
|
||||
|
|
@ -17,13 +17,17 @@
|
|||
<string name="screen_room_change_role_administrators_title">"Golygu Gweinyddwyr"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_description">"Fyddwch chi ddim yn gallu dadwneud y weithred hon. Rydych chi\'n hyrwyddo\'r defnyddiwr i gael yr un lefel pŵer â chi."</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"Ychwanegu Gweinyddwr?"</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_description">"Fyddwch chi ddim yn gallu dadwneud y weithred hon. Rydych yn trosglwyddo\'r berchnogaeth i\'r defnyddwyr a ddewiswyd. Unwaith y byddwch yn gadael bydd hyn yn barhaol."</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_title">"Trosglwyddo perchnogaeth?"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_action">"Gostwng"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_description">"Fyddwch chi ddim yn gallu dadwneud y newid hwn gan eich bod yn israddio eich hun, os mai chi yw\'r defnyddiwr breintiedig olaf yn yr ystafell bydd yn amhosibl adennill breintiau."</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"Israddio eich hun?"</string>
|
||||
<string name="screen_room_change_role_invited_member_name">"%1$s (Yn aros)"</string>
|
||||
<string name="screen_room_change_role_invited_member_name_android">"Yn aros"</string>
|
||||
<string name="screen_room_change_role_moderators_admin_section_footer">"Mae gan weinyddwyr freintiau cymedrolwr yn awtomatig"</string>
|
||||
<string name="screen_room_change_role_moderators_owner_section_footer">"Mae gan berchnogion freintiau gweinyddwr yn awtomatig."</string>
|
||||
<string name="screen_room_change_role_moderators_title">"Golygu Cymedrolwyr"</string>
|
||||
<string name="screen_room_change_role_owners_title">"Dewiswch Berchnogion"</string>
|
||||
<string name="screen_room_change_role_section_administrators">"Gweinyddwyr"</string>
|
||||
<string name="screen_room_change_role_section_moderators">"Cymedrolwyr"</string>
|
||||
<string name="screen_room_change_role_section_users">"Aelodau"</string>
|
||||
|
|
@ -48,15 +52,18 @@
|
|||
<string name="screen_room_member_list_pending_header_title">"Dan ystyriaeth"</string>
|
||||
<string name="screen_room_member_list_role_administrator">"Gweinyddwr"</string>
|
||||
<string name="screen_room_member_list_role_moderator">"Cymedrolwr"</string>
|
||||
<string name="screen_room_member_list_role_owner">"Perchennog"</string>
|
||||
<string name="screen_room_member_list_room_members_header_title">"Aelodau\'r ystafell"</string>
|
||||
<string name="screen_room_member_list_unbanning_user">"Dad-wahardd %1$s"</string>
|
||||
<string name="screen_room_roles_and_permissions_admins">"Gweinyddwyr"</string>
|
||||
<string name="screen_room_roles_and_permissions_admins_and_owners">"Gweinyddwyr a pherchnogion"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_my_role">"Newid fy rôl"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_role_demote_to_member">"Israddio aelod"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_role_demote_to_moderator">"Israddio cymedrolwr"</string>
|
||||
<string name="screen_room_roles_and_permissions_member_moderation">"Cymedroli aelodau"</string>
|
||||
<string name="screen_room_roles_and_permissions_messages_and_content">"Negeseuon a chynnwys"</string>
|
||||
<string name="screen_room_roles_and_permissions_moderators">"Cymedrolwyr"</string>
|
||||
<string name="screen_room_roles_and_permissions_owners">"Perchnogion"</string>
|
||||
<string name="screen_room_roles_and_permissions_permissions_header">"Caniatâd"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset">"Ailosod caniatâd"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset_confirm_description">"Ar ôl i chi ailosod caniatâd, byddwch yn colli\'r gosodiadau cyfredol."</string>
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@
|
|||
<string name="screen_room_change_permissions_send_messages">"Viestien lähettäminen"</string>
|
||||
<string name="screen_room_change_role_administrators_title">"Muokkaa ylläpitäjiä"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_description">"Et voi peruuttaa tätä toimenpidettä. Ylennät käyttäjän samalle oikeustasolle kuin sinä."</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"Lisää ylläpitäjä?"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"Lisätäänkö ylläpitäjä?"</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_description">"Et voi kumota tätä toimintoa. Olet siirtämässä omistajuuden valituille käyttäjille. Kun poistut, muutos on pysyvä."</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_title">"Siirretäänkö omistajuus?"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_action">"Alenna"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_description">"Et voi perua tätä muutosta, koska olet alentamassa itseäsi. Jos olet viimeinen oikeutettu henkilö tässä huoneessa, oikeuksia ei voi enää saada takaisin."</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"Alenna itsesi?"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"Haluatko alentaa itsesi?"</string>
|
||||
<string name="screen_room_change_role_invited_member_name">"%1$s (Kutsuttu)"</string>
|
||||
<string name="screen_room_change_role_invited_member_name_android">"(Kutsuttu)"</string>
|
||||
<string name="screen_room_change_role_moderators_admin_section_footer">"Ylläpitäjillä on automaattisesti valvojan oikeudet"</string>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
<string name="screen_room_change_role_section_moderators">"Valvojat"</string>
|
||||
<string name="screen_room_change_role_section_users">"Jäsenet"</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_description">"Sinulla on tallentamattomia muutoksia"</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_title">"Tallenna muutokset?"</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_title">"Tallennetaanko muutokset?"</string>
|
||||
<string name="screen_room_member_list_banned_empty">"Tässä huoneessa ei ole porttikieltoja"</string>
|
||||
<plurals name="screen_room_member_list_header_title">
|
||||
<item quantity="one">"%1$d henkilö"</item>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_list_item_3">"Delete your account information from our server."</string>
|
||||
</resources>
|
||||
|
|
@ -15,9 +15,6 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
interface FtueService {
|
||||
/** The current state of the FTUE. */
|
||||
val state: StateFlow<FtueState>
|
||||
|
||||
/** Reset the FTUE state. */
|
||||
suspend fun reset()
|
||||
}
|
||||
|
||||
/** The state of the FTUE. */
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -30,18 +29,16 @@ import io.element.android.features.ftue.impl.notifications.NotificationsOptInNod
|
|||
import io.element.android.features.ftue.impl.sessionverification.FtueSessionVerificationFlowNode
|
||||
import io.element.android.features.ftue.impl.state.DefaultFtueService
|
||||
import io.element.android.features.ftue.impl.state.FtueStep
|
||||
import io.element.android.features.ftue.impl.state.InternalFtueState
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
|
|
@ -49,9 +46,8 @@ import kotlinx.parcelize.Parcelize
|
|||
class FtueFlowNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val ftueState: DefaultFtueService,
|
||||
private val defaultFtueService: DefaultFtueService,
|
||||
private val analyticsEntryPoint: AnalyticsEntryPoint,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val lockScreenEntryPoint: LockScreenEntryPoint,
|
||||
) : BaseFlowNode<FtueFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
|
|
@ -80,19 +76,11 @@ class FtueFlowNode(
|
|||
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
|
||||
lifecycle.subscribe(onCreate = {
|
||||
moveToNextStepIfNeeded()
|
||||
})
|
||||
|
||||
analyticsService.didAskUserConsentFlow
|
||||
.distinctUntilChanged()
|
||||
.onEach { moveToNextStepIfNeeded() }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
ftueState.isVerificationStatusKnown
|
||||
.filter { it }
|
||||
.onEach { moveToNextStepIfNeeded() }
|
||||
defaultFtueService.ftueStepStateFlow
|
||||
.filterIsInstance(InternalFtueState.Incomplete::class)
|
||||
.onEach {
|
||||
showStep(it.nextStep)
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +92,7 @@ class FtueFlowNode(
|
|||
is NavTarget.SessionVerification -> {
|
||||
val callback = object : FtueSessionVerificationFlowNode.Callback {
|
||||
override fun onDone() {
|
||||
moveToNextStepIfNeeded()
|
||||
defaultFtueService.onUserCompletedSessionVerification()
|
||||
}
|
||||
}
|
||||
createNode<FtueSessionVerificationFlowNode>(buildContext, listOf(callback))
|
||||
|
|
@ -112,7 +100,7 @@ class FtueFlowNode(
|
|||
NavTarget.NotificationsOptIn -> {
|
||||
val callback = object : NotificationsOptInNode.Callback {
|
||||
override fun onNotificationsOptInFinished() {
|
||||
moveToNextStepIfNeeded()
|
||||
defaultFtueService.updateFtueStep()
|
||||
}
|
||||
}
|
||||
createNode<NotificationsOptInNode>(buildContext, listOf(callback))
|
||||
|
|
@ -123,7 +111,7 @@ class FtueFlowNode(
|
|||
NavTarget.LockScreenSetup -> {
|
||||
val callback = object : LockScreenEntryPoint.Callback {
|
||||
override fun onSetupDone() {
|
||||
moveToNextStepIfNeeded()
|
||||
defaultFtueService.updateFtueStep()
|
||||
}
|
||||
}
|
||||
lockScreenEntryPoint.nodeBuilder(this, buildContext, LockScreenEntryPoint.Target.Setup)
|
||||
|
|
@ -133,8 +121,8 @@ class FtueFlowNode(
|
|||
}
|
||||
}
|
||||
|
||||
private fun moveToNextStepIfNeeded() = lifecycleScope.launch {
|
||||
when (ftueState.getNextStep()) {
|
||||
private fun showStep(ftueStep: FtueStep) {
|
||||
when (ftueStep) {
|
||||
FtueStep.WaitingForInitialState -> {
|
||||
backstack.newRoot(NavTarget.Placeholder)
|
||||
}
|
||||
|
|
@ -150,7 +138,6 @@ class FtueFlowNode(
|
|||
FtueStep.LockscreenSetup -> {
|
||||
backstack.newRoot(NavTarget.LockScreenSetup)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ package io.element.android.features.ftue.impl.state
|
|||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
|
|
@ -26,61 +26,70 @@ import io.element.android.services.analytics.api.AnalyticsService
|
|||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
@SingleIn(SessionScope::class)
|
||||
@Inject
|
||||
class DefaultFtueService(
|
||||
private val sdkVersionProvider: BuildVersionSdkIntProvider,
|
||||
@SessionCoroutineScope sessionCoroutineScope: CoroutineScope,
|
||||
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val permissionStateProvider: PermissionStateProvider,
|
||||
private val lockScreenService: LockScreenService,
|
||||
private val sessionVerificationService: SessionVerificationService,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
) : FtueService {
|
||||
override val state = MutableStateFlow<FtueState>(FtueState.Unknown)
|
||||
private val userNeedsToConfirmSessionVerificationSuccess = MutableStateFlow(false)
|
||||
|
||||
/**
|
||||
* This flow emits true when the FTUE flow is ready to be displayed.
|
||||
* In this case, the FTUE flow is ready when the session verification status is known.
|
||||
*/
|
||||
val isVerificationStatusKnown = sessionVerificationService.sessionVerifiedStatus
|
||||
.map { it != SessionVerifiedStatus.Unknown }
|
||||
.distinctUntilChanged()
|
||||
val ftueStepStateFlow = MutableStateFlow<InternalFtueState>(InternalFtueState.Unknown)
|
||||
|
||||
override suspend fun reset() {
|
||||
analyticsService.reset()
|
||||
if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
|
||||
permissionStateProvider.resetPermission(Manifest.permission.POST_NOTIFICATIONS)
|
||||
override val state = ftueStepStateFlow
|
||||
.mapState {
|
||||
when (it) {
|
||||
is InternalFtueState.Unknown -> FtueState.Unknown
|
||||
is InternalFtueState.Incomplete -> FtueState.Incomplete
|
||||
is InternalFtueState.Complete -> FtueState.Complete
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
combine(
|
||||
sessionVerificationService.sessionVerifiedStatus.onEach { sessionVerifiedStatus ->
|
||||
if (sessionVerifiedStatus == SessionVerifiedStatus.NotVerified) {
|
||||
// Ensure we wait for the user to confirm the session verified screen before going further
|
||||
userNeedsToConfirmSessionVerificationSuccess.value = true
|
||||
}
|
||||
},
|
||||
userNeedsToConfirmSessionVerificationSuccess,
|
||||
analyticsService.didAskUserConsentFlow.distinctUntilChanged(),
|
||||
) {
|
||||
updateFtueStep()
|
||||
}
|
||||
.launchIn(sessionCoroutineScope)
|
||||
}
|
||||
|
||||
fun updateFtueStep() = sessionCoroutineScope.launch {
|
||||
val step = getNextStep(null)
|
||||
ftueStepStateFlow.value = when (step) {
|
||||
null -> InternalFtueState.Complete
|
||||
else -> InternalFtueState.Incomplete(step)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
sessionVerificationService.sessionVerifiedStatus
|
||||
.onEach { updateState() }
|
||||
.launchIn(sessionCoroutineScope)
|
||||
|
||||
analyticsService.didAskUserConsentFlow
|
||||
.distinctUntilChanged()
|
||||
.onEach { updateState() }
|
||||
.launchIn(sessionCoroutineScope)
|
||||
}
|
||||
|
||||
suspend fun getNextStep(currentStep: FtueStep? = null): FtueStep? =
|
||||
when (currentStep) {
|
||||
private suspend fun getNextStep(completedStep: FtueStep? = null): FtueStep? =
|
||||
when (completedStep) {
|
||||
null -> if (!isSessionVerificationStateReady()) {
|
||||
FtueStep.WaitingForInitialState
|
||||
} else {
|
||||
getNextStep(FtueStep.WaitingForInitialState)
|
||||
}
|
||||
FtueStep.WaitingForInitialState -> if (isSessionNotVerified()) {
|
||||
FtueStep.WaitingForInitialState -> if (isSessionNotVerified() || userNeedsToConfirmSessionVerificationSuccess.value) {
|
||||
FtueStep.SessionVerification
|
||||
} else {
|
||||
getNextStep(FtueStep.SessionVerification)
|
||||
|
|
@ -108,9 +117,6 @@ class DefaultFtueService(
|
|||
}
|
||||
|
||||
private suspend fun isSessionNotVerified(): Boolean {
|
||||
// Wait until the session verification status is known
|
||||
isVerificationStatusKnown.filter { it }.first()
|
||||
|
||||
return sessionVerificationService.sessionVerifiedStatus.value == SessionVerifiedStatus.NotVerified && !canSkipVerification()
|
||||
}
|
||||
|
||||
|
|
@ -137,14 +143,8 @@ class DefaultFtueService(
|
|||
return lockScreenService.isSetupRequired().first()
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal suspend fun updateState() {
|
||||
val nextStep = getNextStep()
|
||||
state.value = when {
|
||||
// Final state, there aren't any more next steps
|
||||
nextStep == null -> FtueState.Complete
|
||||
else -> FtueState.Incomplete
|
||||
}
|
||||
fun onUserCompletedSessionVerification() {
|
||||
userNeedsToConfirmSessionVerificationSuccess.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.ftue.impl.state
|
||||
|
||||
sealed interface InternalFtueState {
|
||||
data object Unknown : InternalFtueState
|
||||
|
||||
data class Incomplete(
|
||||
val nextStep: FtueStep,
|
||||
) : InternalFtueState
|
||||
|
||||
data object Complete : InternalFtueState
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Create a new backup password"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Confirm this device to set up secure messaging."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirm it\'s you"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Use backup password"</string>
|
||||
<string name="screen_identity_confirmed_title">"Device confirmed"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Enter backup password"</string>
|
||||
</resources>
|
||||
|
|
@ -14,7 +14,6 @@ import com.bumble.appyx.core.modality.BuildContext
|
|||
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -36,8 +35,7 @@ class DefaultFtueEntryPointTest {
|
|||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
analyticsEntryPoint = { _, _ -> lambdaError() },
|
||||
ftueState = createDefaultFtueService(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
defaultFtueService = createDefaultFtueService(),
|
||||
lockScreenEntryPoint = object : LockScreenEntryPoint {
|
||||
override fun nodeBuilder(
|
||||
parentNode: com.bumble.appyx.core.node.Node,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.ftue.impl.state.DefaultFtueService
|
||||
import io.element.android.features.ftue.impl.state.FtueStep
|
||||
import io.element.android.features.ftue.impl.state.InternalFtueState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.lockscreen.test.FakeLockScreenService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
|
|
@ -26,8 +27,6 @@ import io.element.android.services.analytics.api.AnalyticsService
|
|||
import io.element.android.services.analytics.noop.NoopAnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -69,9 +68,11 @@ class DefaultFtueServiceTest {
|
|||
analyticsService.setDidAskUserConsent()
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
service.updateState()
|
||||
|
||||
assertThat(service.state.value).isEqualTo(FtueState.Complete)
|
||||
service.updateFtueStep()
|
||||
service.state.test {
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Unknown)
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Complete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -90,9 +91,11 @@ class DefaultFtueServiceTest {
|
|||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
service.updateState()
|
||||
|
||||
assertThat(service.state.value).isEqualTo(FtueState.Complete)
|
||||
service.updateFtueStep()
|
||||
service.state.test {
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Unknown)
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Complete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -109,35 +112,30 @@ class DefaultFtueServiceTest {
|
|||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
val steps = mutableListOf<FtueStep?>()
|
||||
|
||||
// Session verification
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
||||
|
||||
// Notifications opt in
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
|
||||
// Entering PIN code
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
// Analytics opt in
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
|
||||
// Final step (null)
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
|
||||
assertThat(steps).containsExactly(
|
||||
FtueStep.SessionVerification,
|
||||
FtueStep.NotificationsOptIn,
|
||||
FtueStep.LockscreenSetup,
|
||||
FtueStep.AnalyticsOptIn,
|
||||
// Final state
|
||||
null,
|
||||
)
|
||||
service.ftueStepStateFlow.test {
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Unknown)
|
||||
// Session verification
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.SessionVerification))
|
||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
// User completes verification
|
||||
service.onUserCompletedSessionVerification()
|
||||
// Notifications opt in
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.NotificationsOptIn))
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
// Simulate event from NotificationsOptInNode.Callback.onNotificationsOptInFinished
|
||||
service.updateFtueStep()
|
||||
// Entering PIN code
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.LockscreenSetup))
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
// Simulate event from LockScreenEntryPoint.Callback.onSetupDone()
|
||||
service.updateFtueStep()
|
||||
// Analytics opt in
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.AnalyticsOptIn))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
// Final step
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Complete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -158,10 +156,13 @@ class DefaultFtueServiceTest {
|
|||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
assertThat(service.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
|
||||
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(service.getNextStep(null)).isNull()
|
||||
service.ftueStepStateFlow.test {
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Unknown)
|
||||
// Analytics opt in
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.AnalyticsOptIn))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Complete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -180,51 +181,13 @@ class DefaultFtueServiceTest {
|
|||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
assertThat(service.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
|
||||
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(service.getNextStep(null)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reset do the expected actions S`() = runTest {
|
||||
val resetAnalyticsLambda = lambdaRecorder<Unit> { }
|
||||
val analyticsService = FakeAnalyticsService(
|
||||
resetLambda = resetAnalyticsLambda
|
||||
)
|
||||
val resetPermissionLambda = lambdaRecorder<String, Unit> { }
|
||||
val permissionStateProvider = FakePermissionStateProvider(
|
||||
resetPermissionLambda = resetPermissionLambda
|
||||
)
|
||||
val service = createDefaultFtueService(
|
||||
sdkIntVersion = Build.VERSION_CODES.S,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
service.reset()
|
||||
resetAnalyticsLambda.assertions().isCalledOnce()
|
||||
resetPermissionLambda.assertions().isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reset do the expected actions TIRAMISU`() = runTest {
|
||||
val resetLambda = lambdaRecorder<Unit> { }
|
||||
val analyticsService = FakeAnalyticsService(
|
||||
resetLambda = resetLambda
|
||||
)
|
||||
val resetPermissionLambda = lambdaRecorder<String, Unit> { }
|
||||
val permissionStateProvider = FakePermissionStateProvider(
|
||||
resetPermissionLambda = resetPermissionLambda
|
||||
)
|
||||
val service = createDefaultFtueService(
|
||||
sdkIntVersion = Build.VERSION_CODES.TIRAMISU,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
service.reset()
|
||||
resetLambda.assertions().isCalledOnce()
|
||||
resetPermissionLambda.assertions().isCalledOnce()
|
||||
.with(value("android.permission.POST_NOTIFICATIONS"))
|
||||
service.ftueStepStateFlow.test {
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Unknown)
|
||||
// Analytics opt in
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.AnalyticsOptIn))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Complete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,18 +9,11 @@ package io.element.android.features.ftue.test
|
|||
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeFtueService(
|
||||
private val resetLambda: () -> Unit = { lambdaError() },
|
||||
) : FtueService {
|
||||
class FakeFtueService : FtueService {
|
||||
override val state: MutableStateFlow<FtueState> = MutableStateFlow(FtueState.Unknown)
|
||||
|
||||
override suspend fun reset() {
|
||||
resetLambda()
|
||||
}
|
||||
|
||||
suspend fun emitState(newState: FtueState) {
|
||||
state.emit(newState)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<string name="full_screen_intent_banner_message">"Er mwyn sicrhau fyddwch chi ddim yn colli galwad bwysig, newidiwch eich gosodiadau i ganiatáu hysbysiadau sgrin lawn pan fydd eich ffôn wedi\'i gloi."</string>
|
||||
<string name="full_screen_intent_banner_title">"Gwella profiad eich galwadau"</string>
|
||||
<string name="screen_home_tab_chats">"Sgyrsiau"</string>
|
||||
<string name="screen_home_tab_spaces">"Gofodau"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Ydych chi\'n siŵr eich bod am wrthod y gwahoddiad i ymuno â %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Gwrthod y gwahoddiad"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Ydych chi\'n siŵr eich bod am wrthod y sgwrs breifat hon gyda %1$s?"</string>
|
||||
|
|
@ -32,6 +33,7 @@ Am y tro, gallwch ddad-ddewis hidlwyr er mwyn gweld eich sgyrsiau eraill"</strin
|
|||
<string name="screen_roomlist_filter_invites">"Gwahoddiadau"</string>
|
||||
<string name="screen_roomlist_filter_invites_empty_state_title">"Does gennych chi ddim gwahoddiadau yn aros."</string>
|
||||
<string name="screen_roomlist_filter_low_priority">"Blaenoriaeth Isel"</string>
|
||||
<string name="screen_roomlist_filter_low_priority_empty_state_title">"Does gennych chi ddim sgyrsiau blaenoriaeth isel eto"</string>
|
||||
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Gallwch ddad-ddewis hidlwyr er mwyn gweld eich sgyrsiau eraill"</string>
|
||||
<string name="screen_roomlist_filter_mixed_empty_state_title">"Does gennych chi ddim sgyrsiau ar gyfer y dewis hwn"</string>
|
||||
<string name="screen_roomlist_filter_people">"Pobl"</string>
|
||||
|
|
|
|||
11
features/home/impl/src/main/res/values-eo/translations.xml
Normal file
11
features/home/impl/src/main/res/values-eo/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="banner_set_up_recovery_content">"Restore your account security and message history with a backup password if you have lost all your existing devices."</string>
|
||||
<string name="banner_set_up_recovery_submit">"Set up backup"</string>
|
||||
<string name="banner_set_up_recovery_title">"Set up backup to protect your account"</string>
|
||||
<string name="confirm_recovery_key_banner_message">"Confirm your backup password to maintain access to your message backup and message history."</string>
|
||||
<string name="confirm_recovery_key_banner_primary_button_title">"Enter your backup password"</string>
|
||||
<string name="confirm_recovery_key_banner_secondary_button_title">"Forgot your backup password?"</string>
|
||||
<string name="confirm_recovery_key_banner_title">"Your message backup is out of sync"</string>
|
||||
<string name="session_verification_banner_message">"Looks like you\'re using a new device. Confirm it with another connected device to access your encrypted messages."</string>
|
||||
</resources>
|
||||
|
|
@ -33,6 +33,7 @@ Toistaiseksi voit poistaa suodattimien valinnan, jotta näet muut keskustelut."<
|
|||
<string name="screen_roomlist_filter_invites">"Kutsut"</string>
|
||||
<string name="screen_roomlist_filter_invites_empty_state_title">"Sinulla ei ole yhtään odottavaa kutsua."</string>
|
||||
<string name="screen_roomlist_filter_low_priority">"Matala prioriteetti"</string>
|
||||
<string name="screen_roomlist_filter_low_priority_empty_state_title">"Sinulla ei ole vielä yhtään matalan prioriteetin keskustelua"</string>
|
||||
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Voit poistaa suodattimien valinnan nähdäksesi muut keskustelusi."</string>
|
||||
<string name="screen_roomlist_filter_mixed_empty_state_title">"Sinulla ei ole sopivia keskusteluja tähän valintaan"</string>
|
||||
<string name="screen_roomlist_filter_people">"Ihmiset"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<string name="full_screen_intent_banner_message">"For å sikre at du aldri går glipp av en viktig samtale, må du endre innstillingene dine for å tillate fullskjermvarsler når telefonen er låst."</string>
|
||||
<string name="full_screen_intent_banner_title">"Forbedre samtaleopplevelsen din"</string>
|
||||
<string name="screen_home_tab_chats">"Chatter"</string>
|
||||
<string name="screen_home_tab_spaces">"Områder"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Er du sikker på at du vil takke nei til invitasjonen til å bli med i %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Avvis invitasjon"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Er du sikker på at du vil avslå denne private chatten med %1$s?"</string>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
<string name="screen_roomlist_filter_invites">"邀請"</string>
|
||||
<string name="screen_roomlist_filter_invites_empty_state_title">"您沒有任何擱置中的邀請。"</string>
|
||||
<string name="screen_roomlist_filter_low_priority">"低優先度"</string>
|
||||
<string name="screen_roomlist_filter_low_priority_empty_state_title">"您尚無任何低優先程度聊天"</string>
|
||||
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"您可以取消選取篩選條件以檢視其他聊天"</string>
|
||||
<string name="screen_roomlist_filter_mixed_empty_state_title">"您並無此選擇的聊天"</string>
|
||||
<string name="screen_roomlist_filter_people">"夥伴"</string>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<string name="screen_join_room_join_action">"Ymuno â\'r ystafell"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"Efallai y bydd angen i chi gael eich gwahodd neu fod yn aelod o ofod er mwyn ymuno."</string>
|
||||
<string name="screen_join_room_knock_action">"Anfon cais i ymuno"</string>
|
||||
<string name="screen_join_room_knock_message_characters_count">"Nodau a ganiateir %1$d o %2$d"</string>
|
||||
<string name="screen_join_room_knock_message_description">"Neges (dewisol)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"Byddwch yn derbyn gwahoddiad i ymuno â\'r ystafell os caiff eich cais ei dderbyn."</string>
|
||||
<string name="screen_join_room_knock_sent_title">"Anfonwyd y cais i ymuno"</string>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,8 @@
|
|||
<string name="leave_conversation_alert_subtitle">"Ydych chi\'n siŵr eich bod am adael y sgwrs hon? Dyw\'r sgwrs hon ddim yn gyhoeddus a fyddwch chi ddim yn gallu ailymuno heb wahoddiad."</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"Ydych chi\'n siŵr eich bod am adael yr ystafell hon? Chi yw\'r unig berson yma. Os byddwch yn gadael, fydd neb yn gallu ymuno yn y dyfodol, gan gynnwys chi."</string>
|
||||
<string name="leave_room_alert_private_subtitle">"Ydych chi\'n siŵr eich bod am adael yr ystafell hon? Dyw\'r ystafell hon ddim yn gyhoeddus a fyddwch chi ddim yn gallu ailymuno heb wahoddiad."</string>
|
||||
<string name="leave_room_alert_select_new_owner_action">"Dewiswch Berchnogion"</string>
|
||||
<string name="leave_room_alert_select_new_owner_subtitle">"Chi yw unig berchennog yr ystafell hon. Mae angen i chi drosglwyddo perchnogaeth i rywun arall cyn i chi adael yr room."</string>
|
||||
<string name="leave_room_alert_select_new_owner_title">"Trosglwyddo perchnogaeth"</string>
|
||||
<string name="leave_room_alert_subtitle">"Ydych chi\'n siŵr eich bod am adael yr ystafell?"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<string name="screen_app_lock_settings_enable_biometric_unlock">"Salli biometrinen tunnistus"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin">"Poista PIN-koodi"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_message">"Haluatko varmasti poistaa PIN-koodin?"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_title">"Poista PIN-koodi?"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_title">"Poistetaanko PIN-koodi?"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Salli %1$s"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_skip">"Käytän mieluummin PIN-koodia"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Säästä aikaa ja ota käyttöön %1$s"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
<string name="screen_change_account_provider_other">"Arall"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Defnyddiwch ddarparwr cyfrif gwahanol, fel eich gweinydd preifat eich hun neu gyfrif gwaith."</string>
|
||||
<string name="screen_change_account_provider_title">"Newid darparwr cyfrif"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"Mae angen yr ap Element Pro ar %1$s. Llwythwch ef o\'r siop."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Mae angen Element Pro"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Doedd dim modd i ni gyrraedd y gweinydd cartref hwn. Gwiriwch eich bod wedi rhoi URL y gweinydd cartref yn gywir. Os yw\'r URL yn gywir, cysylltwch â gweinyddwr eich gweinydd cartref am ragor o help."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Dyw cydweddu llithrig ddim ar gael oherwydd problem yn y ffeil .well-known:
|
||||
%1$s"</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"A secure connection could not be made to the new device. Your existing connected devices are still safe and you don\'t need to worry about them."</string>
|
||||
</resources>
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
<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_title">"Bytt kontotilbyder"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"Element Pro-appen er nødvendig på %1$s. Last den ned fra butikken."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Element Pro kreves"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Vi kunne ikke nå denne hjemmeserveren. Kontroller at du har skrevet inn hjemmeserverens URL riktig. Hvis URL-en er riktig, kontakt administratoren for hjemmeserveren din for å få mer hjelp."</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_signout_key_backup_offline_subtitle">"Your messages were still being backed up when you went offline. Reconnect so that your messages can be backed up before signing out."</string>
|
||||
<string name="screen_signout_key_backup_offline_title">"Your messages are still being backed up"</string>
|
||||
<string name="screen_signout_key_backup_ongoing_title">"Your messages are still being backed up"</string>
|
||||
<string name="screen_signout_recovery_disabled_title">"Backup not set up"</string>
|
||||
<string name="screen_signout_save_recovery_key_title">"Have you saved your backup password?"</string>
|
||||
</resources>
|
||||
|
|
@ -87,6 +87,7 @@ import io.element.android.libraries.designsystem.components.ExpandableBottomShee
|
|||
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayoutState
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
|
|
@ -95,6 +96,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
|||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toAnnotatedString
|
||||
import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
|
@ -111,10 +113,14 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
|||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.wysiwyg.link.Link
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
|
|
@ -202,7 +208,13 @@ fun MessagesView(
|
|||
Column {
|
||||
ConnectivityIndicatorView(isOnline = state.hasNetworkConnection)
|
||||
if (state.timelineState.timelineMode is Timeline.Mode.Thread) {
|
||||
ThreadTopBar(onBackClick = onBackClick)
|
||||
ThreadTopBar(
|
||||
roomName = state.roomName,
|
||||
roomAvatarData = state.roomAvatar,
|
||||
heroes = state.heroes,
|
||||
isTombstoned = state.isTombstoned,
|
||||
onBackClick = onBackClick,
|
||||
)
|
||||
} else {
|
||||
MessagesViewTopBar(
|
||||
roomName = state.roomName,
|
||||
|
|
@ -573,14 +585,48 @@ private fun MessagesViewTopBar(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ThreadTopBar(
|
||||
roomName: String?,
|
||||
roomAvatarData: AvatarData,
|
||||
heroes: ImmutableList<AvatarData>,
|
||||
isTombstoned: Boolean,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
navigationIcon = {
|
||||
BackButton(onClick = onBackClick)
|
||||
},
|
||||
title = {
|
||||
Text(stringResource(CommonStrings.common_thread))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Avatar(
|
||||
avatarData = roomAvatarData,
|
||||
avatarType = AvatarType.Room(
|
||||
heroes = heroes,
|
||||
isTombstoned = isTombstoned,
|
||||
),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp)
|
||||
.semantics {
|
||||
heading()
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(CommonStrings.common_thread),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
)
|
||||
Text(
|
||||
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
fontStyle = FontStyle.Italic.takeIf { roomName == null },
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -673,3 +719,58 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
|
|||
knockRequestsBannerView = {},
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ThreadTopBarPreview() {
|
||||
ElementPreview {
|
||||
val name = "Room name"
|
||||
val initialsAvatarData = AvatarData(
|
||||
id = "id",
|
||||
name = name,
|
||||
url = null,
|
||||
size = AvatarSize.TimelineRoom,
|
||||
)
|
||||
Column {
|
||||
ThreadTopBar(
|
||||
roomName = name,
|
||||
roomAvatarData = initialsAvatarData,
|
||||
heroes = persistentListOf(),
|
||||
isTombstoned = false,
|
||||
onBackClick = {},
|
||||
)
|
||||
HorizontalDivider()
|
||||
ThreadTopBar(
|
||||
roomName = name,
|
||||
roomAvatarData = initialsAvatarData,
|
||||
heroes = aMatrixUserList().map { it.getAvatarData(AvatarSize.TimelineRoom) }.toImmutableList(),
|
||||
isTombstoned = false,
|
||||
onBackClick = {},
|
||||
)
|
||||
HorizontalDivider()
|
||||
ThreadTopBar(
|
||||
roomName = null,
|
||||
roomAvatarData = initialsAvatarData,
|
||||
heroes = persistentListOf(),
|
||||
isTombstoned = false,
|
||||
onBackClick = {},
|
||||
)
|
||||
HorizontalDivider()
|
||||
ThreadTopBar(
|
||||
roomName = name,
|
||||
roomAvatarData = initialsAvatarData.copy(url = "https://some-avatar.jpg"),
|
||||
heroes = persistentListOf(),
|
||||
isTombstoned = false,
|
||||
onBackClick = {},
|
||||
)
|
||||
HorizontalDivider()
|
||||
ThreadTopBar(
|
||||
roomName = name,
|
||||
roomAvatarData = initialsAvatarData,
|
||||
heroes = persistentListOf(),
|
||||
isTombstoned = true,
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<string name="emoji_picker_category_objects">"Předměty"</string>
|
||||
<string name="emoji_picker_category_people">"Smajlíci a lidé"</string>
|
||||
<string name="emoji_picker_category_places">"Cestování a místa"</string>
|
||||
<string name="emoji_picker_category_recent">"Nedávné emotikony"</string>
|
||||
<string name="emoji_picker_category_symbols">"Symboly"</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Titulky nemusí být viditelné pro lidi, kteří používají starší aplikace."</string>
|
||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Klepnutím změníte kvalitu nahrávání videa"</string>
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
<string name="screen_media_upload_preview_error_failed_sending">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_message">"Maximální povolená velikost souboru je %1$s."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Soubor je pro nahrání příliš velký."</string>
|
||||
<string name="screen_media_upload_preview_item_count">"Položka %1$d z %2$d"</string>
|
||||
<string name="screen_media_upload_preview_optimize_image_quality_title">"Optimalizace kvality obrazu"</string>
|
||||
<string name="screen_media_upload_preview_processing">"Probíhá zpracování…"</string>
|
||||
<string name="screen_report_content_block_user">"Zablokovat uživatele"</string>
|
||||
|
|
|
|||
|
|
@ -7,10 +7,18 @@
|
|||
<string name="emoji_picker_category_objects">"Gwrthrychau"</string>
|
||||
<string name="emoji_picker_category_people">"Wynebau Hapus a Phobl"</string>
|
||||
<string name="emoji_picker_category_places">"Teithio a Llefydd"</string>
|
||||
<string name="emoji_picker_category_recent">"Emojis diweddar"</string>
|
||||
<string name="emoji_picker_category_symbols">"Symbolau"</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Efallai na fydd capsiynau yn weladwy i bobl sy\'n defnyddio apiau hŷn."</string>
|
||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tapiwch i newid ansawdd llwytho\'r fideo"</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Nid oedd modd llwytho\'r ffeil."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Wedi methu â phrosesu cyfryngau i\'w llwytho, ceisiwch eto."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Wedi methu llwytho cyfryngau, ceisiwch eto."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_message">"Y maint ffeil mwyaf a ganiateir yw %1$s ."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Mae\'r ffeil yn rhy fawr i\'w llwytho"</string>
|
||||
<string name="screen_media_upload_preview_item_count">"Eitem %1$d o %2$d"</string>
|
||||
<string name="screen_media_upload_preview_optimize_image_quality_title">"Optimeiddio ansawdd delwedd"</string>
|
||||
<string name="screen_media_upload_preview_processing">"Prosesu…"</string>
|
||||
<string name="screen_report_content_block_user">"Rhwystro defnyddiwr"</string>
|
||||
<string name="screen_report_content_block_user_hint">"Gwiriwch a ydych am guddio\'r holl negeseuon presennol ac yn y dyfodol gan y defnyddiwr hwn"</string>
|
||||
<string name="screen_report_content_explanation">"Bydd y neges hon yn cael ei hadrodd i weinyddwr eich gweinyddwr cartref. Fyddan nhw ddim yn gallu darllen unrhyw negeseuon wedi\'u hamgryptio."</string>
|
||||
|
|
@ -38,6 +46,14 @@
|
|||
<string name="screen_room_timeline_less_reactions">"Dangos llai"</string>
|
||||
<string name="screen_room_timeline_message_copied">"Neges wedi\'i chopïo"</string>
|
||||
<string name="screen_room_timeline_no_permission_to_post">"Does gennych chi ddim caniatâd i bostio i\'r ystafell hon"</string>
|
||||
<plurals name="screen_room_timeline_reaction_a11y">
|
||||
<item quantity="zero">"Ymatebodd %1$d aelodau gyda %2$s"</item>
|
||||
<item quantity="one">"Ymatebodd %1$d aelodau gyda %2$s"</item>
|
||||
<item quantity="two">"Ymatebodd %1$d aelod gyda %2$s"</item>
|
||||
<item quantity="few">"Ymatebodd %1$d aelod gyda %2$s"</item>
|
||||
<item quantity="many">"Ymatebodd %1$d aelod gyda %2$s"</item>
|
||||
<item quantity="other">"Ymatebodd %1$d aelod gyda %2$s"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_timeline_reaction_you_a11y">"Rydych chi wedi ymateb gyda %1$s"</string>
|
||||
<string name="screen_room_timeline_reactions_show_less">"Dangos llai"</string>
|
||||
<string name="screen_room_timeline_reactions_show_more">"Dangos rhagor"</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_encrypted_history_banner_unverified">"Message history is unavailable in this room. Confirm this device to see your message history."</string>
|
||||
</resources>
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
<string name="emoji_picker_category_objects">"Esineet"</string>
|
||||
<string name="emoji_picker_category_people">"Hymiöt ja ihmiset"</string>
|
||||
<string name="emoji_picker_category_places">"Matkustaminen ja paikat"</string>
|
||||
<string name="emoji_picker_category_recent">"Viimeaikaiset emojit"</string>
|
||||
<string name="emoji_picker_category_symbols">"Symbolit"</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Kuvatekstit eivät välttämättä näy ihmisille, jotka käyttävät vanhempia sovelluksia."</string>
|
||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Napauta muuttaaksesi videon lähetyslaatua"</string>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@
|
|||
<string name="emoji_picker_category_places">"旅行與景點"</string>
|
||||
<string name="emoji_picker_category_symbols">"標誌"</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"使用舊應用程式的使用者可能看不到標題。"</string>
|
||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"輕點即可變更影片上傳品質"</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"無法上傳檔案。"</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"無法處理要上傳的媒體,請再試一次。"</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"無法上傳媒體檔案,請稍後再試。"</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_message">"允許的最大檔案大小為 %1$s。"</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"檔案太大,無法上傳"</string>
|
||||
<string name="screen_media_upload_preview_optimize_image_quality_title">"最佳化影像品質"</string>
|
||||
<string name="screen_media_upload_preview_processing">"正在處理……"</string>
|
||||
<string name="screen_report_content_block_user">"封鎖使用者"</string>
|
||||
<string name="screen_report_content_block_user_hint">"檢查您是否要隱藏所有來自此使用者的目前及未來的訊息"</string>
|
||||
<string name="screen_report_content_explanation">"此訊息將會回報給您的家伺服器管理員。他們將無法讀取任何已加密的訊息。"</string>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<string name="emoji_picker_category_objects">"Objects"</string>
|
||||
<string name="emoji_picker_category_people">"Smileys & People"</string>
|
||||
<string name="emoji_picker_category_places">"Travel & Places"</string>
|
||||
<string name="emoji_picker_category_recent">"Recent emojis"</string>
|
||||
<string name="emoji_picker_category_symbols">"Symbols"</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Captions might not be visible to people using older apps."</string>
|
||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tap to change the video upload quality"</string>
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_message">"The maximum file size allowed is %1$s."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"The file is too large to upload"</string>
|
||||
<string name="screen_media_upload_preview_item_count">"Item %1$d of %2$d"</string>
|
||||
<string name="screen_media_upload_preview_optimize_image_quality_title">"Optimise image quality"</string>
|
||||
<string name="screen_media_upload_preview_processing">"Processing…"</string>
|
||||
<string name="screen_report_content_block_user">"Block user"</string>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<plurals name="a11y_polls_percent_of_total">
|
||||
<item quantity="zero">"%1$d y cant o\'r holl bleidleisiau"</item>
|
||||
<item quantity="one">"%1$d y cant o\'r holl bleidleisiau"</item>
|
||||
<item quantity="two">"%1$d y cant o\'r holl bleidleisiau"</item>
|
||||
<item quantity="few">"%1$d y cant o\'r holl bleidleisiau"</item>
|
||||
<item quantity="many">"%1$d y cant o\'r holl bleidleisiau"</item>
|
||||
<item quantity="other">"%1$d y cant o\'r holl bleidleisiau"</item>
|
||||
</plurals>
|
||||
<string name="a11y_polls_will_remove_selection">"Bydd yn dileu\'r dewis blaenorol"</string>
|
||||
<string name="a11y_polls_winning_answer">"Dyma\'r ateb buddugol"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ dependencies {
|
|||
implementation(projects.features.rageshake.api)
|
||||
implementation(projects.features.lockscreen.api)
|
||||
implementation(projects.features.analytics.api)
|
||||
implementation(projects.features.ftue.api)
|
||||
implementation(projects.features.licenses.api)
|
||||
implementation(projects.features.logout.api)
|
||||
implementation(projects.features.deactivation.api)
|
||||
|
|
@ -101,7 +100,6 @@ dependencies {
|
|||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.features.ftue.test)
|
||||
testImplementation(projects.features.invite.test)
|
||||
testImplementation(projects.features.rageshake.test)
|
||||
testImplementation(projects.features.logout.test)
|
||||
|
|
|
|||
|
|
@ -208,6 +208,10 @@ class PreferencesFlowNode(
|
|||
navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
override fun openIgnoredUsers() {
|
||||
backstack.push(NavTarget.BlockedUsers)
|
||||
}
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import coil3.SingletonImageLoader
|
|||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.Provider
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.preferences.impl.DefaultCacheService
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
|
|
@ -36,7 +35,6 @@ class DefaultClearCacheUseCase(
|
|||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val defaultCacheService: DefaultCacheService,
|
||||
private val okHttpClient: Provider<OkHttpClient>,
|
||||
private val ftueService: FtueService,
|
||||
private val pushService: PushService,
|
||||
private val seenInvitesStore: SeenInvitesStore,
|
||||
private val activeRoomsHolder: ActiveRoomsHolder,
|
||||
|
|
@ -56,7 +54,6 @@ class DefaultClearCacheUseCase(
|
|||
// Clear app cache
|
||||
context.cacheDir.deleteRecursively()
|
||||
// Clear some settings
|
||||
ftueService.reset()
|
||||
seenInvitesStore.clear()
|
||||
// Ensure any error will be displayed again
|
||||
pushService.setIgnoreRegistrationError(matrixClient.sessionId, false)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@
|
|||
<string name="screen_advanced_settings_media_compression_description">"Llwythwch i fyny lluniau a fideos yn gynt a lleihau\'r defnydd o ddata"</string>
|
||||
<string name="screen_advanced_settings_media_compression_title">"Optimeiddio ansawdd y cyfryngau"</string>
|
||||
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Cymedroli a Diogelwch"</string>
|
||||
<string name="screen_advanced_settings_optimise_image_upload_quality_description">"Optimeiddio delweddau\'n awtomatig ar gyfer llwytho cyflymach a meintiau ffeiliau llai."</string>
|
||||
<string name="screen_advanced_settings_optimise_image_upload_quality_title">"Optimeiddio ansawdd llwytho delweddau"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_description">"%1$s Tapiwch yma i newid."</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_high">"Uchel (1080p)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_low">"Isel (480c)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_standard">"Safonol (720p)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_title">"Ansawdd lwytho fideo"</string>
|
||||
<string name="screen_advanced_settings_push_provider_android">"Darparwr hysbysiad gwthio"</string>
|
||||
<string name="screen_advanced_settings_rich_text_editor_description">"Analluogi\'r golygydd testun cyfoethog i deipio Markdown â llaw."</string>
|
||||
<string name="screen_advanced_settings_send_read_receipts">"Derbynebau darllen"</string>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
<string name="screen_advanced_settings_optimise_image_upload_quality_description">"Optimoi kuvat automaattisesti nopeampia lähetysnopeuksia ja pienempiä tiedostokokoja varten."</string>
|
||||
<string name="screen_advanced_settings_optimise_image_upload_quality_title">"Optimoi kuvien lähetyslaatu"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_description">"%1$s. Napauta tästä vaihtaaksesi."</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_high">"Korkea (1080p)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_low">"Matala (480p)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_standard">"Normaali (720p)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_title">"Videon lähetyslaatu"</string>
|
||||
<string name="screen_advanced_settings_push_provider_android">"Push-ilmoitusten tarjoaja"</string>
|
||||
<string name="screen_advanced_settings_rich_text_editor_description">"Ota rikastettu tekstieditori pois käytöstä, jotta voit kirjoittaa Markdownia manuaalisesti."</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@
|
|||
<string name="screen_advanced_settings_media_compression_description">"上傳照片與影片更快且減少資料使用量"</string>
|
||||
<string name="screen_advanced_settings_media_compression_title">"最佳化媒體品質"</string>
|
||||
<string name="screen_advanced_settings_moderation_and_safety_section_title">"管理與安全"</string>
|
||||
<string name="screen_advanced_settings_optimise_image_upload_quality_description">"自動最佳化影像以提供更快的上傳速度與較小的檔案大小。"</string>
|
||||
<string name="screen_advanced_settings_optimise_image_upload_quality_title">"最佳化影像上傳品質"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_description">"%1$s。輕點此處以變更。"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_high">"高 (1080p)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_low">"低 (480p)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_standard">"標準 (720p)"</string>
|
||||
<string name="screen_advanced_settings_optimise_video_upload_quality_title">"視訊上傳品質"</string>
|
||||
<string name="screen_advanced_settings_push_provider_android">"推播通知提供者"</string>
|
||||
<string name="screen_advanced_settings_rich_text_editor_description">"手動輸入 Markdown,停用格式化文字編輯器。"</string>
|
||||
<string name="screen_advanced_settings_send_read_receipts">"已讀回條"</string>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ package io.element.android.features.preferences.impl.tasks
|
|||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.ftue.test.FakeFtueService
|
||||
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||
import io.element.android.features.preferences.impl.DefaultCacheService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
|
@ -41,10 +40,6 @@ class DefaultClearCacheUseCaseTest {
|
|||
clearCacheLambda = clearCacheLambda,
|
||||
)
|
||||
val defaultCacheService = DefaultCacheService()
|
||||
val resetFtueLambda = lambdaRecorder<Unit> { }
|
||||
val ftueService = FakeFtueService(
|
||||
resetLambda = resetFtueLambda,
|
||||
)
|
||||
val setIgnoreRegistrationErrorLambda = lambdaRecorder<SessionId, Boolean, Unit> { _, _ -> }
|
||||
val resetBatteryOptimizationStateResult = lambdaRecorder<Unit> { }
|
||||
val pushService = FakePushService(
|
||||
|
|
@ -59,7 +54,6 @@ class DefaultClearCacheUseCaseTest {
|
|||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
defaultCacheService = defaultCacheService,
|
||||
okHttpClient = { OkHttpClient.Builder().build() },
|
||||
ftueService = ftueService,
|
||||
pushService = pushService,
|
||||
seenInvitesStore = seenInvitesStore,
|
||||
activeRoomsHolder = activeRoomsHolder,
|
||||
|
|
@ -67,7 +61,6 @@ class DefaultClearCacheUseCaseTest {
|
|||
defaultCacheService.clearedCacheEventFlow.test {
|
||||
sut.invoke()
|
||||
clearCacheLambda.assertions().isCalledOnce()
|
||||
resetFtueLambda.assertions().isCalledOnce()
|
||||
setIgnoreRegistrationErrorLambda.assertions().isCalledOnce()
|
||||
.with(value(matrixClient.sessionId), value(false))
|
||||
resetBatteryOptimizationStateResult.assertions().isCalledOnce()
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
<string name="screen_room_details_error_loading_notification_settings">"Při načítání nastavení oznámení došlo k chybě."</string>
|
||||
<string name="screen_room_details_error_muting">"Ztišení této místnosti se nezdařilo, zkuste to prosím znovu."</string>
|
||||
<string name="screen_room_details_error_unmuting">"Nepodařilo se zrušit ztišení této místnosti, zkuste to prosím znovu."</string>
|
||||
<string name="screen_room_details_invite_people_dont_close">"Nezavírejte aplikaci, dokud neskončíte."</string>
|
||||
<string name="screen_room_details_invite_people_preparing">"Příprava pozvánek…"</string>
|
||||
<string name="screen_room_details_invite_people_title">"Pozvat přátele"</string>
|
||||
<string name="screen_room_details_leave_conversation_title">"Opustit konverzaci"</string>
|
||||
<string name="screen_room_details_leave_room_title">"Opustit místnost"</string>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<string name="screen_polls_history_title">"Pleidleisiau"</string>
|
||||
<string name="screen_room_change_permissions_administrators">"Gweinyddwyr yn unig"</string>
|
||||
<string name="screen_room_change_permissions_ban_people">"Gwahardd pobl"</string>
|
||||
<string name="screen_room_change_permissions_delete_messages">"Dileu negeseuon"</string>
|
||||
<string name="screen_room_change_permissions_delete_messages">"Tynnu negeseuon"</string>
|
||||
<string name="screen_room_change_permissions_everyone">"Pawb"</string>
|
||||
<string name="screen_room_change_permissions_invite_people">"Gwahodd pobl a derbyn ceisiadau i ymuno"</string>
|
||||
<string name="screen_room_change_permissions_member_moderation">"Cymedroli aelodau"</string>
|
||||
|
|
@ -22,13 +22,17 @@
|
|||
<string name="screen_room_change_role_administrators_title">"Golygu Gweinyddwyr"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_description">"Fyddwch chi ddim yn gallu dadwneud y weithred hon. Rydych chi\'n hyrwyddo\'r defnyddiwr i gael yr un lefel pŵer â chi."</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"Ychwanegu Gweinyddwr?"</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_description">"Fyddwch chi ddim yn gallu dadwneud y weithred hon. Rydych yn trosglwyddo\'r berchnogaeth i\'r defnyddwyr a ddewiswyd. Unwaith y byddwch yn gadael bydd hyn yn barhaol."</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_title">"Trosglwyddo perchnogaeth?"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_action">"Gostwng"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_description">"Fyddwch chi ddim yn gallu dadwneud y newid hwn gan eich bod yn israddio eich hun, os mai chi yw\'r defnyddiwr breintiedig olaf yn yr ystafell bydd yn amhosibl adennill breintiau."</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"Israddio eich hun?"</string>
|
||||
<string name="screen_room_change_role_invited_member_name">"%1$s (Yn aros)"</string>
|
||||
<string name="screen_room_change_role_invited_member_name_android">"Yn aros"</string>
|
||||
<string name="screen_room_change_role_moderators_admin_section_footer">"Mae gan weinyddwyr freintiau cymedrolwr yn awtomatig"</string>
|
||||
<string name="screen_room_change_role_moderators_owner_section_footer">"Mae gan berchnogion freintiau gweinyddwr yn awtomatig."</string>
|
||||
<string name="screen_room_change_role_moderators_title">"Golygu Cymedrolwyr"</string>
|
||||
<string name="screen_room_change_role_owners_title">"Dewiswch Berchnogion"</string>
|
||||
<string name="screen_room_change_role_section_administrators">"Gweinyddwyr"</string>
|
||||
<string name="screen_room_change_role_section_moderators">"Cymedrolwyr"</string>
|
||||
<string name="screen_room_change_role_section_users">"Aelodau"</string>
|
||||
|
|
@ -46,6 +50,8 @@
|
|||
<string name="screen_room_details_error_loading_notification_settings">"Digwyddodd gwall wrth lwytho gosodiadau hysbysu."</string>
|
||||
<string name="screen_room_details_error_muting">"Wedi methu tewi\'r ystafell hon, ceisiwch eto."</string>
|
||||
<string name="screen_room_details_error_unmuting">"Wedi methu dad-dewi\'r ystafell hon, ceisiwch eto."</string>
|
||||
<string name="screen_room_details_invite_people_dont_close">"Peidiwch â chau\'r ap nes ei fod wedi gorffen."</string>
|
||||
<string name="screen_room_details_invite_people_preparing">"Wrthi\'n paratoi gwahoddiadau…"</string>
|
||||
<string name="screen_room_details_invite_people_title">"Gwahodd pobl"</string>
|
||||
<string name="screen_room_details_leave_conversation_title">"Gadael y sgwrs"</string>
|
||||
<string name="screen_room_details_leave_room_title">"Gadael yr ystafell"</string>
|
||||
|
|
@ -83,6 +89,7 @@
|
|||
<string name="screen_room_member_list_pending_header_title">"Dan ystyriaeth"</string>
|
||||
<string name="screen_room_member_list_role_administrator">"Gweinyddwr"</string>
|
||||
<string name="screen_room_member_list_role_moderator">"Cymedrolwr"</string>
|
||||
<string name="screen_room_member_list_role_owner">"Perchennog"</string>
|
||||
<string name="screen_room_member_list_room_members_header_title">"Aelodau\'r ystafell"</string>
|
||||
<string name="screen_room_member_list_unbanning_user">"Dad-wahardd %1$s"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom">"Caniatáu gosodiad personol"</string>
|
||||
|
|
@ -100,12 +107,14 @@
|
|||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Crybwylliadau ac Allweddeiriau\'n unig"</string>
|
||||
<string name="screen_room_notification_settings_room_custom_settings_title">"Yn yr ystafell hon, rhowch wybod i mi am"</string>
|
||||
<string name="screen_room_roles_and_permissions_admins">"Gweinyddwyr"</string>
|
||||
<string name="screen_room_roles_and_permissions_admins_and_owners">"Gweinyddwyr a pherchnogion"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_my_role">"Newid fy rôl"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_role_demote_to_member">"Israddio aelod"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_role_demote_to_moderator">"Israddio cymedrolwr"</string>
|
||||
<string name="screen_room_roles_and_permissions_member_moderation">"Cymedroli aelodau"</string>
|
||||
<string name="screen_room_roles_and_permissions_messages_and_content">"Negeseuon a chynnwys"</string>
|
||||
<string name="screen_room_roles_and_permissions_moderators">"Cymedrolwyr"</string>
|
||||
<string name="screen_room_roles_and_permissions_owners">"Perchnogion"</string>
|
||||
<string name="screen_room_roles_and_permissions_permissions_header">"Caniatâd"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset">"Ailosod caniatâd"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset_confirm_description">"Ar ôl i chi ailosod caniatâd, byddwch yn colli\'r gosodiadau cyfredol."</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_details_encryption_enabled_subtitle">"Messages are secured with locks. Only you and the recipients can unlock them."</string>
|
||||
</resources>
|
||||
|
|
@ -21,12 +21,12 @@
|
|||
<string name="screen_room_change_permissions_send_messages">"Viestien lähettäminen"</string>
|
||||
<string name="screen_room_change_role_administrators_title">"Muokkaa ylläpitäjiä"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_description">"Et voi peruuttaa tätä toimenpidettä. Ylennät käyttäjän samalle oikeustasolle kuin sinä."</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"Lisää ylläpitäjä?"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"Lisätäänkö ylläpitäjä?"</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_description">"Et voi kumota tätä toimintoa. Olet siirtämässä omistajuuden valituille käyttäjille. Kun poistut, muutos on pysyvä."</string>
|
||||
<string name="screen_room_change_role_confirm_change_owners_title">"Siirretäänkö omistajuus?"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_action">"Alenna"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_description">"Et voi perua tätä muutosta, koska olet alentamassa itseäsi. Jos olet viimeinen oikeutettu henkilö tässä huoneessa, oikeuksia ei voi enää saada takaisin."</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"Alenna itsesi?"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"Haluatko alentaa itsesi?"</string>
|
||||
<string name="screen_room_change_role_invited_member_name">"%1$s (Kutsuttu)"</string>
|
||||
<string name="screen_room_change_role_invited_member_name_android">"(Kutsuttu)"</string>
|
||||
<string name="screen_room_change_role_moderators_admin_section_footer">"Ylläpitäjillä on automaattisesti valvojan oikeudet"</string>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
<string name="screen_room_change_role_section_moderators">"Valvojat"</string>
|
||||
<string name="screen_room_change_role_section_users">"Jäsenet"</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_description">"Sinulla on tallentamattomia muutoksia"</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_title">"Tallenna muutokset?"</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_title">"Tallennetaanko muutokset?"</string>
|
||||
<string name="screen_room_details_add_topic_title">"Lisää aihe"</string>
|
||||
<string name="screen_room_details_badge_encrypted">"Salattu"</string>
|
||||
<string name="screen_room_details_badge_not_encrypted">"Ei salattu"</string>
|
||||
|
|
@ -50,6 +50,8 @@
|
|||
<string name="screen_room_details_error_loading_notification_settings">"Ilmoitusasetuksia ladattaessa tapahtui virhe."</string>
|
||||
<string name="screen_room_details_error_muting">"Tämän huoneen mykistäminen epäonnistui, yritä uudelleen."</string>
|
||||
<string name="screen_room_details_error_unmuting">"Tämän huoneen mykistyksen poistaminen epäonnistui, yritä uudelleen."</string>
|
||||
<string name="screen_room_details_invite_people_dont_close">"Älä sulje sovellusta ennen kuin se on valmis."</string>
|
||||
<string name="screen_room_details_invite_people_preparing">"Valmistellaan kutsuja…"</string>
|
||||
<string name="screen_room_details_invite_people_title">"Kutsu ihmisiä"</string>
|
||||
<string name="screen_room_details_leave_conversation_title">"Poistu keskustelusta"</string>
|
||||
<string name="screen_room_details_leave_room_title">"Poistu huoneesta"</string>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
<string name="screen_room_details_error_loading_notification_settings">"Une erreur s’est produite lors du chargement des paramètres de notification."</string>
|
||||
<string name="screen_room_details_error_muting">"Échec de la mise en sourdine de ce salon, veuillez réessayer."</string>
|
||||
<string name="screen_room_details_error_unmuting">"Échec de la désactivation de la mise en sourdine de ce salon, veuillez réessayer."</string>
|
||||
<string name="screen_room_details_invite_people_preparing">"Préparation des invitations…"</string>
|
||||
<string name="screen_room_details_invite_people_title">"Inviter des amis"</string>
|
||||
<string name="screen_room_details_leave_conversation_title">"Quitter la discussion"</string>
|
||||
<string name="screen_room_details_leave_room_title">"Quitter le salon"</string>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
<string name="screen_room_details_error_loading_notification_settings">"載入通知設定時發生錯誤。"</string>
|
||||
<string name="screen_room_details_error_muting">"無法關閉聊天室通知,請再試一次。"</string>
|
||||
<string name="screen_room_details_error_unmuting">"無法開啟聊天室通知,請再試一次。"</string>
|
||||
<string name="screen_room_details_invite_people_dont_close">"完成前請勿關閉應用程式。"</string>
|
||||
<string name="screen_room_details_invite_people_preparing">"正在準備邀請……"</string>
|
||||
<string name="screen_room_details_invite_people_title">"邀請夥伴"</string>
|
||||
<string name="screen_room_details_leave_conversation_title">"離開對話"</string>
|
||||
<string name="screen_room_details_leave_room_title">"離開聊天室"</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_chat_backup_key_backup_action_disable">"Delete message backup"</string>
|
||||
<string name="screen_chat_backup_key_backup_description">"Store your account security and messages securely on the server. This will allow you to view your message history on any new devices. %1$s."</string>
|
||||
<string name="screen_chat_backup_key_backup_title">"Message backup"</string>
|
||||
<string name="screen_chat_backup_key_storage_disabled_error">"Turn on message backup to set it up."</string>
|
||||
<string name="screen_chat_backup_key_storage_toggle_description">"Upload messages from this device"</string>
|
||||
<string name="screen_chat_backup_key_storage_toggle_title">"Allow message backup"</string>
|
||||
<string name="screen_chat_backup_recovery_action_change">"Change backup password"</string>
|
||||
<string name="screen_chat_backup_recovery_action_change_description">"Restore your account security and message history with a backup password if you\'ve lost all your existing devices."</string>
|
||||
<string name="screen_chat_backup_recovery_action_confirm">"Enter backup password"</string>
|
||||
<string name="screen_chat_backup_recovery_action_confirm_description">"Your message backup is currently out of sync."</string>
|
||||
<string name="screen_chat_backup_recovery_action_setup">"Set up backup"</string>
|
||||
<string name="screen_create_new_recovery_key_list_item_3">"When asked to confirm your device, select %1$s"</string>
|
||||
<string name="screen_create_new_recovery_key_list_item_4">"Follow the instructions to create a new backup password"</string>
|
||||
<string name="screen_create_new_recovery_key_list_item_5">"Save your new backup password in a password manager or encrypted note"</string>
|
||||
<string name="screen_encryption_reset_bullet_3">"You will need to confirm all your existing devices and verify contacts again"</string>
|
||||
<string name="screen_encryption_reset_footer">"Only start fresh if you don\'t have access to another signed-in device and you\'ve lost your backup password."</string>
|
||||
<string name="screen_encryption_reset_title">"Can\'t confirm? You\'ll need to start fresh."</string>
|
||||
<string name="screen_key_backup_disable_description">"Deleting message backup will remove your account security and messages from the server and turn off the following security features:"</string>
|
||||
<string name="screen_key_backup_disable_title">"Are you sure you want to turn off message backup and delete it?"</string>
|
||||
<string name="screen_recovery_key_change_description">"Get a new backup password if you\'ve lost your existing one. After changing your backup password, your old one will no longer work."</string>
|
||||
<string name="screen_recovery_key_change_generate_key">"Generate a new backup password"</string>
|
||||
<string name="screen_recovery_key_change_success">"Backup password changed"</string>
|
||||
<string name="screen_recovery_key_change_title">"Change backup password?"</string>
|
||||
<string name="screen_recovery_key_confirm_error_content">"Please try again to confirm access to your message backup."</string>
|
||||
<string name="screen_recovery_key_confirm_error_title">"Incorrect backup password"</string>
|
||||
<string name="screen_recovery_key_confirm_key_description">"You might have seen the terms \"recovery key\", \"security key\" or \"security phrase\" instead of \"backup password\". Don\'t worry, this is all the same."</string>
|
||||
<string name="screen_recovery_key_confirm_success">"Backup password confirmed"</string>
|
||||
<string name="screen_recovery_key_confirm_title">"Enter your backup password"</string>
|
||||
<string name="screen_recovery_key_copied_to_clipboard">"Copied backup password"</string>
|
||||
<string name="screen_recovery_key_save_action">"Save backup password"</string>
|
||||
<string name="screen_recovery_key_save_description">"Write down this backup password somewhere safe, like a password manager, encrypted note, or a physical safe."</string>
|
||||
<string name="screen_recovery_key_save_key_description">"Tap to copy backup password"</string>
|
||||
<string name="screen_recovery_key_save_title">"Save your backup password somewhere safe"</string>
|
||||
<string name="screen_recovery_key_setup_confirmation_description">"You will not be able to access your new backup password after this step."</string>
|
||||
<string name="screen_recovery_key_setup_confirmation_title">"Have you saved your backup password?"</string>
|
||||
<string name="screen_recovery_key_setup_description">"Your message backup is protected by a backup password. If you need a new backup password after setup, you can recreate it by selecting ‘Change backup password’."</string>
|
||||
<string name="screen_recovery_key_setup_generate_key">"Generate your backup password"</string>
|
||||
<string name="screen_recovery_key_setup_success">"Backup setup successful"</string>
|
||||
<string name="screen_recovery_key_setup_title">"Set up backup"</string>
|
||||
<string name="screen_reset_encryption_confirmation_alert_title">"Are you sure you want to start fresh?"</string>
|
||||
<string name="screen_reset_encryption_password_subtitle">"Confirm that you want to start fresh."</string>
|
||||
</resources>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
<string name="screen_recovery_key_change_generate_key">"Luo uusi palautusavain"</string>
|
||||
<string name="screen_recovery_key_change_generate_key_description">"Älä jaa tätä kenenkään kanssa!"</string>
|
||||
<string name="screen_recovery_key_change_success">"Palautusavain vaihdettu"</string>
|
||||
<string name="screen_recovery_key_change_title">"Vaihda palautusavain?"</string>
|
||||
<string name="screen_recovery_key_change_title">"Vaihdetaanko palautusavain?"</string>
|
||||
<string name="screen_recovery_key_confirm_create_new_recovery_key">"Luo uusi palautusavain"</string>
|
||||
<string name="screen_recovery_key_confirm_description">"Varmista, ettei kukaan näe tätä ruutua!"</string>
|
||||
<string name="screen_recovery_key_confirm_error_content">"Yritä uudelleen vahvistaaksesi pääsyn avainten säilytykseen."</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Create a new backup password"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Confirm this device to set up secure messaging."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirm it\'s you"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Use backup password"</string>
|
||||
<string name="screen_identity_confirmed_title">"Device confirmed"</string>
|
||||
<string name="screen_session_verification_complete_user_subtitle">"Now you can trust this user when sending or receiving messages."</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Enter backup password"</string>
|
||||
<string name="screen_session_verification_request_success_title">"Device confirmed"</string>
|
||||
<string name="screen_session_verification_use_another_device_title">"Open the app on another confirmed device"</string>
|
||||
<string name="screen_session_verification_user_responder_subtitle">"For extra security, another user wants to verify you. You\'ll be shown a set of emojis to compare."</string>
|
||||
</resources>
|
||||
|
|
@ -219,7 +219,7 @@ inject = "javax.inject:javax.inject:1"
|
|||
metro_runtime = { module = "dev.zacsweers.metro:runtime", version.ref = "metro" }
|
||||
|
||||
# Element Call
|
||||
element_call_embedded = "io.element.android:element-call-embedded:0.15.0"
|
||||
element_call_embedded = "io.element.android:element-call-embedded:0.16.0-rc.4"
|
||||
|
||||
# Auto services
|
||||
google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" }
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ class RustTimeline(
|
|||
)
|
||||
|
||||
override val forwardPaginationStatus = MutableStateFlow(
|
||||
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode !is Timeline.Mode.FocusedOnEvent)
|
||||
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode is Timeline.Mode.FocusedOnEvent)
|
||||
)
|
||||
|
||||
init {
|
||||
|
|
@ -221,7 +221,6 @@ class RustTimeline(
|
|||
items = items,
|
||||
hasMoreToLoadBackward = backwardPaginationStatus.hasMoreToLoad,
|
||||
hasMoreToLoadForward = forwardPaginationStatus.hasMoreToLoad,
|
||||
timelineMode = mode,
|
||||
)
|
||||
}
|
||||
.let { items ->
|
||||
|
|
|
|||
|
|
@ -18,9 +18,8 @@ class LoadingIndicatorsPostProcessor(private val systemClock: SystemClock) {
|
|||
items: List<MatrixTimelineItem>,
|
||||
hasMoreToLoadBackward: Boolean,
|
||||
hasMoreToLoadForward: Boolean,
|
||||
timelineMode: Timeline.Mode,
|
||||
): List<MatrixTimelineItem> {
|
||||
val shouldAddForwardLoadingIndicator = timelineMode is Timeline.Mode.Live && hasMoreToLoadForward && items.isNotEmpty()
|
||||
val shouldAddForwardLoadingIndicator = hasMoreToLoadForward && items.isNotEmpty()
|
||||
val currentTimestamp = systemClock.epochMillis()
|
||||
return buildList {
|
||||
if (hasMoreToLoadBackward) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ class LoadingIndicatorsPostProcessorTest {
|
|||
items = listOf(messageEvent, messageEvent2),
|
||||
hasMoreToLoadBackward = true,
|
||||
hasMoreToLoadForward = false,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
assertThat(result).containsExactly(
|
||||
MatrixTimelineItem.Virtual(
|
||||
|
|
@ -47,7 +46,6 @@ class LoadingIndicatorsPostProcessorTest {
|
|||
items = listOf(messageEvent, messageEvent2),
|
||||
hasMoreToLoadBackward = false,
|
||||
hasMoreToLoadForward = true,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
assertThat(result).containsExactly(
|
||||
messageEvent,
|
||||
|
|
@ -70,7 +68,6 @@ class LoadingIndicatorsPostProcessorTest {
|
|||
items = listOf(messageEvent, messageEvent2),
|
||||
hasMoreToLoadBackward = true,
|
||||
hasMoreToLoadForward = true,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
assertThat(result).containsExactly(
|
||||
MatrixTimelineItem.Virtual(
|
||||
|
|
@ -100,7 +97,6 @@ class LoadingIndicatorsPostProcessorTest {
|
|||
items = listOf(),
|
||||
hasMoreToLoadBackward = true,
|
||||
hasMoreToLoadForward = true,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
assertThat(result).containsExactly(
|
||||
MatrixTimelineItem.Virtual(
|
||||
|
|
|
|||
|
|
@ -16,6 +16,4 @@ interface PermissionStateProvider {
|
|||
|
||||
suspend fun setPermissionAsked(permission: String, value: Boolean)
|
||||
fun isPermissionAsked(permission: String): Flow<Boolean>
|
||||
|
||||
suspend fun resetPermission(permission: String)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,5 +43,6 @@ dependencies {
|
|||
testCommonDependencies(libs)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.troubleshoot.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,4 @@ class DefaultPermissionStateProvider(
|
|||
override suspend fun setPermissionAsked(permission: String, value: Boolean) = permissionsStore.setPermissionAsked(permission, value)
|
||||
|
||||
override fun isPermissionAsked(permission: String): Flow<Boolean> = permissionsStore.isPermissionAsked(permission)
|
||||
|
||||
override suspend fun resetPermission(permission: String) = permissionsStore.resetPermission(permission)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import dev.zacsweers.metro.Inject
|
|||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.permissions.impl.R
|
||||
import io.element.android.libraries.permissions.impl.action.PermissionActions
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
|
|
@ -54,7 +55,10 @@ class NotificationTroubleshootCheckPermissionTest(
|
|||
|
||||
override suspend fun reset() = delegate.reset()
|
||||
|
||||
override suspend fun quickFix(coroutineScope: CoroutineScope) {
|
||||
override suspend fun quickFix(
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
// Do not bother about asking the permission inline, just lead the user to the settings
|
||||
permissionActions.openSettings()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@
|
|||
package io.element.android.libraries.permissions.impl.troubleshoot
|
||||
|
||||
import android.os.Build
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.permissions.impl.action.FakePermissionActions
|
||||
import io.element.android.libraries.permissions.test.FakePermissionStateProvider
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -28,10 +29,7 @@ class NotificationTroubleshootCheckPermissionTestTest {
|
|||
permissionActions = FakePermissionActions(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
@ -47,10 +45,7 @@ class NotificationTroubleshootCheckPermissionTestTest {
|
|||
permissionActions = FakePermissionActions(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
@ -74,17 +69,14 @@ class NotificationTroubleshootCheckPermissionTestTest {
|
|||
permissionActions = actions,
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
// Quick fix
|
||||
launch {
|
||||
sut.quickFix(this)
|
||||
backgroundScope.launch {
|
||||
sut.quickFix(this, FakeNotificationTroubleshootNavigator())
|
||||
// Run the test again (IRL it will be done thanks to the resuming of the application)
|
||||
sut.run(this)
|
||||
}
|
||||
|
|
@ -109,13 +101,10 @@ class NotificationTroubleshootCheckPermissionTestTest {
|
|||
permissionActions = actions,
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ class FakePermissionStateProvider(
|
|||
private var permissionGranted: Boolean = true,
|
||||
permissionDenied: Boolean = false,
|
||||
permissionAsked: Boolean = false,
|
||||
private val resetPermissionLambda: (String) -> Unit = {},
|
||||
) : PermissionStateProvider {
|
||||
private val permissionDeniedFlow = MutableStateFlow(permissionDenied)
|
||||
private val permissionAskedFlow = MutableStateFlow(permissionAsked)
|
||||
|
|
@ -37,10 +36,4 @@ class FakePermissionStateProvider(
|
|||
}
|
||||
|
||||
override fun isPermissionAsked(permission: String): Flow<Boolean> = permissionAskedFlow
|
||||
|
||||
override suspend fun resetPermission(permission: String) {
|
||||
setPermissionAsked(permission, false)
|
||||
setPermissionDenied(permission, false)
|
||||
resetPermissionLambda(permission)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ dependencies {
|
|||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushproviders.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.libraries.troubleshoot.test)
|
||||
testImplementation(projects.features.call.test)
|
||||
testImplementation(projects.features.lockscreen.test)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class CurrentPushProviderTest(
|
|||
} else {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_failure),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.libraries.push.impl.troubleshoot
|
||||
|
||||
import dev.zacsweers.metro.ContributesIntoSet
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@ContributesIntoSet(SessionScope::class)
|
||||
@Inject
|
||||
class IgnoredUsersTest(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val stringProvider: StringProvider,
|
||||
) : NotificationTroubleshootTest {
|
||||
override val order = 80
|
||||
private val delegate = NotificationTroubleshootTestDelegate(
|
||||
defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_blocked_users_title),
|
||||
defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_blocked_users_description),
|
||||
fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY,
|
||||
)
|
||||
override val state: StateFlow<NotificationTroubleshootTestState> = delegate.state
|
||||
|
||||
override suspend fun run(coroutineScope: CoroutineScope) {
|
||||
delegate.start()
|
||||
val ignorerUsers = matrixClient.ignoredUsersFlow.value
|
||||
if (ignorerUsers.isEmpty()) {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_blocked_users_result_none),
|
||||
status = NotificationTroubleshootTestState.Status.Success,
|
||||
)
|
||||
} else {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getQuantityString(
|
||||
R.plurals.troubleshoot_notifications_test_blocked_users_result_some,
|
||||
ignorerUsers.size,
|
||||
ignorerUsers.size
|
||||
),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(
|
||||
hasQuickFix = true,
|
||||
isCritical = false,
|
||||
quickFixButtonString = stringProvider.getString(R.string.troubleshoot_notifications_test_blocked_users_quick_fix),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun quickFix(
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
navigator.openIgnoredUsers()
|
||||
}
|
||||
|
||||
override suspend fun reset() = delegate.reset()
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ class NotificationTest(
|
|||
} else {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_permission_failure),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ class NotificationTest(
|
|||
notificationDisplayer.dismissDiagnosticNotification()
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_failure),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import dev.zacsweers.metro.Inject
|
|||
import io.element.android.libraries.push.api.PushService
|
||||
import io.element.android.libraries.push.api.gateway.PushGatewayFailure
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
|
|
@ -56,7 +57,7 @@ class PushLoopbackTest(
|
|||
val hasQuickFix = pushService.getCurrentPushProvider()?.canRotateToken() == true
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_1),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix)
|
||||
status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = hasQuickFix)
|
||||
)
|
||||
job.cancel()
|
||||
return
|
||||
|
|
@ -64,7 +65,7 @@ class PushLoopbackTest(
|
|||
Timber.e(e, "Failed to test push")
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_2, e.message),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
job.cancel()
|
||||
return
|
||||
|
|
@ -72,7 +73,7 @@ class PushLoopbackTest(
|
|||
if (!testPushResult) {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_3),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
job.cancel()
|
||||
return
|
||||
|
|
@ -93,13 +94,16 @@ class PushLoopbackTest(
|
|||
job.cancel()
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_4),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun quickFix(coroutineScope: CoroutineScope) {
|
||||
override suspend fun quickFix(
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
delegate.start()
|
||||
pushService.getCurrentPushProvider()?.rotateToken()
|
||||
run(coroutineScope)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class PushProvidersTest(
|
|||
} else {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_detect_push_provider_failure),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,14 @@
|
|||
<string name="push_distributor_background_sync_android">"Background synchronization"</string>
|
||||
<string name="push_distributor_firebase_android">"Google Services"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"No valid Google Play Services found. Notifications may not work properly."</string>
|
||||
<string name="troubleshoot_notifications_test_blocked_users_description">"Checking blocked users"</string>
|
||||
<string name="troubleshoot_notifications_test_blocked_users_quick_fix">"View blocked users"</string>
|
||||
<string name="troubleshoot_notifications_test_blocked_users_result_none">"No users are blocked."</string>
|
||||
<plurals name="troubleshoot_notifications_test_blocked_users_result_some">
|
||||
<item quantity="one">"You blocked %1$d user. You will not receive notifications for this user."</item>
|
||||
<item quantity="other">"You blocked %1$d users. You will not receive notifications for these users."</item>
|
||||
</plurals>
|
||||
<string name="troubleshoot_notifications_test_blocked_users_title">"Blocked users"</string>
|
||||
<string name="troubleshoot_notifications_test_current_push_provider_description">"Get the name of the current provider."</string>
|
||||
<string name="troubleshoot_notifications_test_current_push_provider_failure">"No push providers selected."</string>
|
||||
<string name="troubleshoot_notifications_test_current_push_provider_success">"Current push provider: %1$s."</string>
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@
|
|||
|
||||
package io.element.android.libraries.push.impl.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.push.test.FakeGetCurrentPushProvider
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -23,10 +22,7 @@ class CurrentPushProviderTestTest {
|
|||
getCurrentPushProvider = FakeGetCurrentPushProvider("foo"),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
@ -41,14 +37,11 @@ class CurrentPushProviderTestTest {
|
|||
getCurrentPushProvider = FakeGetCurrentPushProvider(null),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.libraries.push.impl.troubleshoot
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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.FakeMatrixClient
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class IgnoredUsersTestTest {
|
||||
@Test
|
||||
fun `test IgnoredUsersTest order`() = runTest {
|
||||
val sut = IgnoredUsersTest(
|
||||
matrixClient = FakeMatrixClient(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
assertThat(sut.order).isEqualTo(80)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test IgnoredUsersTest quick fix`() = runTest {
|
||||
val sut = IgnoredUsersTest(
|
||||
matrixClient = FakeMatrixClient(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
val openIgnoredUsersResult = lambdaRecorder<Unit> {}
|
||||
val navigator = object : NotificationTroubleshootNavigator {
|
||||
override fun openIgnoredUsers() = openIgnoredUsersResult()
|
||||
}
|
||||
sut.quickFix(
|
||||
coroutineScope = backgroundScope,
|
||||
navigator = navigator,
|
||||
)
|
||||
openIgnoredUsersResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test IgnoredUsersTest with no blocked users`() = runTest {
|
||||
val sut = IgnoredUsersTest(
|
||||
matrixClient = FakeMatrixClient(
|
||||
ignoredUsersFlow = MutableStateFlow(persistentListOf())
|
||||
),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test IgnoredUsersTest with blocked users`() = runTest {
|
||||
val sut = IgnoredUsersTest(
|
||||
matrixClient = FakeMatrixClient(
|
||||
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID, A_USER_ID_2))
|
||||
),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
val lastStatus = lastItem.status as NotificationTroubleshootTestState.Status.Failure
|
||||
assertThat(lastStatus.hasQuickFix).isTrue()
|
||||
assertThat(lastStatus.isCritical).isFalse()
|
||||
assertThat(lastStatus.quickFixButtonString).isNotNull()
|
||||
assertThat(lastItem.description).contains("2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,14 +7,13 @@
|
|||
|
||||
package io.element.android.libraries.push.impl.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -31,10 +30,7 @@ class NotificationTestTest {
|
|||
fun `test NotificationTest notification cannot be displayed`() = runTest {
|
||||
fakeNotificationDisplayer.displayDiagnosticNotificationResult = lambdaRecorder { _ -> false }
|
||||
val sut = createNotificationTest()
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isInstanceOf(NotificationTroubleshootTestState.Status.Failure::class.java)
|
||||
|
|
@ -44,10 +40,7 @@ class NotificationTestTest {
|
|||
@Test
|
||||
fun `test NotificationTest user does not click on notification`() = runTest {
|
||||
val sut = createNotificationTest()
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.WaitingForUser)
|
||||
|
|
@ -60,10 +53,7 @@ class NotificationTestTest {
|
|||
@Test
|
||||
fun `test NotificationTest user clicks on notification`() = runTest {
|
||||
val sut = createNotificationTest()
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.WaitingForUser)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
package io.element.android.libraries.push.impl.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_FAILURE_REASON
|
||||
|
|
@ -15,10 +14,11 @@ import io.element.android.libraries.push.api.gateway.PushGatewayFailure
|
|||
import io.element.android.libraries.push.test.FakePushService
|
||||
import io.element.android.libraries.pushproviders.test.FakePushProvider
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -32,14 +32,11 @@ class PushLoopbackTestTest {
|
|||
clock = FakeSystemClock(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,14 +53,11 @@ class PushLoopbackTestTest {
|
|||
clock = FakeSystemClock(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
}
|
||||
|
|
@ -89,17 +83,14 @@ class PushLoopbackTestTest {
|
|||
clock = FakeSystemClock(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
|
||||
sut.quickFix(this)
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
sut.quickFix(this, FakeNotificationTroubleshootNavigator())
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
rotateTokenLambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
|
@ -115,14 +106,11 @@ class PushLoopbackTestTest {
|
|||
clock = FakeSystemClock(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,14 +127,11 @@ class PushLoopbackTestTest {
|
|||
clock = FakeSystemClock(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
assertThat(lastItem.description).contains(A_FAILURE_REASON)
|
||||
}
|
||||
}
|
||||
|
|
@ -163,10 +148,7 @@ class PushLoopbackTestTest {
|
|||
clock = FakeSystemClock(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@
|
|||
|
||||
package io.element.android.libraries.push.impl.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.pushproviders.test.FakePushProvider
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -23,14 +22,11 @@ class PushProvidersTestTest {
|
|||
pushProviders = emptySet(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
}
|
||||
|
|
@ -45,10 +41,7 @@ class PushProvidersTestTest {
|
|||
),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
|
|||
|
|
@ -76,5 +76,6 @@ dependencies {
|
|||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
testImplementation(projects.libraries.troubleshoot.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class FirebaseAvailabilityTest(
|
|||
} else {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_failure),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
|
|||
import io.element.android.libraries.pushproviders.firebase.FirebaseStore
|
||||
import io.element.android.libraries.pushproviders.firebase.FirebaseTroubleshooter
|
||||
import io.element.android.libraries.pushproviders.firebase.R
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
|
|
@ -62,7 +63,7 @@ class FirebaseTokenTest(
|
|||
} else {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_failure),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(true)
|
||||
status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +72,10 @@ class FirebaseTokenTest(
|
|||
|
||||
override suspend fun reset() = delegate.reset()
|
||||
|
||||
override suspend fun quickFix(coroutineScope: CoroutineScope) {
|
||||
override suspend fun quickFix(
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
delegate.start()
|
||||
firebaseTroubleshooter.troubleshoot()
|
||||
run(coroutineScope)
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@
|
|||
|
||||
package io.element.android.libraries.pushproviders.firebase.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.pushproviders.firebase.FakeIsPlayServiceAvailable
|
||||
import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -25,10 +24,7 @@ class FirebaseAvailabilityTestTest {
|
|||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(true),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
@ -42,14 +38,11 @@ class FirebaseAvailabilityTestTest {
|
|||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@
|
|||
|
||||
package io.element.android.libraries.pushproviders.firebase.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.pushproviders.firebase.FakeFirebaseTroubleshooter
|
||||
import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
|
||||
import io.element.android.libraries.pushproviders.firebase.InMemoryFirebaseStore
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -27,10 +27,7 @@ class FirebaseTokenTestTest {
|
|||
firebaseTroubleshooter = FakeFirebaseTroubleshooter(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
@ -53,16 +50,13 @@ class FirebaseTokenTestTest {
|
|||
),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
// Quick fix
|
||||
sut.quickFix(this)
|
||||
sut.quickFix(this, FakeNotificationTroubleshootNavigator())
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
|
||||
}
|
||||
|
|
@ -81,13 +75,10 @@ class FirebaseTokenTestTest {
|
|||
),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ dependencies {
|
|||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushproviders.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.libraries.troubleshoot.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class UnifiedPushMatrixGatewayTest(
|
|||
if (config == null) {
|
||||
delegate.updateState(
|
||||
description = "No current push provider",
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
} else {
|
||||
val gatewayBaseUrl = config.url.removeSuffix("/_matrix/push/v1/notify")
|
||||
|
|
@ -65,13 +65,13 @@ class UnifiedPushMatrixGatewayTest(
|
|||
} else {
|
||||
delegate.updateState(
|
||||
description = "${config.url} is not a Matrix gateway.",
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
delegate.updateState(
|
||||
description = "Fail to check the gateway ${config.url}: ${throwable.localizedMessage}",
|
||||
status = NotificationTroubleshootTestState.Status.Failure(false)
|
||||
status = NotificationTroubleshootTestState.Status.Failure()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import dev.zacsweers.metro.Inject
|
|||
import io.element.android.libraries.pushproviders.unifiedpush.R
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
|
|
@ -57,14 +58,17 @@ class UnifiedPushTest(
|
|||
} else {
|
||||
delegate.updateState(
|
||||
description = stringProvider.getString(R.string.troubleshoot_notifications_test_unified_push_failure),
|
||||
status = NotificationTroubleshootTestState.Status.Failure(true)
|
||||
status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun reset() = delegate.reset()
|
||||
|
||||
override suspend fun quickFix(coroutineScope: CoroutineScope) {
|
||||
override suspend fun quickFix(
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
openDistributorWebPageAction.execute()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig
|
||||
|
|
@ -18,8 +17,8 @@ import io.element.android.libraries.pushproviders.unifiedpush.matrixDiscoveryRes
|
|||
import io.element.android.libraries.pushproviders.unifiedpush.network.DiscoveryResponse
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -31,10 +30,7 @@ class UnifiedPushMatrixGatewayTestTest {
|
|||
currentUserPushConfig = aCurrentUserPushConfig(),
|
||||
discoveryResponse = matrixDiscoveryResponse,
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
@ -48,14 +44,11 @@ class UnifiedPushMatrixGatewayTestTest {
|
|||
currentUserPushConfig = null,
|
||||
discoveryResponse = matrixDiscoveryResponse,
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,14 +58,11 @@ class UnifiedPushMatrixGatewayTestTest {
|
|||
currentUserPushConfig = aCurrentUserPushConfig(),
|
||||
discoveryResponse = invalidDiscoveryResponse,
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
// Reset the error
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
|
|
@ -85,14 +75,11 @@ class UnifiedPushMatrixGatewayTestTest {
|
|||
currentUserPushConfig = aCurrentUserPushConfig(),
|
||||
discoveryResponse = { error("Network error") },
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.test.runAndTestState
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -30,10 +31,7 @@ class UnifiedPushTestTest {
|
|||
openDistributorWebPageAction = FakeOpenDistributorWebPageAction(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
|
|
@ -57,17 +55,14 @@ class UnifiedPushTestTest {
|
|||
),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
// Quick fix
|
||||
launch {
|
||||
sut.quickFix(this)
|
||||
backgroundScope.launch {
|
||||
sut.quickFix(this, FakeNotificationTroubleshootNavigator())
|
||||
sut.run(this)
|
||||
}
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
|
|
@ -91,14 +86,11 @@ class UnifiedPushTestTest {
|
|||
),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
sut.run(this)
|
||||
}
|
||||
sut.state.test {
|
||||
sut.runAndTestState {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
|
||||
val lastItem = awaitItem()
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
|
||||
sut.reset()
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,5 +22,6 @@ interface NotificationTroubleShootEntryPoint : FeatureEntryPoint {
|
|||
|
||||
interface Callback : Plugin {
|
||||
fun onDone()
|
||||
fun openIgnoredUsers()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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.libraries.troubleshoot.api.test
|
||||
|
||||
interface NotificationTroubleshootNavigator {
|
||||
fun openIgnoredUsers()
|
||||
}
|
||||
|
|
@ -16,7 +16,10 @@ interface NotificationTroubleshootTest {
|
|||
fun isRelevant(data: TestFilterData): Boolean = true
|
||||
suspend fun run(coroutineScope: CoroutineScope)
|
||||
suspend fun reset()
|
||||
suspend fun quickFix(coroutineScope: CoroutineScope) {
|
||||
suspend fun quickFix(
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
error("Quick fix not implemented, you need to override this method in your test")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class NotificationTroubleshootTestDelegate(
|
|||
if (isSuccess) {
|
||||
NotificationTroubleshootTestState.Status.Success
|
||||
} else {
|
||||
NotificationTroubleshootTestState.Status.Failure(hasQuickFix)
|
||||
NotificationTroubleshootTestState.Status.Failure(hasQuickFix = hasQuickFix)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ data class NotificationTroubleshootTestState(
|
|||
data object InProgress : Status
|
||||
data object WaitingForUser : Status
|
||||
data object Success : Status
|
||||
data class Failure(val hasQuickFix: Boolean) : Status
|
||||
data class Failure(
|
||||
val hasQuickFix: Boolean = false,
|
||||
val isCritical: Boolean = true,
|
||||
val quickFixButtonString: String? = null,
|
||||
) : Status
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
|
|||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.services.analytics.api.ScreenTracker
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
|
|
@ -26,15 +27,26 @@ import io.element.android.services.analytics.api.ScreenTracker
|
|||
class TroubleshootNotificationsNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: TroubleshootNotificationsPresenter,
|
||||
private val screenTracker: ScreenTracker,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
factory: TroubleshootNotificationsPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins),
|
||||
NotificationTroubleshootNavigator {
|
||||
private val presenter = factory.create(
|
||||
navigator = this,
|
||||
)
|
||||
|
||||
private fun onDone() {
|
||||
plugins<NotificationTroubleShootEntryPoint.Callback>().forEach {
|
||||
it.onDone()
|
||||
}
|
||||
}
|
||||
|
||||
override fun openIgnoredUsers() {
|
||||
plugins<NotificationTroubleShootEntryPoint.Callback>().forEach {
|
||||
it.openIgnoredUsers()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
screenTracker.TrackScreen(MobileScreen.ScreenName.NotificationTroubleshoot)
|
||||
|
|
|
|||
|
|
@ -12,14 +12,23 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Inject
|
||||
class TroubleshootNotificationsPresenter(
|
||||
@Assisted private val navigator: NotificationTroubleshootNavigator,
|
||||
private val troubleshootTestSuite: TroubleshootTestSuite,
|
||||
) : Presenter<TroubleshootNotificationsState> {
|
||||
@AssistedFactory
|
||||
fun interface Factory {
|
||||
fun create(navigator: NotificationTroubleshootNavigator): TroubleshootNotificationsPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): TroubleshootNotificationsState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
|
@ -34,7 +43,11 @@ class TroubleshootNotificationsPresenter(
|
|||
troubleshootTestSuite.runTestSuite(this)
|
||||
}
|
||||
is TroubleshootNotificationsEvents.QuickFix -> coroutineScope.launch {
|
||||
troubleshootTestSuite.quickFix(event.testIndex, this)
|
||||
troubleshootTestSuite.quickFix(
|
||||
testIndex = event.testIndex,
|
||||
coroutineScope = this,
|
||||
navigator = navigator,
|
||||
)
|
||||
}
|
||||
TroubleshootNotificationsEvents.RetryFailedTests -> coroutineScope.launch {
|
||||
troubleshootTestSuite.retryFailedTest(this)
|
||||
|
|
|
|||
|
|
@ -31,8 +31,12 @@ open class TroubleshootNotificationsStateProvider : PreviewParameterProvider<Tro
|
|||
aTroubleshootNotificationsState(
|
||||
listOf(
|
||||
aTroubleshootTestStateSuccess(),
|
||||
aTroubleshootTestStateFailure(
|
||||
isCritical = false,
|
||||
hasQuickFix = true,
|
||||
quickFixButtonString = "Custom quick fix",
|
||||
),
|
||||
aTroubleshootTestStateInProgress(),
|
||||
aTroubleshootTestStateIdle(),
|
||||
)
|
||||
),
|
||||
aTroubleshootNotificationsState(
|
||||
|
|
@ -106,5 +110,14 @@ fun aTroubleshootTestStateWaitingForUser() =
|
|||
fun aTroubleshootTestStateSuccess() =
|
||||
aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.Success)
|
||||
|
||||
fun aTroubleshootTestStateFailure(hasQuickFix: Boolean) =
|
||||
aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = hasQuickFix))
|
||||
fun aTroubleshootTestStateFailure(
|
||||
hasQuickFix: Boolean = false,
|
||||
isCritical: Boolean = true,
|
||||
quickFixButtonString: String? = null,
|
||||
) = aTroubleshootTestState(
|
||||
status = NotificationTroubleshootTestState.Status.Failure(
|
||||
hasQuickFix = hasQuickFix,
|
||||
isCritical = isCritical,
|
||||
quickFixButtonString = quickFixButtonString,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -64,11 +64,12 @@ private fun ColumnScope.TroubleshootTestView(
|
|||
testState: NotificationTroubleshootTestState,
|
||||
onQuickFixClick: () -> Unit,
|
||||
) {
|
||||
if ((testState.status as? Status.Idle)?.visible == false) return
|
||||
val status = testState.status
|
||||
if ((status as? Status.Idle)?.visible == false) return
|
||||
ListItem(
|
||||
headlineContent = { Text(text = testState.name) },
|
||||
supportingContent = { Text(text = testState.description) },
|
||||
trailingContent = when (testState.status) {
|
||||
trailingContent = when (status) {
|
||||
is Status.Idle -> null
|
||||
Status.InProgress -> ListItemContent.Custom {
|
||||
CircularProgressIndicator(
|
||||
|
|
@ -98,20 +99,19 @@ private fun ColumnScope.TroubleshootTestView(
|
|||
Icon(
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
imageVector = CompoundIcons.ErrorSolid(),
|
||||
tint = ElementTheme.colors.textCriticalPrimary
|
||||
imageVector = if (status.isCritical) CompoundIcons.ErrorSolid() else CompoundIcons.Warning(),
|
||||
tint = ElementTheme.colors.iconCriticalPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
if ((testState.status as? Status.Failure)?.hasQuickFix == true) {
|
||||
if (status is Status.Failure && status.hasQuickFix) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
},
|
||||
headlineContent = { },
|
||||
trailingContent = ListItemContent.Custom {
|
||||
Button(
|
||||
text = stringResource(id = R.string.troubleshoot_notifications_screen_quick_fix_action),
|
||||
onClick = onQuickFixClick
|
||||
text = status.quickFixButtonString ?: stringResource(id = R.string.troubleshoot_notifications_screen_quick_fix_action),
|
||||
onClick = onQuickFixClick,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import dev.zacsweers.metro.Inject
|
|||
import im.vector.app.features.analytics.plan.NotificationTroubleshoot
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||
|
|
@ -90,8 +91,12 @@ class TroubleshootTestSuite(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun quickFix(testIndex: Int, coroutineScope: CoroutineScope) {
|
||||
tests[testIndex].quickFix(coroutineScope)
|
||||
suspend fun quickFix(
|
||||
testIndex: Int,
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
tests[testIndex].quickFix(coroutineScope, navigator)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +109,7 @@ fun List<NotificationTroubleshootTestState>.computeMainState(): AsyncAction<Unit
|
|||
else -> {
|
||||
if (any { it.status is NotificationTroubleshootTestState.Status.WaitingForUser }) {
|
||||
AsyncAction.ConfirmingNoParams
|
||||
} else if (any { it.status is NotificationTroubleshootTestState.Status.Failure }) {
|
||||
} else if (any { it.status.let { status -> status is NotificationTroubleshootTestState.Status.Failure && status.isCritical } }) {
|
||||
AsyncAction.Failure(Exception("Some tests failed"))
|
||||
} else {
|
||||
AsyncAction.Success(Unit)
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ class DefaultNotificationTroubleShootEntryPointTest {
|
|||
TroubleshootNotificationsNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
presenter = createTroubleshootNotificationsPresenter(),
|
||||
factory = { createTroubleshootNotificationsPresenter() },
|
||||
screenTracker = FakeScreenTracker(),
|
||||
)
|
||||
}
|
||||
val callback = object : NotificationTroubleShootEntryPoint.Callback {
|
||||
override fun onDone() = lambdaError()
|
||||
override fun openIgnoredUsers() = lambdaError()
|
||||
}
|
||||
val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null))
|
||||
.callback(callback)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.libraries.troubleshoot.impl
|
||||
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -50,7 +51,10 @@ class FakeNotificationTroubleshootTest(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun quickFix(coroutineScope: CoroutineScope) {
|
||||
override suspend fun quickFix(
|
||||
coroutineScope: CoroutineScope,
|
||||
navigator: NotificationTroubleshootNavigator,
|
||||
) {
|
||||
updateState(NotificationTroubleshootTestState.Status.InProgress)
|
||||
quickFixAction()?.let {
|
||||
_state.emit(it)
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@
|
|||
|
||||
package io.element.android.libraries.troubleshoot.impl
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.push.test.FakeGetCurrentPushProvider
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -23,9 +23,7 @@ class TroubleshootNotificationsPresenterTest {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createTroubleshootNotificationsPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.testSuiteState.tests).isEmpty()
|
||||
assertThat(initialState.testSuiteState.mainState).isEqualTo(AsyncAction.Uninitialized)
|
||||
|
|
@ -40,9 +38,7 @@ class TroubleshootNotificationsPresenterTest {
|
|||
val presenter = createTroubleshootNotificationsPresenter(
|
||||
troubleshootTestSuite = troubleshootTestSuite,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(TroubleshootNotificationsEvents.StartTests)
|
||||
skipItems(1)
|
||||
|
|
@ -63,9 +59,7 @@ class TroubleshootNotificationsPresenterTest {
|
|||
val presenter = createTroubleshootNotificationsPresenter(
|
||||
troubleshootTestSuite = troubleshootTestSuite,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(TroubleshootNotificationsEvents.RetryFailedTests)
|
||||
skipItems(1)
|
||||
|
|
@ -74,6 +68,80 @@ class TroubleshootNotificationsPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - critical failed test`() {
|
||||
`present - check main state`(
|
||||
tests = setOf(
|
||||
FakeNotificationTroubleshootTest(
|
||||
firstStatus = NotificationTroubleshootTestState.Status.Failure(isCritical = true)
|
||||
)
|
||||
),
|
||||
expectedIsCritical = true,
|
||||
expectedMainState = AsyncAction.Failure::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - success and critical failed test`() {
|
||||
`present - check main state`(
|
||||
tests = setOf(
|
||||
FakeNotificationTroubleshootTest(
|
||||
firstStatus = NotificationTroubleshootTestState.Status.Success
|
||||
),
|
||||
FakeNotificationTroubleshootTest(
|
||||
firstStatus = NotificationTroubleshootTestState.Status.Failure(isCritical = true)
|
||||
),
|
||||
),
|
||||
expectedIsCritical = true,
|
||||
expectedMainState = AsyncAction.Failure::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - non critical failed test`() {
|
||||
`present - check main state`(
|
||||
tests = setOf(
|
||||
FakeNotificationTroubleshootTest(
|
||||
firstStatus = NotificationTroubleshootTestState.Status.Failure(isCritical = false)
|
||||
)
|
||||
),
|
||||
expectedIsCritical = false,
|
||||
expectedMainState = AsyncAction.Success::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - waiting for user`() {
|
||||
`present - check main state`(
|
||||
tests = setOf(
|
||||
FakeNotificationTroubleshootTest(
|
||||
firstStatus = NotificationTroubleshootTestState.Status.WaitingForUser
|
||||
)
|
||||
),
|
||||
expectedIsCritical = false,
|
||||
expectedMainState = AsyncAction.ConfirmingNoParams::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
private fun `present - check main state`(
|
||||
tests: Set<NotificationTroubleshootTest>,
|
||||
expectedIsCritical: Boolean,
|
||||
expectedMainState: Class<out AsyncAction<*>>,
|
||||
) = runTest {
|
||||
val troubleshootTestSuite = createTroubleshootTestSuite(
|
||||
tests = tests
|
||||
)
|
||||
val presenter = createTroubleshootNotificationsPresenter(
|
||||
troubleshootTestSuite = troubleshootTestSuite,
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasFailedTests).isEqualTo(expectedIsCritical)
|
||||
assertThat(initialState.testSuiteState.mainState).isInstanceOf(expectedMainState)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - quick fix test`() = runTest {
|
||||
val troubleshootTestSuite = createTroubleshootTestSuite(
|
||||
|
|
@ -86,9 +154,7 @@ class TroubleshootNotificationsPresenterTest {
|
|||
val presenter = createTroubleshootNotificationsPresenter(
|
||||
troubleshootTestSuite = troubleshootTestSuite,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.testSuiteState.mainState).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
|
|
@ -111,9 +177,13 @@ private fun createTroubleshootTestSuite(
|
|||
}
|
||||
|
||||
internal fun createTroubleshootNotificationsPresenter(
|
||||
navigator: NotificationTroubleshootNavigator = object : NotificationTroubleshootNavigator {
|
||||
override fun openIgnoredUsers() = lambdaError()
|
||||
},
|
||||
troubleshootTestSuite: TroubleshootTestSuite = createTroubleshootTestSuite(),
|
||||
): TroubleshootNotificationsPresenter {
|
||||
return TroubleshootNotificationsPresenter(
|
||||
navigator = navigator,
|
||||
troubleshootTestSuite = troubleshootTestSuite,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
31
libraries/troubleshoot/test/build.gradle.kts
Normal file
31
libraries/troubleshoot/test/build.gradle.kts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.troubleshoot.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.troubleshoot.api)
|
||||
implementation(projects.tests.testutils)
|
||||
implementation(libs.coroutines.test)
|
||||
implementation(libs.test.core)
|
||||
implementation(libs.test.turbine)
|
||||
}
|
||||
|
||||
ktlint {
|
||||
filter {
|
||||
exclude { element ->
|
||||
val path = element.file.path
|
||||
// Exclude this file, that ktlint cannot parse.
|
||||
path.contains("libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/Utils.kt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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.libraries.troubleshoot.test
|
||||
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeNotificationTroubleshootNavigator(
|
||||
private val openIgnoredUsersResult: () -> Unit = { lambdaError() },
|
||||
) : NotificationTroubleshootNavigator {
|
||||
override fun openIgnoredUsers() = openIgnoredUsersResult()
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:Suppress("UnusedImports")
|
||||
|
||||
package io.element.android.libraries.troubleshoot.test
|
||||
|
||||
import app.cash.turbine.TurbineTestContext
|
||||
import app.cash.turbine.test
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
|
||||
context(testScope: TestScope)
|
||||
suspend fun NotificationTroubleshootTest.runAndTestState(
|
||||
validate: suspend TurbineTestContext<NotificationTroubleshootTestState>.() -> Unit,
|
||||
) {
|
||||
testScope.backgroundScope.launch {
|
||||
run(this)
|
||||
}
|
||||
state.test(validate = validate)
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
<string name="a11y_remove_reaction_with">"Odstranit reakci pomocí %1$s"</string>
|
||||
<string name="a11y_room_avatar">"Avatar místnosti"</string>
|
||||
<string name="a11y_send_files">"Odeslat soubory"</string>
|
||||
<string name="a11y_session_verification_time_limited_action_required">"Vyžaduje se časově omezená akce"</string>
|
||||
<string name="a11y_session_verification_time_limited_action_required">"Vyžaduje se časově omezená akce, na ověření máte jednu minutu"</string>
|
||||
<string name="a11y_show_password">"Zobrazit heslo"</string>
|
||||
<string name="a11y_start_call">"Zahájit hovor"</string>
|
||||
<string name="a11y_tombstoned_room">"Místnost s náhrobkem"</string>
|
||||
|
|
@ -107,6 +107,7 @@
|
|||
<string name="action_leave">"Odejít"</string>
|
||||
<string name="action_leave_conversation">"Opustit konverzaci"</string>
|
||||
<string name="action_leave_room">"Opustit místnost"</string>
|
||||
<string name="action_leave_space">"Opustit prostor"</string>
|
||||
<string name="action_load_more">"Načíst více"</string>
|
||||
<string name="action_manage_account">"Spravovat účet"</string>
|
||||
<string name="action_manage_devices">"Spravovat zařízení"</string>
|
||||
|
|
@ -167,6 +168,8 @@
|
|||
<string name="banner_migrate_to_native_sliding_sync_title">"Upgrade k dispozici"</string>
|
||||
<string name="common_about">"O aplikaci"</string>
|
||||
<string name="common_acceptable_use_policy">"Zásady používání"</string>
|
||||
<string name="common_add_account">"Přidat účet"</string>
|
||||
<string name="common_add_another_account">"Přidat další účet"</string>
|
||||
<string name="common_adding_caption">"Přidání titulku"</string>
|
||||
<string name="common_advanced_settings">"Pokročilá nastavení"</string>
|
||||
<string name="common_an_image">"obrázek"</string>
|
||||
|
|
@ -184,9 +187,11 @@
|
|||
<string name="common_creating_room">"Vytváření místnosti…"</string>
|
||||
<string name="common_current_user_canceled_knock">"Žádost zrušena"</string>
|
||||
<string name="common_current_user_left_room">"Místnost opuštěna"</string>
|
||||
<string name="common_current_user_left_space">"Opustit prostor"</string>
|
||||
<string name="common_current_user_rejected_invite">"Pozvánka odmítnuta"</string>
|
||||
<string name="common_dark">"Tmavé"</string>
|
||||
<string name="common_decryption_error">"Chyba dešifrování"</string>
|
||||
<string name="common_description">"Popis"</string>
|
||||
<string name="common_developer_options">"Možnosti pro vývojáře"</string>
|
||||
<string name="common_device_id">"ID zařízení"</string>
|
||||
<string name="common_direct_chat">"Přímý chat"</string>
|
||||
|
|
@ -299,14 +304,17 @@ Důvod: %1$s."</string>
|
|||
<string name="common_search_results">"Výsledky hledání"</string>
|
||||
<string name="common_security">"Zabezpečení"</string>
|
||||
<string name="common_seen_by">"Viděno"</string>
|
||||
<string name="common_select_account">"Vybrat účet"</string>
|
||||
<string name="common_send_to">"Odeslat do"</string>
|
||||
<string name="common_sending">"Odesílání…"</string>
|
||||
<string name="common_sending_failed">"Odeslání se nezdařilo"</string>
|
||||
<string name="common_sent">"Odesláno"</string>
|
||||
<string name="common_sentence_delimiter">". "</string>
|
||||
<string name="common_server_not_supported">"Server není podporován"</string>
|
||||
<string name="common_server_unreachable">"Server je nedostupný"</string>
|
||||
<string name="common_server_url">"URL serveru"</string>
|
||||
<string name="common_settings">"Nastavení"</string>
|
||||
<string name="common_share_space">"Sdílet prostor"</string>
|
||||
<string name="common_shared_location">"Sdílená poloha"</string>
|
||||
<string name="common_signing_out">"Odhlašování"</string>
|
||||
<string name="common_something_went_wrong">"Něco se nepovedlo"</string>
|
||||
|
|
@ -382,6 +390,8 @@ Opravdu chcete pokračovat?"</string>
|
|||
<string name="dialog_video_quality_selector_subtitle_file_size">"Maximální povolená velikost souboru je: %1$s"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_no_file_size">"Vyberte kvalitu videa, které chcete nahrát."</string>
|
||||
<string name="dialog_video_quality_selector_title">"Vyberte kvalitu nahrávání videa"</string>
|
||||
<string name="emoji_picker_search_placeholder">"Hledat emotikony"</string>
|
||||
<string name="error_account_already_logged_in">"Na tomto zařízení jste již přihlášeni jako %1$s."</string>
|
||||
<string name="error_account_creation_not_possible">"Váš domovský server je třeba upgradovat, aby podporoval službu Matrix Authentication Service a vytváření účtu."</string>
|
||||
<string name="error_failed_creating_the_permalink">"Vytvoření trvalého odkazu se nezdařilo"</string>
|
||||
<string name="error_failed_loading_map">"%1$s nemohl načíst mapu. Zkuste to prosím později."</string>
|
||||
|
|
@ -409,6 +419,9 @@ Opravdu chcete pokračovat?"</string>
|
|||
<string name="invite_friends_text">"Ahoj, ozvi se mi na %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Zatřeste zařízením pro nahlášení chyby"</string>
|
||||
<string name="screen_bottom_sheet_leave_space_subtitle">"Tím budete také odstraněni ze všech místností v tomto prostoru."</string>
|
||||
<string name="screen_bottom_sheet_leave_space_subtitle_admin">"Tímto budete také odstraněni ze všech místností v tomto prostoru, včetně těch, jejichž jediným správcem jste:"</string>
|
||||
<string name="screen_bottom_sheet_leave_space_title">"Opustit %1$s?"</string>
|
||||
<string name="screen_bug_report_a11y_screenshot">"Snímek obrazovky"</string>
|
||||
<string name="screen_create_poll_option_accessibility_label">"%1$s: %2$s"</string>
|
||||
<string name="screen_create_poll_options_section_title">"Možnosti"</string>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="a11y_add_reaction">"Ychwanegu adwaith: %1$s"</string>
|
||||
<string name="a11y_avatar">"Afatar"</string>
|
||||
<string name="a11y_collapse_message_text_field">"Lleihau maes testun neges"</string>
|
||||
<string name="a11y_delete">"Dileu"</string>
|
||||
<plurals name="a11y_digits_entered">
|
||||
<item quantity="zero">"%1$d nodau wedi eu cynnig"</item>
|
||||
|
|
@ -12,10 +13,13 @@
|
|||
<item quantity="other">"%1$d nod wedi eu cynnig"</item>
|
||||
</plurals>
|
||||
<string name="a11y_edit_avatar">"Golygu afatar"</string>
|
||||
<string name="a11y_edit_room_address_hint">"Bydd y cyfeiriad llawn yn%1$s"</string>
|
||||
<string name="a11y_encryption_details">"Manylion amgryptio"</string>
|
||||
<string name="a11y_expand_message_text_field">"Ehangu maes testun neges"</string>
|
||||
<string name="a11y_hide_password">"Cuddio cyfrinair"</string>
|
||||
<string name="a11y_join_call">"Ymuno â galwad"</string>
|
||||
<string name="a11y_jump_to_bottom">"Symud i\'r gwaelod"</string>
|
||||
<string name="a11y_move_the_map_to_my_location">"Symud y map i\'m lleoliad"</string>
|
||||
<string name="a11y_notifications_mentions_only">"Crybwylliadau\'n unig"</string>
|
||||
<string name="a11y_notifications_muted">"Wedi\'i Dewi"</string>
|
||||
<string name="a11y_notifications_new_mentions">"Crybwylliadau newydd"</string>
|
||||
|
|
@ -46,9 +50,10 @@
|
|||
<string name="a11y_remove_reaction_with">"Wedi dileu adwaith gyda %1$s"</string>
|
||||
<string name="a11y_room_avatar">"Afatar ystafell"</string>
|
||||
<string name="a11y_send_files">"Anfon ffeiliau"</string>
|
||||
<string name="a11y_session_verification_time_limited_action_required">"Mae angen gweithredu â chyfyngiad amser"</string>
|
||||
<string name="a11y_session_verification_time_limited_action_required">"Mae angen gweithredu o fewn amser cyfyngedig, mae gennych un funud i wirio"</string>
|
||||
<string name="a11y_show_password">"Dangos y cyfrinair"</string>
|
||||
<string name="a11y_start_call">"Cychwyn galwad"</string>
|
||||
<string name="a11y_tombstoned_room">"Ystafell Tombstoned"</string>
|
||||
<string name="a11y_user_avatar">"Afatar defnyddiwr"</string>
|
||||
<string name="a11y_user_menu">"Dewislen defnyddiwr"</string>
|
||||
<string name="a11y_view_avatar">"Gweld afatar"</string>
|
||||
|
|
@ -93,6 +98,7 @@
|
|||
<string name="action_enable">"Galluogi"</string>
|
||||
<string name="action_end_poll">"Gorffen pleidlais"</string>
|
||||
<string name="action_enter_pin">"Rhoi\'r PIN"</string>
|
||||
<string name="action_finish">"Gorffen"</string>
|
||||
<string name="action_forgot_password">"Wedi anghofio\'ch cyfrinair?"</string>
|
||||
<string name="action_forward">"Ymlaen"</string>
|
||||
<string name="action_go_back">"Mynd nôl"</string>
|
||||
|
|
@ -107,6 +113,7 @@
|
|||
<string name="action_leave">"Gadael"</string>
|
||||
<string name="action_leave_conversation">"Gadael y sgwrs"</string>
|
||||
<string name="action_leave_room">"Gadael yr ystafell"</string>
|
||||
<string name="action_leave_space">"Gadael y gofod"</string>
|
||||
<string name="action_load_more">"Llwytho rhagor"</string>
|
||||
<string name="action_manage_account">"Rheoli cyfrif"</string>
|
||||
<string name="action_manage_devices">"Rheoli dyfeisiau"</string>
|
||||
|
|
@ -124,8 +131,8 @@
|
|||
<string name="action_react">"Ymateb"</string>
|
||||
<string name="action_reject">"Gwrthod"</string>
|
||||
<string name="action_remove">"Tynnu"</string>
|
||||
<string name="action_remove_caption">"Dileu capsiwn"</string>
|
||||
<string name="action_remove_message">"Dileu neges"</string>
|
||||
<string name="action_remove_caption">"Tynnu capsiwn"</string>
|
||||
<string name="action_remove_message">"Tynnu neges"</string>
|
||||
<string name="action_reply">"Ateb"</string>
|
||||
<string name="action_reply_in_thread">"Ateb mewn edefyn"</string>
|
||||
<string name="action_report">"Adroddiadau"</string>
|
||||
|
|
@ -167,10 +174,14 @@
|
|||
<string name="banner_migrate_to_native_sliding_sync_title">"Uwchraddiad ar gael"</string>
|
||||
<string name="common_about">"Ynghylch"</string>
|
||||
<string name="common_acceptable_use_policy">"Polisi defnydd derbyniol"</string>
|
||||
<string name="common_add_account">"Ychwanegu cyfrif"</string>
|
||||
<string name="common_add_another_account">"Ychwanegu cyfrif arall"</string>
|
||||
<string name="common_adding_caption">"Ychwanegu capsiwn"</string>
|
||||
<string name="common_advanced_settings">"Gosodiadau uwch"</string>
|
||||
<string name="common_an_image">"delwedd"</string>
|
||||
<string name="common_analytics">"Dadansoddeg"</string>
|
||||
<string name="common_android_shortcuts_remove_reason_left_room">"Rydych wedi gadael yr ystafell"</string>
|
||||
<string name="common_android_shortcuts_remove_reason_session_logged_out">"Rydych wedi\'ch allgofnodi o\'r sesiwn"</string>
|
||||
<string name="common_appearance">"Gwedd"</string>
|
||||
<string name="common_audio">"Sain"</string>
|
||||
<string name="common_blocked_users">"Defnyddwyr wedi\'u rhwystro"</string>
|
||||
|
|
@ -182,9 +193,11 @@
|
|||
<string name="common_creating_room">"Wrthi\'n creu ystafell…"</string>
|
||||
<string name="common_current_user_canceled_knock">"Cais wedi\'i ddiddymu"</string>
|
||||
<string name="common_current_user_left_room">"Wedi gadael yr ystafell"</string>
|
||||
<string name="common_current_user_left_space">"Gofod chwith"</string>
|
||||
<string name="common_current_user_rejected_invite">"Wedi gwrthod y gwahoddiad"</string>
|
||||
<string name="common_dark">"Tywyll"</string>
|
||||
<string name="common_decryption_error">"Gwall dadgryptio"</string>
|
||||
<string name="common_description">"Disgrifiad"</string>
|
||||
<string name="common_developer_options">"Dewisiadau datblygwr"</string>
|
||||
<string name="common_device_id">"ID dyfais"</string>
|
||||
<string name="common_direct_chat">"Sgwrs uniongyrchol"</string>
|
||||
|
|
@ -233,12 +246,12 @@ Rheswm: %1$s."</string>
|
|||
<item quantity="other">"%d arall"</item>
|
||||
</plurals>
|
||||
<plurals name="common_member_count">
|
||||
<item quantity="zero">"%1$d aelodau"</item>
|
||||
<item quantity="one">"%1$d aelod"</item>
|
||||
<item quantity="two">"%1$d aelod"</item>
|
||||
<item quantity="few">"%1$d aelod"</item>
|
||||
<item quantity="many">"%1$d aelod"</item>
|
||||
<item quantity="other">"%1$d aelod"</item>
|
||||
<item quantity="zero">"%1$d Aelodau"</item>
|
||||
<item quantity="one">"%1$d Aelod"</item>
|
||||
<item quantity="two">"%1$d Aelod"</item>
|
||||
<item quantity="few">"%1$d Aelod"</item>
|
||||
<item quantity="many">"%1$d Aelod"</item>
|
||||
<item quantity="other">"%1$d Aelod"</item>
|
||||
</plurals>
|
||||
<string name="common_message">"Neges"</string>
|
||||
<string name="common_message_actions">"Gweithredoedd neges"</string>
|
||||
|
|
@ -272,14 +285,25 @@ Rheswm: %1$s."</string>
|
|||
<item quantity="many">"%d pleidlais"</item>
|
||||
<item quantity="other">"%d pleidlais"</item>
|
||||
</plurals>
|
||||
<string name="common_preparing">"Yn paratoi…"</string>
|
||||
<string name="common_privacy_policy">"Polisi preifatrwydd"</string>
|
||||
<string name="common_private_room">"Ystafell breifat"</string>
|
||||
<string name="common_private_space">"Gofod preifat"</string>
|
||||
<string name="common_public_room">"Ystafell gyhoeddus"</string>
|
||||
<string name="common_public_space">"Gofod cyhoeddus"</string>
|
||||
<string name="common_reaction">"Adwaith"</string>
|
||||
<string name="common_reactions">"Adweithiau"</string>
|
||||
<string name="common_reason">"Rheswm"</string>
|
||||
<string name="common_recovery_key">"Allwedd adfer"</string>
|
||||
<string name="common_refreshing">"Wrthi\'n adnewyddu…"</string>
|
||||
<plurals name="common_replies">
|
||||
<item quantity="zero">"%1$d atebion"</item>
|
||||
<item quantity="one">"%1$d ateb"</item>
|
||||
<item quantity="two">"%1$d ateb"</item>
|
||||
<item quantity="few">"%1$d ateb"</item>
|
||||
<item quantity="many">"%1$d ateb"</item>
|
||||
<item quantity="other">"%1$d ateb"</item>
|
||||
</plurals>
|
||||
<string name="common_replying_to">"Yn ymateb i %1$s"</string>
|
||||
<string name="common_report_a_bug">"Adrodd ar wall"</string>
|
||||
<string name="common_report_a_problem">"Adrodd am broblem"</string>
|
||||
|
|
@ -288,6 +312,14 @@ Rheswm: %1$s."</string>
|
|||
<string name="common_room">"Ystafell"</string>
|
||||
<string name="common_room_name">"Enw\'r ystafell"</string>
|
||||
<string name="common_room_name_placeholder">"e.e. enw eich project"</string>
|
||||
<plurals name="common_rooms">
|
||||
<item quantity="zero">"%1$d Ystafelloedd"</item>
|
||||
<item quantity="one">"%1$d Ystafell"</item>
|
||||
<item quantity="two">"%1$d Ystafell"</item>
|
||||
<item quantity="few">"%1$d Ystafell"</item>
|
||||
<item quantity="many">"%1$d Ystafell"</item>
|
||||
<item quantity="other">"%1$d Ystafell"</item>
|
||||
</plurals>
|
||||
<string name="common_saved_changes">"Newidiadau wedi\'u cadw"</string>
|
||||
<string name="common_saving">"Cadw"</string>
|
||||
<string name="common_screen_lock">"Clo sgrin"</string>
|
||||
|
|
@ -295,18 +327,30 @@ Rheswm: %1$s."</string>
|
|||
<string name="common_search_results">"Canlyniadau chwilio"</string>
|
||||
<string name="common_security">"Diogelwch"</string>
|
||||
<string name="common_seen_by">"Wedi\'i weld gan"</string>
|
||||
<string name="common_select_account">"Dewis cyfrif"</string>
|
||||
<string name="common_send_to">"Anfon at"</string>
|
||||
<string name="common_sending">"Yn anfon…"</string>
|
||||
<string name="common_sending_failed">"Methodd anfon"</string>
|
||||
<string name="common_sent">"Anfonwyd"</string>
|
||||
<string name="common_sentence_delimiter">". "</string>
|
||||
<string name="common_server_not_supported">"Nid yw\'r gweinydd yn cael ei gynnal"</string>
|
||||
<string name="common_server_unreachable">"Gweinydd yn anghyraeddadwy"</string>
|
||||
<string name="common_server_url">"URL gweinydd"</string>
|
||||
<string name="common_settings">"Gosodiadau"</string>
|
||||
<string name="common_share_space">"Rhannu gofod"</string>
|
||||
<string name="common_shared_location">"Lleoliad yn cael ei rannu"</string>
|
||||
<string name="common_signing_out">"Allgofnodi"</string>
|
||||
<string name="common_something_went_wrong">"Aeth rhywbeth o\'i le"</string>
|
||||
<string name="common_something_went_wrong_message">"Wedi canfod mater. Ceisiwch eto."</string>
|
||||
<string name="common_space">"Gofod"</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="zero">"%1$d Gofodau"</item>
|
||||
<item quantity="one">"%1$d Gofod"</item>
|
||||
<item quantity="two">"%1$d Ofod"</item>
|
||||
<item quantity="few">"%1$d Gofod"</item>
|
||||
<item quantity="many">"%1$d Gofod"</item>
|
||||
<item quantity="other">"%1$d Gofod"</item>
|
||||
</plurals>
|
||||
<string name="common_starting_chat">"Dechrau sgwrs…"</string>
|
||||
<string name="common_sticker">"Sticer"</string>
|
||||
<string name="common_success">"Llwyddiant"</string>
|
||||
|
|
@ -337,6 +381,12 @@ Rheswm: %1$s."</string>
|
|||
<string name="common_verify_identity">"Gwirio hunaniaeth"</string>
|
||||
<string name="common_verify_user">"Gwirio defnyddiwr"</string>
|
||||
<string name="common_video">"Fideo"</string>
|
||||
<string name="common_video_quality_high">"Ansawdd uchel"</string>
|
||||
<string name="common_video_quality_high_description">"Yr ansawdd gorau ond maint ffeil mwy"</string>
|
||||
<string name="common_video_quality_low">"Ansawdd isel"</string>
|
||||
<string name="common_video_quality_low_description">"Y cyflymder llwytho cyflymaf a\'r maint ffeil lleiaf"</string>
|
||||
<string name="common_video_quality_standard">"Ansawdd safonol"</string>
|
||||
<string name="common_video_quality_standard_description">"Cydbwysedd ansawdd a chyflymder llwytho"</string>
|
||||
<string name="common_voice_message">"Neges llais"</string>
|
||||
<string name="common_waiting">"Yn aros…"</string>
|
||||
<string name="common_waiting_for_decryption_key">"Yn aros am y neges hon"</string>
|
||||
|
|
@ -351,6 +401,10 @@ Rheswm: %1$s."</string>
|
|||
|
||||
Ydych chi\'n siŵr eich bod am barhau?"</string>
|
||||
<string name="dialog_confirm_link_title">"Gwnewch yn siŵr fod y ddolen hon yn iawn"</string>
|
||||
<string name="dialog_default_video_quality_selector_subtitle">"Dewiswch ansawdd rhagosodedig y fideos rydych chi\'n eu llwytho."</string>
|
||||
<string name="dialog_default_video_quality_selector_title">"Ansawdd llwytho fideo"</string>
|
||||
<string name="dialog_file_too_large_to_upload_subtitle">"Y maint ffeil mwyaf sy\'n cael ei ganiatáu yw:%1$s"</string>
|
||||
<string name="dialog_file_too_large_to_upload_title">"Mae maint y ffeil yn rhy fawr i\'w llwytho"</string>
|
||||
<string name="dialog_room_reported">"Adroddwyd am yr ystafell"</string>
|
||||
<string name="dialog_room_reported_and_left">"Adroddwyd a gadael yr ystafell"</string>
|
||||
<string name="dialog_title_confirmation">"Cadarnhad"</string>
|
||||
|
|
@ -359,6 +413,11 @@ Ydych chi\'n siŵr eich bod am barhau?"</string>
|
|||
<string name="dialog_title_warning">"Rhybudd"</string>
|
||||
<string name="dialog_unsaved_changes_description_android">"Dyw eich newidiadau heb gael eu cadw. Ydych chi\'n siŵr eich bod am fynd nôl?"</string>
|
||||
<string name="dialog_unsaved_changes_title">"Cadw\'r newidiadau?"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_file_size">"Y maint ffeil mwyaf sy\'n cael ei ganiatáu yw: %1$s"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_no_file_size">"Dewiswch ansawdd y fideo rydych chi am ei llwytho."</string>
|
||||
<string name="dialog_video_quality_selector_title">"Dewiswch ansawdd llwytho fideo"</string>
|
||||
<string name="emoji_picker_search_placeholder">"Chwilio emojis"</string>
|
||||
<string name="error_account_already_logged_in">"Rydych chi eisoes wedi mewngofnodi ar y ddyfais hon fel %1$s ."</string>
|
||||
<string name="error_account_creation_not_possible">"Mae angen uwchraddio eich gweinydd cartref i gefnogi Gwasanaeth Dilysu Matrix a chreu cyfrif."</string>
|
||||
<string name="error_failed_creating_the_permalink">"Wedi methu creu\'r ddolen barhaol"</string>
|
||||
<string name="error_failed_loading_map">"Methodd %1$s â llwytho\'r map. Ceisiwch eto yn nes ymlaen."</string>
|
||||
|
|
@ -386,6 +445,10 @@ Ydych chi\'n siŵr eich bod am barhau?"</string>
|
|||
<string name="invite_friends_text">"Hei, siaradwch â mi ar %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"Android %1$s"</string>
|
||||
<string name="preference_rageshake">"Rageshake i adrodd gwall"</string>
|
||||
<string name="screen_bottom_sheet_leave_space_subtitle">"Bydd hyn hefyd yn eich tynnu o bob ystafell yn y gofod hwn."</string>
|
||||
<string name="screen_bottom_sheet_leave_space_subtitle_admin">"Bydd hyn hefyd yn eich tynnu o bob ystafell yn y gofod hwn, gan gynnwys y rhai rydych chi\'n unig weinyddwr ar eu cyfer:"</string>
|
||||
<string name="screen_bottom_sheet_leave_space_title">"Gadael %1$s ?"</string>
|
||||
<string name="screen_bug_report_a11y_screenshot">"Llun sgrin"</string>
|
||||
<string name="screen_create_poll_option_accessibility_label">"%1$s: %2$s"</string>
|
||||
<string name="screen_create_poll_options_section_title">"Dewisiadau"</string>
|
||||
<string name="screen_create_poll_remove_accessibility_label">"Tynnu %1$s"</string>
|
||||
|
|
@ -412,6 +475,7 @@ Ydych chi\'n siŵr eich bod am barhau?"</string>
|
|||
<string name="screen_resolve_send_failure_unsigned_device_title">"Dyw eich neges heb ei hanfon oherwydd nid yw %1$s wedi gwirio pob dyfais"</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_subtitle">"Mae un neu fwy o\'ch dyfeisiau heb eu gwirio. Gallwch anfon y neges beth bynnag, neu gallwch ei diddymu am y tro a cheisio eto yn nes ymlaen ar ôl i chi ddilysu eich holl ddyfeisiau."</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_title">"Nid yw eich neges wedi\'i hanfon oherwydd nad ydych wedi gwirio un neu fwy o\'ch dyfeisiau"</string>
|
||||
<string name="screen_room_change_role_administrators_or_owners_title">"Golygu Gweinyddwyr neu Berchnogion"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Wedi methu â phrosesu cyfryngau i\'w llwytho, ceisiwch eto."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Methu â nôl manylion defnyddiwr"</string>
|
||||
<string name="screen_room_event_pill">"Neges yn %1$s"</string>
|
||||
|
|
@ -429,6 +493,9 @@ Ydych chi\'n siŵr eich bod am barhau?"</string>
|
|||
<string name="screen_share_open_google_maps">"Agor yn Google Maps"</string>
|
||||
<string name="screen_share_open_osm_maps">"Agor yn OpenStreetMap"</string>
|
||||
<string name="screen_share_this_location_action">"Rhannu\'r lleoliad hwn"</string>
|
||||
<string name="screen_space_list_description">"Gofodau rydych wedi\'u creu neu wedi ymuno â nhw."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_title">"Gofodau"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Heb anfon y neges oherwydd bod hunaniaeth wedi \'i ddilysu %1$s wedi\'i ailosod."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Heb anfon y neges oherwydd nid yw %1$s wedi gwirio pob dyfais."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Heb anfon y neges oherwydd nad ydych wedi gwirio un neu fwy o\'ch dyfeisiau."</string>
|
||||
|
|
|
|||
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