Merge branch 'develop' into renovate/org.maplibre.gl-android-sdk-11.x
This commit is contained in:
commit
232c8de702
1249 changed files with 18041 additions and 8127 deletions
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"გააზიარეთ ანონიმური გამოყენების მონაცემები, რათა დაგვეხმაროთ პრობლემების იდენტიფიცირებაში."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"შეგიძლიათ, წაიკითხოთ ჩვენი ყველა პირობა %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"აქ"</string>
|
||||
<string name="screen_analytics_settings_share_data">"გააზიარეთ ანალიტიკური მონაცემები"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"Partilhe dados de utilização anónimos para nos ajudar a identificar problemas."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Podes ler todos os nossos termos %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"aqui"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Partilhar dados de utilização"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"共享匿名使用数据以帮助我们排查问题。"</string>
|
||||
<string name="screen_analytics_settings_read_terms">"您可以阅读我们的所有条款 %1$s。"</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"此处"</string>
|
||||
<string name="screen_analytics_settings_share_data">"共享分析数据"</string>
|
||||
</resources>
|
||||
|
|
@ -63,15 +63,15 @@ fun AnalyticsOptInView(
|
|||
) {
|
||||
val eventSink = state.eventSink
|
||||
|
||||
fun onTermsAccepted() {
|
||||
fun onAcceptTerms() {
|
||||
eventSink(AnalyticsOptInEvents.EnableAnalytics(true))
|
||||
}
|
||||
|
||||
fun onTermsDeclined() {
|
||||
fun onDeclineTerms() {
|
||||
eventSink(AnalyticsOptInEvents.EnableAnalytics(false))
|
||||
}
|
||||
|
||||
BackHandler(onBack = ::onTermsDeclined)
|
||||
BackHandler(onBack = ::onDeclineTerms)
|
||||
HeaderFooterPage(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -82,8 +82,8 @@ fun AnalyticsOptInView(
|
|||
content = { AnalyticsOptInContent() },
|
||||
footer = {
|
||||
AnalyticsOptInFooter(
|
||||
onTermsAccepted = ::onTermsAccepted,
|
||||
onTermsDeclined = ::onTermsDeclined,
|
||||
onAcceptTerms = ::onAcceptTerms,
|
||||
onDeclineTerms = ::onDeclineTerms,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -165,19 +165,19 @@ private fun AnalyticsOptInContent() {
|
|||
|
||||
@Composable
|
||||
private fun AnalyticsOptInFooter(
|
||||
onTermsAccepted: () -> Unit,
|
||||
onTermsDeclined: () -> Unit,
|
||||
onAcceptTerms: () -> Unit,
|
||||
onDeclineTerms: () -> Unit,
|
||||
) {
|
||||
ButtonColumnMolecule {
|
||||
Button(
|
||||
text = stringResource(id = CommonStrings.action_ok),
|
||||
onClick = onTermsAccepted,
|
||||
onClick = onAcceptTerms,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(id = CommonStrings.action_not_now),
|
||||
size = ButtonSize.Medium,
|
||||
onClick = onTermsDeclined,
|
||||
onClick = onDeclineTerms,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"ჩვენ არ ჩავწერთ და არ დავაფიქსირებთ პერსონალურ მონაცემებს"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"გააზიარეთ ანონიმური გამოყენების მონაცემები, რათა დაგვეხმაროთ პრობლემების იდენტიფიცირებაში."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"შეგიძლიათ, წაიკითხოთ ჩვენი ყველა პირობა %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"აქ"</string>
|
||||
<string name="screen_analytics_prompt_settings">"ამის გამორთვა ნებისმიერ დროს შეგიძლიათ"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"თქვენს მონაცემებს მესამე პირს არ გადავცემთ"</string>
|
||||
<string name="screen_analytics_prompt_title">"დაგვეხმარეთ, გავაუმჯობესოთ %1$s"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Não recolheremos ou analisaremos quaisquer dados pessoais"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Partilhe dados de utilização anónimos para nos ajudar a identificar problemas."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Podes ler todos os nossos termos %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"aqui"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Podes desligar qualquer momento"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Não partilharemos os teus dados com terceiros"</string>
|
||||
<string name="screen_analytics_prompt_title">"Ajude a melhorar a %1$s"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"我们不会记录或分析任何个人数据"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"共享匿名使用数据以帮助我们排查问题。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"您可以阅读我们的所有条款 %1$s。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"此处"</string>
|
||||
<string name="screen_analytics_prompt_settings">"你可以随时关闭此功能"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"我们不会与第三方共享您的数据"</string>
|
||||
<string name="screen_analytics_prompt_title">"帮助改进 %1$s"</string>
|
||||
</resources>
|
||||
|
|
@ -72,15 +72,10 @@ class CallForegroundService : Service() {
|
|||
startForeground(1, notification)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} else {
|
||||
stopForeground(true)
|
||||
}
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
|
|
|
|||
|
|
@ -81,12 +81,12 @@ internal fun CallScreenView(
|
|||
.fillMaxSize(),
|
||||
url = state.urlState,
|
||||
userAgent = state.userAgent,
|
||||
onPermissionsRequested = { request ->
|
||||
onPermissionsRequest = { request ->
|
||||
val androidPermissions = mapWebkitPermissions(request.resources)
|
||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onWebViewCreated = { webView ->
|
||||
onWebViewCreate = { webView ->
|
||||
val interceptor = WebViewWidgetMessageInterceptor(webView)
|
||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
||||
}
|
||||
|
|
@ -98,8 +98,8 @@ internal fun CallScreenView(
|
|||
private fun CallWebView(
|
||||
url: AsyncData<String>,
|
||||
userAgent: String,
|
||||
onPermissionsRequested: (PermissionRequest) -> Unit,
|
||||
onWebViewCreated: (WebView) -> Unit,
|
||||
onPermissionsRequest: (PermissionRequest) -> Unit,
|
||||
onWebViewCreate: (WebView) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (LocalInspectionMode.current) {
|
||||
|
|
@ -111,8 +111,8 @@ private fun CallWebView(
|
|||
modifier = modifier,
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
onWebViewCreated(this)
|
||||
setup(userAgent, onPermissionsRequested)
|
||||
onWebViewCreate(this)
|
||||
setup(userAgent, onPermissionsRequest)
|
||||
}
|
||||
},
|
||||
update = { webView ->
|
||||
|
|
|
|||
6
features/call/src/main/res/values-ka/translations.xml
Normal file
6
features/call/src/main/res/values-ka/translations.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="call_foreground_service_channel_title_android">"მიმდინარე ზარი"</string>
|
||||
<string name="call_foreground_service_message_android">"დააწკაპუნეთ ზარში დასაბრუნებლად"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ ზარი მიმდინარეობს"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="call_foreground_service_channel_title_android">"Chamada em curso"</string>
|
||||
<string name="call_foreground_service_message_android">"Toca para voltar à chamada"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Chamada em curso"</string>
|
||||
</resources>
|
||||
6
features/call/src/main/res/values-zh/translations.xml
Normal file
6
features/call/src/main/res/values-zh/translations.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="call_foreground_service_channel_title_android">"通话进行中"</string>
|
||||
<string name="call_foreground_service_message_android">"点按即可返回通话"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ 通话中"</string>
|
||||
</resources>
|
||||
|
|
@ -30,7 +30,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
|||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver
|
||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
|
|
@ -68,7 +68,7 @@ class CallScreenPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - with CallType RoomCall loads URL and runs WidgetDriver`() = runTest {
|
||||
val widgetDriver = FakeWidgetDriver()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val widgetProvider = FakeCallWidgetProvider(widgetDriver)
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
|
|
@ -91,7 +91,7 @@ class CallScreenPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - set message interceptor, send and receive messages`() = runTest {
|
||||
val widgetDriver = FakeWidgetDriver()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
widgetDriver = widgetDriver,
|
||||
|
|
@ -119,7 +119,7 @@ class CallScreenPresenterTest {
|
|||
@Test
|
||||
fun `present - hang up event closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
val widgetDriver = FakeWidgetDriver()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
widgetDriver = widgetDriver,
|
||||
|
|
@ -149,7 +149,7 @@ class CallScreenPresenterTest {
|
|||
@Test
|
||||
fun `present - a received hang up message closes the screen and stops the widget driver`() = runTest(UnconfinedTestDispatcher()) {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
val widgetDriver = FakeWidgetDriver()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
widgetDriver = widgetDriver,
|
||||
|
|
@ -178,7 +178,7 @@ class CallScreenPresenterTest {
|
|||
@Test
|
||||
fun `present - automatically starts the Matrix client sync when on RoomCall`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
val widgetDriver = FakeWidgetDriver()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
|
|
@ -201,7 +201,7 @@ class CallScreenPresenterTest {
|
|||
@Test
|
||||
fun `present - automatically stops the Matrix client sync on dispose`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
val widgetDriver = FakeWidgetDriver()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
|
|
@ -229,7 +229,7 @@ class CallScreenPresenterTest {
|
|||
private fun TestScope.createCallScreenPresenter(
|
||||
callType: CallType,
|
||||
navigator: CallScreenNavigator = FakeCallScreenNavigator(),
|
||||
widgetDriver: FakeWidgetDriver = FakeWidgetDriver(),
|
||||
widgetDriver: FakeMatrixWidgetDriver = FakeMatrixWidgetDriver(),
|
||||
widgetProvider: FakeCallWidgetProvider = FakeCallWidgetProvider(widgetDriver),
|
||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
matrixClientsProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
|
|||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.widget.FakeCallWidgetSettingsProvider
|
||||
import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver
|
||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -76,7 +76,7 @@ class DefaultCallWidgetProviderTest {
|
|||
fun `getWidget - returns a widget driver when all steps are successful`() = runTest {
|
||||
val room = FakeMatrixRoom().apply {
|
||||
givenGenerateWidgetWebViewUrlResult(Result.success("url"))
|
||||
givenGetWidgetDriverResult(Result.success(FakeWidgetDriver()))
|
||||
givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver()))
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
|
|
@ -89,7 +89,7 @@ class DefaultCallWidgetProviderTest {
|
|||
fun `getWidget - will use a custom base url if it exists`() = runTest {
|
||||
val room = FakeMatrixRoom().apply {
|
||||
givenGenerateWidgetWebViewUrlResult(Result.success("url"))
|
||||
givenGetWidgetDriverResult(Result.success(FakeWidgetDriver()))
|
||||
givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver()))
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ package io.element.android.features.call.utils
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver
|
||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||
|
||||
class FakeCallWidgetProvider(
|
||||
private val widgetDriver: FakeWidgetDriver = FakeWidgetDriver(),
|
||||
private val widgetDriver: FakeMatrixWidgetDriver = FakeMatrixWidgetDriver(),
|
||||
private val url: String = "https://call.element.io",
|
||||
) : CallWidgetProvider {
|
||||
var getWidgetCalled = false
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ class AddPeopleNode @AssistedInject constructor(
|
|||
AddPeopleView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onBackPressed = this::navigateUp,
|
||||
onNextPressed = this::onContinue,
|
||||
onBackClick = this::navigateUp,
|
||||
onNextClick = this::onContinue,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun AddPeopleView(
|
||||
state: UserListState,
|
||||
onBackPressed: () -> Unit,
|
||||
onNextPressed: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
onNextClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
|
|
@ -51,14 +51,14 @@ fun AddPeopleView(
|
|||
topBar = {
|
||||
AddPeopleViewTopBar(
|
||||
hasSelectedUsers = state.selectedUsers.isNotEmpty(),
|
||||
onBackPressed = {
|
||||
onBackClick = {
|
||||
if (state.isSearchActive) {
|
||||
state.eventSink(UserListEvents.OnSearchActiveChanged(false))
|
||||
} else {
|
||||
onBackPressed()
|
||||
onBackClick()
|
||||
}
|
||||
},
|
||||
onNextPressed = onNextPressed,
|
||||
onNextClick = onNextClick,
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
|
|
@ -69,8 +69,8 @@ fun AddPeopleView(
|
|||
.consumeWindowInsets(padding),
|
||||
state = state,
|
||||
showBackButton = false,
|
||||
onUserSelected = {},
|
||||
onUserDeselected = {},
|
||||
onSelectUser = {},
|
||||
onDeselectUser = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -79,8 +79,8 @@ fun AddPeopleView(
|
|||
@Composable
|
||||
private fun AddPeopleViewTopBar(
|
||||
hasSelectedUsers: Boolean,
|
||||
onBackPressed: () -> Unit,
|
||||
onNextPressed: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
onNextClick: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
|
@ -89,12 +89,12 @@ private fun AddPeopleViewTopBar(
|
|||
style = ElementTheme.typography.aliasScreenTitle
|
||||
)
|
||||
},
|
||||
navigationIcon = { BackButton(onClick = onBackPressed) },
|
||||
navigationIcon = { BackButton(onClick = onBackClick) },
|
||||
actions = {
|
||||
val textActionResId = if (hasSelectedUsers) CommonStrings.action_next else CommonStrings.action_skip
|
||||
TextButton(
|
||||
text = stringResource(id = textActionResId),
|
||||
onClick = onNextPressed,
|
||||
onClick = onNextClick,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -105,7 +105,7 @@ private fun AddPeopleViewTopBar(
|
|||
internal fun AddPeopleViewPreview(@PreviewParameter(AddPeopleUserListStateProvider::class) state: UserListState) = ElementPreview {
|
||||
AddPeopleView(
|
||||
state = state,
|
||||
onBackPressed = {},
|
||||
onNextPressed = {},
|
||||
onBackClick = {},
|
||||
onNextClick = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
@Composable
|
||||
fun RoomPrivacyOption(
|
||||
roomPrivacyItem: RoomPrivacyItem,
|
||||
onOptionSelected: (RoomPrivacyItem) -> Unit,
|
||||
onOptionClick: (RoomPrivacyItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isSelected: Boolean = false,
|
||||
) {
|
||||
|
|
@ -50,7 +50,7 @@ fun RoomPrivacyOption(
|
|||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
onClick = { onOptionSelected(roomPrivacyItem) },
|
||||
onClick = { onOptionClick(roomPrivacyItem) },
|
||||
role = Role.RadioButton,
|
||||
)
|
||||
.padding(8.dp),
|
||||
|
|
@ -98,12 +98,12 @@ internal fun RoomPrivacyOptionPreview() = ElementPreview {
|
|||
Column {
|
||||
RoomPrivacyOption(
|
||||
roomPrivacyItem = aRoomPrivacyItem,
|
||||
onOptionSelected = {},
|
||||
onOptionClick = {},
|
||||
isSelected = true,
|
||||
)
|
||||
RoomPrivacyOption(
|
||||
roomPrivacyItem = aRoomPrivacyItem,
|
||||
onOptionSelected = {},
|
||||
onOptionClick = {},
|
||||
isSelected = false,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ fun SearchUserBar(
|
|||
showLoader: Boolean,
|
||||
selectedUsers: ImmutableList<MatrixUser>,
|
||||
active: Boolean,
|
||||
isMultiSelectionEnabled: Boolean,
|
||||
onActiveChanged: (Boolean) -> Unit,
|
||||
onTextChanged: (String) -> Unit,
|
||||
onUserSelected: (MatrixUser) -> Unit,
|
||||
onUserDeselected: (MatrixUser) -> Unit,
|
||||
isMultiSelectionEnable: Boolean,
|
||||
onActiveChange: (Boolean) -> Unit,
|
||||
onTextChange: (String) -> Unit,
|
||||
onUserSelect: (MatrixUser) -> Unit,
|
||||
onUserDeselect: (MatrixUser) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
showBackButton: Boolean = true,
|
||||
placeHolderTitle: String = stringResource(CommonStrings.common_search_for_someone),
|
||||
|
|
@ -66,14 +66,14 @@ fun SearchUserBar(
|
|||
|
||||
SearchBar(
|
||||
query = query,
|
||||
onQueryChange = onTextChanged,
|
||||
onQueryChange = onTextChange,
|
||||
active = active,
|
||||
onActiveChange = onActiveChanged,
|
||||
onActiveChange = onActiveChange,
|
||||
modifier = modifier,
|
||||
placeHolderTitle = placeHolderTitle,
|
||||
showBackButton = showBackButton,
|
||||
contentPrefix = {
|
||||
if (isMultiSelectionEnabled && active && selectedUsers.isNotEmpty()) {
|
||||
if (isMultiSelectionEnable && active && selectedUsers.isNotEmpty()) {
|
||||
// We want the selected users to behave a bit like a top bar - when the list below is scrolled, the colour
|
||||
// should change to indicate elevation.
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ fun SearchUserBar(
|
|||
contentPadding = PaddingValues(16.dp),
|
||||
selectedUsers = selectedUsers,
|
||||
autoScroll = true,
|
||||
onUserRemoved = onUserDeselected,
|
||||
onUserRemove = onUserDeselect,
|
||||
modifier = Modifier.background(appBarContainerColor)
|
||||
)
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ fun SearchUserBar(
|
|||
resultState = state,
|
||||
resultHandler = { users ->
|
||||
LazyColumn(state = columnState) {
|
||||
if (isMultiSelectionEnabled) {
|
||||
if (isMultiSelectionEnable) {
|
||||
itemsIndexed(users) { index, searchResult ->
|
||||
SearchMultipleUsersResultItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
@ -117,9 +117,9 @@ fun SearchUserBar(
|
|||
isUserSelected = selectedUsers.contains(searchResult.matrixUser),
|
||||
onCheckedChange = { checked ->
|
||||
if (checked) {
|
||||
onUserSelected(searchResult.matrixUser)
|
||||
onUserSelect(searchResult.matrixUser)
|
||||
} else {
|
||||
onUserDeselected(searchResult.matrixUser)
|
||||
onUserDeselect(searchResult.matrixUser)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -132,7 +132,7 @@ fun SearchUserBar(
|
|||
SearchSingleUserResultItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
searchResult = searchResult,
|
||||
onClick = { onUserSelected(searchResult.matrixUser) }
|
||||
onClick = { onUserSelect(searchResult.matrixUser) }
|
||||
)
|
||||
if (index < users.lastIndex) {
|
||||
HorizontalDivider()
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun UserListView(
|
||||
state: UserListState,
|
||||
onUserSelected: (MatrixUser) -> Unit,
|
||||
onUserDeselected: (MatrixUser) -> Unit,
|
||||
onSelectUser: (MatrixUser) -> Unit,
|
||||
onDeselectUser: (MatrixUser) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
showBackButton: Boolean = true,
|
||||
) {
|
||||
|
|
@ -59,17 +59,17 @@ fun UserListView(
|
|||
selectedUsers = state.selectedUsers,
|
||||
active = state.isSearchActive,
|
||||
showLoader = state.showSearchLoader,
|
||||
isMultiSelectionEnabled = state.isMultiSelectionEnabled,
|
||||
isMultiSelectionEnable = state.isMultiSelectionEnabled,
|
||||
showBackButton = showBackButton,
|
||||
onActiveChanged = { state.eventSink(UserListEvents.OnSearchActiveChanged(it)) },
|
||||
onTextChanged = { state.eventSink(UserListEvents.UpdateSearchQuery(it)) },
|
||||
onUserSelected = {
|
||||
onActiveChange = { state.eventSink(UserListEvents.OnSearchActiveChanged(it)) },
|
||||
onTextChange = { state.eventSink(UserListEvents.UpdateSearchQuery(it)) },
|
||||
onUserSelect = {
|
||||
state.eventSink(UserListEvents.AddToSelection(it))
|
||||
onUserSelected(it)
|
||||
onSelectUser(it)
|
||||
},
|
||||
onUserDeselected = {
|
||||
onUserDeselect = {
|
||||
state.eventSink(UserListEvents.RemoveFromSelection(it))
|
||||
onUserDeselected(it)
|
||||
onDeselectUser(it)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -78,9 +78,9 @@ fun UserListView(
|
|||
contentPadding = PaddingValues(16.dp),
|
||||
selectedUsers = state.selectedUsers,
|
||||
autoScroll = true,
|
||||
onUserRemoved = {
|
||||
onUserRemove = {
|
||||
state.eventSink(UserListEvents.RemoveFromSelection(it))
|
||||
onUserDeselected(it)
|
||||
onDeselectUser(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -102,10 +102,10 @@ fun UserListView(
|
|||
onCheckedChange = {
|
||||
if (isSelected) {
|
||||
state.eventSink(UserListEvents.RemoveFromSelection(recentDirectRoom.matrixUser))
|
||||
onUserDeselected(recentDirectRoom.matrixUser)
|
||||
onDeselectUser(recentDirectRoom.matrixUser)
|
||||
} else {
|
||||
state.eventSink(UserListEvents.AddToSelection(recentDirectRoom.matrixUser))
|
||||
onUserSelected(recentDirectRoom.matrixUser)
|
||||
onSelectUser(recentDirectRoom.matrixUser)
|
||||
}
|
||||
},
|
||||
data = CheckableUserRowData.Resolved(
|
||||
|
|
@ -129,7 +129,7 @@ fun UserListView(
|
|||
internal fun UserListViewPreview(@PreviewParameter(UserListStateProvider::class) state: UserListState) = ElementPreview {
|
||||
UserListView(
|
||||
state = state,
|
||||
onUserSelected = {},
|
||||
onUserDeselected = {},
|
||||
onSelectUser = {},
|
||||
onDeselectUser = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class ConfigureRoomNode @AssistedInject constructor(
|
|||
fun onCreateRoomSuccess(roomId: RoomId)
|
||||
}
|
||||
|
||||
private fun onRoomCreated(roomId: RoomId) {
|
||||
private fun onCreateRoomSuccess(roomId: RoomId) {
|
||||
plugins<Callback>().forEach { it.onCreateRoomSuccess(roomId) }
|
||||
}
|
||||
|
||||
|
|
@ -60,8 +60,8 @@ class ConfigureRoomNode @AssistedInject constructor(
|
|||
ConfigureRoomView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onBackPressed = this::navigateUp,
|
||||
onRoomCreated = this::onRoomCreated,
|
||||
onBackClick = this::navigateUp,
|
||||
onCreateRoomSuccess = this::onCreateRoomSuccess,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,12 +29,10 @@ import androidx.compose.foundation.rememberScrollState
|
|||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
|
|
@ -63,27 +61,20 @@ import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
|||
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
|
||||
import io.element.android.libraries.permissions.api.PermissionsView
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ConfigureRoomView(
|
||||
state: ConfigureRoomState,
|
||||
onBackPressed: () -> Unit,
|
||||
onRoomCreated: (RoomId) -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
onCreateRoomSuccess: (RoomId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val focusManager = LocalFocusManager.current
|
||||
val itemActionsBottomSheetState = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Hidden,
|
||||
)
|
||||
val isAvatarActionsSheetVisible = remember { mutableStateOf(false) }
|
||||
|
||||
fun onAvatarClicked() {
|
||||
fun onAvatarClick() {
|
||||
focusManager.clearFocus()
|
||||
coroutineScope.launch {
|
||||
itemActionsBottomSheetState.show()
|
||||
}
|
||||
isAvatarActionsSheetVisible.value = true
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
|
|
@ -91,8 +82,8 @@ fun ConfigureRoomView(
|
|||
topBar = {
|
||||
ConfigureRoomToolbar(
|
||||
isNextActionEnabled = state.isCreateButtonEnabled,
|
||||
onBackPressed = onBackPressed,
|
||||
onNextPressed = {
|
||||
onBackClick = onBackClick,
|
||||
onNextClick = {
|
||||
focusManager.clearFocus()
|
||||
state.eventSink(ConfigureRoomEvents.CreateRoom(state.config))
|
||||
},
|
||||
|
|
@ -111,20 +102,20 @@ fun ConfigureRoomView(
|
|||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
avatarUri = state.config.avatarUri,
|
||||
roomName = state.config.roomName.orEmpty(),
|
||||
onAvatarClick = ::onAvatarClicked,
|
||||
onRoomNameChanged = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
|
||||
onAvatarClick = ::onAvatarClick,
|
||||
onChangeRoomName = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
|
||||
)
|
||||
RoomTopic(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
topic = state.config.topic.orEmpty(),
|
||||
onTopicChanged = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
|
||||
onTopicChange = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
|
||||
)
|
||||
if (state.config.invites.isNotEmpty()) {
|
||||
SelectedUsersRowList(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
contentPadding = PaddingValues(horizontal = 24.dp),
|
||||
selectedUsers = state.config.invites,
|
||||
onUserRemoved = {
|
||||
onUserRemove = {
|
||||
focusManager.clearFocus()
|
||||
state.eventSink(ConfigureRoomEvents.RemoveFromSelection(it))
|
||||
},
|
||||
|
|
@ -133,7 +124,7 @@ fun ConfigureRoomView(
|
|||
RoomPrivacyOptions(
|
||||
modifier = Modifier.padding(bottom = 40.dp),
|
||||
selected = state.config.privacy,
|
||||
onOptionSelected = {
|
||||
onOptionClick = {
|
||||
focusManager.clearFocus()
|
||||
state.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(it.privacy))
|
||||
},
|
||||
|
|
@ -143,8 +134,9 @@ fun ConfigureRoomView(
|
|||
|
||||
AvatarActionBottomSheet(
|
||||
actions = state.avatarActions,
|
||||
modalBottomSheetState = itemActionsBottomSheetState,
|
||||
onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
|
||||
isVisible = isAvatarActionsSheetVisible.value,
|
||||
onDismiss = { isAvatarActionsSheetVisible.value = false },
|
||||
onSelectAction = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
|
||||
)
|
||||
|
||||
AsyncActionView(
|
||||
|
|
@ -154,7 +146,7 @@ fun ConfigureRoomView(
|
|||
progressText = stringResource(CommonStrings.common_creating_room),
|
||||
)
|
||||
},
|
||||
onSuccess = { onRoomCreated(it) },
|
||||
onSuccess = { onCreateRoomSuccess(it) },
|
||||
errorMessage = { stringResource(R.string.screen_create_room_error_creating_room) },
|
||||
onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) },
|
||||
onErrorDismiss = { state.eventSink(ConfigureRoomEvents.CancelCreateRoom) },
|
||||
|
|
@ -169,8 +161,8 @@ fun ConfigureRoomView(
|
|||
@Composable
|
||||
private fun ConfigureRoomToolbar(
|
||||
isNextActionEnabled: Boolean,
|
||||
onBackPressed: () -> Unit,
|
||||
onNextPressed: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
onNextClick: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
|
@ -179,12 +171,12 @@ private fun ConfigureRoomToolbar(
|
|||
style = ElementTheme.typography.aliasScreenTitle,
|
||||
)
|
||||
},
|
||||
navigationIcon = { BackButton(onClick = onBackPressed) },
|
||||
navigationIcon = { BackButton(onClick = onBackClick) },
|
||||
actions = {
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_create),
|
||||
enabled = isNextActionEnabled,
|
||||
onClick = onNextPressed,
|
||||
onClick = onNextClick,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -195,7 +187,7 @@ private fun RoomNameWithAvatar(
|
|||
avatarUri: Uri?,
|
||||
roomName: String,
|
||||
onAvatarClick: () -> Unit,
|
||||
onRoomNameChanged: (String) -> Unit,
|
||||
onChangeRoomName: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -213,7 +205,7 @@ private fun RoomNameWithAvatar(
|
|||
value = roomName,
|
||||
placeholder = stringResource(CommonStrings.common_room_name_placeholder),
|
||||
singleLine = true,
|
||||
onValueChange = onRoomNameChanged,
|
||||
onValueChange = onChangeRoomName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -221,7 +213,7 @@ private fun RoomNameWithAvatar(
|
|||
@Composable
|
||||
private fun RoomTopic(
|
||||
topic: String,
|
||||
onTopicChanged: (String) -> Unit,
|
||||
onTopicChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LabelledTextField(
|
||||
|
|
@ -229,7 +221,7 @@ private fun RoomTopic(
|
|||
label = stringResource(R.string.screen_create_room_topic_label),
|
||||
value = topic,
|
||||
placeholder = stringResource(CommonStrings.common_topic_placeholder),
|
||||
onValueChange = onTopicChanged,
|
||||
onValueChange = onTopicChange,
|
||||
maxLines = 3,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
|
|
@ -240,7 +232,7 @@ private fun RoomTopic(
|
|||
@Composable
|
||||
private fun RoomPrivacyOptions(
|
||||
selected: RoomPrivacy?,
|
||||
onOptionSelected: (RoomPrivacyItem) -> Unit,
|
||||
onOptionClick: (RoomPrivacyItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val items = roomPrivacyItems()
|
||||
|
|
@ -249,7 +241,7 @@ private fun RoomPrivacyOptions(
|
|||
RoomPrivacyOption(
|
||||
roomPrivacyItem = item,
|
||||
isSelected = selected == item.privacy,
|
||||
onOptionSelected = onOptionSelected,
|
||||
onOptionClick = onOptionClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -260,7 +252,7 @@ private fun RoomPrivacyOptions(
|
|||
internal fun ConfigureRoomViewPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) = ElementPreview {
|
||||
ConfigureRoomView(
|
||||
state = state,
|
||||
onBackPressed = {},
|
||||
onRoomCreated = {},
|
||||
onBackClick = {},
|
||||
onCreateRoomSuccess = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ class CreateRoomRootNode @AssistedInject constructor(
|
|||
CreateRoomRootView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onClosePressed = this::navigateUp,
|
||||
onNewRoomClicked = ::onCreateNewRoom,
|
||||
onCloseClick = this::navigateUp,
|
||||
onNewRoomClick = ::onCreateNewRoom,
|
||||
onOpenDM = ::onStartChatSuccess,
|
||||
onInviteFriendsClicked = { invitePeople(activity) }
|
||||
onInviteFriendsClick = { invitePeople(activity) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,17 +59,17 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
@Composable
|
||||
fun CreateRoomRootView(
|
||||
state: CreateRoomRootState,
|
||||
onClosePressed: () -> Unit,
|
||||
onNewRoomClicked: () -> Unit,
|
||||
onCloseClick: () -> Unit,
|
||||
onNewRoomClick: () -> Unit,
|
||||
onOpenDM: (RoomId) -> Unit,
|
||||
onInviteFriendsClicked: () -> Unit,
|
||||
onInviteFriendsClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
topBar = {
|
||||
if (!state.userListState.isSearchActive) {
|
||||
CreateRoomRootViewTopBar(onClosePressed = onClosePressed)
|
||||
CreateRoomRootViewTopBar(onCloseClick = onCloseClick)
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
|
|
@ -86,18 +86,18 @@ fun CreateRoomRootView(
|
|||
state = state.userListState.copy(
|
||||
recentDirectRooms = persistentListOf(),
|
||||
),
|
||||
onUserSelected = {
|
||||
onSelectUser = {
|
||||
state.eventSink(CreateRoomRootEvents.StartDM(it))
|
||||
},
|
||||
onUserDeselected = { },
|
||||
onDeselectUser = { },
|
||||
)
|
||||
|
||||
if (!state.userListState.isSearchActive) {
|
||||
CreateRoomActionButtonsList(
|
||||
state = state,
|
||||
onNewRoomClicked = onNewRoomClicked,
|
||||
onInvitePeopleClicked = onInviteFriendsClicked,
|
||||
onDmClicked = onOpenDM,
|
||||
onNewRoomClick = onNewRoomClick,
|
||||
onInvitePeopleClick = onInviteFriendsClick,
|
||||
onDmClick = onOpenDM,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ fun CreateRoomRootView(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CreateRoomRootViewTopBar(
|
||||
onClosePressed: () -> Unit,
|
||||
onCloseClick: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
|
@ -137,7 +137,7 @@ private fun CreateRoomRootViewTopBar(
|
|||
navigationIcon = {
|
||||
BackButton(
|
||||
imageVector = CompoundIcons.Close(),
|
||||
onClick = onClosePressed,
|
||||
onClick = onCloseClick,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -146,23 +146,23 @@ private fun CreateRoomRootViewTopBar(
|
|||
@Composable
|
||||
private fun CreateRoomActionButtonsList(
|
||||
state: CreateRoomRootState,
|
||||
onNewRoomClicked: () -> Unit,
|
||||
onInvitePeopleClicked: () -> Unit,
|
||||
onDmClicked: (RoomId) -> Unit,
|
||||
onNewRoomClick: () -> Unit,
|
||||
onInvitePeopleClick: () -> Unit,
|
||||
onDmClick: (RoomId) -> Unit,
|
||||
) {
|
||||
LazyColumn {
|
||||
item {
|
||||
CreateRoomActionButton(
|
||||
iconRes = CompoundDrawables.ic_compound_plus,
|
||||
text = stringResource(id = R.string.screen_create_room_action_create_room),
|
||||
onClick = onNewRoomClicked,
|
||||
onClick = onNewRoomClick,
|
||||
)
|
||||
}
|
||||
item {
|
||||
CreateRoomActionButton(
|
||||
iconRes = CompoundDrawables.ic_compound_share_android,
|
||||
text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName),
|
||||
onClick = onInvitePeopleClicked,
|
||||
onClick = onInvitePeopleClick,
|
||||
)
|
||||
}
|
||||
if (state.userListState.recentDirectRooms.isNotEmpty()) {
|
||||
|
|
@ -177,7 +177,7 @@ private fun CreateRoomActionButtonsList(
|
|||
MatrixUserRow(
|
||||
modifier = Modifier.clickable(
|
||||
onClick = {
|
||||
onDmClicked(recentDirectRoom.roomId)
|
||||
onDmClick(recentDirectRoom.roomId)
|
||||
}
|
||||
),
|
||||
matrixUser = recentDirectRoom.matrixUser,
|
||||
|
|
@ -222,9 +222,9 @@ internal fun CreateRoomRootViewPreview(@PreviewParameter(CreateRoomRootStateProv
|
|||
ElementPreview {
|
||||
CreateRoomRootView(
|
||||
state = state,
|
||||
onClosePressed = {},
|
||||
onNewRoomClicked = {},
|
||||
onCloseClick = {},
|
||||
onNewRoomClick = {},
|
||||
onOpenDM = {},
|
||||
onInviteFriendsClicked = {},
|
||||
onInviteFriendsClick = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"ახალი ოთახი"</string>
|
||||
<string name="screen_create_room_add_people_title">"ხალხის მოწვევა"</string>
|
||||
<string name="screen_create_room_error_creating_room">"ოთახის შექმნისას შეცდომა მოხდა"</string>
|
||||
<string name="screen_create_room_private_option_description">"ამ ოთახში შეტყობინებები დაშიფრულია. შემდგომ დაშიფვრის გამორთვა შეუძლებელია."</string>
|
||||
<string name="screen_create_room_private_option_title">"კერძო ოთახი (მხოლოდ მოწვევა)"</string>
|
||||
<string name="screen_create_room_public_option_description">"შეტყობინებები არ არის დაშიფრული და ყველას შეუძლია მათი წაკითხვა. შეგიძლიათ ჩართოთ დაშიფვრა მოგვიანებით."</string>
|
||||
<string name="screen_create_room_public_option_title">"საჯარო ოთახი (ნებისმიერი)"</string>
|
||||
<string name="screen_create_room_room_name_label">"ოთახის სახელი"</string>
|
||||
<string name="screen_create_room_title">"ოთახის შექმნა"</string>
|
||||
<string name="screen_create_room_topic_label">"თემა (სურვილისამებრ)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"ჩატის დაწყების მცდელობისას შეცდომა მოხდა"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"Nova sala"</string>
|
||||
<string name="screen_create_room_add_people_title">"Convidar pessoas"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Ocorreu um erro ao criar a sala"</string>
|
||||
<string name="screen_create_room_private_option_description">"As mensagens serão cifradas. Uma vez ativada, não é possível desativar a cifragem."</string>
|
||||
<string name="screen_create_room_private_option_title">"Sala privada (entrada apenas por convite)"</string>
|
||||
<string name="screen_create_room_public_option_description">"As mensagens não serão cifradas e qualquer um as poderá ler. É possível ativar a cifragem posteriormente."</string>
|
||||
<string name="screen_create_room_public_option_title">"Sala pública (entrada livre)"</string>
|
||||
<string name="screen_create_room_room_name_label">"Nome da sala"</string>
|
||||
<string name="screen_create_room_title">"Criar uma sala"</string>
|
||||
<string name="screen_create_room_topic_label">"Descrição (opcional)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Ocorreu um erro ao tentar iniciar uma conversa"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"新聊天室"</string>
|
||||
<string name="screen_create_room_add_people_title">"邀请朋友"</string>
|
||||
<string name="screen_create_room_error_creating_room">"创建房间时出错"</string>
|
||||
<string name="screen_create_room_private_option_description">"此聊天室中的消息已加密。加密无法禁用。"</string>
|
||||
<string name="screen_create_room_private_option_title">"私人房间(仅限受邀者)"</string>
|
||||
<string name="screen_create_room_public_option_description">"消息未加密,任何人都可以查看。你可以稍后启用加密。"</string>
|
||||
<string name="screen_create_room_public_option_title">"公共房间(任何人)"</string>
|
||||
<string name="screen_create_room_room_name_label">"房间名称"</string>
|
||||
<string name="screen_create_room_title">"创建房间"</string>
|
||||
<string name="screen_create_room_topic_label">"主题(可选)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"在开始聊天时发生了错误"</string>
|
||||
</resources>
|
||||
|
|
@ -47,7 +47,7 @@ class AddPeopleViewTest {
|
|||
aUserListState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onBackPressed = it
|
||||
onBackClick = it
|
||||
)
|
||||
rule.pressBack()
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ class AddPeopleViewTest {
|
|||
aUserListState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onNextPressed = it
|
||||
onNextClick = it
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_skip)
|
||||
}
|
||||
|
|
@ -85,14 +85,14 @@ class AddPeopleViewTest {
|
|||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAddPeopleView(
|
||||
state: UserListState,
|
||||
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||
onNextPressed: () -> Unit = EnsureNeverCalled(),
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
onNextClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
AddPeopleView(
|
||||
state = state,
|
||||
onBackPressed = onBackPressed,
|
||||
onNextPressed = onNextPressed,
|
||||
onBackClick = onBackClick,
|
||||
onNextClick = onNextClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class CreateRoomRootViewTest {
|
|||
aCreateRoomRootState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onClosePressed = it
|
||||
onCloseClick = it
|
||||
)
|
||||
rule.pressBack()
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ class CreateRoomRootViewTest {
|
|||
aCreateRoomRootState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onNewRoomClicked = it
|
||||
onNewRoomClick = it
|
||||
)
|
||||
rule.clickOn(R.string.screen_create_room_action_create_room)
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ class CreateRoomRootViewTest {
|
|||
applicationName = "test",
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onInviteFriendsClicked = it
|
||||
onInviteFriendsClick = it
|
||||
)
|
||||
val text = rule.activity.getString(CommonStrings.action_invite_friends_to_app, "test")
|
||||
rule.onNodeWithText(text).performClick()
|
||||
|
|
@ -114,18 +114,18 @@ class CreateRoomRootViewTest {
|
|||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreateRoomRootView(
|
||||
state: CreateRoomRootState,
|
||||
onClosePressed: () -> Unit = EnsureNeverCalled(),
|
||||
onNewRoomClicked: () -> Unit = EnsureNeverCalled(),
|
||||
onCloseClick: () -> Unit = EnsureNeverCalled(),
|
||||
onNewRoomClick: () -> Unit = EnsureNeverCalled(),
|
||||
onOpenDM: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onInviteFriendsClicked: () -> Unit = EnsureNeverCalled(),
|
||||
onInviteFriendsClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
CreateRoomRootView(
|
||||
state = state,
|
||||
onClosePressed = onClosePressed,
|
||||
onNewRoomClicked = onNewRoomClicked,
|
||||
onCloseClick = onCloseClick,
|
||||
onNewRoomClick = onNewRoomClick,
|
||||
onOpenDM = onOpenDM,
|
||||
onInviteFriendsClicked = onInviteFriendsClicked,
|
||||
onInviteFriendsClick = onInviteFriendsClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,9 +132,8 @@ class FtueFlowNode @AssistedInject constructor(
|
|||
lifecycleScope.launch { moveToNextStep() }
|
||||
}
|
||||
}
|
||||
lockScreenEntryPoint.nodeBuilder(this, buildContext)
|
||||
lockScreenEntryPoint.nodeBuilder(this, buildContext, LockScreenEntryPoint.Target.Setup)
|
||||
.callback(callback)
|
||||
.target(LockScreenEntryPoint.Target.Setup)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,18 +34,18 @@ class WelcomeNode @AssistedInject constructor(
|
|||
private val buildMeta: BuildMeta,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun onContinueClicked()
|
||||
fun onContinueClick()
|
||||
}
|
||||
|
||||
private fun onContinueClicked() {
|
||||
plugins.filterIsInstance<Callback>().forEach { it.onContinueClicked() }
|
||||
private fun onContinueClick() {
|
||||
plugins.filterIsInstance<Callback>().forEach { it.onContinueClick() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
WelcomeView(
|
||||
applicationName = buildMeta.applicationName,
|
||||
onContinueClicked = ::onContinueClicked,
|
||||
onContinueClick = ::onContinueClick,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
fun WelcomeView(
|
||||
applicationName: String,
|
||||
modifier: Modifier = Modifier,
|
||||
onContinueClicked: () -> Unit,
|
||||
onContinueClick: () -> Unit,
|
||||
) {
|
||||
BackHandler(onBack = onContinueClicked)
|
||||
BackHandler(onBack = onContinueClick)
|
||||
OnBoardingPage(
|
||||
modifier = modifier
|
||||
.systemBarsPadding()
|
||||
|
|
@ -90,7 +90,7 @@ fun WelcomeView(
|
|||
Button(
|
||||
text = stringResource(CommonStrings.action_continue),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onContinueClicked
|
||||
onClick = onContinueClick
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
|
|
@ -113,6 +113,6 @@ private fun listItems() = persistentListOf(
|
|||
@Composable
|
||||
internal fun WelcomeViewPreview() {
|
||||
ElementPreview {
|
||||
WelcomeView(applicationName = "Element X", onContinueClicked = {})
|
||||
WelcomeView(applicationName = "Element X", onContinueClick = {})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,24 @@
|
|||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Злучэнне небяспечнае"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Вам будзе прапанавана ўвесці дзве лічбы, паказаныя на гэтай прыладзе."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Увядзіце наступны нумар на іншай прыладзе."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"Уваход быў адменены на іншай прыладзе."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Запыт на ўваход скасаваны"</string>
|
||||
<string name="screen_qr_code_login_error_declined_subtitle">"Запыт на іншай прыладзе не быў прыняты."</string>
|
||||
<string name="screen_qr_code_login_error_declined_title">"Уваход адхілены"</string>
|
||||
<string name="screen_qr_code_login_error_expired_subtitle">"Тэрмін уваходу скончыўся. Калі ласка, паспрабуйце яшчэ раз."</string>
|
||||
<string name="screen_qr_code_login_error_expired_title">"Уваход у сістэму не быў завершаны своечасова"</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Ваша іншая прылада не падтрымлівае ўваход у %s з дапамогай QR-кода.
|
||||
|
||||
Паспрабуйце ўвайсці ў сістэму ўручную або адскануйце QR-код з дапамогай іншай прылады."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR-код не падтрымліваецца"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Ваш правайдар уліковага запісу не падтрымлівае %1$s."</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s не падтрымліваецца"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Гатовы да сканавання"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Адкрыйце %1$s на настольнай прыладзе"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Націсніце на свой аватар"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Выберыце %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Звязаць новую прыладу”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Выконвайце паказаныя інструкцыі"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Адсканіруйце QR-код з дапамогай гэтай прылады"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Адкрыйце %1$s на іншай прыладзе, каб атрымаць QR-код"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Выкарыстоўвайце QR-код, паказаны на іншай прыладзе."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Паўтарыць спробу"</string>
|
||||
|
|
|
|||
|
|
@ -11,11 +11,24 @@
|
|||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Připojení není zabezpečené"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Budete požádáni o zadání dvou níže uvedených číslic."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Zadejte níže uvedené číslo na svém dalším zařízení"</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"Přihlášení bylo na druhém zařízení zrušeno."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Žádost o přihlášení zrušena"</string>
|
||||
<string name="screen_qr_code_login_error_declined_subtitle">"Požadavek na vašem druhém zařízení nebyl přijat."</string>
|
||||
<string name="screen_qr_code_login_error_declined_title">"Přihlášení odmítnuto"</string>
|
||||
<string name="screen_qr_code_login_error_expired_subtitle">"Platnost přihlášení vypršela. Zkuste to prosím znovu."</string>
|
||||
<string name="screen_qr_code_login_error_expired_title">"Přihlášení nebylo dokončeno včas"</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Vaše druhé zařízení nepodporuje přihlášení k %su pomocí QR kódu.
|
||||
|
||||
Zkuste se přihlásit ručně nebo naskenujte QR kód pomocí jiného zařízení."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR kód není podporován"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Váš poskytovatel účtu nepodporuje %1$s."</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s není podporováno"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Připraveno ke skenování"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Otevřete %1$s na stolním počítači"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Klikněte na svůj avatar"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Vybrat %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Připojit nové zařízení\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Postupujte podle uvedených pokynů"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Naskenujte QR kód pomocí tohoto zařízení"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Otevřete %1$s na jiném zařízení pro získání QR kódu"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Použijte QR kód zobrazený na druhém zařízení."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Zkusit znovu"</string>
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@
|
|||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Trage die unten angezeigte Zahl auf einem anderen Device ein"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Bereit zum Scannen"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Wähle %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Neues Gerät verknüpfen\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Befolge die angezeigten Anweisungen"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Scanne den QR-Code mit diesem Gerät"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Öffne %1$s auf einem anderen Gerät, um den QR-Code zu erhalten"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Verwende den QR-Code, der auf dem anderen Gerät angezeigt wird."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Erneut versuchen"</string>
|
||||
|
|
|
|||
|
|
@ -11,11 +11,22 @@
|
|||
<string name="screen_qr_code_login_connection_note_secure_state_title">"La connexion n’est pas sécurisée"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Il vous sera demandé de saisir les deux chiffres affichés sur cet appareil."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Saisissez le nombre ci-dessous sur votre autre appareil"</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"La connexion a été annulée sur l’autre appareil."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Demande de connexion annulée"</string>
|
||||
<string name="screen_qr_code_login_error_declined_subtitle">"La demande sur l’autre appareil n’a pas été acceptée."</string>
|
||||
<string name="screen_qr_code_login_error_declined_title">"Connexion refusée"</string>
|
||||
<string name="screen_qr_code_login_error_expired_subtitle">"Connexion expirée. Veuillez essayer à nouveau."</string>
|
||||
<string name="screen_qr_code_login_error_expired_title">"La connexion a pris trop de temps."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Votre autre appareil ne supporte pas la connexion à %s avec un code QR. Essayer de vous connecter manuellement, ou scanner le code QR avec un autre appareil."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_title">"Code QR non supporté"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Votre fournisseur de compte ne supporte pas %1$s."</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s n’est pas supporté"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Prêt à scanner"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Ouvrez %1$s sur un ordinateur"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Cliquez sur votre image de profil"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Choisissez %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Associer une nouvelle session”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Suivez les instructions affichées"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Scanner le code QR avec cet appareil"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Ouvrez %1$s sur un autre appareil pour obtenir le QR code"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Scannez le QR code affiché sur l’autre appareil."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Essayer à nouveau"</string>
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@
|
|||
<string name="screen_qr_code_login_connection_note_secure_state_title">"A kapcsolat nem biztonságos"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"A rendszer kérni fogja, hogy adja meg az alábbi két számjegyet az eszközén."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Adja meg az alábbi számot a másik eszközén"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Készen áll a beolvasásra"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Nyissa meg az %1$set egy asztali eszközön"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Kattintson a profilképére"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Válassza ezt: %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"„Új eszköz összekapcsolása”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Kövesse a látható utasításokat"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Olvassa be a QR-kódot ezzel az eszközzel"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Nyissa meg az %1$set egy másik eszközön a QR-kód lekéréséhez."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Használja a másik eszközön látható QR-kódot."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Próbálja újra"</string>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,33 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_notification_optin_subtitle">"Potrai modificare le tue impostazioni in seguito."</string>
|
||||
<string name="screen_notification_optin_title">"Consenti le notifiche e non perdere mai un messaggio"</string>
|
||||
<string name="screen_qr_code_login_connecting_subtitle">"Stabilendo la connessione"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Non è stato possibile stabilire una connessione sicura con il nuovo dispositivo. I tuoi dispositivi esistenti sono ancora al sicuro e non devi preoccuparti di loro."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"E adesso?"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Prova ad accedere di nuovo con un codice QR nel caso si sia verificato un problema di rete."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Se riscontri lo stesso problema, prova con un altra rete wifi o usa i dati mobili al posto del wifi."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Se il problema persiste, accedi manualmente"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"La connessione non è sicura"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Ti verrà chiesto di inserire le due cifre mostrate su questo dispositivo."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Inserisci il numero qui sotto sull\'altro dispositivo"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Apri %1$s su un dispositivo desktop"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Clicca sul tuo avatar"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Seleziona %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Collega un nuovo dispositivo\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Segui le istruzioni mostrate"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Apri %1$s su un altro dispositivo per ottenere il codice QR"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Usa il codice QR mostrato sull\'altro dispositivo."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Riprova"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Codice QR sbagliato"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_button">"Vai alle impostazioni della fotocamera"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_description">"Per continuare, è necessario fornire l\'autorizzazione a %1$s per utilizzare la fotocamera del dispositivo."</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_title">"Consenti l\'accesso alla fotocamera per la scansione del codice QR"</string>
|
||||
<string name="screen_qr_code_login_scanning_state_title">"Scansiona il codice QR"</string>
|
||||
<string name="screen_qr_code_login_start_over_button">"Ricomincia"</string>
|
||||
<string name="screen_qr_code_login_unknown_error_description">"Si è verificato un errore inatteso. Riprova."</string>
|
||||
<string name="screen_qr_code_login_verify_code_loading">"In attesa dell\'altro dispositivo"</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"Il fornitore dell\'account potrebbe richiedere il seguente codice per verificare l\'accesso."</string>
|
||||
<string name="screen_qr_code_login_verify_code_title">"Il tuo codice di verifica"</string>
|
||||
<string name="screen_welcome_bullet_1">"Chiamate, sondaggi, ricerche e altro ancora saranno aggiunti nel corso dell\'anno."</string>
|
||||
<string name="screen_welcome_bullet_2">"La cronologia dei messaggi per le stanze crittografate non è ancora disponibile."</string>
|
||||
<string name="screen_welcome_bullet_3">"Ci piacerebbe sentire il tuo parere, facci sapere cosa ne pensi tramite la pagina delle impostazioni."</string>
|
||||
|
|
|
|||
11
features/ftue/impl/src/main/res/values-ka/translations.xml
Normal file
11
features/ftue/impl/src/main/res/values-ka/translations.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_notification_optin_subtitle">"თქვენ შეგიძლიათ შეცვალოთ თქვენი პარამეტრები მოგვიანებით."</string>
|
||||
<string name="screen_notification_optin_title">"ყველა შეტყობინებაზე შეტყობინებების მიღება"</string>
|
||||
<string name="screen_welcome_bullet_1">"ზარები, გამოკითხვები, ძიება და სხვა დაემატება ამ წლის ბოლოს."</string>
|
||||
<string name="screen_welcome_bullet_2">"დაშიფრული ოთახებისთვის შეტყობინებების ისტორია ჯერ არ არის ხელმისაწვდომი."</string>
|
||||
<string name="screen_welcome_bullet_3">"ჩვენ სიამოვნებით მოვისმინოთ თქვენგან, შეგვატყობინეთ რას ფიქრობთ პარამეტრების გვერდზე."</string>
|
||||
<string name="screen_welcome_button">"დავიწყოთ!"</string>
|
||||
<string name="screen_welcome_subtitle">"აი, რა უნდა იცოდეთ:"</string>
|
||||
<string name="screen_welcome_title">"კეთილი იყოს თქვენი მობრძანება %1$s-ში!"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_notification_optin_subtitle">"Podes alterar as tuas definições mais tarde."</string>
|
||||
<string name="screen_notification_optin_title">"Permite as notificações e nunca percas uma mensagem"</string>
|
||||
<string name="screen_qr_code_login_connecting_subtitle">"A estabelecer uma ligação segura"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Não foi possível estabelecer uma ligação segura com o novo dispositivo. Os teus outros dispositivos continuam seguros, não precisas de te preocupar com eles."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"E agora?"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Tenta iniciar sessão novamente com um código QR, caso se trate de um problema de rede"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Se tiveres o mesmo problema, experimenta uma rede Wi-Fi diferente ou utiliza os teus dados móveis."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Se isso não funcionar, inicia sessão manualmente"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Ligação insegura"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Ser-te-á pedido que insiras os dois dígitos indicados neste dispositivo."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Insere o número abaixo no teu dispositivo"</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Pedido de início de sessão cancelado"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Pronto para ler"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Abre a %1$s num computador"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Carrega no teu avatar"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Seleciona %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Ligar novo dispositivo”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Lê o código QR com este dispositivo"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Abre a %1$s noutro dispositivo para obteres o código QR"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Lê o código QR apresentado no outro dispositivo."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Tentar novamente"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Código QR inválido"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_button">"Ir para as configurações da câmara"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_description">"Para continuar, tens que dar permissão à %1$s para aceder à câmara do teu dispositivo."</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_title">"Permitir o acesso à câmara para ler o código QR"</string>
|
||||
<string name="screen_qr_code_login_scanning_state_title">"Ler o código QR"</string>
|
||||
<string name="screen_qr_code_login_start_over_button">"Começar de novo"</string>
|
||||
<string name="screen_qr_code_login_unknown_error_description">"Ocorreu um erro inesperado. Tenta novamente."</string>
|
||||
<string name="screen_qr_code_login_verify_code_loading">"À espera do teu outro dispositivo"</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"O teu fornecedor de conta pode pedir o seguinte código para verificar o início de sessão."</string>
|
||||
<string name="screen_qr_code_login_verify_code_title">"O teu código de verificação"</string>
|
||||
<string name="screen_welcome_bullet_1">"Chamadas, sondagens, pesquisa e mais funcionalidades vão ser adicionadas ao longo do ano."</string>
|
||||
<string name="screen_welcome_bullet_2">"O histórico de mensagens em salas cifradas ainda não está disponível."</string>
|
||||
<string name="screen_welcome_bullet_3">"Gostaríamos de ouvir a tua opinião, diz-nos o que pensas através da página de configurações."</string>
|
||||
<string name="screen_welcome_button">"Vamos lá!"</string>
|
||||
<string name="screen_welcome_subtitle">"Eis o que tens de saber:"</string>
|
||||
<string name="screen_welcome_title">"Bem-vindo à %1$s!"</string>
|
||||
</resources>
|
||||
|
|
@ -2,7 +2,34 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_notification_optin_subtitle">"Puteți modifica setările mai târziu."</string>
|
||||
<string name="screen_notification_optin_title">"Permiteți notificările și nu pierdeți niciodată un mesaj"</string>
|
||||
<string name="screen_qr_code_login_connecting_subtitle">"Se stabilește o conexiune securizată"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nu a putut fi făcută o conexiune sigură la noul dispozitiv. Dispozitivele existente sunt încă în siguranță și nu trebuie să vă faceți griji cu privire la ele."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Și acum?"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Încercați să vă conectați din nou cu un cod QR în cazul în care a fost o problemă de rețea."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Dacă întâmpinați aceeași problemă, încercați o altă rețea Wi-Fi sau utilizați datele mobile în loc de Wi-Fi."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Dacă nu funcționează, conectați-vă manual"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Conexiunea nu este sigură"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Vi se va cere să introduceți cele două cifre afișate pe acest dispozitiv."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Introduceți numărul de mai jos pe celălalt dispozitiv"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Gata de scanare"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Deschideți %1$s pe un dispozitiv desktop"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Faceți clic pe avatarul dumneavoastră"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Selectați %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"„Conectați un dispozitiv nou”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Scanați codul QR cu acest dispozitiv"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Deschideți %1$s pe un alt dispozitiv pentru a obține codul QR"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Utilizați codul QR afișat pe celălalt dispozitiv."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Încercați din nou"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Cod QR greșit"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_button">"Mergeți la setările camerei"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_description">"Trebuie să acordați permisiunea ca %1$s să folosească camera dispozitivului pentru a continua."</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_title">"Permiteți accesul la cameră pentru a scana codul QR"</string>
|
||||
<string name="screen_qr_code_login_scanning_state_title">"Scanați codul QR"</string>
|
||||
<string name="screen_qr_code_login_start_over_button">"Începeți din nou"</string>
|
||||
<string name="screen_qr_code_login_unknown_error_description">"A apărut o eroare neașteptată. Vă rugăm să încercați din nou."</string>
|
||||
<string name="screen_qr_code_login_verify_code_loading">"În așteptarea celuilalt dispozitiv"</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"Furnizorul dumneavoastră de cont poate solicita următorul cod pentru a verifica conectarea."</string>
|
||||
<string name="screen_qr_code_login_verify_code_title">"Codul dumneavoastră de verificare"</string>
|
||||
<string name="screen_welcome_bullet_1">"Apelurile, sondajele, căutare și multe altele vor fi adăugate în cursul acestui an."</string>
|
||||
<string name="screen_welcome_bullet_2">"Istoricul mesajelor pentru camerele criptate nu va fi disponibil în această actualizare."</string>
|
||||
<string name="screen_welcome_bullet_3">"Ne-ar plăcea să auzim de la dumneavoastră, spuneți-ne ce părere aveți prin intermediul paginii de setări."</string>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,33 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_notification_optin_subtitle">"Вы можете изменить настройки позже."</string>
|
||||
<string name="screen_notification_optin_title">"Разрешите уведомления и никогда не пропустите сообщение"</string>
|
||||
<string name="screen_qr_code_login_connecting_subtitle">"Установление соединения"</string>
|
||||
<string name="screen_qr_code_login_connecting_subtitle">"Установление безопасного соединения"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Что теперь?"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Попробуйте снова войти в систему с помощью QR-кода, если это была сетевая проблема"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Если вы столкнулись с той же проблемой, попробуйте сменить точку доступа Wi-Fi или используйте мобильные данные"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Если это не помогло, войдите вручную"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Соединение не защищено"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Вам будет предложено ввести две цифры, показанные ниже."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Введите номер на своем устройстве"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Вам нужно будет ввести две цифры, показанные на этом устройстве."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Введите показанный номер на своем другом устройстве"</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"Вход на другом устройстве был отменен."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Запрос на вход отменен"</string>
|
||||
<string name="screen_qr_code_login_error_declined_subtitle">"Запрос не был принят на другом устройстве."</string>
|
||||
<string name="screen_qr_code_login_error_declined_title">"Вход отклонен"</string>
|
||||
<string name="screen_qr_code_login_error_expired_subtitle">"Срок действия входа истек. Пожалуйста, попробуйте еще раз."</string>
|
||||
<string name="screen_qr_code_login_error_expired_title">"Вход в систему не был выполнен вовремя"</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Другое устройство не поддерживает вход в %s с помощью QR-кода.
|
||||
|
||||
Попробуйте войти вручную или отсканируйте QR-код на другом устройстве."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR-код не поддерживается"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Поставщик учетной записи не поддерживает %1$s."</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s не поддерживается"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Готово к сканированию"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Откройте %1$s на настольном устройстве"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Нажмите на свое изображение"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Выбрать %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Привязать новое устройство\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Отсканируйте QR-код с помощью этого устройства"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Откройте %1$s на другом устройстве, чтобы получить QR-код"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Используйте QR-код, показанный на другом устройстве."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Повторить попытку"</string>
|
||||
|
|
|
|||
|
|
@ -11,11 +11,24 @@
|
|||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Pripojenie nie je bezpečené"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Budete požiadaní o zadanie dvoch číslic zobrazených na tomto zariadení."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Zadajte nižšie uvedené číslo na vašom druhom zariadení"</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"Prihlásenie bolo zrušené na druhom zariadení."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Žiadosť o prihlásenie bola zrušená"</string>
|
||||
<string name="screen_qr_code_login_error_declined_subtitle">"Žiadosť na vašom druhom zariadení nebola prijatá."</string>
|
||||
<string name="screen_qr_code_login_error_declined_title">"Prihlásenie bolo odmietnuté"</string>
|
||||
<string name="screen_qr_code_login_error_expired_subtitle">"Platnosť prihlásenia vypršala. Skúste to prosím znova."</string>
|
||||
<string name="screen_qr_code_login_error_expired_title">"Prihlásenie nebolo včas dokončené"</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Vaše druhé zariadenie nepodporuje prihlásenie do aplikácie %s pomocou QR kódu.
|
||||
|
||||
Skúste sa prihlásiť manuálne alebo naskenujte QR kód pomocou iného zariadenia."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR kód nie je podporovaný"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Poskytovateľ vášho účtu nepodporuje %1$s."</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s nie je podporovaný"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Pripravené na skenovanie"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Otvorte %1$s na stolnom zariadení"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Kliknite na svoj obrázok"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Vyberte %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"„Prepojiť nové zariadenie“"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Postupujte podľa zobrazených pokynov"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Naskenujte QR kód pomocou tohto zariadenia"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Ak chcete získať QR kód, otvorte %1$s na inom zariadení"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Použite QR kód zobrazený na druhom zariadení."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Skúste to znova"</string>
|
||||
|
|
|
|||
38
features/ftue/impl/src/main/res/values-zh/translations.xml
Normal file
38
features/ftue/impl/src/main/res/values-zh/translations.xml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_notification_optin_subtitle">"您可以稍后更改设置。"</string>
|
||||
<string name="screen_notification_optin_title">"允许通知,绝不错过任何消息"</string>
|
||||
<string name="screen_qr_code_login_connecting_subtitle">"建立安全连接"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"现在怎么办?"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"如果这是网络问题,请尝试使用二维码再次登录"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"如果你遇到同样的问题,请尝试使用不同的 WiFi 网络或使用你的移动数据代替 WiFi"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"如果不起作用,请手动登录"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"连接不安全"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"您会被要求输入此设备上显示的两位数。"</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"在您的其他设备上输入下面的数字"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"在桌面设备上打开 %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"点击你的头像"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"选择 %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"「连接新设备」"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"按照说明进行操作"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"在另一台设备上打开 %1$s 以获取二维码"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"使用其他设备上显示的二维码。"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"再试一次"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"二维码错误"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_button">"转到摄像头设置"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_description">"您需要授予 %1$s 使用设备摄像头的权限才能继续。"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_title">"允许摄像头权限以扫描 QR 码"</string>
|
||||
<string name="screen_qr_code_login_scanning_state_title">"扫描二维码"</string>
|
||||
<string name="screen_qr_code_login_start_over_button">"重新开始"</string>
|
||||
<string name="screen_qr_code_login_unknown_error_description">"发生了意外错误。请再试一次。"</string>
|
||||
<string name="screen_qr_code_login_verify_code_loading">"等着您的其他设备"</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"您的账户提供商可能会要求您提供以下代码来验证登录。"</string>
|
||||
<string name="screen_qr_code_login_verify_code_title">"您的验证码"</string>
|
||||
<string name="screen_welcome_bullet_1">"今年晚些时候将增加通话、投票、搜索等功能。"</string>
|
||||
<string name="screen_welcome_bullet_2">"加密房间的消息历史记录尚不可用。"</string>
|
||||
<string name="screen_welcome_bullet_3">"我们很乐意听取您的意见,请通过设置页面告诉我们您的想法。"</string>
|
||||
<string name="screen_welcome_button">"开始吧!"</string>
|
||||
<string name="screen_welcome_subtitle">"以下是您需要了解的内容:"</string>
|
||||
<string name="screen_welcome_title">"欢迎使用 %1$s"</string>
|
||||
</resources>
|
||||
|
|
@ -11,11 +11,24 @@
|
|||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Connection not secure"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"You’ll be asked to enter the two digits shown on this device."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Enter the number below on your other device"</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"The sign in was cancelled on the other device."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Sign in request cancelled"</string>
|
||||
<string name="screen_qr_code_login_error_declined_subtitle">"The request on your other device was not accepted."</string>
|
||||
<string name="screen_qr_code_login_error_declined_title">"Sign in declined"</string>
|
||||
<string name="screen_qr_code_login_error_expired_subtitle">"Sign in expired. Please try again."</string>
|
||||
<string name="screen_qr_code_login_error_expired_title">"The sign in was not completed in time"</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Your other device does not support signing in to %s with a QR code.
|
||||
|
||||
Try signing in manually, or scan the QR code with another device."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR code not supported"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Your account provider does not support %1$s."</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s not supported"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Ready to scan"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Open %1$s on a desktop device"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Click on your avatar"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Select %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Link new device”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Follow the instructions shown"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Scan the QR code with this device"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Open %1$s on another device to get the QR code"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Use the QR code shown on the other device."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Try again"</string>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.ftue.impl.welcome.state
|
||||
|
||||
class FakeWelcomeState : WelcomeScreenState {
|
||||
class FakeWelcomeScreenState : WelcomeScreenState {
|
||||
private var isWelcomeScreenNeeded = true
|
||||
|
||||
override fun isWelcomeScreenNeeded(): Boolean {
|
||||
|
|
@ -25,4 +25,5 @@ android {
|
|||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ interface AcceptDeclineInviteView {
|
|||
@Composable
|
||||
fun Render(
|
||||
state: AcceptDeclineInviteState,
|
||||
onInviteAccepted: (RoomId) -> Unit,
|
||||
onInviteDeclined: (RoomId) -> Unit,
|
||||
onAcceptInvite: (RoomId) -> Unit,
|
||||
onDeclineInvite: (RoomId) -> Unit,
|
||||
modifier: Modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,9 +33,8 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState
|
|||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.api.extensions.toAnalyticsJoinedRoom
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Optional
|
||||
|
|
@ -44,7 +43,7 @@ import kotlin.jvm.optionals.getOrNull
|
|||
|
||||
class AcceptDeclineInvitePresenter @Inject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val joinRoom: JoinRoom,
|
||||
private val notificationDrawerManager: NotificationDrawerManager,
|
||||
) : Presenter<AcceptDeclineInviteState> {
|
||||
@Composable
|
||||
|
|
@ -59,9 +58,11 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
|||
fun handleEvents(event: AcceptDeclineInviteEvents) {
|
||||
when (event) {
|
||||
is AcceptDeclineInviteEvents.AcceptInvite -> {
|
||||
currentInvite = Optional.of(event.invite)
|
||||
localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction)
|
||||
// currentInvite is used to render the decline confirmation dialog
|
||||
// and to reuse the roomId when the user confirm the rejection of the invitation.
|
||||
// Just set it to empty here.
|
||||
currentInvite = Optional.empty()
|
||||
localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction)
|
||||
}
|
||||
|
||||
is AcceptDeclineInviteEvents.DeclineInvite -> {
|
||||
|
|
@ -100,14 +101,18 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState<AsyncAction<RoomId>>) = launch {
|
||||
private fun CoroutineScope.acceptInvite(
|
||||
roomId: RoomId,
|
||||
acceptedAction: MutableState<AsyncAction<RoomId>>,
|
||||
) = launch {
|
||||
acceptedAction.runUpdatingState {
|
||||
client.joinRoom(roomId)
|
||||
joinRoom(
|
||||
roomId = roomId,
|
||||
serverNames = emptyList(),
|
||||
trigger = JoinedRoom.Trigger.Invite,
|
||||
)
|
||||
.onSuccess {
|
||||
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId, doRender = true)
|
||||
client.getRoom(roomId)?.use { room ->
|
||||
analyticsService.capture(room.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
|
||||
}
|
||||
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
||||
}
|
||||
.map { roomId }
|
||||
}
|
||||
|
|
@ -117,7 +122,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
|||
suspend {
|
||||
client.getRoom(roomId)?.use {
|
||||
it.leave().getOrThrow()
|
||||
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId, doRender = true)
|
||||
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
||||
}
|
||||
roomId
|
||||
}.runCatchingUpdatingState(declinedAction)
|
||||
|
|
|
|||
|
|
@ -36,21 +36,21 @@ import kotlin.jvm.optionals.getOrNull
|
|||
@Composable
|
||||
fun AcceptDeclineInviteView(
|
||||
state: AcceptDeclineInviteState,
|
||||
onInviteAccepted: (RoomId) -> Unit,
|
||||
onInviteDeclined: (RoomId) -> Unit,
|
||||
onAcceptInvite: (RoomId) -> Unit,
|
||||
onDeclineInvite: (RoomId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
AsyncActionView(
|
||||
async = state.acceptAction,
|
||||
onSuccess = onInviteAccepted,
|
||||
onSuccess = onAcceptInvite,
|
||||
onErrorDismiss = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.DismissAcceptError)
|
||||
},
|
||||
)
|
||||
AsyncActionView(
|
||||
async = state.declineAction,
|
||||
onSuccess = onInviteDeclined,
|
||||
onSuccess = onDeclineInvite,
|
||||
onErrorDismiss = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.DismissDeclineError)
|
||||
},
|
||||
|
|
@ -59,10 +59,10 @@ fun AcceptDeclineInviteView(
|
|||
if (invite != null) {
|
||||
DeclineConfirmationDialog(
|
||||
invite = invite,
|
||||
onConfirmClicked = {
|
||||
onConfirmClick = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite)
|
||||
},
|
||||
onDismissClicked = {
|
||||
onDismissClick = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.CancelDeclineInvite)
|
||||
}
|
||||
)
|
||||
|
|
@ -75,8 +75,8 @@ fun AcceptDeclineInviteView(
|
|||
@Composable
|
||||
private fun DeclineConfirmationDialog(
|
||||
invite: InviteData,
|
||||
onConfirmClicked: () -> Unit,
|
||||
onDismissClicked: () -> Unit,
|
||||
onConfirmClick: () -> Unit,
|
||||
onDismissClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val contentResource = if (invite.isDirect) {
|
||||
|
|
@ -97,8 +97,8 @@ private fun DeclineConfirmationDialog(
|
|||
title = stringResource(titleResource),
|
||||
submitText = stringResource(CommonStrings.action_decline),
|
||||
cancelText = stringResource(CommonStrings.action_cancel),
|
||||
onSubmitClicked = onConfirmClicked,
|
||||
onDismiss = onDismissClicked,
|
||||
onSubmitClick = onConfirmClick,
|
||||
onDismiss = onDismissClick,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ internal fun AcceptDeclineInviteViewPreview(@PreviewParameter(AcceptDeclineInvit
|
|||
ElementPreview {
|
||||
AcceptDeclineInviteView(
|
||||
state = state,
|
||||
onInviteAccepted = {},
|
||||
onInviteDeclined = {},
|
||||
onAcceptInvite = {},
|
||||
onDeclineInvite = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ class AcceptDeclineInviteViewWrapper @Inject constructor() : AcceptDeclineInvite
|
|||
@Composable
|
||||
override fun Render(
|
||||
state: AcceptDeclineInviteState,
|
||||
onInviteAccepted: (RoomId) -> Unit,
|
||||
onInviteDeclined: (RoomId) -> Unit,
|
||||
onAcceptInvite: (RoomId) -> Unit,
|
||||
onDeclineInvite: (RoomId) -> Unit,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
AcceptDeclineInviteView(
|
||||
state = state,
|
||||
onInviteAccepted = onInviteAccepted,
|
||||
onInviteDeclined = onInviteDeclined,
|
||||
onAcceptInvite = onAcceptInvite,
|
||||
onDeclineInvite = onDeclineInvite,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_invites_decline_chat_message">"დარწმუნებული ხართ, რომ გსურთ, უარი თქვათ მოწვევაზე %1$s-ში?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"მოწვევაზე უარის თქმა"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"დარწმუნებული ხართ, რომ გსურთ, უარი თქვათ ჩატზე %1$s-თან?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"ჩატზე უარის თქვა"</string>
|
||||
<string name="screen_invites_empty_list">"მოწვევები არ არის"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) მოგიწვიათ"</string>
|
||||
</resources>
|
||||
|
|
@ -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_invites_decline_chat_message">"Tens a certeza que queres rejeitar o convite para %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Rejeitar conite"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Tens a certeza que queres rejeitar esta conversa privada com %1$s?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"Rejeitar conversa"</string>
|
||||
<string name="screen_invites_empty_list">"Sem convites"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) convidou-te"</string>
|
||||
</resources>
|
||||
|
|
@ -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_invites_decline_chat_message">"您确定要拒绝加入 %1$s 的邀请吗?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"拒绝邀请"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"您确定要拒绝与 %1$s 开始私聊吗?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"拒绝聊天"</string>
|
||||
<string name="screen_invites_empty_list">"没有邀请"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s)邀请了你"</string>
|
||||
</resources>
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.features.invite.impl.response
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
|
@ -26,13 +27,13 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
|||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
|
|
@ -163,13 +164,10 @@ class AcceptDeclineInvitePresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - accepting invite error flow`() = runTest {
|
||||
val joinRoomFailure = lambdaRecorder { roomId: RoomId ->
|
||||
val joinRoomFailure = lambdaRecorder { roomId: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
Result.failure<Unit>(RuntimeException("Failed to join room $roomId"))
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
joinRoomLambda = joinRoomFailure
|
||||
}
|
||||
val presenter = createAcceptDeclineInvitePresenter(client = client)
|
||||
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure)
|
||||
presenter.test {
|
||||
val inviteData = anInviteData()
|
||||
awaitItem().also { state ->
|
||||
|
|
@ -177,33 +175,37 @@ class AcceptDeclineInvitePresenterTest {
|
|||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||
)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.invite).isEqualTo(Optional.of(inviteData))
|
||||
assertThat(state.invite).isEqualTo(Optional.empty<InviteData>())
|
||||
assertThat(state.acceptAction).isEqualTo(AsyncAction.Loading)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
state.eventSink(
|
||||
InternalAcceptDeclineInviteEvents.DismissAcceptError
|
||||
)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.invite).isEqualTo(Optional.empty<InviteData>())
|
||||
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
}
|
||||
cancelAndConsumeRemainingEvents()
|
||||
}
|
||||
assert(joinRoomFailure).isCalledOnce()
|
||||
assert(joinRoomFailure)
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID),
|
||||
value(emptyList<String>()),
|
||||
value(JoinedRoom.Trigger.Invite)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - accepting invite success flow`() = runTest {
|
||||
val joinRoomSuccess = lambdaRecorder { _: RoomId ->
|
||||
val joinRoomSuccess = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
joinRoomLambda = joinRoomSuccess
|
||||
}
|
||||
val presenter = createAcceptDeclineInvitePresenter(client = client)
|
||||
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomSuccess)
|
||||
presenter.test {
|
||||
val inviteData = anInviteData()
|
||||
awaitItem().also { state ->
|
||||
|
|
@ -211,14 +213,22 @@ class AcceptDeclineInvitePresenterTest {
|
|||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||
)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.invite).isEqualTo(Optional.of(inviteData))
|
||||
assertThat(state.invite).isEqualTo(Optional.empty<InviteData>())
|
||||
assertThat(state.acceptAction).isEqualTo(AsyncAction.Loading)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.acceptAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
}
|
||||
cancelAndConsumeRemainingEvents()
|
||||
}
|
||||
assert(joinRoomSuccess).isCalledOnce()
|
||||
assert(joinRoomSuccess)
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID),
|
||||
value(emptyList<String>()),
|
||||
value(JoinedRoom.Trigger.Invite)
|
||||
)
|
||||
}
|
||||
|
||||
private fun anInviteData(
|
||||
|
|
@ -235,12 +245,14 @@ class AcceptDeclineInvitePresenterTest {
|
|||
|
||||
private fun createAcceptDeclineInvitePresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
},
|
||||
notificationDrawerManager: NotificationDrawerManager = FakeNotificationDrawerManager(),
|
||||
): AcceptDeclineInvitePresenter {
|
||||
return AcceptDeclineInvitePresenter(
|
||||
client = client,
|
||||
analyticsService = analyticsService,
|
||||
joinRoom = FakeJoinRoom(joinRoomLambda),
|
||||
notificationDrawerManager = notificationDrawerManager,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,5 @@ dependencies {
|
|||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.features.roomdirectory.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.joinroom.api
|
|||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
|
|
@ -32,5 +33,7 @@ interface JoinRoomEntryPoint : FeatureEntryPoint {
|
|||
val roomId: RoomId,
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val roomDescription: Optional<RoomDescription>,
|
||||
val serverNames: List<String>,
|
||||
val trigger: JoinedRoom.Trigger,
|
||||
) : NodeInputs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,9 +44,10 @@ dependencies {
|
|||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.features.invite.api)
|
||||
implementation(projects.features.roomdirectory.api)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.services.analytics.api)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ class JoinRoomNode @AssistedInject constructor(
|
|||
inputs.roomId,
|
||||
inputs.roomIdOrAlias,
|
||||
inputs.roomDescription,
|
||||
inputs.serverNames,
|
||||
inputs.trigger,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -48,14 +50,15 @@ class JoinRoomNode @AssistedInject constructor(
|
|||
val state = presenter.present()
|
||||
JoinRoomView(
|
||||
state = state,
|
||||
onBackPressed = ::navigateUp,
|
||||
onBackClick = ::navigateUp,
|
||||
onJoinSuccess = ::navigateUp,
|
||||
onKnockSuccess = ::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
acceptDeclineInviteView.Render(
|
||||
state = state.acceptDeclineInviteState,
|
||||
onInviteAccepted = {},
|
||||
onInviteDeclined = { navigateUp() },
|
||||
onAcceptInvite = {},
|
||||
onDeclineInvite = { navigateUp() },
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.setValue
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
|
|
@ -41,10 +42,10 @@ import io.element.android.libraries.core.meta.BuildMeta
|
|||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -55,7 +56,10 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
@Assisted private val roomId: RoomId,
|
||||
@Assisted private val roomIdOrAlias: RoomIdOrAlias,
|
||||
@Assisted private val roomDescription: Optional<RoomDescription>,
|
||||
@Assisted private val serverNames: List<String>,
|
||||
@Assisted private val trigger: JoinedRoom.Trigger,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val joinRoom: JoinRoom,
|
||||
private val knockRoom: KnockRoom,
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
private val buildMeta: BuildMeta,
|
||||
|
|
@ -65,6 +69,8 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
roomId: RoomId,
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
roomDescription: Optional<RoomDescription>,
|
||||
serverNames: List<String>,
|
||||
trigger: JoinedRoom.Trigger,
|
||||
): JoinRoomPresenter
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +79,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
val coroutineScope = rememberCoroutineScope()
|
||||
var retryCount by remember { mutableIntStateOf(0) }
|
||||
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
||||
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val contentState by produceState<ContentState>(
|
||||
initialValue = ContentState.Loading(roomIdOrAlias),
|
||||
|
|
@ -88,7 +95,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
}
|
||||
else -> {
|
||||
value = ContentState.Loading(roomIdOrAlias)
|
||||
val result = matrixClient.getRoomPreview(roomId.toRoomIdOrAlias())
|
||||
val result = matrixClient.getRoomPreviewFromRoomId(roomId, serverNames)
|
||||
value = result.fold(
|
||||
onSuccess = { roomPreview ->
|
||||
roomPreview.toContentState()
|
||||
|
|
@ -108,16 +115,14 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
|
||||
fun handleEvents(event: JoinRoomEvents) {
|
||||
when (event) {
|
||||
JoinRoomEvents.AcceptInvite,
|
||||
JoinRoomEvents.JoinRoom -> {
|
||||
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
||||
JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction)
|
||||
JoinRoomEvents.AcceptInvite -> {
|
||||
val inviteData = contentState.toInviteData() ?: return
|
||||
acceptDeclineInviteState.eventSink(
|
||||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||
)
|
||||
}
|
||||
JoinRoomEvents.KnockRoom -> {
|
||||
coroutineScope.knockRoom(roomId, knockAction)
|
||||
}
|
||||
JoinRoomEvents.DeclineInvite -> {
|
||||
val inviteData = contentState.toInviteData() ?: return
|
||||
acceptDeclineInviteState.eventSink(
|
||||
|
|
@ -129,6 +134,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
}
|
||||
JoinRoomEvents.ClearError -> {
|
||||
knockAction.value = AsyncAction.Uninitialized
|
||||
joinAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,13 +142,24 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
return JoinRoomState(
|
||||
contentState = contentState,
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
joinAction = joinAction.value,
|
||||
knockAction = knockAction.value,
|
||||
applicationName = buildMeta.applicationName,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.knockRoom(roomId: RoomId, knockAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||
private fun CoroutineScope.joinRoom(joinAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||
joinAction.runUpdatingState {
|
||||
joinRoom.invoke(
|
||||
roomId = roomId,
|
||||
serverNames = serverNames,
|
||||
trigger = trigger
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.knockRoom(knockAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||
knockAction.runUpdatingState {
|
||||
knockRoom(roomId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.ui.model.InviteSender
|
|||
data class JoinRoomState(
|
||||
val contentState: ContentState,
|
||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||
val joinAction: AsyncAction<Unit>,
|
||||
val knockAction: AsyncAction<Unit>,
|
||||
val applicationName: String,
|
||||
val eventSink: (JoinRoomEvents) -> Unit
|
||||
|
|
|
|||
|
|
@ -125,11 +125,13 @@ fun aLoadedContentState(
|
|||
fun aJoinRoomState(
|
||||
contentState: ContentState = aLoadedContentState(),
|
||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||
joinAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (JoinRoomEvents) -> Unit = {}
|
||||
) = JoinRoomState(
|
||||
contentState = contentState,
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
joinAction = joinAction,
|
||||
knockAction = knockAction,
|
||||
applicationName = "AppName",
|
||||
eventSink = eventSink
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun JoinRoomView(
|
||||
state: JoinRoomState,
|
||||
onBackPressed: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
onJoinSuccess: () -> Unit,
|
||||
onKnockSuccess: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -77,7 +78,7 @@ fun JoinRoomView(
|
|||
containerColor = Color.Transparent,
|
||||
paddingValues = PaddingValues(16.dp),
|
||||
topBar = {
|
||||
JoinRoomTopBar(onBackClicked = onBackPressed)
|
||||
JoinRoomTopBar(onBackClick = onBackClick)
|
||||
},
|
||||
content = {
|
||||
JoinRoomContent(
|
||||
|
|
@ -103,12 +104,16 @@ fun JoinRoomView(
|
|||
onRetry = {
|
||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||
},
|
||||
onGoBack = onBackPressed,
|
||||
onGoBack = onBackClick,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AsyncActionView(
|
||||
async = state.joinAction,
|
||||
onSuccess = { onJoinSuccess() },
|
||||
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearError) },
|
||||
)
|
||||
AsyncActionView(
|
||||
async = state.knockAction,
|
||||
onSuccess = { onKnockSuccess() },
|
||||
|
|
@ -307,11 +312,11 @@ private fun JoinRoomContent(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun JoinRoomTopBar(
|
||||
onBackClicked: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
navigationIcon = {
|
||||
BackButton(onClick = onBackClicked)
|
||||
BackButton(onClick = onBackClick)
|
||||
},
|
||||
title = {},
|
||||
)
|
||||
|
|
@ -322,7 +327,8 @@ private fun JoinRoomTopBar(
|
|||
internal fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class) state: JoinRoomState) = ElementPreview {
|
||||
JoinRoomView(
|
||||
state = state,
|
||||
onBackPressed = { },
|
||||
onBackClick = { },
|
||||
onJoinSuccess = { },
|
||||
onKnockSuccess = { },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.joinroom.impl.di
|
|||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.joinroom.impl.JoinRoomPresenter
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
|
|
@ -28,6 +29,7 @@ import io.element.android.libraries.di.SessionScope
|
|||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import java.util.Optional
|
||||
|
||||
@Module
|
||||
|
|
@ -36,6 +38,7 @@ object JoinRoomModule {
|
|||
@Provides
|
||||
fun providesJoinRoomPresenterFactory(
|
||||
client: MatrixClient,
|
||||
joinRoom: JoinRoom,
|
||||
knockRoom: KnockRoom,
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
buildMeta: BuildMeta,
|
||||
|
|
@ -45,12 +48,17 @@ object JoinRoomModule {
|
|||
roomId: RoomId,
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
roomDescription: Optional<RoomDescription>,
|
||||
serverNames: List<String>,
|
||||
trigger: JoinedRoom.Trigger,
|
||||
): JoinRoomPresenter {
|
||||
return JoinRoomPresenter(
|
||||
roomId = roomId,
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
roomDescription = roomDescription,
|
||||
serverNames = serverNames,
|
||||
trigger = trigger,
|
||||
matrixClient = client,
|
||||
joinRoom = joinRoom,
|
||||
knockRoom = knockRoom,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
buildMeta = buildMeta,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_join_action">"Entra nella stanza"</string>
|
||||
<string name="screen_join_room_knock_action">"Bussa per partecipare"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s non supporta ancora gli spazi. Puoi accedere agli spazi sul web."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Gli spazi non sono ancora supportati"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"Clicca sul pulsante qui sotto e un amministratore della stanza riceverà una notifica. Potrai partecipare alla conversazione una volta approvato."</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"Per visualizzare la cronologia dei messaggi devi essere un membro di questa stanza."</string>
|
||||
<string name="screen_join_room_title_knock">"Vuoi entrare in questa stanza?"</string>
|
||||
<string name="screen_join_room_title_no_preview">"L\'anteprima non è disponibile"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_join_action">"Entrar na sala"</string>
|
||||
<string name="screen_join_room_knock_action">"Bater à porta"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"A %1$s ainda não funciona com espaços. Podes usá-los na aplicação web."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Os espaços ainda não estão implementados"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"Carrega no botão abaixo para notificar um administrador da sala. Poderás entrar quando te aprovarem."</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"Apenas os participantes podem ver o histórico de mensagens."</string>
|
||||
<string name="screen_join_room_title_knock">"Queres entrar nesta sala?"</string>
|
||||
<string name="screen_join_room_title_no_preview">"Pré-visualização indisponível"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_join_action">"Alăturați-vă camerei"</string>
|
||||
<string name="screen_join_room_knock_action">"Bateți pentru a vă alătura"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s nu suporta încă spații. Puteți accesa spațiile pe web."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Spațiile nu sunt încă suportate"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"Faceți clic pe butonul de mai jos și un administrator de cameră va fi notificat. Veți putea să vă alăturați conversației odată aprobată."</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"Trebuie să fiți membru al acestei camere pentru a vizualiza istoricul mesajelor."</string>
|
||||
<string name="screen_join_room_title_knock">"Doriți să vă alăturați acestei camere?"</string>
|
||||
<string name="screen_join_room_title_no_preview">"Previzualizare indisponibilă"</string>
|
||||
</resources>
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_join_action">"Присоединиться к комнате"</string>
|
||||
<string name="screen_join_room_knock_action">"Постучите, чтобы присоединиться"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s еще не поддерживает пространства. Вы можете получить к ним доступ в веб-версии."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Пространства пока не поддерживаются"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"Нажмите кнопку ниже и администратор комнаты получит уведомление. После одобрения вы сможете присоединиться к обсуждению."</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"Вы должны быть участником этой комнаты, чтобы просмотреть историю сообщений."</string>
|
||||
<string name="screen_join_room_title_knock">"Хотите присоединиться к этой комнате?"</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_join_action">"加入聊天室"</string>
|
||||
<string name="screen_join_room_knock_action">"加入房间"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s 尚不支持空间。您可以通过 Web 端访问空间"</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"空间尚不支持"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"点击下面的按钮,系统将通知房间管理员。获得批准后,您将能够加入对话。"</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"只有聊天室成员才能查看消息历史记录。"</string>
|
||||
<string name="screen_join_room_title_knock">"想加入这个房间吗?"</string>
|
||||
<string name="screen_join_room_title_no_preview">"预览不可用"</string>
|
||||
</resources>
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.features.joinroom.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||
|
|
@ -36,10 +37,12 @@ import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
|||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.A_SERVER_LIST
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
|
|
@ -174,6 +177,59 @@ class JoinRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is joined with success, all the parameters are provided`() = runTest {
|
||||
val aTrigger = JoinedRoom.Trigger.MobilePermalink
|
||||
val joinRoomLambda = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val presenter = createJoinRoomPresenter(
|
||||
trigger = aTrigger,
|
||||
serverNames = A_SERVER_LIST,
|
||||
joinRoomLambda = joinRoomLambda,
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Loading)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Success(Unit))
|
||||
}
|
||||
joinRoomLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_ROOM_ID), value(A_SERVER_LIST), value(aTrigger))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is joined with error, it is possible to clear the error`() = runTest {
|
||||
val presenter = createJoinRoomPresenter(
|
||||
joinRoomLambda = { _, _, _ ->
|
||||
Result.failure(AN_EXCEPTION)
|
||||
},
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Loading)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
|
||||
state.eventSink(JoinRoomEvents.ClearError)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is left and public then join authorization is equal to canJoin`() = runTest {
|
||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.LEFT, isPublic = true)
|
||||
|
|
@ -310,7 +366,7 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = {
|
||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||
Result.success(
|
||||
RoomPreview(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -355,7 +411,7 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = {
|
||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||
Result.failure(AN_EXCEPTION)
|
||||
}
|
||||
)
|
||||
|
|
@ -393,7 +449,7 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = {
|
||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||
Result.failure(Exception("403"))
|
||||
}
|
||||
)
|
||||
|
|
@ -415,7 +471,12 @@ class JoinRoomPresenterTest {
|
|||
private fun createJoinRoomPresenter(
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
roomDescription: Optional<RoomDescription> = Optional.empty(),
|
||||
serverNames: List<String> = emptyList(),
|
||||
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
},
|
||||
knockRoom: KnockRoom = FakeKnockRoom(),
|
||||
buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"),
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() }
|
||||
|
|
@ -424,7 +485,10 @@ class JoinRoomPresenterTest {
|
|||
roomId = roomId,
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
roomDescription = roomDescription,
|
||||
serverNames = serverNames,
|
||||
trigger = trigger,
|
||||
matrixClient = matrixClient,
|
||||
joinRoom = FakeJoinRoom(joinRoomLambda),
|
||||
knockRoom = knockRoom,
|
||||
buildMeta = buildMeta,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class JoinRoomViewTest {
|
|||
aJoinRoomState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onBackPressed = it
|
||||
onBackClick = it
|
||||
)
|
||||
rule.pressBack()
|
||||
}
|
||||
|
|
@ -91,6 +91,34 @@ class JoinRoomViewTest {
|
|||
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on closing Join error emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
||||
joinAction = AsyncAction.Failure(Exception("Error")),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when joining room is successful, the expected callback is invoked`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||
ensureCalledOnce {
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
joinAction = AsyncAction.Success(Unit),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onJoinSuccess = it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Accept invitation IsInvited room emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||
|
|
@ -139,7 +167,7 @@ class JoinRoomViewTest {
|
|||
contentState = aLoadedContentState(roomType = RoomType.Space),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onBackPressed = it
|
||||
onBackClick = it
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_go_back)
|
||||
}
|
||||
|
|
@ -148,13 +176,15 @@ class JoinRoomViewTest {
|
|||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinRoomView(
|
||||
state: JoinRoomState,
|
||||
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
onJoinSuccess: () -> Unit = EnsureNeverCalled(),
|
||||
onKnockSuccess: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
JoinRoomView(
|
||||
state = state,
|
||||
onBackPressed = onBackPressed,
|
||||
onBackClick = onBackClick,
|
||||
onJoinSuccess = onJoinSuccess,
|
||||
onKnockSuccess = onKnockSuccess,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ private fun LeaveRoomConfirmationDialog(
|
|||
title = stringResource(if (isDm) CommonStrings.action_leave_conversation else CommonStrings.action_leave_room),
|
||||
content = stringResource(text),
|
||||
submitText = stringResource(CommonStrings.action_leave),
|
||||
onSubmitClicked = { eventSink(LeaveRoomEvent.LeaveRoom(roomId)) },
|
||||
onSubmitClick = { eventSink(LeaveRoomEvent.LeaveRoom(roomId)) },
|
||||
onDismiss = { eventSink(LeaveRoomEvent.HideConfirmation) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_conversation_alert_subtitle">"¿Estás seguro de que quieres salir de esta conversación? Esta conversación no es pública y no podrás volver a unirte sin una invitación."</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"¿Estás seguro de que quieres salir de esta sala? Eres la única persona aquí. Si te vas, nadie podrá unirse en el futuro, ni siquiera tú."</string>
|
||||
<string name="leave_room_alert_private_subtitle">"¿Estás seguro de que quieres abandonar esta sala? Esta sala no es pública y no podrás volver a entrar sin una invitación."</string>
|
||||
<string name="leave_room_alert_subtitle">"¿Seguro que quieres salir de la habitación?"</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_room_alert_empty_subtitle">"დარწმუნებული ბრძანდებით, რომ ამ ოთახის დატოვება გსურთ? თქვენ აქ მარტო ხართ და ჩატის დატოვებისას აქ თქვენს ჩათვლით ვერავინ ვერ გაწევრიანდება."</string>
|
||||
<string name="leave_room_alert_private_subtitle">"დარწმუნებული ბრძანდებით, რომ ამ ოთახის დატოვება გსურთ? ეს ოთახი არ არის საჯარო და მოწვევის გარეშე ვერ შეძლებთ ხელახლა გაწევრიანებას."</string>
|
||||
<string name="leave_room_alert_subtitle">"დარწმუნებული ბრძანდებით, რომ ოთახის დატოვება გსურთ?"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_conversation_alert_subtitle">"Tens a certeza que queres sair desta conversa? Não é pública, logo não poderás voltar a participar sem um convite."</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"Tens a certeza que queres sair desta sala? És o único participante. Se saíres, ninguém mais poderá entrar, incluindo tu."</string>
|
||||
<string name="leave_room_alert_private_subtitle">"Tens a certeza que queres sair desta sala? Atenta que não é pública e portanto não poderás voltar a entrar sem um novo convite."</string>
|
||||
<string name="leave_room_alert_subtitle">"Tens a certeza que queres sair da sala?"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_conversation_alert_subtitle">"您确定要离开此对话吗?此对话不公开,未经邀请您将无法重新加入。"</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"你确定要离开这个房间吗?这里只有你一个人,如果你走了,包括你在内的所有人都无法进入此房间。"</string>
|
||||
<string name="leave_room_alert_private_subtitle">"你确定要离开这个房间吗?这个房间不是公开的,如果没有邀请,你将无法重新加入。"</string>
|
||||
<string name="leave_room_alert_subtitle">"你确定要离开房间吗?"</string>
|
||||
</resources>
|
||||
|
|
@ -29,7 +29,7 @@ internal fun PermissionDeniedDialog(
|
|||
) {
|
||||
ConfirmationDialog(
|
||||
content = stringResource(CommonStrings.error_missing_location_auth_android, appName),
|
||||
onSubmitClicked = onContinue,
|
||||
onSubmitClick = onContinue,
|
||||
onDismiss = onDismiss,
|
||||
submitText = stringResource(CommonStrings.action_continue),
|
||||
cancelText = stringResource(CommonStrings.action_cancel),
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ internal fun PermissionRationaleDialog(
|
|||
) {
|
||||
ConfirmationDialog(
|
||||
content = stringResource(CommonStrings.error_missing_location_rationale_android, appName),
|
||||
onSubmitClicked = onContinue,
|
||||
onSubmitClick = onContinue,
|
||||
onDismiss = onDismiss,
|
||||
submitText = stringResource(CommonStrings.action_continue),
|
||||
cancelText = stringResource(CommonStrings.action_cancel),
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class ShowLocationNode @AssistedInject constructor(
|
|||
ShowLocationView(
|
||||
state = presenter.present(),
|
||||
modifier = modifier,
|
||||
onBackPressed = ::navigateUp
|
||||
onBackClick = ::navigateUp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ import org.maplibre.android.geometry.LatLng
|
|||
@Composable
|
||||
fun ShowLocationView(
|
||||
state: ShowLocationState,
|
||||
onBackPressed: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state.permissionDialog) {
|
||||
|
|
@ -121,7 +121,7 @@ fun ShowLocationView(
|
|||
},
|
||||
navigationIcon = {
|
||||
BackButton(
|
||||
onClick = onBackPressed,
|
||||
onClick = onBackClick,
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
|
|
@ -194,7 +194,7 @@ fun ShowLocationView(
|
|||
internal fun ShowLocationViewPreview(@PreviewParameter(ShowLocationStateProvider::class) state: ShowLocationState) = ElementPreview {
|
||||
ShowLocationView(
|
||||
state = state,
|
||||
onBackPressed = {},
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.location.impl.common.permissions
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
class PermissionsPresenterFake : PermissionsPresenter {
|
||||
class FakePermissionsPresenter : PermissionsPresenter {
|
||||
val events = mutableListOf<PermissionsEvents>()
|
||||
|
||||
private fun handleEvent(event: PermissionsEvents) {
|
||||
|
|
@ -24,9 +24,9 @@ import im.vector.app.features.analytics.plan.Composer
|
|||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.impl.aPermissionsState
|
||||
import io.element.android.features.location.impl.common.actions.FakeLocationActions
|
||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsPresenterFake
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsState
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
|
|
@ -45,7 +45,7 @@ class SendLocationPresenterTest {
|
|||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val permissionsPresenterFake = PermissionsPresenterFake()
|
||||
private val fakePermissionsPresenter = FakePermissionsPresenter()
|
||||
private val fakeMatrixRoom = FakeMatrixRoom()
|
||||
private val fakeAnalyticsService = FakeAnalyticsService()
|
||||
private val fakeMessageComposerContext = FakeMessageComposerContext()
|
||||
|
|
@ -53,7 +53,7 @@ class SendLocationPresenterTest {
|
|||
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
|
||||
private val sendLocationPresenter: SendLocationPresenter = SendLocationPresenter(
|
||||
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
|
||||
override fun create(permissions: List<String>): PermissionsPresenter = permissionsPresenterFake
|
||||
override fun create(permissions: List<String>): PermissionsPresenter = fakePermissionsPresenter
|
||||
},
|
||||
room = fakeMatrixRoom,
|
||||
analyticsService = fakeAnalyticsService,
|
||||
|
|
@ -64,7 +64,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `initial state with permissions granted`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.AllGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -90,7 +90,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `initial state with permissions partially granted`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.SomeGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -116,7 +116,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `initial state with permissions denied`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -142,7 +142,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `initial state with permissions denied once`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = true,
|
||||
|
|
@ -168,7 +168,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `rationale dialog dismiss`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = true,
|
||||
|
|
@ -199,7 +199,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `rationale dialog continue`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = true,
|
||||
|
|
@ -221,13 +221,13 @@ class SendLocationPresenterTest {
|
|||
|
||||
// Continue the dialog sends permission request to the permissions presenter
|
||||
myLocationState.eventSink(SendLocationEvents.RequestPermissions)
|
||||
assertThat(permissionsPresenterFake.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
|
||||
assertThat(fakePermissionsPresenter.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `permission denied dialog dismiss`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -258,7 +258,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `share sender location`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.AllGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -314,7 +314,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `share pin location`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -370,7 +370,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `composer context passes through analytics`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -418,7 +418,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `open settings activity`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.impl.aPermissionsState
|
||||
import io.element.android.features.location.impl.common.actions.FakeLocationActions
|
||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsPresenterFake
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsState
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
|
|
@ -38,13 +38,13 @@ class ShowLocationPresenterTest {
|
|||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val permissionsPresenterFake = PermissionsPresenterFake()
|
||||
private val fakePermissionsPresenter = FakePermissionsPresenter()
|
||||
private val fakeLocationActions = FakeLocationActions()
|
||||
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
|
||||
private val location = Location(1.23, 4.56, 7.8f)
|
||||
private val presenter = ShowLocationPresenter(
|
||||
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
|
||||
override fun create(permissions: List<String>): PermissionsPresenter = permissionsPresenterFake
|
||||
override fun create(permissions: List<String>): PermissionsPresenter = fakePermissionsPresenter
|
||||
},
|
||||
fakeLocationActions,
|
||||
fakeBuildMeta,
|
||||
|
|
@ -54,7 +54,7 @@ class ShowLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `emits initial state with no location permission`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -74,7 +74,7 @@ class ShowLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `emits initial state location permission denied once`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = true,
|
||||
|
|
@ -94,7 +94,7 @@ class ShowLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `emits initial state with location permission`() = runTest {
|
||||
permissionsPresenterFake.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -109,7 +109,7 @@ class ShowLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `emits initial state with partial location permission`() = runTest {
|
||||
permissionsPresenterFake.givenState(aPermissionsState(permissions = PermissionsState.Permissions.SomeGranted))
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.SomeGranted))
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -137,7 +137,7 @@ class ShowLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `centers on user location`() = runTest {
|
||||
permissionsPresenterFake.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -165,7 +165,7 @@ class ShowLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `rationale dialog dismiss`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = true,
|
||||
|
|
@ -196,7 +196,7 @@ class ShowLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `rationale dialog continue`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = true,
|
||||
|
|
@ -218,13 +218,13 @@ class ShowLocationPresenterTest {
|
|||
|
||||
// Continue the dialog sends permission request to the permissions presenter
|
||||
trackLocationState.eventSink(ShowLocationEvents.RequestPermissions)
|
||||
assertThat(permissionsPresenterFake.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
|
||||
assertThat(fakePermissionsPresenter.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `permission denied dialog dismiss`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
@ -255,7 +255,7 @@ class ShowLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `open settings activity`() = runTest {
|
||||
permissionsPresenterFake.givenState(
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class ShowLocationViewTest {
|
|||
state = aShowLocationState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = callback,
|
||||
onBackClick = callback,
|
||||
)
|
||||
rule.pressBack()
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ class ShowLocationViewTest {
|
|||
aShowLocationState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
onBackClick = EnsureNeverCalled(),
|
||||
)
|
||||
val shareContentDescription = rule.activity.getString(CommonStrings.action_share)
|
||||
rule.onNodeWithContentDescription(shareContentDescription).performClick()
|
||||
|
|
@ -76,7 +76,7 @@ class ShowLocationViewTest {
|
|||
aShowLocationState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
onBackClick = EnsureNeverCalled(),
|
||||
)
|
||||
rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick()
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.TrackMyLocation(true))
|
||||
|
|
@ -90,7 +90,7 @@ class ShowLocationViewTest {
|
|||
permissionDialog = ShowLocationState.Dialog.PermissionDenied,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
onBackClick = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.OpenAppSettings)
|
||||
|
|
@ -104,7 +104,7 @@ class ShowLocationViewTest {
|
|||
permissionDialog = ShowLocationState.Dialog.PermissionDenied,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
onBackClick = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.DismissDialog)
|
||||
|
|
@ -118,7 +118,7 @@ class ShowLocationViewTest {
|
|||
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
onBackClick = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.RequestPermissions)
|
||||
|
|
@ -132,7 +132,7 @@ class ShowLocationViewTest {
|
|||
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
onBackClick = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.DismissDialog)
|
||||
|
|
@ -141,14 +141,14 @@ class ShowLocationViewTest {
|
|||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setShowLocationView(
|
||||
state: ShowLocationState,
|
||||
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
// Simulate a LocalInspectionMode for MapLibreMap
|
||||
CompositionLocalProvider(LocalInspectionMode provides true) {
|
||||
ShowLocationView(
|
||||
state = state,
|
||||
onBackPressed = onBackPressed,
|
||||
onBackClick = onBackClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,19 @@
|
|||
|
||||
package io.element.android.features.lockscreen.api
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
|
||||
interface LockScreenEntryPoint : FeatureEntryPoint {
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: Target): NodeBuilder
|
||||
fun pinUnlockIntent(context: Context): Intent
|
||||
|
||||
interface NodeBuilder {
|
||||
fun callback(callback: Callback): NodeBuilder
|
||||
fun target(target: Target): NodeBuilder
|
||||
fun build(): Node
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +39,5 @@ interface LockScreenEntryPoint : FeatureEntryPoint {
|
|||
enum class Target {
|
||||
Settings,
|
||||
Setup,
|
||||
Unlock
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
features/lockscreen/impl/src/main/AndroidManifest.xml
Normal file
25
features/lockscreen/impl/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!--
|
||||
~ Copyright (c) 2024 New Vector Ltd
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name="io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity"
|
||||
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -16,18 +16,20 @@
|
|||
|
||||
package io.element.android.features.lockscreen.impl
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): LockScreenEntryPoint.NodeBuilder {
|
||||
var innerTarget: LockScreenEntryPoint.Target = LockScreenEntryPoint.Target.Unlock
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: LockScreenEntryPoint.Target): LockScreenEntryPoint.NodeBuilder {
|
||||
val callbacks = mutableListOf<LockScreenEntryPoint.Callback>()
|
||||
|
||||
return object : LockScreenEntryPoint.NodeBuilder {
|
||||
|
|
@ -36,15 +38,9 @@ class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
|
|||
return this
|
||||
}
|
||||
|
||||
override fun target(target: LockScreenEntryPoint.Target): LockScreenEntryPoint.NodeBuilder {
|
||||
innerTarget = target
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
val inputs = LockScreenFlowNode.Inputs(
|
||||
when (innerTarget) {
|
||||
LockScreenEntryPoint.Target.Unlock -> LockScreenFlowNode.NavTarget.Unlock
|
||||
when (navTarget) {
|
||||
LockScreenEntryPoint.Target.Setup -> LockScreenFlowNode.NavTarget.Setup
|
||||
LockScreenEntryPoint.Target.Settings -> LockScreenFlowNode.NavTarget.Settings
|
||||
}
|
||||
|
|
@ -54,4 +50,8 @@ class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pinUnlockIntent(context: Context): Intent {
|
||||
return PinUnlockActivity.newIntent(context)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import io.element.android.anvilannotations.ContributesNode
|
|||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.features.lockscreen.impl.settings.LockScreenSettingsFlowNode
|
||||
import io.element.android.features.lockscreen.impl.setup.LockScreenSetupFlowNode
|
||||
import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
|
|
@ -44,20 +43,17 @@ class LockScreenFlowNode @AssistedInject constructor(
|
|||
@Assisted plugins: List<Plugin>,
|
||||
) : BaseFlowNode<LockScreenFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = plugins.filterIsInstance(Inputs::class.java).first().initialNavTarget,
|
||||
initialElement = plugins.filterIsInstance<Inputs>().first().initialNavTarget,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
) {
|
||||
data class Inputs(
|
||||
val initialNavTarget: NavTarget = NavTarget.Unlock,
|
||||
val initialNavTarget: NavTarget,
|
||||
) : NodeInputs
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data object Unlock : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object Setup : NavTarget
|
||||
|
||||
|
|
@ -75,10 +71,6 @@ class LockScreenFlowNode @AssistedInject constructor(
|
|||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Unlock -> {
|
||||
val inputs = PinUnlockNode.Inputs(isInAppUnlock = false)
|
||||
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs))
|
||||
}
|
||||
NavTarget.Setup -> {
|
||||
val callback = OnSetupDoneCallback(plugins())
|
||||
createNode<LockScreenSetupFlowNode>(buildContext, plugins = listOf(callback))
|
||||
|
|
|
|||
|
|
@ -103,20 +103,19 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
|
|||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Unlock -> {
|
||||
val inputs = PinUnlockNode.Inputs(isInAppUnlock = true)
|
||||
val callback = object : PinUnlockNode.Callback {
|
||||
override fun onUnlock() {
|
||||
backstack.newRoot(NavTarget.Settings)
|
||||
}
|
||||
}
|
||||
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs, callback))
|
||||
createNode<PinUnlockNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
NavTarget.SetupPin -> {
|
||||
createNode<SetupPinNode>(buildContext)
|
||||
}
|
||||
NavTarget.Settings -> {
|
||||
val callback = object : LockScreenSettingsNode.Callback {
|
||||
override fun onChangePinClicked() {
|
||||
override fun onChangePinClick() {
|
||||
backstack.push(NavTarget.SetupPin)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@ class LockScreenSettingsNode @AssistedInject constructor(
|
|||
private val presenter: LockScreenSettingsPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun onChangePinClicked()
|
||||
fun onChangePinClick()
|
||||
}
|
||||
|
||||
private fun onChangePinClicked() {
|
||||
plugins<Callback>().forEach { it.onChangePinClicked() }
|
||||
private fun onChangePinClick() {
|
||||
plugins<Callback>().forEach { it.onChangePinClick() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -46,8 +46,8 @@ class LockScreenSettingsNode @AssistedInject constructor(
|
|||
val state = presenter.present()
|
||||
LockScreenSettingsView(
|
||||
state = state,
|
||||
onBackPressed = this::navigateUp,
|
||||
onChangePinClicked = this::onChangePinClicked,
|
||||
onBackClick = this::navigateUp,
|
||||
onChangePinClick = this::onChangePinClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,19 +34,19 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||
@Composable
|
||||
fun LockScreenSettingsView(
|
||||
state: LockScreenSettingsState,
|
||||
onChangePinClicked: () -> Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
onChangePinClick: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
PreferencePage(
|
||||
title = stringResource(id = io.element.android.libraries.ui.strings.R.string.common_screen_lock),
|
||||
onBackPressed = onBackPressed,
|
||||
onBackClick = onBackClick,
|
||||
modifier = modifier
|
||||
) {
|
||||
PreferenceCategory(showDivider = false) {
|
||||
PreferenceCategory(showTopDivider = false) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = R.string.screen_app_lock_settings_change_pin),
|
||||
onClick = onChangePinClicked
|
||||
onClick = onChangePinClick
|
||||
)
|
||||
PreferenceDivider()
|
||||
if (state.showRemovePinOption) {
|
||||
|
|
@ -74,7 +74,7 @@ fun LockScreenSettingsView(
|
|||
ConfirmationDialog(
|
||||
title = stringResource(id = R.string.screen_app_lock_settings_remove_pin_alert_title),
|
||||
content = stringResource(id = R.string.screen_app_lock_settings_remove_pin_alert_message),
|
||||
onSubmitClicked = {
|
||||
onSubmitClick = {
|
||||
state.eventSink(LockScreenSettingsEvents.ConfirmRemovePin)
|
||||
},
|
||||
onDismiss = {
|
||||
|
|
@ -92,8 +92,8 @@ internal fun LockScreenSettingsViewPreview(
|
|||
ElementPreview {
|
||||
LockScreenSettingsView(
|
||||
state = state,
|
||||
onChangePinClicked = {},
|
||||
onBackPressed = {},
|
||||
onChangePinClick = {},
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ fun SetupBiometricView(
|
|||
},
|
||||
footer = {
|
||||
SetupBiometricFooter(
|
||||
onAllowClicked = { state.eventSink(SetupBiometricEvents.AllowBiometric) },
|
||||
onSkipClicked = { state.eventSink(SetupBiometricEvents.UsePin) }
|
||||
onAllowClick = { state.eventSink(SetupBiometricEvents.AllowBiometric) },
|
||||
onSkipClick = { state.eventSink(SetupBiometricEvents.UsePin) }
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -68,18 +68,18 @@ private fun SetupBiometricHeader() {
|
|||
|
||||
@Composable
|
||||
private fun SetupBiometricFooter(
|
||||
onAllowClicked: () -> Unit,
|
||||
onSkipClicked: () -> Unit,
|
||||
onAllowClick: () -> Unit,
|
||||
onSkipClick: () -> Unit,
|
||||
) {
|
||||
ButtonColumnMolecule {
|
||||
val biometricAuth = stringResource(id = R.string.screen_app_lock_biometric_authentication)
|
||||
Button(
|
||||
text = stringResource(id = R.string.screen_app_lock_setup_biometric_unlock_allow_title, biometricAuth),
|
||||
onClick = onAllowClicked
|
||||
onClick = onAllowClick
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(id = R.string.screen_app_lock_setup_biometric_unlock_skip),
|
||||
onClick = onSkipClicked
|
||||
onClick = onSkipClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class SetupPinNode @AssistedInject constructor(
|
|||
val state = presenter.present()
|
||||
SetupPinView(
|
||||
state = state,
|
||||
onBackClicked = this::navigateUp,
|
||||
onBackClick = this::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
|||
@Composable
|
||||
fun SetupPinView(
|
||||
state: SetupPinState,
|
||||
onBackClicked: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
|
|
@ -60,7 +60,7 @@ fun SetupPinView(
|
|||
topBar = {
|
||||
TopAppBar(
|
||||
navigationIcon = {
|
||||
BackButton(onClick = onBackClicked)
|
||||
BackButton(onClick = onBackClick)
|
||||
},
|
||||
title = {}
|
||||
)
|
||||
|
|
@ -154,7 +154,7 @@ internal fun SetupPinViewPreview(@PreviewParameter(SetupPinStateProvider::class)
|
|||
ElementPreview {
|
||||
SetupPinView(
|
||||
state = state,
|
||||
onBackClicked = {},
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@ import com.bumble.appyx.core.plugin.plugins
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
|
|
@ -40,12 +38,6 @@ class PinUnlockNode @AssistedInject constructor(
|
|||
fun onUnlock()
|
||||
}
|
||||
|
||||
data class Inputs(
|
||||
val isInAppUnlock: Boolean
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs: Inputs = inputs()
|
||||
|
||||
private fun onUnlock() {
|
||||
plugins<Callback>().forEach {
|
||||
it.onUnlock()
|
||||
|
|
@ -62,7 +54,9 @@ class PinUnlockNode @AssistedInject constructor(
|
|||
}
|
||||
PinUnlockView(
|
||||
state = state,
|
||||
isInAppUnlock = inputs.isInAppUnlock,
|
||||
// UnlockNode is only used for in-app unlock, so we can safely set isInAppUnlock to true.
|
||||
// It's set to false in PinUnlockActivity.
|
||||
isInAppUnlock = true,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockMana
|
|||
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
||||
import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -41,7 +41,7 @@ import javax.inject.Inject
|
|||
class PinUnlockPresenter @Inject constructor(
|
||||
private val pinCodeManager: PinCodeManager,
|
||||
private val biometricUnlockManager: BiometricUnlockManager,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val signOut: SignOut,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val pinUnlockHelper: PinUnlockHelper,
|
||||
) : Presenter<PinUnlockState> {
|
||||
|
|
@ -179,7 +179,7 @@ class PinUnlockPresenter @Inject constructor(
|
|||
|
||||
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncData<String?>>) = launch {
|
||||
suspend {
|
||||
matrixClient.logout(ignoreSdkError = true)
|
||||
signOut()
|
||||
}.runCatchingUpdatingState(signOutAction)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ private fun SignOutPrompt(
|
|||
ConfirmationDialog(
|
||||
title = stringResource(id = R.string.screen_app_lock_signout_alert_title),
|
||||
content = stringResource(id = R.string.screen_app_lock_signout_alert_message),
|
||||
onSubmitClicked = onSignOut,
|
||||
onSubmitClick = onSignOut,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.lockscreen.impl.unlock.activity
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.lockscreen.api.LockScreenLockState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter
|
||||
import io.element.android.features.lockscreen.impl.unlock.PinUnlockView
|
||||
import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class PinUnlockActivity : AppCompatActivity() {
|
||||
internal companion object {
|
||||
fun newIntent(context: Context): Intent {
|
||||
return Intent(context, PinUnlockActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Inject lateinit var presenter: PinUnlockPresenter
|
||||
@Inject lateinit var lockScreenService: LockScreenService
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
bindings<PinUnlockBindings>().inject(this)
|
||||
setContent {
|
||||
ElementTheme {
|
||||
val state = presenter.present()
|
||||
PinUnlockView(state = state, isInAppUnlock = false)
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
lockScreenService.lockState.collect { state ->
|
||||
if (state == LockScreenLockState.Unlocked) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
moveTaskToBack(true)
|
||||
}
|
||||
}
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,13 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdirectory.impl.root
|
||||
package io.element.android.features.lockscreen.impl.unlock.di
|
||||
|
||||
import io.element.android.features.roomdirectory.impl.root.di.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
||||
class FakeJoinRoom(
|
||||
var lambda: (RoomId) -> Result<Unit> = { Result.success(Unit) }
|
||||
) : JoinRoom {
|
||||
override suspend fun invoke(roomId: RoomId) = lambda(roomId)
|
||||
@ContributesTo(AppScope::class)
|
||||
interface PinUnlockBindings {
|
||||
fun inject(activity: PinUnlockActivity)
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.lockscreen.impl.unlock.signout
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultSignOut @Inject constructor(
|
||||
private val authenticationService: MatrixAuthenticationService,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
) : SignOut {
|
||||
override suspend fun invoke(): String? {
|
||||
val currentSession = authenticationService.getLatestSessionId()
|
||||
return if (currentSession != null) {
|
||||
matrixClientProvider.getOrRestore(currentSession)
|
||||
.getOrThrow()
|
||||
.logout(ignoreSdkError = true)
|
||||
} else {
|
||||
error("No session to sign out")
|
||||
}
|
||||
}
|
||||
}
|
||||
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