Merge branch 'develop' into julioromano/poll_history_entry_point

This commit is contained in:
ganfra 2023-12-13 17:22:55 +01:00
commit 863d156e4d
738 changed files with 9387 additions and 1581 deletions

View file

@ -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_share_data">"Elemzési adatok megosztása"</string>
<string name="screen_analytics_settings_help_us_improve">"Anonim használati adatok megosztása a problémák azonosítása érdekében."</string>
<string name="screen_analytics_settings_read_terms">"%1$s olvashatja el a feltételeinket."</string>
<string name="screen_analytics_settings_read_terms_content_link">"Itt"</string>
</resources>

View file

@ -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_share_data">"Bagikan data analitik"</string>
<string name="screen_analytics_settings_help_us_improve">"Bagikan data penggunaan anonim untuk membantu kami mengidentifikasi masalah."</string>
<string name="screen_analytics_settings_read_terms">"Anda dapat membaca semua persyaratan kami %1$s."</string>
<string name="screen_analytics_settings_read_terms_content_link">"di sini"</string>
</resources>

View file

@ -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">"Nem rögzítünk vagy profilozunk személyes adatokat"</string>
<string name="screen_analytics_prompt_help_us_improve">"Anonim használati adatok megosztása a problémák azonosítása érdekében."</string>
<string name="screen_analytics_prompt_read_terms">"%1$s olvashatja el a feltételeinket."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"Itt"</string>
<string name="screen_analytics_prompt_settings">"Ezt bármikor kikapcsolhatja"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Adatait nem osztjuk meg harmadik felekkel"</string>
<string name="screen_analytics_prompt_title">"Segítsen az %1$s fejlesztésében"</string>
</resources>

View file

@ -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">"Kami tidak akan merekam atau memprofil data pribadi apa pun"</string>
<string name="screen_analytics_prompt_help_us_improve">"Bagikan data penggunaan anonim untuk membantu kami mengidentifikasi masalah."</string>
<string name="screen_analytics_prompt_read_terms">"Anda dapat membaca semua persyaratan kami %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"di sini"</string>
<string name="screen_analytics_prompt_settings">"Anda dapat mematikan ini kapan saja"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Kami tidak akan membagikan data Anda dengan pihak ketiga"</string>
<string name="screen_analytics_prompt_title">"Bantu sempurnakan %1$s"</string>
</resources>

View file

@ -16,7 +16,7 @@
package io.element.android.features.cachecleaner.impl
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@ -46,9 +46,9 @@ class DefaultCacheCleanerTest {
// Check the files are gone but the sub dirs are not.
DefaultCacheCleaner.SUBDIRS_TO_CLEANUP.forEach {
File(temporaryFolder.root, it).apply {
Truth.assertThat(exists()).isTrue()
Truth.assertThat(isDirectory).isTrue()
Truth.assertThat(listFiles()).isEmpty()
assertThat(exists()).isTrue()
assertThat(isDirectory).isTrue()
assertThat(listFiles()).isEmpty()
}
}
}

View 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">"Folyamatban lévő hívás"</string>
<string name="call_foreground_service_message_android">"Koppintson a híváshoz való visszatéréshez"</string>
<string name="call_foreground_service_title_android">"☎️ Hívás folyamatban"</string>
</resources>

View 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">"Panggilan berlangsung"</string>
<string name="call_foreground_service_message_android">"Ketuk untuk kembali ke panggilan"</string>
<string name="call_foreground_service_title_android">"☎️ Panggilan sedang berlangsung"</string>
</resources>

View file

@ -39,7 +39,6 @@ class MapWebkitPermissionsTest {
@Test
fun `given any other permission, it returns nothing`() {
val permission = mapWebkitPermissions(arrayOf(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID))
assertThat(permission).isEqualTo(emptyList<String>())
assertThat(permission).isEmpty()
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 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.createroom.api
import androidx.compose.runtime.MutableState
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
interface StartDMAction {
/**
* Try to find an existing DM with the given user, or create one if none exists.
* @param userId The user to start a DM with.
* @param actionState The state to update with the result of the action.
*/
suspend fun execute(userId: UserId, actionState: MutableState<Async<RoomId>>)
}

View file

@ -67,6 +67,7 @@ dependencies {
testImplementation(projects.libraries.mediaupload.test)
testImplementation(projects.libraries.permissions.test)
testImplementation(projects.libraries.usersearch.test)
testImplementation(projects.features.createroom.test)
testImplementation(projects.tests.testutils)
ksp(libs.showkase.processor)

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 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.createroom.impl
import androidx.compose.runtime.MutableState
import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.CreatedRoom
import io.element.android.features.createroom.api.StartDMAction
import io.element.android.libraries.architecture.Async
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.UserId
import io.element.android.libraries.matrix.api.room.StartDMResult
import io.element.android.libraries.matrix.api.room.startDM
import io.element.android.services.analytics.api.AnalyticsService
import javax.inject.Inject
@ContributesBinding(SessionScope::class)
class DefaultStartDMAction @Inject constructor(
private val matrixClient: MatrixClient,
private val analyticsService: AnalyticsService,
) : StartDMAction {
override suspend fun execute(userId: UserId, actionState: MutableState<Async<RoomId>>) {
actionState.value = Async.Loading()
when (val result = matrixClient.startDM(userId)) {
is StartDMResult.Success -> {
if (result.isNew) {
analyticsService.capture(CreatedRoom(isDM = true))
}
actionState.value = Async.Success(result.roomId)
}
is StartDMResult.Failure -> {
actionState.value = Async.Failure(result.throwable)
}
}
}
}

View file

@ -21,21 +21,16 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import im.vector.app.features.analytics.plan.CreatedRoom
import io.element.android.features.createroom.api.StartDMAction
import io.element.android.features.createroom.impl.userlist.SelectionMode
import io.element.android.features.createroom.impl.userlist.UserListDataStore
import io.element.android.features.createroom.impl.userlist.UserListPresenter
import io.element.android.features.createroom.impl.userlist.UserListPresenterArgs
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
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.user.MatrixUser
import io.element.android.libraries.usersearch.api.UserRepository
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -43,8 +38,7 @@ class CreateRoomRootPresenter @Inject constructor(
presenterFactory: UserListPresenter.Factory,
userRepository: UserRepository,
userListDataStore: UserListDataStore,
private val matrixClient: MatrixClient,
private val analyticsService: AnalyticsService,
private val startDMAction: StartDMAction,
private val buildMeta: BuildMeta,
) : Presenter<CreateRoomRootState> {
@ -61,37 +55,22 @@ class CreateRoomRootPresenter @Inject constructor(
val userListState = presenter.present()
val localCoroutineScope = rememberCoroutineScope()
val startDmAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
val startDmActionState: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
fun handleEvents(event: CreateRoomRootEvents) {
when (event) {
is CreateRoomRootEvents.StartDM -> localCoroutineScope.startDm(event.matrixUser, startDmAction)
CreateRoomRootEvents.CancelStartDM -> startDmAction.value = Async.Uninitialized
is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch {
startDMAction.execute(event.matrixUser.userId, startDmActionState)
}
CreateRoomRootEvents.CancelStartDM -> startDmActionState.value = Async.Uninitialized
}
}
return CreateRoomRootState(
applicationName = buildMeta.applicationName,
userListState = userListState,
startDmAction = startDmAction.value,
startDmAction = startDmActionState.value,
eventSink = ::handleEvents,
)
}
private fun CoroutineScope.startDm(matrixUser: MatrixUser, startDmAction: MutableState<Async<RoomId>>) = launch {
suspend {
matrixClient.findDM(matrixUser.userId).use { existingDM ->
existingDM?.roomId ?: createDM(matrixUser)
}
}.runCatchingUpdatingState(startDmAction)
}
private suspend fun createDM(user: MatrixUser): RoomId {
return matrixClient
.createDM(user.userId)
.onSuccess {
analyticsService.capture(CreatedRoom(isDM = true))
}
.getOrThrow()
}
}

View file

@ -0,0 +1,15 @@
<?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">"Új szoba"</string>
<string name="screen_create_room_action_invite_people">"Hívja meg ismerőseit az Elementbe"</string>
<string name="screen_create_room_add_people_title">"Emberek meghívása"</string>
<string name="screen_create_room_error_creating_room">"Hiba történt a szoba létrehozásakor"</string>
<string name="screen_create_room_private_option_description">"A szobában lévő üzenetek titkosítottak. A titkosítást utólag nem lehet kikapcsolni."</string>
<string name="screen_create_room_private_option_title">"Privát szoba (csak meghívással)"</string>
<string name="screen_create_room_public_option_description">"Az üzenetek nincsenek titkosítva, és bárki elolvashatja őket. A titkosítást később is engedélyezheti."</string>
<string name="screen_create_room_public_option_title">"Nyilvános szoba (bárki)"</string>
<string name="screen_create_room_room_name_label">"Szoba neve"</string>
<string name="screen_create_room_topic_label">"Téma (nem kötelező)"</string>
<string name="screen_start_chat_error_starting_chat">"Hiba történt a csevegés indításakor"</string>
<string name="screen_create_room_title">"Szoba létrehozása"</string>
</resources>

View file

@ -0,0 +1,15 @@
<?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">"Ruangan baru"</string>
<string name="screen_create_room_action_invite_people">"Undang teman ke Element"</string>
<string name="screen_create_room_add_people_title">"Undang seseorang"</string>
<string name="screen_create_room_error_creating_room">"Terjadi kesalahan saat membuat ruangan"</string>
<string name="screen_create_room_private_option_description">"Pesan di ruangan ini dienkripsi. Enkripsi tidak dapat dinonaktifkan setelahnya."</string>
<string name="screen_create_room_private_option_title">"Ruangan pribadi (hanya undangan)"</string>
<string name="screen_create_room_public_option_description">"Pesan tidak dienkripsi dan siapa pun dapat membacanya. Anda dapat mengaktifkan enkripsi di kemudian hari."</string>
<string name="screen_create_room_public_option_title">"Ruang publik (siapa saja)"</string>
<string name="screen_create_room_room_name_label">"Nama ruangan"</string>
<string name="screen_create_room_topic_label">"Topik (opsional)"</string>
<string name="screen_start_chat_error_starting_chat">"Terjadi kesalahan saat mencoba memulai obrolan"</string>
<string name="screen_create_room_title">"Buat ruangan"</string>
</resources>

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2023 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.createroom.impl
import androidx.compose.runtime.mutableStateOf
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.CreatedRoom
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import kotlinx.coroutines.test.runTest
import org.junit.Test
class DefaultStartDMActionTests {
@Test
fun `when dm is found, assert state is updated with given room id`() = runTest {
val matrixClient = FakeMatrixClient().apply {
givenFindDmResult(A_ROOM_ID)
}
val action = createStartDMAction(matrixClient)
val state = mutableStateOf<Async<RoomId>>(Async.Uninitialized)
action.execute(A_USER_ID, state)
assertThat(state.value).isEqualTo(Async.Success(A_ROOM_ID))
}
@Test
fun `when dm is not found, assert dm is created, state is updated with given room id and analytics get called`() = runTest {
val matrixClient = FakeMatrixClient().apply {
givenFindDmResult(null)
givenCreateDmResult(Result.success(A_ROOM_ID))
}
val analyticsService = FakeAnalyticsService()
val action = createStartDMAction(matrixClient, analyticsService)
val state = mutableStateOf<Async<RoomId>>(Async.Uninitialized)
action.execute(A_USER_ID, state)
assertThat(state.value).isEqualTo(Async.Success(A_ROOM_ID))
assertThat(analyticsService.capturedEvents).containsExactly(CreatedRoom(isDM = true))
}
@Test
fun `when dm creation fails, assert state is updated with given error`() = runTest {
val matrixClient = FakeMatrixClient().apply {
givenFindDmResult(null)
givenCreateDmResult(Result.failure(A_THROWABLE))
}
val action = createStartDMAction(matrixClient)
val state = mutableStateOf<Async<RoomId>>(Async.Uninitialized)
action.execute(A_USER_ID, state)
assertThat(state.value).isEqualTo(Async.Failure<RoomId>(A_THROWABLE))
}
private fun createStartDMAction(
matrixClient: MatrixClient = FakeMatrixClient(),
analyticsService: AnalyticsService = FakeAnalyticsService(),
): DefaultStartDMAction {
return DefaultStartDMAction(
matrixClient = matrixClient,
analyticsService = analyticsService,
)
}
}

View file

@ -20,25 +20,21 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.CreatedRoom
import io.element.android.features.createroom.api.StartDMAction
import io.element.android.features.createroom.impl.userlist.FakeUserListPresenter
import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory
import io.element.android.features.createroom.impl.userlist.UserListDataStore
import io.element.android.features.createroom.impl.userlist.aUserListState
import io.element.android.features.createroom.test.FakeStartDMAction
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_THROWABLE
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.FakeMatrixRoom
import io.element.android.libraries.usersearch.test.FakeUserRepository
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -47,142 +43,57 @@ class CreateRoomRootPresenterTests {
@get:Rule
val warmUpRule = WarmUpRule()
private lateinit var userRepository: FakeUserRepository
private lateinit var presenter: CreateRoomRootPresenter
private lateinit var fakeUserListPresenter: FakeUserListPresenter
private lateinit var fakeMatrixClient: FakeMatrixClient
private lateinit var fakeAnalyticsService: FakeAnalyticsService
@Before
fun setup() {
fakeUserListPresenter = FakeUserListPresenter()
fakeMatrixClient = FakeMatrixClient()
fakeAnalyticsService = FakeAnalyticsService()
userRepository = FakeUserRepository()
presenter = CreateRoomRootPresenter(
presenterFactory = FakeUserListPresenterFactory(fakeUserListPresenter),
userRepository = userRepository,
userListDataStore = UserListDataStore(),
matrixClient = fakeMatrixClient,
analyticsService = fakeAnalyticsService,
buildMeta = aBuildMeta(),
)
}
@Test
fun `present - initial state`() = runTest {
fun `present - start DM action complete scenario`() = runTest {
val startDMAction = FakeStartDMAction()
val presenter = createCreateRoomRootPresenter(startDMAction)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.startDmAction).isInstanceOf(Async.Uninitialized::class.java)
assertThat(initialState.applicationName).isEqualTo(aBuildMeta().applicationName)
assertThat(initialState.userListState.selectedUsers).isEmpty()
assertThat(initialState.userListState.isSearchActive).isFalse()
assertThat(initialState.userListState.isMultiSelectionEnabled).isFalse()
}
}
@Test
fun `present - trigger create DM action`() = runTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val matrixUser = MatrixUser(UserId("@name:domain"))
val createDmResult = Result.success(RoomId("!createDmResult:domain"))
fakeMatrixClient.givenFindDmResult(null)
fakeMatrixClient.givenCreateDmResult(createDmResult)
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
val stateAfterStartDM = awaitItem()
assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java)
assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull())
}
}
@Test
fun `present - creating a DM records analytics event`() = runTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val matrixUser = MatrixUser(UserId("@name:domain"))
val createDmResult = Result.success(RoomId("!createDmResult:domain"))
fakeMatrixClient.givenFindDmResult(null)
fakeMatrixClient.givenCreateDmResult(createDmResult)
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
skipItems(2)
val analyticsEvent = fakeAnalyticsService.capturedEvents.filterIsInstance<CreatedRoom>().firstOrNull()
assertThat(analyticsEvent).isNotNull()
assertThat(analyticsEvent?.isDM).isTrue()
}
}
@Test
fun `present - trigger retrieve DM action`() = runTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val matrixUser = MatrixUser(UserId("@name:domain"))
val fakeDmResult = FakeMatrixRoom(roomId = RoomId("!fakeDmResult:domain"))
fakeMatrixClient.givenFindDmResult(fakeDmResult)
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
val stateAfterStartDM = awaitItem()
assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java)
assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(fakeDmResult.roomId)
assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance<CreatedRoom>()).isEmpty()
}
}
@Test
fun `present - trigger retry create DM action`() = runTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val matrixUser = MatrixUser(UserId("@name:domain"))
val createDmResult = Result.success(RoomId("!createDmResult:domain"))
fakeUserListPresenter.givenState(aUserListState().copy(selectedUsers = persistentListOf(matrixUser)))
fakeMatrixClient.givenFindDmResult(null)
fakeMatrixClient.givenCreateDmError(A_THROWABLE)
fakeMatrixClient.givenCreateDmResult(createDmResult)
val startDMSuccessResult = Async.Success(A_ROOM_ID)
val startDMFailureResult = Async.Failure<RoomId>(A_THROWABLE)
// Failure
startDMAction.givenExecuteResult(startDMFailureResult)
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
val stateAfterStartDM = awaitItem()
assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Failure::class.java)
assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance<CreatedRoom>()).isEmpty()
awaitItem().also { state ->
assertThat(state.startDmAction).isEqualTo(startDMFailureResult)
state.eventSink(CreateRoomRootEvents.CancelStartDM)
}
// Cancel
stateAfterStartDM.eventSink(CreateRoomRootEvents.CancelStartDM)
val stateAfterCancel = awaitItem()
assertThat(stateAfterCancel.startDmAction).isInstanceOf(Async.Uninitialized::class.java)
// Failure
stateAfterCancel.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
// Success
startDMAction.givenExecuteResult(startDMSuccessResult)
awaitItem().also { state ->
assertThat(state.startDmAction).isEqualTo(Async.Uninitialized)
state.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
}
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
val stateAfterSecondAttempt = awaitItem()
assertThat(stateAfterSecondAttempt.startDmAction).isInstanceOf(Async.Failure::class.java)
assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance<CreatedRoom>()).isEmpty()
awaitItem().also { state ->
assertThat(state.startDmAction).isEqualTo(startDMSuccessResult)
}
// Retry with success
fakeMatrixClient.givenCreateDmError(null)
stateAfterSecondAttempt.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
val stateAfterRetryStartDM = awaitItem()
assertThat(stateAfterRetryStartDM.startDmAction).isInstanceOf(Async.Success::class.java)
assertThat(stateAfterRetryStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull())
}
}
private fun createCreateRoomRootPresenter(
startDMAction: StartDMAction = FakeStartDMAction(),
): CreateRoomRootPresenter {
return CreateRoomRootPresenter(
presenterFactory = FakeUserListPresenterFactory(FakeUserListPresenter()),
userRepository = FakeUserRepository(),
userListDataStore = UserListDataStore(),
startDMAction = startDMAction,
buildMeta = aBuildMeta(),
)
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 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.
*/
plugins {
id("io.element.android-compose-library")
}
android {
namespace = "io.element.android.features.createroom.test"
}
dependencies {
implementation(libs.coroutines.core)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrix.test)
implementation(projects.libraries.architecture)
api(projects.features.createroom.api)
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2023 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.createroom.test
import androidx.compose.runtime.MutableState
import io.element.android.features.createroom.api.StartDMAction
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.test.A_ROOM_ID
import kotlinx.coroutines.delay
class FakeStartDMAction : StartDMAction {
private var executeResult: Async<RoomId> = Async.Success(A_ROOM_ID)
fun givenExecuteResult(result: Async<RoomId>) {
executeResult = result
}
override suspend fun execute(userId: UserId, actionState: MutableState<Async<RoomId>>) {
actionState.value = Async.Loading()
delay(1)
actionState.value = executeResult
}
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Ez egy egyszeri folyamat, köszönjük a türelmét."</string>
<string name="screen_migration_title">"A fiók beállítása."</string>
<string name="screen_notification_optin_subtitle">"A beállításokat később is módosíthatja."</string>
<string name="screen_notification_optin_title">"Értesítések engedélyezése, hogy soha ne maradjon le egyetlen üzenetről sem"</string>
<string name="screen_welcome_bullet_1">"A hívások, szavazások, keresések és egyebek az év további részében kerülnek hozzáadásra."</string>
<string name="screen_welcome_bullet_2">"A titkosított szobák üzenetelőzményei nem lesznek elérhetők ebben a frissítésben."</string>
<string name="screen_welcome_bullet_3">"Szeretnénk hallani a véleményét, ossza meg velünk a beállítások oldalon."</string>
<string name="screen_welcome_button">"Lássunk neki!"</string>
<string name="screen_welcome_subtitle">"A következőket kell tudnia:"</string>
<string name="screen_welcome_title">"Üdvözli az %1$s!"</string>
</resources>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Ini adalah proses satu kali, terima kasih telah menunggu."</string>
<string name="screen_migration_title">"Menyiapkan akun Anda."</string>
<string name="screen_notification_optin_subtitle">"Anda dapat mengubah pengaturan Anda nanti."</string>
<string name="screen_notification_optin_title">"Izinkan pemberitahuan dan jangan pernah melewatkan pesan"</string>
<string name="screen_welcome_bullet_1">"Panggilan, pemungutan suara, pencarian, dan lainnya akan ditambahkan di tahun ini."</string>
<string name="screen_welcome_bullet_2">"Riwayat pesan untuk ruangan terenkripsi tidak akan tersedia dalam pembaruan ini."</string>
<string name="screen_welcome_bullet_3">"Kami ingin mendengar dari Anda, beri tahu kami pendapat Anda melalui halaman pengaturan."</string>
<string name="screen_welcome_button">"Ayo!"</string>
<string name="screen_welcome_subtitle">"Berikut adalah yang perlu Anda ketahui:"</string>
<string name="screen_welcome_title">"Selamat datang di %1$s!"</string>
</resources>

View file

@ -20,7 +20,7 @@ import android.os.Build
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.permissions.api.PermissionStateProvider
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.impl.FakePermissionStateProvider
@ -52,7 +52,7 @@ class NotificationsOptInPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.notificationsPermissionState.showDialog).isFalse()
assertThat(initialState.notificationsPermissionState.showDialog).isFalse()
}
}
@ -65,7 +65,7 @@ class NotificationsOptInPresenterTests {
}.test {
val initialState = awaitItem()
initialState.eventSink(NotificationsOptInEvents.ContinueClicked)
Truth.assertThat(awaitItem().notificationsPermissionState.showDialog).isTrue()
assertThat(awaitItem().notificationsPermissionState.showDialog).isTrue()
}
}
@ -80,7 +80,7 @@ class NotificationsOptInPresenterTests {
}.test {
val initialState = awaitItem()
initialState.eventSink(NotificationsOptInEvents.ContinueClicked)
Truth.assertThat(isFinished).isTrue()
assertThat(isFinished).isTrue()
}
}
@ -96,7 +96,7 @@ class NotificationsOptInPresenterTests {
}.test {
val initialState = awaitItem()
initialState.eventSink(NotificationsOptInEvents.NotNowClicked)
Truth.assertThat(isFinished).isTrue()
assertThat(isFinished).isTrue()
}
}
@ -122,7 +122,7 @@ class NotificationsOptInPresenterTests {
val isPermissionDenied = runBlocking {
permissionStateProvider.isPermissionDenied("notifications").first()
}
Truth.assertThat(isPermissionDenied).isTrue()
assertThat(isPermissionDenied).isTrue()
}
}

View file

@ -33,7 +33,7 @@ data class InviteListInviteSummary(
val isNew: Boolean = false,
)
data class InviteSender constructor(
data class InviteSender(
val userId: UserId,
val displayName: String,
val avatarData: AvatarData = AvatarData(userId.value, displayName, size = AvatarSize.InviteSender),

View file

@ -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">"Biztos, hogy elutasítja a meghívást, hogy csatlakozzon ehhez: %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Meghívás elutasítása"</string>
<string name="screen_invites_decline_direct_chat_message">"Biztos, hogy elutasítja ezt a privát csevegést vele: %1$s?"</string>
<string name="screen_invites_decline_direct_chat_title">"Csevegés elutasítása"</string>
<string name="screen_invites_empty_list">"Nincsenek meghívások"</string>
<string name="screen_invites_invited_you">"%1$s (%2$s) meghívta"</string>
</resources>

View file

@ -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">"Apakah Anda yakin ingin menolak undangan untuk bergabung ke %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Tolak undangan"</string>
<string name="screen_invites_decline_direct_chat_message">"Apakah Anda yakin ingin menolak obrolan pribadi dengan %1$s?"</string>
<string name="screen_invites_decline_direct_chat_title">"Tolak obrolan"</string>
<string name="screen_invites_empty_list">"Tidak ada undangan"</string>
<string name="screen_invites_invited_you">"%1$s (%2$s) mengundang Anda"</string>
</resources>

View file

@ -19,7 +19,7 @@ package io.element.android.features.invitelist.impl
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.features.invitelist.api.SeenInvitesStore
import io.element.android.features.invitelist.test.FakeSeenInvitesStore
import io.element.android.libraries.architecture.Async
@ -63,14 +63,14 @@ class InviteListPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.inviteList).isEmpty()
assertThat(initialState.inviteList).isEmpty()
roomListService.postInviteRooms(listOf(aRoomSummary()))
val withInviteState = awaitItem()
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
Truth.assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
Truth.assertThat(withInviteState.inviteList[0].roomName).isEqualTo(A_ROOM_NAME)
assertThat(withInviteState.inviteList.size).isEqualTo(1)
assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
assertThat(withInviteState.inviteList[0].roomName).isEqualTo(A_ROOM_NAME)
}
}
@ -84,11 +84,11 @@ class InviteListPresenterTests {
presenter.present()
}.test {
val withInviteState = awaitItem()
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
Truth.assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
Truth.assertThat(withInviteState.inviteList[0].roomAlias).isEqualTo(A_USER_ID.value)
Truth.assertThat(withInviteState.inviteList[0].roomName).isEqualTo(A_ROOM_NAME)
Truth.assertThat(withInviteState.inviteList[0].roomAvatarData).isEqualTo(
assertThat(withInviteState.inviteList.size).isEqualTo(1)
assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
assertThat(withInviteState.inviteList[0].roomAlias).isEqualTo(A_USER_ID.value)
assertThat(withInviteState.inviteList[0].roomName).isEqualTo(A_ROOM_NAME)
assertThat(withInviteState.inviteList[0].roomAvatarData).isEqualTo(
AvatarData(
id = A_USER_ID.value,
name = A_USER_NAME,
@ -96,7 +96,7 @@ class InviteListPresenterTests {
size = AvatarSize.RoomInviteItem,
)
)
Truth.assertThat(withInviteState.inviteList[0].sender).isNull()
assertThat(withInviteState.inviteList[0].sender).isNull()
}
}
@ -110,10 +110,10 @@ class InviteListPresenterTests {
presenter.present()
}.test {
val withInviteState = awaitItem()
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
Truth.assertThat(withInviteState.inviteList[0].sender?.displayName).isEqualTo(A_USER_NAME)
Truth.assertThat(withInviteState.inviteList[0].sender?.userId).isEqualTo(A_USER_ID)
Truth.assertThat(withInviteState.inviteList[0].sender?.avatarData).isEqualTo(
assertThat(withInviteState.inviteList.size).isEqualTo(1)
assertThat(withInviteState.inviteList[0].sender?.displayName).isEqualTo(A_USER_NAME)
assertThat(withInviteState.inviteList[0].sender?.userId).isEqualTo(A_USER_ID)
assertThat(withInviteState.inviteList[0].sender?.avatarData).isEqualTo(
AvatarData(
id = A_USER_ID.value,
name = A_USER_NAME,
@ -142,11 +142,11 @@ class InviteListPresenterTests {
originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0]))
val newState = awaitItem()
Truth.assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Visible::class.java)
assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Visible::class.java)
val confirmDialog = newState.declineConfirmationDialog as InviteDeclineConfirmationDialog.Visible
Truth.assertThat(confirmDialog.isDirect).isTrue()
Truth.assertThat(confirmDialog.name).isEqualTo(A_ROOM_NAME)
assertThat(confirmDialog.isDirect).isTrue()
assertThat(confirmDialog.name).isEqualTo(A_ROOM_NAME)
}
}
@ -163,11 +163,11 @@ class InviteListPresenterTests {
originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0]))
val newState = awaitItem()
Truth.assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Visible::class.java)
assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Visible::class.java)
val confirmDialog = newState.declineConfirmationDialog as InviteDeclineConfirmationDialog.Visible
Truth.assertThat(confirmDialog.isDirect).isFalse()
Truth.assertThat(confirmDialog.name).isEqualTo(A_ROOM_NAME)
assertThat(confirmDialog.isDirect).isFalse()
assertThat(confirmDialog.name).isEqualTo(A_ROOM_NAME)
}
}
@ -188,7 +188,7 @@ class InviteListPresenterTests {
originalState.eventSink(InviteListEvents.CancelDeclineInvite)
val newState = awaitItem()
Truth.assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Hidden::class.java)
assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Hidden::class.java)
}
}
@ -215,7 +215,7 @@ class InviteListPresenterTests {
skipItems(2)
Truth.assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1)
assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1)
}
}
@ -245,7 +245,7 @@ class InviteListPresenterTests {
val newState = awaitItem()
Truth.assertThat(newState.declinedAction).isEqualTo(Async.Failure<Unit>(ex))
assertThat(newState.declinedAction).isEqualTo(Async.Failure<Unit>(ex))
}
}
@ -277,7 +277,7 @@ class InviteListPresenterTests {
val newState = awaitItem()
Truth.assertThat(newState.declinedAction).isEqualTo(Async.Uninitialized)
assertThat(newState.declinedAction).isEqualTo(Async.Uninitialized)
}
}
@ -300,8 +300,8 @@ class InviteListPresenterTests {
val newState = awaitItem()
Truth.assertThat(newState.acceptedAction).isEqualTo(Async.Success(A_ROOM_ID))
Truth.assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1)
assertThat(newState.acceptedAction).isEqualTo(Async.Success(A_ROOM_ID))
assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1)
}
}
@ -323,7 +323,7 @@ class InviteListPresenterTests {
val originalState = awaitItem()
originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0]))
Truth.assertThat(awaitItem().acceptedAction).isEqualTo(Async.Failure<RoomId>(ex))
assertThat(awaitItem().acceptedAction).isEqualTo(Async.Failure<RoomId>(ex))
}
}
@ -350,7 +350,7 @@ class InviteListPresenterTests {
originalState.eventSink(InviteListEvents.DismissAcceptError)
val newState = awaitItem()
Truth.assertThat(newState.acceptedAction).isEqualTo(Async.Uninitialized)
assertThat(newState.acceptedAction).isEqualTo(Async.Uninitialized)
}
}
@ -375,19 +375,19 @@ class InviteListPresenterTests {
roomListService.postInviteRooms(listOf(aRoomSummary()))
awaitItem()
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID))
assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID))
// When a second is added, both are saved
roomListService.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
awaitItem()
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID, A_ROOM_ID_2))
assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID, A_ROOM_ID_2))
// When they're both dismissed, an empty set is saved
roomListService.postInviteRooms(listOf())
awaitItem()
Truth.assertThat(store.getProvidedRoomIds()).isEmpty()
assertThat(store.getProvidedRoomIds()).isEmpty()
}
}
@ -413,11 +413,11 @@ class InviteListPresenterTests {
skipItems(1)
val withInviteState = awaitItem()
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(2)
Truth.assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
Truth.assertThat(withInviteState.inviteList[0].isNew).isFalse()
Truth.assertThat(withInviteState.inviteList[1].roomId).isEqualTo(A_ROOM_ID_2)
Truth.assertThat(withInviteState.inviteList[1].isNew).isTrue()
assertThat(withInviteState.inviteList.size).isEqualTo(2)
assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
assertThat(withInviteState.inviteList[0].isNew).isFalse()
assertThat(withInviteState.inviteList[1].roomId).isEqualTo(A_ROOM_ID_2)
assertThat(withInviteState.inviteList[1].isNew).isTrue()
}
}

View 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="leave_room_alert_empty_subtitle">"Biztos, hogy elhagyja ezt a szobát? Ön az egyedüli ember itt. Ha kilép, akkor senki sem fog tudni csatlakozni a jövőben, Önt is beleértve."</string>
<string name="leave_room_alert_private_subtitle">"Biztos, hogy elhagyja ezt a szobát? Ez a szoba nem nyilvános, és meghívó nélkül nem fog tudni újra belépni."</string>
<string name="leave_room_alert_subtitle">"Biztos, hogy elhagyja a szobát?"</string>
</resources>

View 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="leave_room_alert_empty_subtitle">"Apakah Anda yakin ingin meninggalkan ruangan ini? Anda adalah orang satu-satunya di sini. Jika Anda pergi, tidak akan ada yang bisa bergabung di masa depan, termasuk Anda."</string>
<string name="leave_room_alert_private_subtitle">"Apakah Anda yakin ingin meninggalkan ruangan ini? Ruangan ini tidak umum dan Anda tidak akan dapat bergabung kembali tanpa undangan."</string>
<string name="leave_room_alert_subtitle">"Apakah Anda yakin ingin meninggalkan ruangan?"</string>
</resources>

View file

@ -19,7 +19,7 @@ package io.element.android.features.location.impl.send
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Composer
import io.element.android.features.location.api.Location
import io.element.android.features.location.impl.aPermissionsState
@ -77,16 +77,16 @@ class SendLocationPresenterTest {
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.SenderLocation)
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(true)
assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.SenderLocation)
assertThat(initialState.hasLocationPermission).isTrue()
// Swipe the map to switch mode
initialState.eventSink(SendLocationEvents.SwitchToPinLocationMode)
val myLocationState = awaitItem()
Truth.assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(myLocationState.hasLocationPermission).isEqualTo(true)
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isTrue()
}
}
@ -104,16 +104,16 @@ class SendLocationPresenterTest {
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.SenderLocation)
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(true)
assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.SenderLocation)
assertThat(initialState.hasLocationPermission).isTrue()
// Swipe the map to switch mode
initialState.eventSink(SendLocationEvents.SwitchToPinLocationMode)
val myLocationState = awaitItem()
Truth.assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(myLocationState.hasLocationPermission).isEqualTo(true)
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isTrue()
}
}
@ -130,16 +130,16 @@ class SendLocationPresenterTest {
sendLocationPresenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(false)
assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(initialState.hasLocationPermission).isFalse()
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
Truth.assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionDenied)
Truth.assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(myLocationState.hasLocationPermission).isEqualTo(false)
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionDenied)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
}
}
@ -156,16 +156,16 @@ class SendLocationPresenterTest {
sendLocationPresenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(false)
assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(initialState.hasLocationPermission).isFalse()
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
Truth.assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
Truth.assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(myLocationState.hasLocationPermission).isEqualTo(false)
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
}
}
@ -187,16 +187,16 @@ class SendLocationPresenterTest {
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
Truth.assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
Truth.assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(myLocationState.hasLocationPermission).isEqualTo(false)
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
// Dismiss the dialog
myLocationState.eventSink(SendLocationEvents.DismissDialog)
val dialogDismissedState = awaitItem()
Truth.assertThat(dialogDismissedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(dialogDismissedState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(dialogDismissedState.hasLocationPermission).isEqualTo(false)
assertThat(dialogDismissedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(dialogDismissedState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
}
}
@ -218,13 +218,13 @@ class SendLocationPresenterTest {
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
Truth.assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
Truth.assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(myLocationState.hasLocationPermission).isEqualTo(false)
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
// Continue the dialog sends permission request to the permissions presenter
myLocationState.eventSink(SendLocationEvents.RequestPermissions)
Truth.assertThat(permissionsPresenterFake.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
assertThat(permissionsPresenterFake.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
}
}
@ -246,16 +246,16 @@ class SendLocationPresenterTest {
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
Truth.assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionDenied)
Truth.assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(myLocationState.hasLocationPermission).isEqualTo(false)
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionDenied)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
// Dismiss the dialog
myLocationState.eventSink(SendLocationEvents.DismissDialog)
val dialogDismissedState = awaitItem()
Truth.assertThat(dialogDismissedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(dialogDismissedState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
Truth.assertThat(dialogDismissedState.hasLocationPermission).isEqualTo(false)
assertThat(dialogDismissedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(dialogDismissedState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
}
}
@ -292,8 +292,8 @@ class SendLocationPresenterTest {
delay(1) // Wait for the coroutine to finish
Truth.assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1)
Truth.assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo(
assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1)
assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo(
SendLocationInvocation(
body = "Location was shared at geo:3.0,4.0;u=5.0",
geoUri = "geo:3.0,4.0;u=5.0",
@ -303,8 +303,8 @@ class SendLocationPresenterTest {
)
)
Truth.assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
Truth.assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
Composer(
inThread = false,
isEditing = false,
@ -348,8 +348,8 @@ class SendLocationPresenterTest {
delay(1) // Wait for the coroutine to finish
Truth.assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1)
Truth.assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo(
assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1)
assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo(
SendLocationInvocation(
body = "Location was shared at geo:0.0,1.0",
geoUri = "geo:0.0,1.0",
@ -359,8 +359,8 @@ class SendLocationPresenterTest {
)
)
Truth.assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
Truth.assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
Composer(
inThread = false,
isEditing = false,
@ -405,8 +405,8 @@ class SendLocationPresenterTest {
delay(1) // Wait for the coroutine to finish
Truth.assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
Truth.assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
Composer(
inThread = false,
isEditing = true,
@ -444,8 +444,8 @@ class SendLocationPresenterTest {
dialogShownState.eventSink(SendLocationEvents.OpenAppSettings)
val settingsOpenedState = awaitItem()
Truth.assertThat(settingsOpenedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
Truth.assertThat(fakeLocationActions.openSettingsInvocationsCount).isEqualTo(1)
assertThat(settingsOpenedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(fakeLocationActions.openSettingsInvocationsCount).isEqualTo(1)
}
}
@ -455,7 +455,7 @@ class SendLocationPresenterTest {
sendLocationPresenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.appName).isEqualTo("app name")
assertThat(initialState.appName).isEqualTo("app name")
}
}
}

View file

@ -19,7 +19,7 @@ package io.element.android.features.location.impl.show
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
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
@ -66,10 +66,10 @@ class ShowLocationPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.location).isEqualTo(location)
Truth.assertThat(initialState.description).isEqualTo(A_DESCRIPTION)
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(false)
Truth.assertThat(initialState.isTrackMyLocation).isEqualTo(false)
assertThat(initialState.location).isEqualTo(location)
assertThat(initialState.description).isEqualTo(A_DESCRIPTION)
assertThat(initialState.hasLocationPermission).isFalse()
assertThat(initialState.isTrackMyLocation).isFalse()
}
}
@ -86,10 +86,10 @@ class ShowLocationPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.location).isEqualTo(location)
Truth.assertThat(initialState.description).isEqualTo(A_DESCRIPTION)
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(false)
Truth.assertThat(initialState.isTrackMyLocation).isEqualTo(false)
assertThat(initialState.location).isEqualTo(location)
assertThat(initialState.description).isEqualTo(A_DESCRIPTION)
assertThat(initialState.hasLocationPermission).isFalse()
assertThat(initialState.isTrackMyLocation).isFalse()
}
}
@ -101,10 +101,10 @@ class ShowLocationPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.location).isEqualTo(location)
Truth.assertThat(initialState.description).isEqualTo(A_DESCRIPTION)
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(true)
Truth.assertThat(initialState.isTrackMyLocation).isEqualTo(false)
assertThat(initialState.location).isEqualTo(location)
assertThat(initialState.description).isEqualTo(A_DESCRIPTION)
assertThat(initialState.hasLocationPermission).isTrue()
assertThat(initialState.isTrackMyLocation).isFalse()
}
}
@ -116,10 +116,10 @@ class ShowLocationPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.location).isEqualTo(location)
Truth.assertThat(initialState.description).isEqualTo(A_DESCRIPTION)
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(true)
Truth.assertThat(initialState.isTrackMyLocation).isEqualTo(false)
assertThat(initialState.location).isEqualTo(location)
assertThat(initialState.description).isEqualTo(A_DESCRIPTION)
assertThat(initialState.hasLocationPermission).isTrue()
assertThat(initialState.isTrackMyLocation).isFalse()
}
}
@ -131,8 +131,8 @@ class ShowLocationPresenterTest {
val initialState = awaitItem()
initialState.eventSink(ShowLocationEvents.Share)
Truth.assertThat(fakeLocationActions.sharedLocation).isEqualTo(location)
Truth.assertThat(fakeLocationActions.sharedLabel).isEqualTo(A_DESCRIPTION)
assertThat(fakeLocationActions.sharedLocation).isEqualTo(location)
assertThat(fakeLocationActions.sharedLabel).isEqualTo(A_DESCRIPTION)
}
}
@ -144,23 +144,23 @@ class ShowLocationPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.hasLocationPermission).isEqualTo(true)
Truth.assertThat(initialState.isTrackMyLocation).isEqualTo(false)
assertThat(initialState.hasLocationPermission).isTrue()
assertThat(initialState.isTrackMyLocation).isFalse()
initialState.eventSink(ShowLocationEvents.TrackMyLocation(true))
val trackMyLocationState = awaitItem()
delay(1)
Truth.assertThat(trackMyLocationState.hasLocationPermission).isEqualTo(true)
Truth.assertThat(trackMyLocationState.isTrackMyLocation).isEqualTo(true)
assertThat(trackMyLocationState.hasLocationPermission).isTrue()
assertThat(trackMyLocationState.isTrackMyLocation).isTrue()
// Swipe the map to switch mode
initialState.eventSink(ShowLocationEvents.TrackMyLocation(false))
val trackLocationDisabledState = awaitItem()
Truth.assertThat(trackLocationDisabledState.permissionDialog).isEqualTo(ShowLocationState.Dialog.None)
Truth.assertThat(trackLocationDisabledState.isTrackMyLocation).isEqualTo(false)
Truth.assertThat(trackLocationDisabledState.hasLocationPermission).isEqualTo(true)
assertThat(trackLocationDisabledState.permissionDialog).isEqualTo(ShowLocationState.Dialog.None)
assertThat(trackLocationDisabledState.isTrackMyLocation).isFalse()
assertThat(trackLocationDisabledState.hasLocationPermission).isTrue()
}
}
@ -182,16 +182,16 @@ class ShowLocationPresenterTest {
// Click on the button to switch mode
initialState.eventSink(ShowLocationEvents.TrackMyLocation(true))
val trackLocationState = awaitItem()
Truth.assertThat(trackLocationState.permissionDialog).isEqualTo(ShowLocationState.Dialog.PermissionRationale)
Truth.assertThat(trackLocationState.isTrackMyLocation).isEqualTo(false)
Truth.assertThat(trackLocationState.hasLocationPermission).isEqualTo(false)
assertThat(trackLocationState.permissionDialog).isEqualTo(ShowLocationState.Dialog.PermissionRationale)
assertThat(trackLocationState.isTrackMyLocation).isFalse()
assertThat(trackLocationState.hasLocationPermission).isFalse()
// Dismiss the dialog
initialState.eventSink(ShowLocationEvents.DismissDialog)
val dialogDismissedState = awaitItem()
Truth.assertThat(dialogDismissedState.permissionDialog).isEqualTo(ShowLocationState.Dialog.None)
Truth.assertThat(dialogDismissedState.isTrackMyLocation).isEqualTo(false)
Truth.assertThat(dialogDismissedState.hasLocationPermission).isEqualTo(false)
assertThat(dialogDismissedState.permissionDialog).isEqualTo(ShowLocationState.Dialog.None)
assertThat(dialogDismissedState.isTrackMyLocation).isFalse()
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
}
}
@ -213,13 +213,13 @@ class ShowLocationPresenterTest {
// Click on the button to switch mode
initialState.eventSink(ShowLocationEvents.TrackMyLocation(true))
val trackLocationState = awaitItem()
Truth.assertThat(trackLocationState.permissionDialog).isEqualTo(ShowLocationState.Dialog.PermissionRationale)
Truth.assertThat(trackLocationState.isTrackMyLocation).isEqualTo(false)
Truth.assertThat(trackLocationState.hasLocationPermission).isEqualTo(false)
assertThat(trackLocationState.permissionDialog).isEqualTo(ShowLocationState.Dialog.PermissionRationale)
assertThat(trackLocationState.isTrackMyLocation).isFalse()
assertThat(trackLocationState.hasLocationPermission).isFalse()
// Continue the dialog sends permission request to the permissions presenter
trackLocationState.eventSink(ShowLocationEvents.RequestPermissions)
Truth.assertThat(permissionsPresenterFake.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
assertThat(permissionsPresenterFake.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
}
}
@ -241,16 +241,16 @@ class ShowLocationPresenterTest {
// Click on the button to switch mode
initialState.eventSink(ShowLocationEvents.TrackMyLocation(true))
val trackLocationState = awaitItem()
Truth.assertThat(trackLocationState.permissionDialog).isEqualTo(ShowLocationState.Dialog.PermissionDenied)
Truth.assertThat(trackLocationState.isTrackMyLocation).isEqualTo(false)
Truth.assertThat(trackLocationState.hasLocationPermission).isEqualTo(false)
assertThat(trackLocationState.permissionDialog).isEqualTo(ShowLocationState.Dialog.PermissionDenied)
assertThat(trackLocationState.isTrackMyLocation).isFalse()
assertThat(trackLocationState.hasLocationPermission).isFalse()
// Dismiss the dialog
initialState.eventSink(ShowLocationEvents.DismissDialog)
val dialogDismissedState = awaitItem()
Truth.assertThat(dialogDismissedState.permissionDialog).isEqualTo(ShowLocationState.Dialog.None)
Truth.assertThat(dialogDismissedState.isTrackMyLocation).isEqualTo(false)
Truth.assertThat(dialogDismissedState.hasLocationPermission).isEqualTo(false)
assertThat(dialogDismissedState.permissionDialog).isEqualTo(ShowLocationState.Dialog.None)
assertThat(dialogDismissedState.isTrackMyLocation).isFalse()
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
}
}
@ -276,8 +276,8 @@ class ShowLocationPresenterTest {
dialogShownState.eventSink(ShowLocationEvents.OpenAppSettings)
val settingsOpenedState = awaitItem()
Truth.assertThat(settingsOpenedState.permissionDialog).isEqualTo(ShowLocationState.Dialog.None)
Truth.assertThat(fakeLocationActions.openSettingsInvocationsCount).isEqualTo(1)
assertThat(settingsOpenedState.permissionDialog).isEqualTo(ShowLocationState.Dialog.None)
assertThat(fakeLocationActions.openSettingsInvocationsCount).isEqualTo(1)
}
}
@ -287,7 +287,7 @@ class ShowLocationPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.appName).isEqualTo("app name")
assertThat(initialState.appName).isEqualTo("app name")
}
}

View file

@ -1,4 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="screen_app_lock_subtitle">
<item quantity="one">"Du hast %1$d Versuch zu entsperren"</item>
<item quantity="other">"Du hast %1$d Versuche zum Entsperren"</item>
</plurals>
<plurals name="screen_app_lock_subtitle_wrong_pin">
<item quantity="one">"Falsche PIN. Du hast %1$d weitere Chance"</item>
<item quantity="other">"Falsche PIN. Du hast %1$d weitere Chancen"</item>
</plurals>
<string name="screen_app_lock_biometric_authentication">"biometrische Authentifizierung"</string>
<string name="screen_app_lock_biometric_unlock">"biometrisches Entsperren"</string>
<string name="screen_app_lock_biometric_unlock_title_android">"Mit Biometrie entsperren"</string>
<string name="screen_app_lock_forgot_pin">"PIN vergessen?"</string>
<string name="screen_app_lock_settings_change_pin">"PIN-Code ändern"</string>
<string name="screen_app_lock_settings_enable_biometric_unlock">"Biometrisches Entsperren zulassen"</string>
<string name="screen_app_lock_settings_remove_pin">"Pin entfernen"</string>
<string name="screen_app_lock_settings_remove_pin_alert_message">"Bist du sicher, dass du die PIN entfernen willst?"</string>
<string name="screen_app_lock_settings_remove_pin_alert_title">"PIN entfernen?"</string>
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"%1$s zulassen"</string>
<string name="screen_app_lock_setup_biometric_unlock_skip">"Ich verwende lieber die PIN"</string>
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Spare dir etwas Zeit und benutze %1$s, um die App jedes Mal zu entsperren"</string>
<string name="screen_app_lock_setup_choose_pin">"PIN wählen"</string>
<string name="screen_app_lock_setup_confirm_pin">"PIN bestätigen"</string>
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"Aus Sicherheitsgründen kannst du das nicht als deinen PIN-Code wählen"</string>
<string name="screen_app_lock_setup_pin_blacklisted_dialog_title">"Wähle eine andere PIN"</string>
<string name="screen_app_lock_setup_pin_context">"Sperre %1$s, um deine Chats noch sicherer zu machen.
Wähle etwas Einprägsames. Wenn du diese PIN vergisst, wirst du aus der App ausgeloggt."</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Bitte gib die gleiche PIN zweimal ein"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Die PINs stimmen nicht überein"</string>
<string name="screen_app_lock_signout_alert_message">"Um fortzufahren, musst du dich erneut anmelden und eine neue PIN erstellen"</string>
<string name="screen_app_lock_signout_alert_title">"Du wirst abgemeldet"</string>
<string name="screen_app_lock_use_biometric_android">"Biometrie verwenden"</string>
<string name="screen_app_lock_use_pin_android">"PIN verwenden"</string>
<string name="screen_signout_in_progress_dialog_content">"Abmelden…"</string>
</resources>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="screen_app_lock_subtitle">
<item quantity="one">"%1$d próbálkozása van a feloldáshoz"</item>
<item quantity="other">"%1$d próbálkozása van a feloldáshoz"</item>
</plurals>
<plurals name="screen_app_lock_subtitle_wrong_pin">
<item quantity="one">"Hibás PIN-kód. Még %1$d próbálkozási lehetősége maradt."</item>
<item quantity="other">"Hibás PIN-kód. Még %1$d próbálkozási lehetősége maradt."</item>
</plurals>
<string name="screen_app_lock_biometric_authentication">"biometrikus hitelesítés"</string>
<string name="screen_app_lock_biometric_unlock">"biometrikus feloldás"</string>
<string name="screen_app_lock_biometric_unlock_title_android">"Feloldás biometrikus adatokkal"</string>
<string name="screen_app_lock_forgot_pin">"Elfelejtette a PIN-kódot?"</string>
<string name="screen_app_lock_settings_change_pin">"PIN-kód módosítása"</string>
<string name="screen_app_lock_settings_enable_biometric_unlock">"Biometrikus feloldás engedélyezése"</string>
<string name="screen_app_lock_settings_remove_pin">"PIN-kód eltávolítása"</string>
<string name="screen_app_lock_settings_remove_pin_alert_message">"Biztos, hogy eltávolítja a PIN-kódot?"</string>
<string name="screen_app_lock_settings_remove_pin_alert_title">"PIN-kód eltávolítása?"</string>
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"A %1$s engedélyezése"</string>
<string name="screen_app_lock_setup_biometric_unlock_skip">"Inkább PIN-kód használata"</string>
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Spóroljon meg némi időt, és használja a %1$st az alkalmazás feloldásához"</string>
<string name="screen_app_lock_setup_choose_pin">"PIN-kód kiválasztása"</string>
<string name="screen_app_lock_setup_confirm_pin">"PIN-kód megerősítése"</string>
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"Ezt biztonsági okokból nem választhatja PIN-kódként"</string>
<string name="screen_app_lock_setup_pin_blacklisted_dialog_title">"Válasszon egy másik PIN-kódot"</string>
<string name="screen_app_lock_setup_pin_context">"Az %1$s zárolása a csevegései nagyobb biztonsága érdekében.
Válasszon valami megjegyezhetőt. Ha elfelejti a PIN-kódot, akkor ki lesz jelentkeztetve az alkalmazásból."</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Adja meg a PIN-kódját kétszer"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"A PIN-kódok nem egyeznek"</string>
<string name="screen_app_lock_signout_alert_message">"A folytatáshoz újra be kell jelentkeznie, és létre kell hoznia egy új PIN-kódot"</string>
<string name="screen_app_lock_signout_alert_title">"Kijelentkeztetésre kerül"</string>
<string name="screen_app_lock_use_biometric_android">"Biometrikus adatok használata"</string>
<string name="screen_app_lock_use_pin_android">"PIN-kód használata"</string>
<string name="screen_signout_in_progress_dialog_content">"Kijelentkezés…"</string>
</resources>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="screen_app_lock_subtitle">
<item quantity="other">"Anda memiliki %1$d percobaan lagi untuk membuka kunci"</item>
</plurals>
<plurals name="screen_app_lock_subtitle_wrong_pin">
<item quantity="other">"PIN salah. Anda memiliki %1$d percobaan lagi"</item>
</plurals>
<string name="screen_app_lock_biometric_authentication">"autentikasi biometrik"</string>
<string name="screen_app_lock_biometric_unlock">"pembukaan biometrik"</string>
<string name="screen_app_lock_biometric_unlock_title_android">"Buka kunci dengan biometrik"</string>
<string name="screen_app_lock_forgot_pin">"Lupa PIN?"</string>
<string name="screen_app_lock_settings_change_pin">"Ubah kode PIN"</string>
<string name="screen_app_lock_settings_enable_biometric_unlock">"Perbolehkan pembukaan biometrik"</string>
<string name="screen_app_lock_settings_remove_pin">"Hapus PIN"</string>
<string name="screen_app_lock_settings_remove_pin_alert_message">"Apakah Anda yakin ingin menghapus PIN?"</string>
<string name="screen_app_lock_settings_remove_pin_alert_title">"Hapus PIN?"</string>
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Perbolehkan %1$s"</string>
<string name="screen_app_lock_setup_biometric_unlock_skip">"Saya lebih suka menggunakan PIN"</string>
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Hemat waktu Anda dan gunakan %1$s untuk membuka kunci aplikasi setiap kalinya"</string>
<string name="screen_app_lock_setup_choose_pin">"Pilih PIN"</string>
<string name="screen_app_lock_setup_confirm_pin">"Konfirmasi PIN"</string>
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"Anda tidak dapat memilih PIN ini demi keamanan"</string>
<string name="screen_app_lock_setup_pin_blacklisted_dialog_title">"Pilih PIN yang lain"</string>
<string name="screen_app_lock_setup_pin_context">"Kunci %1$s untuk menambahkan keamanan tambahan pada percakapan Anda.
Pilih sesuatu yang mudah untuk diingat. Jika Anda lupa PIN ini, Anda akan dikeluarkan dari aplikasi."</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Silakan masukkan PIN yang sama dua kali"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN tidak cocok"</string>
<string name="screen_app_lock_signout_alert_message">"Anda harus masuk ulang dan membuat PIN baru untuk melanjutkan"</string>
<string name="screen_app_lock_signout_alert_title">"Anda sedang dikeluarkan"</string>
<string name="screen_app_lock_use_biometric_android">"Gunakan biometrik"</string>
<string name="screen_app_lock_use_pin_android">"Gunakan PIN"</string>
<string name="screen_signout_in_progress_dialog_content">"Mengeluarkan dari akun…"</string>
</resources>

View file

@ -94,8 +94,8 @@ class PinUnlockPresenterTest {
}
awaitLastSequentialItem().also { state ->
assertThat(state.remainingAttempts.dataOrNull()).isEqualTo(0)
assertThat(state.showSignOutPrompt).isEqualTo(true)
assertThat(state.isSignOutPromptCancellable).isEqualTo(false)
assertThat(state.showSignOutPrompt).isTrue()
assertThat(state.isSignOutPromptCancellable).isFalse()
}
}
}
@ -112,16 +112,16 @@ class PinUnlockPresenterTest {
state.eventSink(PinUnlockEvents.OnForgetPin)
}
awaitLastSequentialItem().also { state ->
assertThat(state.showSignOutPrompt).isEqualTo(true)
assertThat(state.isSignOutPromptCancellable).isEqualTo(true)
assertThat(state.showSignOutPrompt).isTrue()
assertThat(state.isSignOutPromptCancellable).isTrue()
state.eventSink(PinUnlockEvents.ClearSignOutPrompt)
}
awaitLastSequentialItem().also { state ->
assertThat(state.showSignOutPrompt).isEqualTo(false)
assertThat(state.showSignOutPrompt).isFalse()
state.eventSink(PinUnlockEvents.OnForgetPin)
}
awaitLastSequentialItem().also { state ->
assertThat(state.showSignOutPrompt).isEqualTo(true)
assertThat(state.showSignOutPrompt).isTrue()
state.eventSink(PinUnlockEvents.SignOut)
}
consumeItemsUntilPredicate { state ->

View file

@ -16,7 +16,7 @@
package io.element.android.features.login.impl.resolver
data class HomeserverData constructor(
data class HomeserverData(
// The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url
val homeserverUrl: String,
// True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid

View file

@ -18,6 +18,7 @@
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
<string name="screen_change_server_form_notice">"Du kannst nur eine Verbindung zu einem vorhandenen Server herstellen, der Sliding Sync unterstützt. Dein Homeserver-Administrator muss das konfigurieren. %1$s"</string>
<string name="screen_change_server_subtitle">"Wie lautet die Adresse deines Servers?"</string>
<string name="screen_change_server_title">"Wähle deinen Server aus"</string>
<string name="screen_login_error_deactivated_account">"Dieses Konto wurde deaktiviert."</string>
<string name="screen_login_error_invalid_credentials">"Falscher Benutzername und/oder Passwort"</string>
<string name="screen_login_error_invalid_user_id">"Dies ist keine gültige Benutzerkennung. Erwartetes Format: \'@user:homeserver.org\'"</string>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_account_provider_change">"Fiókszolgáltató módosítása"</string>
<string name="screen_account_provider_form_hint">"Matrix-kiszolgáló webcíme"</string>
<string name="screen_account_provider_form_notice">"Adjon meg egy keresési kifejezést vagy egy tartománycímet."</string>
<string name="screen_account_provider_form_subtitle">"Keresés egy cégre, közösségre vagy privát kiszolgálóra."</string>
<string name="screen_account_provider_form_title">"Fiókszolgáltató keresése"</string>
<string name="screen_account_provider_signin_subtitle">"Itt lesznek a beszélgetései ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez."</string>
<string name="screen_account_provider_signin_title">"Hamarosan bejelentkezik ide: %s"</string>
<string name="screen_account_provider_signup_subtitle">"Itt lesznek a beszélgetései ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez."</string>
<string name="screen_account_provider_signup_title">"Hamarosan létrehoz egy fiókot itt: %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"A Matrix.org egy nagy, ingyenes kiszolgáló a nyilvános Matrix-hálózaton, a biztonságos, decentralizált kommunikáció érdekében, amelyet a Matrix.org Alapítvány üzemeltet."</string>
<string name="screen_change_account_provider_other">"Egyéb"</string>
<string name="screen_change_account_provider_subtitle">"Másik fiókszolgáltató, például a saját privát kiszolgáló vagy egy munkahelyi fiók használata."</string>
<string name="screen_change_account_provider_title">"Fiókszolgáltató módosítása"</string>
<string name="screen_change_server_error_invalid_homeserver">"Nem sikerült elérni ezt a Matrix-kiszolgálót. Ellenőrizze, hogy helyesen adta-e meg a Matrix-kiszolgáló webcímét. Ha a webcím helyes, akkor további segítségért lépjen kapcsolatba a Matrix-kiszolgáló rendszergazdájával."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"A kiszolgáló jelenleg nem támogatja a Sliding sync protokollt."</string>
<string name="screen_change_server_form_header">"Matrix-kiszolgáló webcíme"</string>
<string name="screen_change_server_form_notice">"Csak olyan meglévő kiszolgálóhoz csatlakozhat, amely támogatja a Sliding sync protokollt. Ezt a Matrix-kiszolgáló rendszergazdájának kell beállítania. %1$s"</string>
<string name="screen_change_server_subtitle">"Mi a kiszolgálója címe?"</string>
<string name="screen_change_server_title">"Válassza ki a kiszolgálóját"</string>
<string name="screen_login_error_deactivated_account">"Ez a fiók deaktiválva lett."</string>
<string name="screen_login_error_invalid_credentials">"Helytelen felhasználónév vagy jelszó"</string>
<string name="screen_login_error_invalid_user_id">"Ez nem érvényes felhasználóazonosító. A várt formátum: „@user:homeserver.org”"</string>
<string name="screen_login_error_unsupported_authentication">"A kiválasztott Matrix-kiszolgáló nem támogatja a jelszavas vagy OIDC-alapú bejelentkezést. Lépjen kapcsolatba a kiszolgáló rendszergazdájával, vagy válasszon másik Matrix-kiszolgálót."</string>
<string name="screen_login_form_header">"Adja meg adatait"</string>
<string name="screen_login_title">"Örülünk, hogy visszatért!"</string>
<string name="screen_login_title_with_homeserver">"Bejelentkezés ide: %1$s"</string>
<string name="screen_server_confirmation_change_server">"Fiókszolgáltató módosítása"</string>
<string name="screen_server_confirmation_message_login_element_dot_io">"Egy privát kiszolgáló az Element alkalmazottai számára."</string>
<string name="screen_server_confirmation_message_login_matrix_dot_org">"A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz."</string>
<string name="screen_server_confirmation_message_register">"Itt lesznek a beszélgetései ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez."</string>
<string name="screen_server_confirmation_title_login">"Hamarosan bejelentkezik ebbe: %1$s"</string>
<string name="screen_server_confirmation_title_register">"Hamarosan létrehoz egy fiókot ezen: %1$s"</string>
<string name="screen_waitlist_message">"Jelenleg nagy a kereslet a(z) %2$s oldalon futó %1$s iránt. Térjen vissza néhány nap múlva az alkalmazáshoz, és próbálja újra.
Köszönjük a türelmét!"</string>
<string name="screen_waitlist_title">"Már majdnem kész van."</string>
<string name="screen_waitlist_title_success">"Bent van."</string>
<string name="screen_login_subtitle">"A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz."</string>
<string name="screen_waitlist_message_success">"Üdvözli az %1$s!"</string>
</resources>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_account_provider_change">"Ubah penyedia akun"</string>
<string name="screen_account_provider_form_hint">"Alamat homeserver"</string>
<string name="screen_account_provider_form_notice">"Masukkan istilah pencarian atau alamat domain."</string>
<string name="screen_account_provider_form_subtitle">"Cari perusahaan, komunitas, atau server pribadi."</string>
<string name="screen_account_provider_form_title">"Cari penyedia akun"</string>
<string name="screen_account_provider_signin_subtitle">"Di sinilah percakapan Anda akan berlangsung — sama seperti Anda menggunakan penyedia surel untuk menyimpan surel Anda."</string>
<string name="screen_account_provider_signin_title">"Anda akan masuk ke %s"</string>
<string name="screen_account_provider_signup_subtitle">"Di sinilah percakapan Anda akan berlangsung — sama seperti Anda menggunakan penyedia surel untuk menyimpan surel Anda."</string>
<string name="screen_account_provider_signup_title">"Anda akan membuat akun di %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org adalah server besar dan gratis di jaringan Matrix publik untuk komunikasi yang aman dan terdesentralisasi, disediakan oleh Yayasan Matrix.org."</string>
<string name="screen_change_account_provider_other">"Lainnya"</string>
<string name="screen_change_account_provider_subtitle">"Gunakan penyedia akun yang berbeda, seperti server pribadi Anda sendiri atau akun kerja."</string>
<string name="screen_change_account_provider_title">"Ubah penyedia akun"</string>
<string name="screen_change_server_error_invalid_homeserver">"Kami tidak dapat menjangkau server ini. Periksa apakah Anda telah memasukkan URL homeserver dengan benar. Jika URL sudah benar, hubungi administrator homeserver Anda untuk bantuan lebih lanjut."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Server ini saat ini tidak mendukung sinkronisasi geser."</string>
<string name="screen_change_server_form_header">"URL Homeserver"</string>
<string name="screen_change_server_form_notice">"Anda hanya dapat terhubung ke server yang ada yang mendukung sinkronisasi geser. Admin homeserver Anda perlu mengaturnya. %1$s"</string>
<string name="screen_change_server_subtitle">"Apa alamat server Anda?"</string>
<string name="screen_change_server_title">"Pilih server Anda"</string>
<string name="screen_login_error_deactivated_account">"Akun ini telah dinonaktifkan."</string>
<string name="screen_login_error_invalid_credentials">"Nama pengguna dan/atau kata sandi salah"</string>
<string name="screen_login_error_invalid_user_id">"Ini bukan pengenal pengguna yang valid. Format yang diharapkan: \'@pengguna:homeserver.org\'"</string>
<string name="screen_login_error_unsupported_authentication">"Homeserver yang dipilih tidak mendukung log masuk kata sandi atau OIDC. Silakan hubungi admin Anda atau pilih homeserver yang lain."</string>
<string name="screen_login_form_header">"Masukkan detail Anda"</string>
<string name="screen_login_title">"Selamat datang kembali!"</string>
<string name="screen_login_title_with_homeserver">"Masuk ke %1$s"</string>
<string name="screen_server_confirmation_change_server">"Ubah penyedia akun"</string>
<string name="screen_server_confirmation_message_login_element_dot_io">"Server pribadi untuk karyawan Element."</string>
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix adalah jaringan terbuka untuk komunikasi yang aman dan terdesentralisasi."</string>
<string name="screen_server_confirmation_message_register">"Di sinilah percakapan Anda akan berlangsung — sama seperti Anda menggunakan penyedia surel untuk menyimpan surel Anda."</string>
<string name="screen_server_confirmation_title_login">"Anda akan masuk ke %1$s"</string>
<string name="screen_server_confirmation_title_register">"Anda akan membuat akun di %1$s"</string>
<string name="screen_waitlist_message">"Ada permintaan tinggi untuk %1$s di %2$s saat ini. Kembalilah ke aplikasi dalam beberapa hari dan coba lagi.
Terima kasih atas kesabaran Anda!"</string>
<string name="screen_waitlist_title">"Anda hampir selesai."</string>
<string name="screen_waitlist_title_success">"Anda sudah masuk."</string>
<string name="screen_login_subtitle">"Matrix adalah jaringan terbuka untuk komunikasi yang aman dan terdesentralisasi."</string>
<string name="screen_waitlist_message_success">"Selamat datang di %1$s!"</string>
</resources>

View file

@ -2,4 +2,17 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Bist du sicher, dass du dich abmelden willst?"</string>
<string name="screen_signout_in_progress_dialog_content">"Abmelden…"</string>
<string name="screen_signout_key_backup_disabled_subtitle">"Du bist dabei, dich von deiner letzten Sitzung abzumelden. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten."</string>
<string name="screen_signout_key_backup_disabled_title">"Du hast das Backup ausgeschaltet"</string>
<string name="screen_signout_key_backup_offline_subtitle">"Deine Schlüssel wurden noch gesichert, als du offline gegangen bist. Stelle die Verbindung wieder her, damit deine Schlüssel gesichert werden können, bevor du dich abmeldest."</string>
<string name="screen_signout_key_backup_offline_title">"Deine Schlüssel werden noch gesichert"</string>
<string name="screen_signout_key_backup_ongoing_subtitle">"Bitte warte, bis der Vorgang abgeschlossen ist, bevor du dich abmeldest."</string>
<string name="screen_signout_key_backup_ongoing_title">"Deine Schlüssel werden noch gesichert"</string>
<string name="screen_signout_recovery_disabled_subtitle">"Du bist dabei, dich von deiner letzten Sitzung abzumelden. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten."</string>
<string name="screen_signout_recovery_disabled_title">"Wiederherstellung nicht eingerichtet"</string>
<string name="screen_signout_save_recovery_key_subtitle">"Du bist dabei, dich von deiner letzten Sitzung abzumelden. Wenn du dich jetzt abmeldest, verlierst du möglicherweise den Zugriff auf deine verschlüsselten Nachrichten."</string>
<string name="screen_signout_save_recovery_key_title">"Hast du deinen Wiederherstellungsschlüssel gespeichert?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Abmelden"</string>
<string name="screen_signout_confirmation_dialog_title">"Abmelden"</string>
<string name="screen_signout_preference_item">"Abmelden"</string>
</resources>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Biztos, hogy kijelentkezik?"</string>
<string name="screen_signout_in_progress_dialog_content">"Kijelentkezés…"</string>
<string name="screen_signout_key_backup_disabled_subtitle">"Arra készül, hogy kijelentkezzen az utolsó munkamenetéből is. Ha most kijelentkezik, akkor elveszti a hozzáférését a titkosított üzeneteihez."</string>
<string name="screen_signout_key_backup_disabled_title">"Kikapcsolta a biztonsági mentést"</string>
<string name="screen_signout_key_backup_offline_subtitle">"A kulcsai mentése során bontotta a kapcsolatot. Kapcsolódjon újra, hogy a kulcsai továbbra is mentésre kerüljenek mielőtt kijelentkezik."</string>
<string name="screen_signout_key_backup_offline_title">"A kulcsai mentése még folyamatban van"</string>
<string name="screen_signout_key_backup_ongoing_subtitle">"Kijelentkezés előtt várja meg a befejezését."</string>
<string name="screen_signout_key_backup_ongoing_title">"A kulcsai mentése még folyamatban van"</string>
<string name="screen_signout_recovery_disabled_subtitle">"Arra készül, hogy kijelentkezzen az utolsó munkamenetéből is. Ha most kijelentkezik, akkor elveszti a hozzáférését a titkosított üzeneteihez."</string>
<string name="screen_signout_recovery_disabled_title">"A helyreállítás nincs beállítva"</string>
<string name="screen_signout_save_recovery_key_subtitle">"Arra készül, hogy kijelentkezzen az utolsó munkamenetéből is. Ha most kijelentkezik, akkor elveszítheti a hozzáférését a titkosított üzeneteihez."</string>
<string name="screen_signout_save_recovery_key_title">"Mentette a helyreállítási kulcsát?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Kijelentkezés"</string>
<string name="screen_signout_confirmation_dialog_title">"Kijelentkezés"</string>
<string name="screen_signout_preference_item">"Kijelentkezés"</string>
</resources>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Apakah Anda yakin ingin keluar dari akun?"</string>
<string name="screen_signout_in_progress_dialog_content">"Mengeluarkan dari akun…"</string>
<string name="screen_signout_key_backup_disabled_subtitle">"Anda akan keluar dari sesi terakhir Anda. Jika Anda keluar sekarang, Anda akan kehilangan akses ke pesan terenkripsi Anda."</string>
<string name="screen_signout_key_backup_disabled_title">"Anda telah menonaktifkan pencadangan"</string>
<string name="screen_signout_key_backup_offline_subtitle">"Kunci Anda masih dicadangkan saat Anda luring. Sambungkan kembali sehingga kunci Anda dapat dicadangkan sebelum keluar."</string>
<string name="screen_signout_key_backup_offline_title">"Kunci Anda masih dicadangkan"</string>
<string name="screen_signout_key_backup_ongoing_subtitle">"Mohon tunggu hingga proses ini selesai sebelum keluar."</string>
<string name="screen_signout_key_backup_ongoing_title">"Kunci Anda masih dicadangkan"</string>
<string name="screen_signout_recovery_disabled_subtitle">"Anda akan keluar dari sesi Anda yang terakhir. Jika Anda keluar sekarang, Anda akan kehilangan akses ke pesan terenkripsi Anda."</string>
<string name="screen_signout_recovery_disabled_title">"Pemulihan belum disiapkan"</string>
<string name="screen_signout_save_recovery_key_subtitle">"Anda akan keluar dari sesi terakhir Anda. Jika Anda keluar sekarang, Anda mungkin kehilangan akses ke pesan terenkripsi Anda."</string>
<string name="screen_signout_save_recovery_key_title">"Apakah Anda sudah menyimpan kunci pemulihan Anda?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Keluar dari akun"</string>
<string name="screen_signout_confirmation_dialog_title">"Keluar dari akun"</string>
<string name="screen_signout_preference_item">"Keluar dari akun"</string>
</resources>

View file

@ -33,6 +33,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.coroutines.coroutineContext
class AttachmentsPreviewPresenter @AssistedInject constructor(
@ -114,6 +115,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
sendActionState.value = SendActionState.Done
},
onFailure = { error ->
Timber.e(error, "Failed to send attachment")
if (error is CancellationException) {
throw error
} else {

View file

@ -72,6 +72,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.coroutines.coroutineContext
import kotlin.time.Duration.Companion.seconds
@ -432,6 +433,7 @@ class MessageComposerPresenter @Inject constructor(
attachmentState.value = AttachmentsState.None
}
.onFailure { cause ->
Timber.e(cause, "Failed to send attachment")
attachmentState.value = AttachmentsState.None
if (cause is CancellationException) {
throw cause

View file

@ -175,6 +175,9 @@ class TimelinePresenter @AssistedInject constructor(
}
return TimelineState(
timelineRoomInfo = TimelineRoomInfo(
isDirect = room.isDirect
),
highlightedEventId = highlightedEventId.value,
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
paginationState = paginationState,

View file

@ -27,6 +27,7 @@ import kotlinx.collections.immutable.ImmutableList
@Immutable
data class TimelineState(
val timelineItems: ImmutableList<TimelineItem>,
val timelineRoomInfo: TimelineRoomInfo,
val showReadReceipts: Boolean,
val highlightedEventId: EventId?,
val userHasPermissionToSendMessage: Boolean,
@ -35,3 +36,8 @@ data class TimelineState(
val sessionState: SessionState,
val eventSink: (TimelineEvents) -> Unit
)
@Immutable
data class TimelineRoomInfo(
val isDirect: Boolean,
)

View file

@ -47,6 +47,7 @@ import kotlin.random.Random
fun aTimelineState(timelineItems: ImmutableList<TimelineItem> = persistentListOf()) = TimelineState(
timelineItems = timelineItems,
timelineRoomInfo = aTimelineRoomInfo(),
showReadReceipts = false,
paginationState = MatrixTimeline.PaginationState(
isBackPaginating = false,
@ -212,3 +213,9 @@ internal fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents {
aggregatedReadReceipts = events.flatMap { it.readReceiptState.receipts }.toImmutableList(),
)
}
internal fun aTimelineRoomInfo(
isDirect: Boolean = false,
) = TimelineRoomInfo(
isDirect = isDirect,
)

View file

@ -118,6 +118,7 @@ fun TimelineView(
) { timelineItem ->
TimelineItemRow(
timelineItem = timelineItem,
timelineRoomInfo = state.timelineRoomInfo,
showReadReceipts = state.showReadReceipts,
isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true
&& state.timelineItems.first().identifier() == timelineItem.identifier(),

View file

@ -17,17 +17,21 @@
package io.element.android.features.messages.impl.timeline.components
import androidx.compose.runtime.Composable
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItem
// For previews
@Composable
internal fun ATimelineItemEventRow(
event: TimelineItem.Event,
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
showReadReceipts: Boolean = false,
isLastOutgoingMessage: Boolean = false,
isHighlighted: Boolean = false,
) = TimelineItemEventRow(
event = event,
timelineRoomInfo = timelineRoomInfo,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = isHighlighted,

View file

@ -35,17 +35,17 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleStateProvider
import io.element.android.libraries.core.extensions.to01
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.messageFromMeBackground
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
import io.element.android.compound.theme.ElementTheme
private val BUBBLE_RADIUS = 12.dp
private val BUBBLE_INCOMING_OFFSET = 16.dp
@ -91,10 +91,10 @@ fun MessageEventBubble(
}
fun Modifier.offsetForItem(): Modifier {
return if (state.isMine) {
this
} else {
offset(x = BUBBLE_INCOMING_OFFSET)
return when {
state.isMine -> this
state.timelineRoomInfo.isDirect -> this
else -> offset(x = BUBBLE_INCOMING_OFFSET)
}
}

View file

@ -60,6 +60,7 @@ import androidx.compose.ui.zIndex
import androidx.constraintlayout.compose.ConstrainScope
import androidx.constraintlayout.compose.ConstraintLayout
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
@ -101,6 +102,7 @@ import kotlin.math.roundToInt
@Composable
fun TimelineItemEventRow(
event: TimelineItem.Event,
timelineRoomInfo: TimelineRoomInfo,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
isHighlighted: Boolean,
@ -163,9 +165,8 @@ fun TimelineItemEventRow(
state = state.draggableState,
),
event = event,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
interactionSource = interactionSource,
onClick = onClick,
onLongClick = onLongClick,
@ -175,7 +176,6 @@ fun TimelineItemEventRow(
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClicked = { onMoreReactionsClick(event) },
onReadReceiptsClicked = { onReadReceiptClick(event) },
eventSink = eventSink,
)
}
@ -183,9 +183,8 @@ fun TimelineItemEventRow(
} else {
TimelineItemEventRowContent(
event = event,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
interactionSource = interactionSource,
onClick = onClick,
onLongClick = onLongClick,
@ -195,10 +194,20 @@ fun TimelineItemEventRow(
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClicked = { onMoreReactionsClick(event) },
onReadReceiptsClicked = { onReadReceiptClick(event) },
eventSink = eventSink,
)
}
// Read receipts / Send state
TimelineItemReadReceiptView(
state = ReadReceiptViewState(
sendState = event.localSendState,
isLastOutgoingMessage = isLastOutgoingMessage,
receipts = event.readReceiptState.receipts,
),
showReadReceipts = showReadReceipts,
onReadReceiptsClicked = { onReadReceiptClick(event) },
modifier = Modifier.padding(top = 4.dp),
)
}
}
@ -228,9 +237,8 @@ private fun SwipeSensitivity(
@Composable
private fun TimelineItemEventRowContent(
event: TimelineItem.Event,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
isHighlighted: Boolean,
timelineRoomInfo: TimelineRoomInfo,
interactionSource: MutableInteractionSource,
onClick: () -> Unit,
onLongClick: () -> Unit,
@ -238,7 +246,6 @@ private fun TimelineItemEventRowContent(
inReplyToClicked: () -> Unit,
onUserDataClicked: () -> Unit,
onReactionClicked: (emoji: String) -> Unit,
onReadReceiptsClicked: () -> Unit,
onReactionLongClicked: (emoji: String) -> Unit,
onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit,
eventSink: (TimelineEvents) -> Unit,
@ -259,12 +266,11 @@ private fun TimelineItemEventRowContent(
sender,
message,
reactions,
readReceipts,
) = createRefs()
// Sender
val avatarStrokeSize = 3.dp
if (event.showSenderInformation) {
if (event.showSenderInformation && !timelineRoomInfo.isDirect) {
MessageSenderInformation(
event.safeSenderName,
event.senderAvatar,
@ -284,6 +290,7 @@ private fun TimelineItemEventRowContent(
groupPosition = event.groupPosition,
isMine = event.isMine,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
)
MessageEventBubble(
modifier = Modifier
@ -326,25 +333,6 @@ private fun TimelineItemEventRowContent(
.padding(start = if (event.isMine) 16.dp else 36.dp, end = 16.dp)
)
}
// Read receipts / Send state
TimelineItemReadReceiptView(
state = ReadReceiptViewState(
sendState = event.localSendState,
isLastOutgoingMessage = isLastOutgoingMessage,
receipts = event.readReceiptState.receipts,
),
showReadReceipts = showReadReceipts,
onReadReceiptsClicked = onReadReceiptsClicked,
modifier = Modifier
.constrainAs(readReceipts) {
if (event.reactionsState.reactions.isNotEmpty()) {
top.linkTo(reactions.bottom, margin = 4.dp)
} else {
top.linkTo(message.bottom, margin = 4.dp)
}
}
)
}
}
@ -378,6 +366,7 @@ private fun MessageSenderInformation(
Avatar(senderAvatar)
Spacer(modifier = Modifier.width(4.dp))
Text(
modifier = Modifier.clipToBounds(),
text = sender,
maxLines = 1,
overflow = TextOverflow.Ellipsis,

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 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.messages.impl.timeline.components
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@PreviewsDayNight
@Composable
internal fun TimelineItemEventRowForDirectRoomPreview() = ElementPreview {
Column {
sequenceOf(false, true).forEach {
ATimelineItemEventRow(
event = aTimelineItemEvent(
isMine = it,
content = aTimelineItemTextContent().copy(
body = "A long text which will be displayed on several lines and" +
" hopefully can be manually adjusted to test different behaviors."
),
groupPosition = TimelineItemGroupPosition.First,
),
timelineRoomInfo = aTimelineRoomInfo(
isDirect = true,
),
)
ATimelineItemEventRow(
event = aTimelineItemEvent(
isMine = it,
content = aTimelineItemImageContent().copy(
aspectRatio = 5f
),
groupPosition = TimelineItemGroupPosition.Last,
),
timelineRoomInfo = aTimelineRoomInfo(
isDirect = true,
),
)
}
}
}

View file

@ -24,8 +24,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import io.element.android.features.messages.impl.R
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.aGroupedEvents
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
@ -40,6 +42,7 @@ import io.element.android.libraries.matrix.api.core.UserId
@Composable
fun TimelineItemGroupedEventsRow(
timelineItem: TimelineItem.GroupedEvents,
timelineRoomInfo: TimelineRoomInfo,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
highlightedItem: String?,
@ -66,6 +69,7 @@ fun TimelineItemGroupedEventsRow(
isExpanded = isExpanded.value,
onExpandGroupClick = ::onExpandGroupClick,
timelineItem = timelineItem,
timelineRoomInfo = timelineRoomInfo,
highlightedItem = highlightedItem,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
@ -89,6 +93,7 @@ private fun TimelineItemGroupedEventsRowContent(
isExpanded: Boolean,
onExpandGroupClick: () -> Unit,
timelineItem: TimelineItem.GroupedEvents,
timelineRoomInfo: TimelineRoomInfo,
highlightedItem: String?,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
@ -121,6 +126,7 @@ private fun TimelineItemGroupedEventsRowContent(
timelineItem.events.forEach { subGroupEvent ->
TimelineItemRow(
timelineItem = subGroupEvent,
timelineRoomInfo = timelineRoomInfo,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
highlightedItem = highlightedItem,
@ -148,7 +154,8 @@ private fun TimelineItemGroupedEventsRowContent(
receipts = timelineItem.aggregatedReadReceipts,
),
showReadReceipts = true,
onReadReceiptsClicked = { /* No op for group event */ })
onReadReceiptsClicked = onExpandGroupClick
)
}
}
}
@ -160,6 +167,7 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi
isExpanded = true,
onExpandGroupClick = {},
timelineItem = aGroupedEvents(),
timelineRoomInfo = aTimelineRoomInfo(),
highlightedItem = null,
showReadReceipts = true,
isLastOutgoingMessage = false,
@ -184,6 +192,7 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi
isExpanded = false,
onExpandGroupClick = {},
timelineItem = aGroupedEvents(),
timelineRoomInfo = aTimelineRoomInfo(),
highlightedItem = null,
showReadReceipts = true,
isLastOutgoingMessage = false,

View file

@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
@ -29,6 +30,7 @@ import io.element.android.libraries.matrix.api.core.UserId
@Composable
internal fun TimelineItemRow(
timelineItem: TimelineItem,
timelineRoomInfo: TimelineRoomInfo,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
highlightedItem: String?,
@ -71,6 +73,7 @@ internal fun TimelineItemRow(
} else {
TimelineItemEventRow(
event = timelineItem,
timelineRoomInfo = timelineRoomInfo,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = highlightedItem == timelineItem.identifier(),
@ -93,6 +96,7 @@ internal fun TimelineItemRow(
is TimelineItem.GroupedEvents -> {
TimelineItemGroupedEventsRow(
timelineItem = timelineItem,
timelineRoomInfo = timelineRoomInfo,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
highlightedItem = highlightedItem,

View file

@ -16,6 +16,8 @@
package io.element.android.features.messages.impl.timeline.components.group
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -26,6 +28,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -76,9 +79,17 @@ fun GroupHeaderView(
color = MaterialTheme.colorScheme.secondary,
style = ElementTheme.typography.fontBodyMdRegular,
)
val rotation: Float by animateFloatAsState(
targetValue = if (isExpanded) 90f else 0f,
animationSpec = tween(
delayMillis = 0,
durationMillis = 300,
),
label = "chevron"
)
Icon(
modifier = Modifier.rotate(if (isExpanded) 180f else 0f),
imageVector = CompoundIcons.ChevronDown,
modifier = Modifier.rotate(rotation),
imageVector = CompoundIcons.ChevronRight,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary
)

View file

@ -16,10 +16,12 @@
package io.element.android.features.messages.impl.timeline.model.bubble
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
data class BubbleState(
val groupPosition: TimelineItemGroupPosition,
val isMine: Boolean,
val isHighlighted: Boolean,
val timelineRoomInfo: TimelineRoomInfo,
)

View file

@ -17,6 +17,8 @@
package io.element.android.features.messages.impl.timeline.model.bubble
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
open class BubbleStateProvider : PreviewParameterProvider<BubbleState> {
@ -29,7 +31,11 @@ open class BubbleStateProvider : PreviewParameterProvider<BubbleState> {
).map { groupPosition ->
sequenceOf(false, true).map { isMine ->
sequenceOf(false, true).map { isHighlighted ->
BubbleState(groupPosition, isMine = isMine, isHighlighted = isHighlighted)
aBubbleState(
groupPosition = groupPosition,
isMine = isMine,
isHighlighted = isHighlighted,
)
}
}
.flatten()
@ -37,8 +43,14 @@ open class BubbleStateProvider : PreviewParameterProvider<BubbleState> {
.flatten()
}
fun aBubbleState() = BubbleState(
groupPosition = TimelineItemGroupPosition.First,
isMine = false,
isHighlighted = false,
internal fun aBubbleState(
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.First,
isMine: Boolean = false,
isHighlighted: Boolean = false,
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
) = BubbleState(
groupPosition = groupPosition,
isMine = isMine,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
)

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.mxc.MxcTools
import java.io.File
/**
@ -66,6 +67,7 @@ interface VoiceMessageMediaRepo {
class DefaultVoiceMessageMediaRepo @AssistedInject constructor(
@CacheDirectory private val cacheDir: File,
mxcTools: MxcTools,
private val matrixMediaLoader: MatrixMediaLoader,
@Assisted private val mediaSource: MediaSource,
@Assisted("mimeType") private val mimeType: String?,
@ -101,7 +103,7 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor(
}
}
private val cachedFile: File? = mxcUri2FilePath(mediaSource.url)?.let {
private val cachedFile: File? = mxcTools.mxcUri2FilePath(mediaSource.url)?.let {
File("${cacheDir.path}/$CACHE_VOICE_SUBDIR/$it")
}
}
@ -110,24 +112,3 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor(
* Subdirectory of the application's cache directory where voice messages are stored.
*/
private const val CACHE_VOICE_SUBDIR = "temp/voice"
/**
* Regex to match a Matrix Content (mxc://) URI.
*
* See: https://spec.matrix.org/v1.8/client-server-api/#matrix-content-mxc-uris
*/
private val mxcRegex = Regex("""^mxc:\/\/([^\/]+)\/([^\/]+)$""")
/**
* Sanitizes an mxcUri to be used as a relative file path.
*
* @param mxcUri the Matrix Content (mxc://) URI of the voice message.
* @return the relative file path as "<server-name>/<media-id>" or null if the mxcUri is invalid.
*/
private fun mxcUri2FilePath(mxcUri: String): String? = mxcRegex.matchEntire(mxcUri)?.let { match ->
buildString {
append(match.groupValues[1])
append("/")
append(match.groupValues[2])
}
}

View file

@ -17,6 +17,7 @@
<string name="room_timeline_beginning_of_room">"Dies ist der Anfang von %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Dies ist der Anfang dieses Gesprächs."</string>
<string name="room_timeline_read_marker_title">"Neu"</string>
<string name="screen_room_mentions_at_room_subtitle">"Den ganzen Raum benachrichtigen"</string>
<string name="screen_report_content_block_user_hint">"Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Foto machen"</string>
@ -27,6 +28,7 @@
<string name="screen_room_attachment_source_poll">"Umfrage"</string>
<string name="screen_room_attachment_text_formatting">"Textformatierung"</string>
<string name="screen_room_encrypted_history_banner">"Der Nachrichtenverlauf ist derzeit in diesem Raum nicht verfügbar"</string>
<string name="screen_room_encrypted_history_banner_unverified">"Der Nachrichtenverlauf ist in diesem Raum nicht verfügbar. Verifiziere dieses Gerät, um deinen Nachrichtenverlauf zu sehen."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Benutzerdetails konnten nicht abgerufen werden"</string>
<string name="screen_room_invite_again_alert_message">"Möchtest du sie wieder einladen?"</string>
<string name="screen_room_invite_again_alert_title">"Du bist allein in diesem Chat"</string>
@ -42,6 +44,7 @@
<string name="screen_room_notification_settings_error_loading_settings">"Beim Laden der Benachrichtigungseinstellungen ist ein Fehler aufgetreten."</string>
<string name="screen_room_notification_settings_error_restoring_default">"Fehler beim Wiederherstellen des Standardmodus. Bitte versuche es erneut."</string>
<string name="screen_room_notification_settings_error_setting_mode">"Fehler beim Einstellen des Modus. Bitte versuche es erneut."</string>
<string name="screen_room_notification_settings_mentions_only_disclaimer">"Dein Homeserver unterstützt diese Option in verschlüsselten Räumen nicht. Du wirst in diesem Raum nicht benachrichtigt."</string>
<string name="screen_room_notification_settings_mode_all_messages">"Alle Nachrichten"</string>
<string name="screen_room_notification_settings_room_custom_settings_title">"Benachrichtige mich in diesem Raum bei"</string>
<string name="screen_room_reactions_show_less">"Weniger anzeigen"</string>
@ -50,6 +53,8 @@
<string name="screen_room_retry_send_menu_title">"Deine Nachricht konnte nicht gesendet werden"</string>
<string name="screen_room_timeline_add_reaction">"Emoji hinzufügen"</string>
<string name="screen_room_timeline_less_reactions">"Weniger anzeigen"</string>
<string name="screen_room_voice_message_tooltip">"Zum Aufnehmen gedrückt halten"</string>
<string name="screen_room_mentions_at_room_title">"Alle"</string>
<string name="screen_report_content_block_user">"Benutzer sperren"</string>
<string name="screen_room_error_failed_processing_media">"Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut."</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Nur Erwähnungen und Schlüsselwörter"</string>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="emoji_picker_category_activity">"Tevékenységek"</string>
<string name="emoji_picker_category_flags">"Zászlók"</string>
<string name="emoji_picker_category_foods">"Étel és ital"</string>
<string name="emoji_picker_category_nature">"Állatok és természet"</string>
<string name="emoji_picker_category_objects">"Tárgyak"</string>
<string name="emoji_picker_category_people">"Mosolyok és emberek"</string>
<string name="emoji_picker_category_places">"Utazás és helyek"</string>
<string name="emoji_picker_category_symbols">"Szimbólumok"</string>
<string name="report_content_explanation">"Ez az üzenet jelentve lesz a Matrix-kiszolgáló rendszergazdájának. Nem fogja tudni elolvasni a titkosított üzeneteket."</string>
<string name="report_content_hint">"A tartalom jelentésének oka"</string>
<string name="room_timeline_beginning_of_room">"Ez a(z) %1$s kezdete."</string>
<string name="room_timeline_beginning_of_room_no_name">"Ez a beszélgetés kezdete."</string>
<string name="room_timeline_read_marker_title">"Új"</string>
<string name="screen_room_mentions_at_room_subtitle">"Teljes szoba értesítése"</string>
<string name="screen_report_content_block_user_hint">"Jelölje be, ha el akarja rejteni az összes jelenlegi és jövőbeli üzenetet ettől a felhasználótól"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Fénykép készítése"</string>
<string name="screen_room_attachment_source_camera_video">"Videó rögzítése"</string>
<string name="screen_room_attachment_source_files">"Melléklet"</string>
<string name="screen_room_attachment_source_gallery">"Fénykép- és videótár"</string>
<string name="screen_room_attachment_source_location">"Hely"</string>
<string name="screen_room_attachment_source_poll">"Szavazás"</string>
<string name="screen_room_attachment_text_formatting">"Szövegformázás"</string>
<string name="screen_room_encrypted_history_banner">"Az üzenetelőzmények jelenleg nem érhetők el."</string>
<string name="screen_room_encrypted_history_banner_unverified">"Az üzenetelőzmények nem érhetők el ebben a szobában. Ellenőrizze ezt az eszközt, hogy lássa az előzményeket."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Nem sikerült letölteni a felhasználói adatokat"</string>
<string name="screen_room_invite_again_alert_message">"Visszahívja?"</string>
<string name="screen_room_invite_again_alert_title">"Egyedül van ebben a csevegésben"</string>
<string name="screen_room_message_copied">"Üzenet másolva"</string>
<string name="screen_room_no_permission_to_post">"Nincs jogosultsága arra, hogy bejegyzést tegyen közzé ebben a szobában"</string>
<string name="screen_room_notification_settings_allow_custom">"Egyéni beállítás engedélyezése"</string>
<string name="screen_room_notification_settings_allow_custom_footnote">"Ennek bekapcsolása felülírja az alapértelmezett beállítást"</string>
<string name="screen_room_notification_settings_custom_settings_title">"Értesítések kérése ebben a csevegésben ezekről:"</string>
<string name="screen_room_notification_settings_default_setting_footnote">"Megváltoztathatja a %1$s."</string>
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"globális beállításokban"</string>
<string name="screen_room_notification_settings_default_setting_title">"Alapértelmezett beállítás"</string>
<string name="screen_room_notification_settings_edit_remove_setting">"Egyéni beállítás eltávolítása"</string>
<string name="screen_room_notification_settings_error_loading_settings">"Hiba történt az értesítési beállítások betöltésekor."</string>
<string name="screen_room_notification_settings_error_restoring_default">"Nem sikerült visszaállítani az alapértelmezett módot, próbálja újra."</string>
<string name="screen_room_notification_settings_error_setting_mode">"Nem sikerült a mód beállítása, próbálja újra."</string>
<string name="screen_room_notification_settings_mentions_only_disclaimer">"A Matrix-kiszolgálója nem támogatja ezt a beállítást a titkosított szobákban, egyes szobákban nem fog értesítéseket kapni."</string>
<string name="screen_room_notification_settings_mode_all_messages">"Összes üzenet"</string>
<string name="screen_room_notification_settings_room_custom_settings_title">"Ebben a szobában, értesítés ezekről:"</string>
<string name="screen_room_reactions_show_less">"Kevesebb megjelenítése"</string>
<string name="screen_room_reactions_show_more">"Több megjelenítése"</string>
<string name="screen_room_retry_send_menu_send_again_action">"Újraküldés"</string>
<string name="screen_room_retry_send_menu_title">"Az üzenet elküldése sikertelen"</string>
<string name="screen_room_timeline_add_reaction">"Emodzsi hozzáadása"</string>
<string name="screen_room_timeline_less_reactions">"Kevesebb megjelenítése"</string>
<string name="screen_room_voice_message_tooltip">"Tartsa a rögzítéshez"</string>
<string name="screen_room_mentions_at_room_title">"Mindenki"</string>
<string name="screen_report_content_block_user">"Felhasználó letiltása"</string>
<string name="screen_room_error_failed_processing_media">"Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra."</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Csak említések és kulcsszavak"</string>
</resources>

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="emoji_picker_category_activity">"Aktivitas"</string>
<string name="emoji_picker_category_flags">"Bendera"</string>
<string name="emoji_picker_category_foods">"Makanan &amp; Minuman"</string>
<string name="emoji_picker_category_nature">"Hewan &amp; Alam"</string>
<string name="emoji_picker_category_objects">"Objek"</string>
<string name="emoji_picker_category_people">"Senyuman &amp; Orang"</string>
<string name="emoji_picker_category_places">"Wisata &amp; Tempat"</string>
<string name="emoji_picker_category_symbols">"Simbol"</string>
<plurals name="room_timeline_state_changes">
<item quantity="other">"%1$d perubahan ruangan"</item>
</plurals>
<string name="report_content_explanation">"Pesan ini akan dilaporkan ke administrator homeserver Anda. Mereka tidak akan dapat membaca pesan terenkripsi apa pun."</string>
<string name="report_content_hint">"Alasan melaporkan konten ini"</string>
<string name="room_timeline_beginning_of_room">"Ini adalah awal dari %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Ini adalah awal dari percakapan ini."</string>
<string name="room_timeline_read_marker_title">"Baru"</string>
<string name="screen_room_mentions_at_room_subtitle">"Beri tahu seluruh ruangan"</string>
<string name="screen_report_content_block_user_hint">"Centang jika Anda ingin menyembunyikan semua pesan saat ini dan yang akan datang dari pengguna ini"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Ambil foto"</string>
<string name="screen_room_attachment_source_camera_video">"Rekam video"</string>
<string name="screen_room_attachment_source_files">"Lampiran"</string>
<string name="screen_room_attachment_source_gallery">"Pustaka Foto &amp; Video"</string>
<string name="screen_room_attachment_source_location">"Lokasi"</string>
<string name="screen_room_attachment_source_poll">"Pemungutan suara"</string>
<string name="screen_room_attachment_text_formatting">"Pemformatan Teks"</string>
<string name="screen_room_encrypted_history_banner">"Riwayat pesan saat ini tidak tersedia di ruangan ini"</string>
<string name="screen_room_encrypted_history_banner_unverified">"Riwayat pesan tidak tersedia di ruangan ini. Verifikasi perangkat ini untuk melihat riwayat pesan."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Tidak dapat mengambil detail pengguna"</string>
<string name="screen_room_invite_again_alert_message">"Apakah Anda ingin mengundang mereka kembali?"</string>
<string name="screen_room_invite_again_alert_title">"Anda sendirian di obrolan ini"</string>
<string name="screen_room_message_copied">"Pesan disalin"</string>
<string name="screen_room_no_permission_to_post">"Anda tidak memiliki izin untuk mengirim di ruangan ini"</string>
<string name="screen_room_notification_settings_allow_custom">"Izinkan pengaturan khusus"</string>
<string name="screen_room_notification_settings_allow_custom_footnote">"Mengaktifkan ini akan mengganti pengaturan bawaan Anda"</string>
<string name="screen_room_notification_settings_custom_settings_title">"Beri tahu saya di obrolan ini tentang"</string>
<string name="screen_room_notification_settings_default_setting_footnote">"Anda dapat mengubahnya di %1$s Anda."</string>
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"pengaturan global"</string>
<string name="screen_room_notification_settings_default_setting_title">"Pengaturan bawaan"</string>
<string name="screen_room_notification_settings_edit_remove_setting">"Hapus pengaturan khusus"</string>
<string name="screen_room_notification_settings_error_loading_settings">"Terjadi kesalahan saat memuat pengaturan pemberitahuan."</string>
<string name="screen_room_notification_settings_error_restoring_default">"Gagal memulihkan mode bawaan, silakan coba lagi."</string>
<string name="screen_room_notification_settings_error_setting_mode">"Gagal mengatur mode, silakan coba lagi."</string>
<string name="screen_room_notification_settings_mentions_only_disclaimer">"Homeserver Anda tidak mendukung opsi ini dalam ruangan terenkripsi, Anda tidak akan diberi tahu dalam ruangan ini."</string>
<string name="screen_room_notification_settings_mode_all_messages">"Semua pesan"</string>
<string name="screen_room_notification_settings_room_custom_settings_title">"Di ruangan ini, beri tahu saya tentang"</string>
<string name="screen_room_reactions_show_less">"Tampilkan lebih sedikit"</string>
<string name="screen_room_reactions_show_more">"Tampilkan lebih banyak"</string>
<string name="screen_room_retry_send_menu_send_again_action">"Kirim ulang"</string>
<string name="screen_room_retry_send_menu_title">"Pesan Anda gagal dikirim"</string>
<string name="screen_room_timeline_add_reaction">"Tambahkan emoji"</string>
<string name="screen_room_timeline_less_reactions">"Tampilkan lebih sedikit"</string>
<string name="screen_room_voice_message_tooltip">"Tahan untuk merekam"</string>
<string name="screen_room_mentions_at_room_title">"Semua orang"</string>
<string name="screen_report_content_block_user">"Blokir pengguna"</string>
<string name="screen_room_error_failed_processing_media">"Gagal memproses media untuk diunggah, silakan coba lagi."</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Sebutan dan Kata Kunci saja"</string>
</resources>

View file

@ -379,9 +379,9 @@ class MessageComposerPresenterTest {
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.showAttachmentSourcePicker).isEqualTo(false)
assertThat(initialState.showAttachmentSourcePicker).isFalse()
initialState.eventSink(MessageComposerEvents.AddAttachment)
assertThat(awaitItem().showAttachmentSourcePicker).isEqualTo(true)
assertThat(awaitItem().showAttachmentSourcePicker).isTrue()
}
}

View file

@ -53,7 +53,7 @@ class ReactionSummaryPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.target).isEqualTo(null)
assertThat(initialState.target).isNull()
initialState.eventSink(summaryEvent)
assertThat(awaitItem().target).isNotNull()
@ -69,7 +69,7 @@ class ReactionSummaryPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.target).isEqualTo(null)
assertThat(initialState.target).isNull()
initialState.eventSink(summaryEvent)
val reactions = awaitItem().target?.reactions

View file

@ -26,7 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.AudioInfo
@ -59,7 +59,7 @@ class InReplyToMetadataKtTest {
anInReplyToDetails(eventContent = aMessageContent()).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(InReplyToMetadata.Text("textContent"))
assertThat(it).isEqualTo(InReplyToMetadata.Text("textContent"))
}
}
}
@ -78,7 +78,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = aMediaSource(),
@ -115,7 +115,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = aMediaSource(),
@ -148,7 +148,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = aMediaSource(),
@ -180,7 +180,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
textContent = "body",
@ -209,7 +209,7 @@ class InReplyToMetadataKtTest {
}
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = null,
@ -240,7 +240,7 @@ class InReplyToMetadataKtTest {
}
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = null,
@ -262,7 +262,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = null,
@ -284,7 +284,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(null)
assertThat(it).isNull()
}
}
}

View file

@ -16,10 +16,11 @@
package io.element.android.features.messages.impl.voicemessages.timeline
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.mxc.MxcTools
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@ -43,10 +44,10 @@ class DefaultVoiceMessageMediaRepoTest {
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isSuccess).isTrue()
assertThat(result.isSuccess).isTrue()
result.getOrThrow().let { file ->
Truth.assertThat(file.path).isEqualTo(temporaryFolder.cachedFilePath)
Truth.assertThat(file.exists()).isTrue()
assertThat(file.path).isEqualTo(temporaryFolder.cachedFilePath)
assertThat(file.exists()).isTrue()
}
}
}
@ -62,9 +63,9 @@ class DefaultVoiceMessageMediaRepoTest {
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isFailure).isTrue()
assertThat(result.isFailure).isTrue()
result.exceptionOrNull()!!.let { exception ->
Truth.assertThat(exception).isInstanceOf(RuntimeException::class.java)
assertThat(exception).isInstanceOf(RuntimeException::class.java)
}
}
}
@ -87,9 +88,9 @@ class DefaultVoiceMessageMediaRepoTest {
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isFailure).isTrue()
assertThat(result.isFailure).isTrue()
result.exceptionOrNull()?.let { exception ->
Truth.assertThat(exception).apply {
assertThat(exception).apply {
isInstanceOf(IllegalStateException::class.java)
hasMessageThat().isEqualTo("Failed to move file to cache.")
}
@ -109,10 +110,10 @@ class DefaultVoiceMessageMediaRepoTest {
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isSuccess).isTrue()
assertThat(result.isSuccess).isTrue()
result.getOrThrow().let { file ->
Truth.assertThat(file.path).isEqualTo(temporaryFolder.cachedFilePath)
Truth.assertThat(file.exists()).isTrue()
assertThat(file.path).isEqualTo(temporaryFolder.cachedFilePath)
assertThat(file.exists()).isTrue()
}
}
}
@ -124,10 +125,10 @@ class DefaultVoiceMessageMediaRepoTest {
mxcUri = INVALID_MXC_URI,
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isFailure).isTrue()
assertThat(result.isFailure).isTrue()
result.exceptionOrNull()!!.let { exception ->
Truth.assertThat(exception).isInstanceOf(RuntimeException::class.java)
Truth.assertThat(exception).hasMessageThat().isEqualTo("Invalid mxcUri.")
assertThat(exception).isInstanceOf(RuntimeException::class.java)
assertThat(exception).hasMessageThat().isEqualTo("Invalid mxcUri.")
}
}
}
@ -139,6 +140,7 @@ private fun createDefaultVoiceMessageMediaRepo(
mxcUri: String = MXC_URI,
) = DefaultVoiceMessageMediaRepo(
cacheDir = temporaryFolder.root,
mxcTools = MxcTools(),
matrixMediaLoader = matrixMediaLoader,
mediaSource = MediaSource(
url = mxcUri,

View file

@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.voicemessages.timeline
import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource
@ -42,7 +42,7 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
}
}
@ -56,7 +56,7 @@ class DefaultVoiceMessagePlayerTest {
)
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isFailure).isTrue()
assertThat(player.prepare().isFailure).isTrue()
}
}
@ -67,7 +67,7 @@ class DefaultVoiceMessagePlayerTest {
)
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isFailure).isTrue()
assertThat(player.prepare().isFailure).isTrue()
}
}
@ -76,12 +76,12 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
player.play()
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.isPlaying).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
}
}
}
@ -96,15 +96,15 @@ class DefaultVoiceMessagePlayerTest {
)
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState(fakeTotalDurationMs = 1000)
player.play()
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
}
}
@ -121,72 +121,72 @@ class DefaultVoiceMessagePlayerTest {
// Play player1 until the end.
player1.state.test {
matchInitialState()
Truth.assertThat(player1.prepare().isSuccess).isTrue()
assertThat(player1.prepare().isSuccess).isTrue()
matchReadyState(1_000L)
player1.play()
awaitItem().let { // it plays until the end.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
}
// Play player2 until the end.
player2.state.test {
matchInitialState()
Truth.assertThat(player2.prepare().isSuccess).isTrue()
assertThat(player2.prepare().isSuccess).isTrue()
awaitItem().let { // Additional spurious state due to MediaPlayer owner change.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
awaitItem().let {// Additional spurious state due to MediaPlayer owner change.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(0)
Truth.assertThat(it.duration).isEqualTo(null)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(0)
assertThat(it.duration).isNull()
}
matchReadyState(1_000L)
player2.play()
awaitItem().let { // it plays until the end.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
}
// Play player1 again.
player1.state.test {
awaitItem().let {// Last previous state/
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
Truth.assertThat(player1.prepare().isSuccess).isTrue()
assertThat(player1.prepare().isSuccess).isTrue()
awaitItem().let {// Additional spurious state due to MediaPlayer owner change.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(0)
Truth.assertThat(it.duration).isEqualTo(null)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(0)
assertThat(it.duration).isNull()
}
matchReadyState(1_000L)
player1.play()
awaitItem().let { // it played again until the end.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
}
}
@ -196,14 +196,14 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
player.play()
skipItems(1) // skip play state
player.pause()
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.isPlaying).isFalse()
assertThat(it.currentPosition).isEqualTo(1000)
}
}
}
@ -213,7 +213,7 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
player.play()
skipItems(1) // skip play state
@ -221,8 +221,8 @@ class DefaultVoiceMessagePlayerTest {
skipItems(1) // skip pause state
player.play()
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(2000)
assertThat(it.isPlaying).isTrue()
assertThat(it.currentPosition).isEqualTo(2000)
}
}
}
@ -234,19 +234,19 @@ class DefaultVoiceMessagePlayerTest {
matchInitialState()
player.seekTo(2000)
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(2000)
Truth.assertThat(it.duration).isEqualTo(null)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(2000)
assertThat(it.duration).isNull()
}
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(true)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(2000)
Truth.assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS)
assertThat(it.isReady).isTrue()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(2000)
assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS)
}
}
}
@ -256,15 +256,15 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
player.seekTo(2000)
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(true)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(2000)
Truth.assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS)
assertThat(it.isReady).isTrue()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(2000)
assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS)
}
}
}
@ -296,11 +296,11 @@ private const val MXC_URI = "mxc://matrix.org/1234567890abcdefg"
private suspend fun TurbineTestContext<VoiceMessagePlayer.State>.matchInitialState() {
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(0)
Truth.assertThat(it.duration).isEqualTo(null)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(0)
assertThat(it.duration).isNull()
}
}
@ -308,10 +308,10 @@ private suspend fun TurbineTestContext<VoiceMessagePlayer.State>.matchReadyState
fakeTotalDurationMs: Long = FAKE_TOTAL_DURATION_MS,
) {
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(true)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(0)
Truth.assertThat(it.duration).isEqualTo(fakeTotalDurationMs)
assertThat(it.isReady).isTrue()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(0)
assertThat(it.duration).isEqualTo(fakeTotalDurationMs)
}
}

View file

@ -16,7 +16,7 @@
package io.element.android.features.messages.impl.voicemessages.timeline
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
@ -44,13 +44,13 @@ class RedactedVoiceMessageManagerTest {
}
val manager = aDefaultRedactedVoiceMessageManager(mediaPlayer = mediaPlayer)
Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue()
assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
assertThat(mediaPlayer.state.value.isPlaying).isTrue()
manager.onEachMatrixTimelineItem(aRedactedMatrixTimeline(AN_EVENT_ID_2))
Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue()
assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
assertThat(mediaPlayer.state.value.isPlaying).isTrue()
}
@Test
@ -61,13 +61,13 @@ class RedactedVoiceMessageManagerTest {
}
val manager = aDefaultRedactedVoiceMessageManager(mediaPlayer = mediaPlayer)
Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue()
assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
assertThat(mediaPlayer.state.value.isPlaying).isTrue()
manager.onEachMatrixTimelineItem(aRedactedMatrixTimeline(AN_EVENT_ID))
Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
Truth.assertThat(mediaPlayer.state.value.isPlaying).isFalse()
assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
assertThat(mediaPlayer.state.value.isPlaying).isFalse()
}
}

View file

@ -19,7 +19,7 @@ package io.element.android.features.messages.impl.voicemessages.timeline
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
@ -39,9 +39,9 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
awaitItem().let {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("1:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("1:01")
}
}
}
@ -56,27 +56,27 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:00")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:00")
}
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:01")
}
}
}
@ -94,25 +94,25 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Retry)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Retry)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
analyticsService.trackedErrors.first().also {
Truth.assertThat(it).apply {
assertThat(it).apply {
isInstanceOf(VoiceMessageException.PlayMessageError::class.java)
hasMessageThat().isEqualTo("Error while trying to play voice message")
}
@ -130,25 +130,25 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
skipItems(2) // skip downloading states
val playingState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:01")
}
playingState.eventSink(VoiceMessageEvents.PlayPause)
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:01")
}
}
}
@ -162,9 +162,9 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Disabled)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("1:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Disabled)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("1:01")
}
}
}
@ -179,17 +179,17 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:10")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:10")
}
initialState.eventSink(VoiceMessageEvents.Seek(0.5f))
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:05")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:05")
}
}
}
@ -203,9 +203,9 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:10")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:10")
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
@ -213,17 +213,17 @@ class VoiceMessagePresenterTest {
skipItems(2) // skip downloading states
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.1f)
Truth.assertThat(it.time).isEqualTo("0:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
assertThat(it.progress).isEqualTo(0.1f)
assertThat(it.time).isEqualTo("0:01")
}
initialState.eventSink(VoiceMessageEvents.Seek(0.5f))
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:05")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:05")
}
}
}

View file

@ -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_onboarding_sign_in_manually">"Kézi bejelentkezés"</string>
<string name="screen_onboarding_sign_in_with_qr_code">"Bejelentkezés QR-kóddal"</string>
<string name="screen_onboarding_sign_up">"Fiók létrehozása"</string>
<string name="screen_onboarding_welcome_message">"Üdvözöljük a valaha volt leggyorsabb Elementben. Felturbózva, a sebesség és az egyszerűség érdekében."</string>
<string name="screen_onboarding_welcome_subtitle">"Üdvözli az %1$s. Felturbózva, a sebesség és az egyszerűség jegyében."</string>
<string name="screen_onboarding_welcome_title">"Legyen elemében"</string>
</resources>

View file

@ -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_onboarding_sign_in_manually">"Masuk secara manual"</string>
<string name="screen_onboarding_sign_in_with_qr_code">"Masuk dengan kode QR"</string>
<string name="screen_onboarding_sign_up">"Buat akun"</string>
<string name="screen_onboarding_welcome_message">"Selamat datang di Element tercepat yang pernah ada. Berdaya besar untuk kecepatan dan kesederhanaan."</string>
<string name="screen_onboarding_welcome_subtitle">"Selamat datang di %1$s. Berdaya penuh, untuk kecepatan dan kesederhanaan."</string>
<string name="screen_onboarding_welcome_title">"Berada di elemen Anda"</string>
</resources>

View file

@ -4,7 +4,11 @@
<string name="screen_create_poll_anonymous_desc">"Ergebnisse erst nach Ende der Umfrage anzeigen"</string>
<string name="screen_create_poll_anonymous_headline">"Anonyme Umfrage"</string>
<string name="screen_create_poll_answer_hint">"Option %1$d"</string>
<string name="screen_create_poll_cancel_confirmation_content_android">"Deine Änderungen wurden nicht gespeichert. Bist du sicher, dass du zurückgehen willst?"</string>
<string name="screen_create_poll_question_desc">"Frage oder Thema"</string>
<string name="screen_create_poll_question_hint">"Worum geht es bei der Umfrage?"</string>
<string name="screen_create_poll_title">"Umfrage erstellen"</string>
<string name="screen_edit_poll_delete_confirmation">"Bist du dir sicher, dass du diese Umfrage löschen möchtest?"</string>
<string name="screen_edit_poll_delete_confirmation_title">"Umfrage löschen"</string>
<string name="screen_edit_poll_title">"Umfrage bearbeiten"</string>
</resources>

View file

@ -4,7 +4,11 @@
<string name="screen_create_poll_anonymous_desc">"Afficher les résultats uniquement après la fin du sondage"</string>
<string name="screen_create_poll_anonymous_headline">"Masquer les votes"</string>
<string name="screen_create_poll_answer_hint">"Option %1$d"</string>
<string name="screen_create_poll_cancel_confirmation_content_android">"Vos modifications nont pas été enregistrées. Êtes-vous certain de vouloir quitter?"</string>
<string name="screen_create_poll_question_desc">"Question ou sujet"</string>
<string name="screen_create_poll_question_hint">"Quel est le sujet du sondage ?"</string>
<string name="screen_create_poll_title">"Créer un sondage"</string>
<string name="screen_edit_poll_delete_confirmation">"Êtes-vous certain de vouloir supprimer ce sondage?"</string>
<string name="screen_edit_poll_delete_confirmation_title">"Supprimer le sondage"</string>
<string name="screen_edit_poll_title">"Modifier le sondage"</string>
</resources>

View file

@ -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_poll_add_option_btn">"Lehetőség hozzáadása"</string>
<string name="screen_create_poll_anonymous_desc">"Eredmények megjelenítése csak a szavazás befejezése után"</string>
<string name="screen_create_poll_anonymous_headline">"Szavazatok elrejtése"</string>
<string name="screen_create_poll_answer_hint">"%1$d. lehetőség"</string>
<string name="screen_create_poll_cancel_confirmation_content_android">"A módosítások nem lettek mentve. Biztos, hogy vissza akar lépni?"</string>
<string name="screen_create_poll_question_desc">"Kérdés vagy téma"</string>
<string name="screen_create_poll_question_hint">"Miről szól ez a szavazás?"</string>
<string name="screen_create_poll_title">"Szavazás létrehozása"</string>
<string name="screen_edit_poll_delete_confirmation">"Biztos, hogy törli ezt a szavazást?"</string>
<string name="screen_edit_poll_delete_confirmation_title">"Szavazás törlése"</string>
<string name="screen_edit_poll_title">"Szavazás szerkesztése"</string>
</resources>

View file

@ -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_poll_add_option_btn">"Tambahkan opsi"</string>
<string name="screen_create_poll_anonymous_desc">"Tampilkan hasil hanya setelah pemungutan suara berakhir"</string>
<string name="screen_create_poll_anonymous_headline">"Pemungutan suara anonim"</string>
<string name="screen_create_poll_answer_hint">"Opsi %1$d"</string>
<string name="screen_create_poll_cancel_confirmation_content_android">"Perubahan Anda belum disimpan. Apakah Anda yakin ingin kembali?"</string>
<string name="screen_create_poll_question_desc">"Pertanyaan atau topik"</string>
<string name="screen_create_poll_question_hint">"Tentang apa pemungutan suara ini?"</string>
<string name="screen_create_poll_title">"Buat pemungutan suara"</string>
<string name="screen_edit_poll_delete_confirmation">"Apakah Anda yakin ingin menghapus pemungutan suara ini?"</string>
<string name="screen_edit_poll_delete_confirmation_title">"Hapus pemungutan suara"</string>
<string name="screen_edit_poll_title">"Sunting pemungutan suara"</string>
</resources>

View file

@ -20,7 +20,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Composer
import im.vector.app.features.analytics.plan.PollCreation
import io.element.android.features.messages.test.FakeMessageComposerContext
@ -88,8 +88,8 @@ class CreatePollPresenterTest {
presenter.present()
}.test {
awaitDefaultItem()
Truth.assertThat(fakeAnalyticsService.trackedErrors.filterIsInstance<CreatePollException.GetPollFailed>()).isNotEmpty()
Truth.assertThat(navUpInvocationsCount).isEqualTo(1)
assertThat(fakeAnalyticsService.trackedErrors.filterIsInstance<CreatePollException.GetPollFailed>()).isNotEmpty()
assertThat(navUpInvocationsCount).isEqualTo(1)
}
}
@ -100,19 +100,19 @@ class CreatePollPresenterTest {
presenter.present()
}.test {
val initial = awaitItem()
Truth.assertThat(initial.canSave).isFalse()
assertThat(initial.canSave).isFalse()
initial.eventSink(CreatePollEvents.SetQuestion("A question?"))
val questionSet = awaitItem()
Truth.assertThat(questionSet.canSave).isFalse()
assertThat(questionSet.canSave).isFalse()
questionSet.eventSink(CreatePollEvents.SetAnswer(0, "Answer 1"))
val answer1Set = awaitItem()
Truth.assertThat(answer1Set.canSave).isFalse()
assertThat(answer1Set.canSave).isFalse()
answer1Set.eventSink(CreatePollEvents.SetAnswer(1, "Answer 2"))
val answer2Set = awaitItem()
Truth.assertThat(answer2Set.canSave).isTrue()
assertThat(answer2Set.canSave).isTrue()
}
}
@ -129,8 +129,8 @@ class CreatePollPresenterTest {
skipItems(3)
initial.eventSink(CreatePollEvents.Save)
delay(1) // Wait for the coroutine to finish
Truth.assertThat(fakeMatrixRoom.createPollInvocations.size).isEqualTo(1)
Truth.assertThat(fakeMatrixRoom.createPollInvocations.last()).isEqualTo(
assertThat(fakeMatrixRoom.createPollInvocations.size).isEqualTo(1)
assertThat(fakeMatrixRoom.createPollInvocations.last()).isEqualTo(
SavePollInvocation(
question = "A question?",
answers = listOf("Answer 1", "Answer 2"),
@ -138,8 +138,8 @@ class CreatePollPresenterTest {
pollKind = PollKind.Disclosed
)
)
Truth.assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2)
Truth.assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo(
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2)
assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo(
Composer(
inThread = false,
isEditing = false,
@ -147,7 +147,7 @@ class CreatePollPresenterTest {
messageType = Composer.MessageType.Poll,
)
)
Truth.assertThat(fakeAnalyticsService.capturedEvents[1]).isEqualTo(
assertThat(fakeAnalyticsService.capturedEvents[1]).isEqualTo(
PollCreation(
action = PollCreation.Action.Create,
isUndisclosed = false,
@ -170,10 +170,10 @@ class CreatePollPresenterTest {
awaitItem().eventSink(CreatePollEvents.SetAnswer(1, "Answer 2"))
awaitItem().eventSink(CreatePollEvents.Save)
delay(1) // Wait for the coroutine to finish
Truth.assertThat(fakeMatrixRoom.createPollInvocations).hasSize(1)
Truth.assertThat(fakeAnalyticsService.capturedEvents).isEmpty()
Truth.assertThat(fakeAnalyticsService.trackedErrors).hasSize(1)
Truth.assertThat(fakeAnalyticsService.trackedErrors).containsExactly(
assertThat(fakeMatrixRoom.createPollInvocations).hasSize(1)
assertThat(fakeAnalyticsService.capturedEvents).isEmpty()
assertThat(fakeAnalyticsService.trackedErrors).hasSize(1)
assertThat(fakeAnalyticsService.trackedErrors).containsExactly(
CreatePollException.SavePollFailed("Failed to create poll", error)
)
}
@ -203,8 +203,8 @@ class CreatePollPresenterTest {
eventSink(CreatePollEvents.Save)
}
delay(1) // Wait for the coroutine to finish
Truth.assertThat(fakeMatrixRoom.editPollInvocations.size).isEqualTo(1)
Truth.assertThat(fakeMatrixRoom.editPollInvocations.last()).isEqualTo(
assertThat(fakeMatrixRoom.editPollInvocations.size).isEqualTo(1)
assertThat(fakeMatrixRoom.editPollInvocations.last()).isEqualTo(
SavePollInvocation(
question = "Changed question",
answers = listOf("Changed answer 1", "Changed answer 2", "Maybe"),
@ -212,8 +212,8 @@ class CreatePollPresenterTest {
pollKind = PollKind.Disclosed
)
)
Truth.assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2)
Truth.assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo(
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2)
assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo(
Composer(
inThread = false,
isEditing = true,
@ -221,7 +221,7 @@ class CreatePollPresenterTest {
messageType = Composer.MessageType.Poll,
)
)
Truth.assertThat(fakeAnalyticsService.capturedEvents[1]).isEqualTo(
assertThat(fakeAnalyticsService.capturedEvents[1]).isEqualTo(
PollCreation(
action = PollCreation.Action.Edit,
isUndisclosed = false,
@ -243,10 +243,10 @@ class CreatePollPresenterTest {
awaitPollLoaded().eventSink(CreatePollEvents.SetAnswer(0, "A"))
awaitPollLoaded(newAnswer1 = "A").eventSink(CreatePollEvents.Save)
delay(1) // Wait for the coroutine to finish
Truth.assertThat(fakeMatrixRoom.editPollInvocations).hasSize(1)
Truth.assertThat(fakeAnalyticsService.capturedEvents).isEmpty()
Truth.assertThat(fakeAnalyticsService.trackedErrors).hasSize(1)
Truth.assertThat(fakeAnalyticsService.trackedErrors).containsExactly(
assertThat(fakeMatrixRoom.editPollInvocations).hasSize(1)
assertThat(fakeAnalyticsService.capturedEvents).isEmpty()
assertThat(fakeAnalyticsService.trackedErrors).hasSize(1)
assertThat(fakeAnalyticsService.trackedErrors).containsExactly(
CreatePollException.SavePollFailed("Failed to edit poll", error)
)
}
@ -259,16 +259,16 @@ class CreatePollPresenterTest {
presenter.present()
}.test {
val initial = awaitItem()
Truth.assertThat(initial.answers.size).isEqualTo(2)
assertThat(initial.answers.size).isEqualTo(2)
initial.eventSink(CreatePollEvents.AddAnswer)
val answerAdded = awaitItem()
Truth.assertThat(answerAdded.answers.size).isEqualTo(3)
Truth.assertThat(answerAdded.answers[2].text).isEmpty()
assertThat(answerAdded.answers.size).isEqualTo(3)
assertThat(answerAdded.answers[2].text).isEmpty()
initial.eventSink(CreatePollEvents.RemoveAnswer(2))
val answerRemoved = awaitItem()
Truth.assertThat(answerRemoved.answers.size).isEqualTo(2)
assertThat(answerRemoved.answers.size).isEqualTo(2)
}
}
@ -281,7 +281,7 @@ class CreatePollPresenterTest {
val initial = awaitItem()
initial.eventSink(CreatePollEvents.SetQuestion("A question?"))
val questionSet = awaitItem()
Truth.assertThat(questionSet.question).isEqualTo("A question?")
assertThat(questionSet.question).isEqualTo("A question?")
}
}
@ -294,7 +294,7 @@ class CreatePollPresenterTest {
val initial = awaitItem()
initial.eventSink(CreatePollEvents.SetAnswer(0, "This is answer 1"))
val answerSet = awaitItem()
Truth.assertThat(answerSet.answers.first().text).isEqualTo("This is answer 1")
assertThat(answerSet.answers.first().text).isEqualTo("This is answer 1")
}
}
@ -307,7 +307,7 @@ class CreatePollPresenterTest {
val initial = awaitItem()
initial.eventSink(CreatePollEvents.SetPollKind(PollKind.Undisclosed))
val kindSet = awaitItem()
Truth.assertThat(kindSet.pollKind).isEqualTo(PollKind.Undisclosed)
assertThat(kindSet.pollKind).isEqualTo(PollKind.Undisclosed)
}
}
@ -318,13 +318,13 @@ class CreatePollPresenterTest {
presenter.present()
}.test {
val initial = awaitItem()
Truth.assertThat(initial.canAddAnswer).isTrue()
assertThat(initial.canAddAnswer).isTrue()
repeat(17) {
initial.eventSink(CreatePollEvents.AddAnswer)
Truth.assertThat(awaitItem().canAddAnswer).isTrue()
assertThat(awaitItem().canAddAnswer).isTrue()
}
initial.eventSink(CreatePollEvents.AddAnswer)
Truth.assertThat(awaitItem().canAddAnswer).isFalse()
assertThat(awaitItem().canAddAnswer).isFalse()
}
}
@ -335,9 +335,9 @@ class CreatePollPresenterTest {
presenter.present()
}.test {
val initial = awaitItem()
Truth.assertThat(initial.answers.all { it.canDelete }).isFalse()
assertThat(initial.answers.all { it.canDelete }).isFalse()
initial.eventSink(CreatePollEvents.AddAnswer)
Truth.assertThat(awaitItem().answers.all { it.canDelete }).isTrue()
assertThat(awaitItem().answers.all { it.canDelete }).isTrue()
}
}
@ -349,7 +349,7 @@ class CreatePollPresenterTest {
}.test {
val initial = awaitItem()
initial.eventSink(CreatePollEvents.SetAnswer(0, "A".repeat(241)))
Truth.assertThat(awaitItem().answers.first().text.length).isEqualTo(240)
assertThat(awaitItem().answers.first().text.length).isEqualTo(240)
}
}
@ -360,9 +360,9 @@ class CreatePollPresenterTest {
presenter.present()
}.test {
val initial = awaitItem()
Truth.assertThat(navUpInvocationsCount).isEqualTo(0)
assertThat(navUpInvocationsCount).isEqualTo(0)
initial.eventSink(CreatePollEvents.NavBack)
Truth.assertThat(navUpInvocationsCount).isEqualTo(1)
assertThat(navUpInvocationsCount).isEqualTo(1)
}
}
@ -373,10 +373,10 @@ class CreatePollPresenterTest {
presenter.present()
}.test {
val initial = awaitItem()
Truth.assertThat(navUpInvocationsCount).isEqualTo(0)
Truth.assertThat(initial.showBackConfirmation).isFalse()
assertThat(navUpInvocationsCount).isEqualTo(0)
assertThat(initial.showBackConfirmation).isFalse()
initial.eventSink(CreatePollEvents.ConfirmNavBack)
Truth.assertThat(navUpInvocationsCount).isEqualTo(1)
assertThat(navUpInvocationsCount).isEqualTo(1)
}
}
@ -388,12 +388,12 @@ class CreatePollPresenterTest {
}.test {
val initial = awaitItem()
initial.eventSink(CreatePollEvents.SetQuestion("Non blank"))
Truth.assertThat(awaitItem().showBackConfirmation).isFalse()
assertThat(awaitItem().showBackConfirmation).isFalse()
initial.eventSink(CreatePollEvents.ConfirmNavBack)
Truth.assertThat(awaitItem().showBackConfirmation).isTrue()
assertThat(awaitItem().showBackConfirmation).isTrue()
initial.eventSink(CreatePollEvents.HideConfirmation)
Truth.assertThat(awaitItem().showBackConfirmation).isFalse()
Truth.assertThat(navUpInvocationsCount).isEqualTo(0)
assertThat(awaitItem().showBackConfirmation).isFalse()
assertThat(navUpInvocationsCount).isEqualTo(0)
}
}
@ -405,10 +405,10 @@ class CreatePollPresenterTest {
}.test {
awaitDefaultItem()
val loaded = awaitPollLoaded()
Truth.assertThat(navUpInvocationsCount).isEqualTo(0)
Truth.assertThat(loaded.showBackConfirmation).isFalse()
assertThat(navUpInvocationsCount).isEqualTo(0)
assertThat(loaded.showBackConfirmation).isFalse()
loaded.eventSink(CreatePollEvents.ConfirmNavBack)
Truth.assertThat(navUpInvocationsCount).isEqualTo(1)
assertThat(navUpInvocationsCount).isEqualTo(1)
}
}
@ -421,12 +421,12 @@ class CreatePollPresenterTest {
awaitDefaultItem()
val loaded = awaitPollLoaded()
loaded.eventSink(CreatePollEvents.SetQuestion("CHANGED"))
Truth.assertThat(awaitItem().showBackConfirmation).isFalse()
assertThat(awaitItem().showBackConfirmation).isFalse()
loaded.eventSink(CreatePollEvents.ConfirmNavBack)
Truth.assertThat(awaitItem().showBackConfirmation).isTrue()
assertThat(awaitItem().showBackConfirmation).isTrue()
loaded.eventSink(CreatePollEvents.HideConfirmation)
Truth.assertThat(awaitItem().showBackConfirmation).isFalse()
Truth.assertThat(navUpInvocationsCount).isEqualTo(0)
assertThat(awaitItem().showBackConfirmation).isFalse()
assertThat(navUpInvocationsCount).isEqualTo(0)
}
}
@ -439,7 +439,7 @@ class CreatePollPresenterTest {
awaitDefaultItem()
awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false))
awaitDeleteConfirmation()
Truth.assertThat(fakeMatrixRoom.redactEventEventIdParam).isNull()
assertThat(fakeMatrixRoom.redactEventEventIdParam).isNull()
}
}
@ -451,12 +451,12 @@ class CreatePollPresenterTest {
}.test {
awaitDefaultItem()
awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false))
Truth.assertThat(fakeMatrixRoom.redactEventEventIdParam).isNull()
assertThat(fakeMatrixRoom.redactEventEventIdParam).isNull()
awaitDeleteConfirmation().eventSink(CreatePollEvents.HideConfirmation)
awaitPollLoaded().apply {
Truth.assertThat(showDeleteConfirmation).isFalse()
assertThat(showDeleteConfirmation).isFalse()
}
Truth.assertThat(fakeMatrixRoom.redactEventEventIdParam).isNull()
assertThat(fakeMatrixRoom.redactEventEventIdParam).isNull()
}
}
@ -468,29 +468,29 @@ class CreatePollPresenterTest {
}.test {
awaitDefaultItem()
awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false))
Truth.assertThat(fakeMatrixRoom.redactEventEventIdParam).isNull()
assertThat(fakeMatrixRoom.redactEventEventIdParam).isNull()
awaitDeleteConfirmation().eventSink(CreatePollEvents.Delete(confirmed = true))
awaitPollLoaded().apply {
Truth.assertThat(showDeleteConfirmation).isFalse()
assertThat(showDeleteConfirmation).isFalse()
}
Truth.assertThat(fakeMatrixRoom.redactEventEventIdParam).isEqualTo(pollEventId)
assertThat(fakeMatrixRoom.redactEventEventIdParam).isEqualTo(pollEventId)
}
}
private suspend fun TurbineTestContext<CreatePollState>.awaitDefaultItem() =
awaitItem().apply {
Truth.assertThat(canSave).isFalse()
Truth.assertThat(canAddAnswer).isTrue()
Truth.assertThat(question).isEmpty()
Truth.assertThat(answers).isEqualTo(listOf(Answer("", false), Answer("", false)))
Truth.assertThat(pollKind).isEqualTo(PollKind.Disclosed)
Truth.assertThat(showBackConfirmation).isFalse()
Truth.assertThat(showDeleteConfirmation).isFalse()
assertThat(canSave).isFalse()
assertThat(canAddAnswer).isTrue()
assertThat(question).isEmpty()
assertThat(answers).isEqualTo(listOf(Answer("", false), Answer("", false)))
assertThat(pollKind).isEqualTo(PollKind.Disclosed)
assertThat(showBackConfirmation).isFalse()
assertThat(showDeleteConfirmation).isFalse()
}
private suspend fun TurbineTestContext<CreatePollState>.awaitDeleteConfirmation() =
awaitItem().apply {
Truth.assertThat(showDeleteConfirmation).isTrue()
assertThat(showDeleteConfirmation).isTrue()
}
private suspend fun TurbineTestContext<CreatePollState>.awaitPollLoaded(
@ -499,14 +499,14 @@ class CreatePollPresenterTest {
newAnswer2: String? = null,
) =
awaitItem().apply {
Truth.assertThat(canSave).isTrue()
Truth.assertThat(canAddAnswer).isTrue()
Truth.assertThat(question).isEqualTo(newQuestion ?: existingPoll.question)
Truth.assertThat(answers).isEqualTo(existingPoll.expectedAnswersState().toMutableList().apply {
assertThat(canSave).isTrue()
assertThat(canAddAnswer).isTrue()
assertThat(question).isEqualTo(newQuestion ?: existingPoll.question)
assertThat(answers).isEqualTo(existingPoll.expectedAnswersState().toMutableList().apply {
newAnswer1?.let { this[0] = Answer(it, true) }
newAnswer2?.let { this[1] = Answer(it, true) }
})
Truth.assertThat(pollKind).isEqualTo(existingPoll.kind)
assertThat(pollKind).isEqualTo(existingPoll.kind)
}
private fun createCreatePollPresenter(

View file

@ -17,11 +17,11 @@
package io.element.android.features.preferences.impl.notifications
sealed interface NotificationSettingsEvents {
data object RefreshSystemNotificationsEnabled : NotificationSettingsEvents
data class SetNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data class SetAtRoomNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data class SetInviteForMeNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data object FixConfigurationMismatch : NotificationSettingsEvents
data object ClearConfigurationMismatchError : NotificationSettingsEvents
data object ClearNotificationChangeError : NotificationSettingsEvents

View file

@ -76,6 +76,9 @@ class NotificationSettingsPresenter @Inject constructor(
is NotificationSettingsEvents.SetCallNotificationsEnabled -> {
localCoroutineScope.setCallNotificationsEnabled(event.enabled, changeNotificationSettingAction)
}
is NotificationSettingsEvents.SetInviteForMeNotificationsEnabled -> {
localCoroutineScope.setInviteForMeNotificationsEnabled(event.enabled, changeNotificationSettingAction)
}
is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled)
NotificationSettingsEvents.ClearConfigurationMismatchError -> {
matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
@ -123,10 +126,12 @@ class NotificationSettingsPresenter @Inject constructor(
val callNotificationsEnabled = notificationSettingsService.isCallEnabled().getOrThrow()
val atRoomNotificationsEnabled = notificationSettingsService.isRoomMentionEnabled().getOrThrow()
val inviteForMeNotificationsEnabled = notificationSettingsService.isInviteForMeEnabled().getOrThrow()
target.value = NotificationSettingsState.MatrixSettings.Valid(
atRoomNotificationsEnabled = atRoomNotificationsEnabled,
callNotificationsEnabled = callNotificationsEnabled,
inviteForMeNotificationsEnabled = inviteForMeNotificationsEnabled,
defaultGroupNotificationMode = encryptedGroupDefaultMode,
defaultOneToOneNotificationMode = encryptedOneToOneDefaultMode,
)
@ -175,6 +180,12 @@ class NotificationSettingsPresenter @Inject constructor(
}.runCatchingUpdatingState(action)
}
private fun CoroutineScope.setInviteForMeNotificationsEnabled(enabled: Boolean, action: MutableState<Async<Unit>>) = launch {
suspend {
notificationSettingsService.setInviteForMeEnabled(enabled).getOrThrow()
}.runCatchingUpdatingState(action)
}
private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch {
userPushStore.setNotificationEnabledForDevice(enabled)
}

View file

@ -32,6 +32,7 @@ data class NotificationSettingsState(
data class Valid(
val atRoomNotificationsEnabled: Boolean,
val callNotificationsEnabled: Boolean,
val inviteForMeNotificationsEnabled: Boolean,
val defaultGroupNotificationMode: RoomNotificationMode?,
val defaultOneToOneNotificationMode: RoomNotificationMode?,
) : MatrixSettings

View file

@ -35,6 +35,7 @@ fun aNotificationSettingsState(
matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
atRoomNotificationsEnabled = true,
callNotificationsEnabled = true,
inviteForMeNotificationsEnabled = true,
defaultGroupNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES,
),

View file

@ -77,6 +77,7 @@ fun NotificationSettingsView(
onMentionNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(it)) },
// TODO We are removing the call notification toggle until support for call notifications has been added
// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) },
onInviteForMeNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) },
)
}
AsyncView(
@ -98,6 +99,7 @@ private fun NotificationSettingsContentView(
onMentionNotificationsChanged: (Boolean) -> Unit,
// TODO We are removing the call notification toggle until support for call notifications has been added
// onCallsNotificationsChanged: (Boolean) -> Unit,
onInviteForMeNotificationsChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
@ -147,8 +149,8 @@ private fun NotificationSettingsContentView(
onCheckedChange = onMentionNotificationsChanged
)
}
// TODO We are removing the call notification toggle until support for call notifications has been added
// PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_additional_settings_section_title)) {
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_additional_settings_section_title)) {
// TODO We are removing the call notification toggle until support for call notifications has been added
// PreferenceSwitch(
// modifier = Modifier,
// title = stringResource(id = CommonStrings.screen_notification_settings_calls_label),
@ -156,7 +158,14 @@ private fun NotificationSettingsContentView(
// switchAlignment = Alignment.Top,
// onCheckedChange = onCallsNotificationsChanged
// )
// }
PreferenceSwitch(
modifier = Modifier,
title = stringResource(id = R.string.screen_notification_settings_invite_for_me_label),
isChecked = matrixSettings.inviteForMeNotificationsEnabled,
switchAlignment = Alignment.Top,
onCheckedChange = onInviteForMeNotificationsChanged
)
}
}
}

View file

@ -31,6 +31,7 @@ Pokud budete pokračovat, některá nastavení se mohou změnit."</string>
<string name="screen_notification_settings_enable_notifications">"Povolit oznámení na tomto zařízení"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Konfigurace nebyla opravena, zkuste to prosím znovu."</string>
<string name="screen_notification_settings_group_chats">"Skupinové chaty"</string>
<string name="screen_notification_settings_invite_for_me_label">"Pozvánky"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Váš domovský server tuto možnost v zašifrovaných místnostech nepodporuje, v některých místnostech nemusíte být upozorněni."</string>
<string name="screen_notification_settings_mentions_section_title">"Zmínky"</string>
<string name="screen_notification_settings_mode_all">"Vše"</string>

View file

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_advanced_settings_element_call_base_url">"Benutzerdefinierte Element-Aufruf-Basis-URL"</string>
<string name="screen_advanced_settings_element_call_base_url_description">"Lege eine eigene Basis-URL für Element Call fest."</string>
<string name="screen_advanced_settings_element_call_base_url_validation_error">"Ungültige URL, bitte stelle sicher, dass du das Protokoll (http/https) und die richtige Adresse angibst."</string>
<string name="screen_advanced_settings_developer_mode">"Entwickler-Modus"</string>
<string name="screen_advanced_settings_developer_mode_description">"Aktivieren, um Zugriff auf Features und Funktionen für Entwickler zu aktivieren."</string>
<string name="screen_advanced_settings_rich_text_editor_description">"Deaktiviere den Rich-Text-Editor, um Markdown manuell einzugeben."</string>
<string name="screen_advanced_settings_view_source_description">"Option aktiveren, um Nachrichtenquelle in der Zeitleiste anzuzeigen."</string>
<string name="screen_edit_profile_display_name">"Anzeigename"</string>
<string name="screen_edit_profile_display_name_placeholder">"Dein Anzeigename"</string>
<string name="screen_edit_profile_error">"Ein unbekannter Fehler ist aufgetreten und die Informationen konnten nicht geändert werden."</string>
@ -22,6 +27,7 @@
<string name="screen_notification_settings_enable_notifications">"Benachrichtigungen auf diesem Gerät aktivieren"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Die Konfiguration wurde nicht korrigiert, bitte versuche es erneut."</string>
<string name="screen_notification_settings_group_chats">"Gruppenchats"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Dein Homeserver unterstützt diese Option in verschlüsselten Räumen nicht. In einigen Räumen wirst du möglicherweise nicht benachrichtigt."</string>
<string name="screen_notification_settings_mentions_section_title">"Erwähnungen"</string>
<string name="screen_notification_settings_mode_all">"Alle"</string>
<string name="screen_notification_settings_mode_mentions">"Erwähnungen"</string>

View file

@ -6,6 +6,7 @@
<string name="screen_advanced_settings_developer_mode">"Mode développeur"</string>
<string name="screen_advanced_settings_developer_mode_description">"Activer pour pouvoir accéder aux fonctionnalités destinées aux développeurs."</string>
<string name="screen_advanced_settings_rich_text_editor_description">"Désactivez léditeur de texte enrichi pour saisir manuellement du Markdown."</string>
<string name="screen_advanced_settings_view_source_description">"Activer cette option pour pouvoir voir la source des messages dans la discussion."</string>
<string name="screen_edit_profile_display_name">"Pseudonyme"</string>
<string name="screen_edit_profile_display_name_placeholder">"Votre pseudonyme"</string>
<string name="screen_edit_profile_error">"Une erreur inconnue sest produite et les informations nont pas pu être modifiées."</string>
@ -30,6 +31,7 @@ Si vous continuez, il est possible que certains de vos paramètres soient modifi
<string name="screen_notification_settings_enable_notifications">"Activer les notifications sur cet appareil"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"La configuration na pas été corrigée, veuillez réessayer."</string>
<string name="screen_notification_settings_group_chats">"Discussions de groupe"</string>
<string name="screen_notification_settings_invite_for_me_label">"Invitations"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Votre serveur daccueil ne supporte pas cette option pour les salons chiffrés, vous pourriez ne pas être notifié(e) dans certains salons."</string>
<string name="screen_notification_settings_mentions_section_title">"Mentions"</string>
<string name="screen_notification_settings_mode_all">"Tous"</string>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_advanced_settings_element_call_base_url">"Egyéni Element Call alapwebcím"</string>
<string name="screen_advanced_settings_element_call_base_url_description">"Egyéni alapwebcím beállítása az Element Callhoz."</string>
<string name="screen_advanced_settings_element_call_base_url_validation_error">"Érvénytelen webcím, győződjön meg arról, hogy szerepel benne a protokoll (http/https), és hogy helyes a cím."</string>
<string name="screen_advanced_settings_developer_mode">"Fejlesztői mód"</string>
<string name="screen_advanced_settings_developer_mode_description">"Engedélyezze, hogy elérje a fejlesztőknek szánt funkciókat."</string>
<string name="screen_advanced_settings_rich_text_editor_description">"A formázott szöveges szerkesztő letiltása, hogy kézzel írhasson Markdownt."</string>
<string name="screen_advanced_settings_view_source_description">"Engedélyezze a beállítást az üzenet forrásának megjelenítéséhez az idővonalon."</string>
<string name="screen_edit_profile_display_name">"Megjelenítendő név"</string>
<string name="screen_edit_profile_display_name_placeholder">"Saját megjelenítendő név"</string>
<string name="screen_edit_profile_error">"Ismeretlen hiba történt, és az információ módosítása nem sikerült."</string>
<string name="screen_edit_profile_error_title">"Nem sikerült frissíteni a profilt"</string>
<string name="screen_edit_profile_title">"Profil szerkesztése"</string>
<string name="screen_edit_profile_updating_details">"Profil frissítése…"</string>
<string name="screen_notification_settings_additional_settings_section_title">"További beállítások"</string>
<string name="screen_notification_settings_calls_label">"Hang- és videóhívások"</string>
<string name="screen_notification_settings_configuration_mismatch">"Konfigurációs eltérés"</string>
<string name="screen_notification_settings_configuration_mismatch_description">"Egyszerűsítettük az értesítési beállításokat, hogy könnyebben megtalálhatók legyenek a lehetőségek. A korábban kiválasztott egyéni beállítások némelyike nem jelenik meg itt, de továbbra is aktív.
Ha folytatja, egyes beállítások megváltozhatnak."</string>
<string name="screen_notification_settings_direct_chats">"Közvetlen csevegések"</string>
<string name="screen_notification_settings_edit_custom_settings_section_title">"Egyéni beállítás csevegésenként"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Hiba történt az értesítési beállítás frissítésekor."</string>
<string name="screen_notification_settings_edit_mode_all_messages">"Összes üzenet"</string>
<string name="screen_notification_settings_edit_mode_mentions_and_keywords">"Csak említések és kulcsszavak"</string>
<string name="screen_notification_settings_edit_screen_direct_section_header">"Közvetlen csevegéseknél értesítés ezekről:"</string>
<string name="screen_notification_settings_edit_screen_group_section_header">"Csoportos csevegésekben értesítés ezekről:"</string>
<string name="screen_notification_settings_enable_notifications">"Értesítések engedélyezése ezen az eszközön"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"A konfiguráció nem lett kijavítva, próbálja újra."</string>
<string name="screen_notification_settings_group_chats">"Csoportos csevegések"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"A Matrix-kiszolgálója nem támogatja ezt a beállítást a titkosított szobákban, előfordulhat, hogy egyes szobákban nem kap értesítést."</string>
<string name="screen_notification_settings_mentions_section_title">"Említések"</string>
<string name="screen_notification_settings_mode_all">"Összes"</string>
<string name="screen_notification_settings_mode_mentions">"Említések"</string>
<string name="screen_notification_settings_notification_section_title">"Értesítés ezekről:"</string>
<string name="screen_notification_settings_room_mention_label">"Értesítés a @room említésekor"</string>
<string name="screen_notification_settings_system_notifications_action_required">"Az értesítések fogadásához kérjük, módosítsa a %1$s."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"rendszerbeállításokat"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"A rendszerértesítések ki vannak kapcsolva"</string>
<string name="screen_notification_settings_title">"Értesítések"</string>
</resources>

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_advanced_settings_element_call_base_url">"URL dasar Element Call khusus"</string>
<string name="screen_advanced_settings_element_call_base_url_description">"Tetapkan URL dasar khusus untuk Element Call."</string>
<string name="screen_advanced_settings_element_call_base_url_validation_error">"URL tidak valid, pastikan Anda menyertakan protokol (http/https) dan alamat yang benar."</string>
<string name="screen_advanced_settings_developer_mode">"Mode pengembang"</string>
<string name="screen_advanced_settings_developer_mode_description">"Aktifkan untuk mengakses fitur dan fungsi untuk para pengembang."</string>
<string name="screen_advanced_settings_rich_text_editor_description">"Nonaktifkan penyunting teks kaya untuk mengetik Markdown secara manual."</string>
<string name="screen_advanced_settings_view_source_description">"Aktifkan opsi untuk melihat sumber pesan dalam lini masa."</string>
<string name="screen_edit_profile_display_name">"Nama tampilan"</string>
<string name="screen_edit_profile_display_name_placeholder">"Nama tampilan Anda"</string>
<string name="screen_edit_profile_error">"Terjadi kesalahan yang tidak diketahui dan informasi tidak dapat diubah."</string>
<string name="screen_edit_profile_error_title">"Tidak dapat memperbarui profil"</string>
<string name="screen_edit_profile_title">"Sunting profil"</string>
<string name="screen_edit_profile_updating_details">"Memperbarui profil…"</string>
<string name="screen_notification_settings_additional_settings_section_title">"Pengaturan tambahan"</string>
<string name="screen_notification_settings_calls_label">"Panggilan audio dan video"</string>
<string name="screen_notification_settings_configuration_mismatch">"Ketidakcocokan pengaturan"</string>
<string name="screen_notification_settings_configuration_mismatch_description">"Kami telah menyederhanakan Pengaturan Pemberitahuan untuk membuat opsi lebih mudah ditemukan.
Beberapa pengaturan khusus yang Anda pilih di masa lalu tidak ditampilkan di sini, tetapi masih aktif.
Jika Anda melanjutkan, beberapa pengaturan Anda dapat berubah."</string>
<string name="screen_notification_settings_direct_chats">"Obrolan langsung"</string>
<string name="screen_notification_settings_edit_custom_settings_section_title">"Pengaturan khusus per obrolan"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Terjadi kesalahan saat memperbarui pengaturan pemberitahuan."</string>
<string name="screen_notification_settings_edit_mode_all_messages">"Semua pesan"</string>
<string name="screen_notification_settings_edit_mode_mentions_and_keywords">"Sebutan dan Kata Kunci saja"</string>
<string name="screen_notification_settings_edit_screen_direct_section_header">"Di obrolan langsung, beri tahu saya tentang"</string>
<string name="screen_notification_settings_edit_screen_group_section_header">"Di obrolan grup, beri tahu tentang"</string>
<string name="screen_notification_settings_enable_notifications">"Aktifkan pemberitahuan di perangkat ini"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Pengaturan belum diperbaiki, silakan coba lagi."</string>
<string name="screen_notification_settings_group_chats">"Obrolan grup"</string>
<string name="screen_notification_settings_invite_for_me_label">"Undangan"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Homeserver Anda tidak mendukung opsi ini dalam ruangan terenkripsi, Anda mungkin tidak diberi tahu dalam beberapa ruangan."</string>
<string name="screen_notification_settings_mentions_section_title">"Sebutan"</string>
<string name="screen_notification_settings_mode_all">"Semua"</string>
<string name="screen_notification_settings_mode_mentions">"Sebutan"</string>
<string name="screen_notification_settings_notification_section_title">"Beri tahu saya tentang"</string>
<string name="screen_notification_settings_room_mention_label">"Beri tahu saya pada @room"</string>
<string name="screen_notification_settings_system_notifications_action_required">"Untuk menerima pemberitahuan, silakan ubah %1$s Anda."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"pengaturan sistem"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Pemberitahuan sistem dimatikan"</string>
<string name="screen_notification_settings_title">"Notifikasi"</string>
</resources>

View file

@ -29,6 +29,7 @@
<string name="screen_notification_settings_enable_notifications">"Включить уведомления на данном устройстве"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Конфигурация не была исправлена, попробуйте еще раз."</string>
<string name="screen_notification_settings_group_chats">"Групповые чаты"</string>
<string name="screen_notification_settings_invite_for_me_label">"Приглашения"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, в некоторых комнатах вы можете не получать уведомления."</string>
<string name="screen_notification_settings_mentions_section_title">"Упоминания"</string>
<string name="screen_notification_settings_mode_all">"Все"</string>

View file

@ -31,6 +31,7 @@ Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť."</stri
<string name="screen_notification_settings_enable_notifications">"Povoliť oznámenia na tomto zariadení"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Konfigurácia nebola opravená, skúste to prosím znova."</string>
<string name="screen_notification_settings_group_chats">"Skupinové rozhovory"</string>
<string name="screen_notification_settings_invite_for_me_label">"Pozvánky"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v niektorých miestnostiach nemusíte dostať upozornenie."</string>
<string name="screen_notification_settings_mentions_section_title">"Zmienky"</string>
<string name="screen_notification_settings_mode_all">"Všetky"</string>

View file

@ -19,7 +19,7 @@ package io.element.android.features.preferences.impl.notifications
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@ -44,13 +44,13 @@ class EditDefaultNotificationSettingsPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.mode).isNull()
Truth.assertThat(initialState.isOneToOne).isFalse()
assertThat(initialState.mode).isNull()
assertThat(initialState.isOneToOne).isFalse()
val loadedState = consumeItemsUntilPredicate {
it.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
}.last()
Truth.assertThat(loadedState.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
assertThat(loadedState.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
}
}
@ -73,7 +73,7 @@ class EditDefaultNotificationSettingsPresenterTests {
val loadedState = consumeItemsUntilPredicate { state ->
state.roomsWithUserDefinedMode.any { it.details.notificationMode == RoomNotificationMode.ALL_MESSAGES }
}.last()
Truth.assertThat(loadedState.roomsWithUserDefinedMode.any { it.details.notificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue()
assertThat(loadedState.roomsWithUserDefinedMode.any { it.details.notificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue()
}
}
@ -87,7 +87,7 @@ class EditDefaultNotificationSettingsPresenterTests {
val loadedState = consumeItemsUntilPredicate {
it.mode == RoomNotificationMode.ALL_MESSAGES
}.last()
Truth.assertThat(loadedState.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
assertThat(loadedState.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
}
}
@ -103,12 +103,12 @@ class EditDefaultNotificationSettingsPresenterTests {
val errorState = consumeItemsUntilPredicate {
it.changeNotificationSettingAction.isFailure()
}.last()
Truth.assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue()
assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue()
errorState.eventSink(EditDefaultNotificationSettingStateEvents.ClearError)
val clearErrorState = consumeItemsUntilPredicate {
it.changeNotificationSettingAction.isUninitialized()
}.last()
Truth.assertThat(clearErrorState.changeNotificationSettingAction.isUninitialized()).isTrue()
assertThat(clearErrorState.changeNotificationSettingAction.isUninitialized()).isTrue()
}
}

View file

@ -20,7 +20,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.FakeMatrixClient
@ -38,20 +38,20 @@ class NotificationSettingsPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
Truth.assertThat(initialState.appSettings.appNotificationsEnabled).isFalse()
Truth.assertThat(initialState.appSettings.systemNotificationsEnabled).isTrue()
Truth.assertThat(initialState.matrixSettings).isEqualTo(NotificationSettingsState.MatrixSettings.Uninitialized)
assertThat(initialState.appSettings.appNotificationsEnabled).isFalse()
assertThat(initialState.appSettings.systemNotificationsEnabled).isTrue()
assertThat(initialState.matrixSettings).isEqualTo(NotificationSettingsState.MatrixSettings.Uninitialized)
val loadedState = consumeItemsUntilPredicate {
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
}.last()
Truth.assertThat(loadedState.appSettings.appNotificationsEnabled).isTrue()
Truth.assertThat(loadedState.appSettings.systemNotificationsEnabled).isTrue()
assertThat(loadedState.appSettings.appNotificationsEnabled).isTrue()
assertThat(loadedState.appSettings.systemNotificationsEnabled).isTrue()
val valid = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
Truth.assertThat(valid?.atRoomNotificationsEnabled).isFalse()
Truth.assertThat(valid?.callNotificationsEnabled).isFalse()
Truth.assertThat(valid?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
Truth.assertThat(valid?.defaultOneToOneNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
assertThat(valid?.atRoomNotificationsEnabled).isFalse()
assertThat(valid?.callNotificationsEnabled).isFalse()
assertThat(valid?.inviteForMeNotificationsEnabled).isFalse()
assertThat(valid?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
assertThat(valid?.defaultOneToOneNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
cancelAndIgnoreRemainingEvents()
}
}
@ -63,7 +63,6 @@ class NotificationSettingsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false, mode = RoomNotificationMode.ALL_MESSAGES)
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false, mode = RoomNotificationMode.ALL_MESSAGES)
val updatedState = consumeItemsUntilPredicate {
@ -71,7 +70,7 @@ class NotificationSettingsPresenterTests {
?.defaultGroupNotificationMode == RoomNotificationMode.ALL_MESSAGES
}.last()
val valid = updatedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
Truth.assertThat(valid?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
assertThat(valid?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
}
}
@ -82,7 +81,6 @@ class NotificationSettingsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
notificationSettingsService.setDefaultRoomNotificationMode(
isEncrypted = true,
isOneToOne = false,
@ -96,7 +94,7 @@ class NotificationSettingsPresenterTests {
val updatedState = consumeItemsUntilPredicate {
it.matrixSettings is NotificationSettingsState.MatrixSettings.Invalid
}.last()
Truth.assertThat(updatedState.matrixSettings).isEqualTo(NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false))
assertThat(updatedState.matrixSettings).isEqualTo(NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false))
}
}
@ -118,9 +116,8 @@ class NotificationSettingsPresenterTests {
val fixedState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) {
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
}.last()
val fixedMatrixState = fixedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
Truth.assertThat(fixedMatrixState?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
assertThat(fixedMatrixState?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
}
}
@ -133,13 +130,12 @@ class NotificationSettingsPresenterTests {
val loadedState = consumeItemsUntilPredicate {
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
}.last()
Truth.assertThat(loadedState.appSettings.appNotificationsEnabled).isTrue()
assertThat(loadedState.appSettings.appNotificationsEnabled).isTrue()
loadedState.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(false))
val updatedState = consumeItemsUntilPredicate {
!it.appSettings.appNotificationsEnabled
}.last()
Truth.assertThat(updatedState.appSettings.appNotificationsEnabled).isFalse()
assertThat(updatedState.appSettings.appNotificationsEnabled).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@ -154,14 +150,34 @@ class NotificationSettingsPresenterTests {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.callNotificationsEnabled == false
}.last()
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
Truth.assertThat(validMatrixState?.callNotificationsEnabled).isFalse()
assertThat(validMatrixState?.callNotificationsEnabled).isFalse()
loadedState.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(true))
val updatedState = consumeItemsUntilPredicate {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.callNotificationsEnabled == true
}.last()
val updatedMatrixState = updatedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
Truth.assertThat(updatedMatrixState?.callNotificationsEnabled).isTrue()
assertThat(updatedMatrixState?.callNotificationsEnabled).isTrue()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - set invite for me notifications enabled`() = runTest {
val presenter = createNotificationSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val loadedState = consumeItemsUntilPredicate {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.inviteForMeNotificationsEnabled == false
}.last()
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(validMatrixState?.inviteForMeNotificationsEnabled).isFalse()
loadedState.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(true))
val updatedState = consumeItemsUntilPredicate {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.inviteForMeNotificationsEnabled == true
}.last()
val updatedMatrixState = updatedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(updatedMatrixState?.inviteForMeNotificationsEnabled).isTrue()
cancelAndIgnoreRemainingEvents()
}
}
@ -176,14 +192,13 @@ class NotificationSettingsPresenterTests {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == false
}.last()
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
Truth.assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse()
assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse()
loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true))
val updatedState = consumeItemsUntilPredicate {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == true
}.last()
val updatedMatrixState = updatedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
Truth.assertThat(updatedMatrixState?.atRoomNotificationsEnabled).isTrue()
assertThat(updatedMatrixState?.atRoomNotificationsEnabled).isTrue()
cancelAndIgnoreRemainingEvents()
}
}
@ -200,19 +215,17 @@ class NotificationSettingsPresenterTests {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == false
}.last()
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
Truth.assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse()
assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse()
loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true))
val errorState = consumeItemsUntilPredicate {
it.changeNotificationSettingAction.isFailure()
}.last()
Truth.assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue()
assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue()
errorState.eventSink(NotificationSettingsEvents.ClearNotificationChangeError)
val clearErrorState = consumeItemsUntilPredicate {
it.changeNotificationSettingAction.isUninitialized()
}.last()
Truth.assertThat(clearErrorState.changeNotificationSettingAction.isUninitialized()).isTrue()
assertThat(clearErrorState.changeNotificationSettingAction.isUninitialized()).isTrue()
cancelAndIgnoreRemainingEvents()
}
}

View file

@ -73,8 +73,8 @@ class PreferencesRootPresenterTest {
avatarUrl = AN_AVATAR_URL
)
)
assertThat(loadedState.showDeveloperSettings).isEqualTo(true)
assertThat(loadedState.showAnalyticsSettings).isEqualTo(false)
assertThat(loadedState.showDeveloperSettings).isTrue()
assertThat(loadedState.showAnalyticsSettings).isFalse()
assertThat(loadedState.accountManagementUrl).isNull()
assertThat(loadedState.devicesManagementUrl).isNull()
}

View file

@ -47,4 +47,9 @@ interface BugReporter {
* Provide the log directory.
*/
fun logDirectory(): File
/**
* Set the current tracing filter.
*/
fun setCurrentTracingFilter(tracingFilter: String)
}

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"%1$s ist bei der letzten Nutzung abgestürzt. Möchtest du einen Absturzbericht mit uns teilen?"</string>
<string name="rageshake_detection_dialog_content">"Du scheinst das Telefon aus Frustration zu schütteln. Möchtest du den Bildschirm für den Fehlerbericht öffnen?"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Erkennungsschwelle"</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"Az %1$s összeomlott a legutóbbi használata óta. Megosztja velünk az összeomlás-jelentést?"</string>
<string name="rageshake_detection_dialog_content">"Úgy tűnik, mintha mérgében a telefont rázná. Megnyitja a hibajelentési képernyőt?"</string>
<string name="settings_rageshake">"Ideges rázás"</string>
<string name="settings_rageshake_detection_threshold">"Észlelési küszöb"</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"%1$s mengalami kemogokan saat terakhir kali digunakan. Apakah Anda ingin berbagi laporan kerusakan dengan kami?"</string>
<string name="rageshake_detection_dialog_content">"Anda tampaknya mengguncang telepon karena frustrasi. Apakah Anda ingin membuka layar laporan kutu?"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Ambang batas deteksi"</string>
</resources>

View file

@ -136,7 +136,7 @@ class VectorFileLogger(
*
* @return The list of files with logs.
*/
fun getLogFiles(): List<File> {
private fun getLogFiles(): List<File> {
return tryOrNull(
onError = { Timber.e(it, "## getLogFiles() failed") }
) {

View file

@ -35,6 +35,7 @@ import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.services.toolbox.api.systemclock.SystemClock
@ -63,6 +64,7 @@ import javax.inject.Provider
/**
* BugReporter creates and sends the bug reports.
*/
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class DefaultBugReporter @Inject constructor(
@ApplicationContext private val context: Context,
@ -86,10 +88,10 @@ class DefaultBugReporter @Inject constructor(
// the pending bug report call
private var bugReportCall: Call? = null
// boolean to cancel the bug report
private val isCancelled = false
private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*")
private var currentTracingFilter: String? = null
override suspend fun sendBugReport(
withDevicesLogs: Boolean,
@ -153,6 +155,9 @@ class DefaultBugReporter @Inject constructor(
.addFormDataPart("device_id", deviceId)
.addFormDataPart("device", Build.MODEL.trim())
.addFormDataPart("locale", Locale.getDefault().toString())
currentTracingFilter?.let {
builder.addFormDataPart("tracing_filter", it)
}
// add the gzipped files, don't cancel the whole upload if only some file failed to upload
var uploadedSomeLogs = false
@ -323,6 +328,10 @@ class DefaultBugReporter @Inject constructor(
}
}
override fun setCurrentTracingFilter(tracingFilter: String) {
currentTracingFilter = tracingFilter
}
/**
* @return the files on the log directory.
*/

View file

@ -4,7 +4,7 @@
<string name="screen_bug_report_contact_me">"Sie können mich kontaktieren, wenn Sie weitere Fragen haben."</string>
<string name="screen_bug_report_contact_me_title">"Kontaktieren Sie mich"</string>
<string name="screen_bug_report_edit_screenshot">"Bildschirmfoto bearbeiten"</string>
<string name="screen_bug_report_editor_description">"Bitte beschreibe den Fehler. Was hast du getan? Was hast du erwartet, was passiert? Was ist tatsächlich passiert. Bitte gehe so detailliert wie möglich vor."</string>
<string name="screen_bug_report_editor_description">"Bitte beschreibe den Fehler. Was hast du getan? Was hast du erwartet, was passiert? Was ist tatsächlich passiert? Bitte gehe so detailliert wie möglich vor."</string>
<string name="screen_bug_report_editor_placeholder">"Beschreibe den Fehler…"</string>
<string name="screen_bug_report_editor_supporting">"Wenn möglich, verfasse die Beschreibung bitte auf Englisch."</string>
<string name="screen_bug_report_include_crash_logs">"Absturzprotokolle senden"</string>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_bug_report_attach_screenshot">"Képernyőkép mellékelése"</string>
<string name="screen_bug_report_contact_me">"Felveheti velem a kapcsolatot, ha bármilyen további kérdése van."</string>
<string name="screen_bug_report_contact_me_title">"Kapcsolat"</string>
<string name="screen_bug_report_edit_screenshot">"Képernyőkép szerkesztése"</string>
<string name="screen_bug_report_editor_description">"Írja le a hibát. Mit csinált? Mire számított, hogy mi fog történni? Mi történt valójában? Fogalmazzon a lehető legrészletesebben."</string>
<string name="screen_bug_report_editor_placeholder">"Írja le a problémát…"</string>
<string name="screen_bug_report_editor_supporting">"Ha lehetséges, a leírást angolul írja meg."</string>
<string name="screen_bug_report_include_crash_logs">"Összeomlásnaplók küldése"</string>
<string name="screen_bug_report_include_logs">"Naplók engedélyezése"</string>
<string name="screen_bug_report_include_screenshot">"Képernyőkép küldése"</string>
<string name="screen_bug_report_logs_description">"A naplók szerepelni fognak az üzenetben, hogy megbizonyosodhassunk arról, hogy minden megfelelően működik-e. Ha naplók nélkül szeretné elküldeni az üzenetet, akkor kapcsolja ki ezt a beállítást."</string>
<string name="screen_bug_report_rash_logs_alert_title">"Az %1$s összeomlott a legutóbbi használata óta. Megosztja velünk az összeomlás-jelentést?"</string>
</resources>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_bug_report_attach_screenshot">"Lampirkan tangkapan layar"</string>
<string name="screen_bug_report_contact_me">"Anda dapat menghubungi saya jika Anda memiliki pertanyaan lebih lanjut."</string>
<string name="screen_bug_report_contact_me_title">"Hubungi saya"</string>
<string name="screen_bug_report_edit_screenshot">"Sunting tangkapan layar"</string>
<string name="screen_bug_report_editor_description">"Silakan jelaskan masalah tersebut. Apa yang Anda lakukan? Apa yang Anda harapkan untuk terjadi? Apa yang sebenarnya terjadi? Jelaskan sedetail mungkin."</string>
<string name="screen_bug_report_editor_placeholder">"Jelaskan masalah tersebut…"</string>
<string name="screen_bug_report_editor_supporting">"Jika memungkinkan, silakan tulis deskripsi dalam bahasa Inggris."</string>
<string name="screen_bug_report_include_crash_logs">"Kirim log kerusakan"</string>
<string name="screen_bug_report_include_logs">"Izinkan log"</string>
<string name="screen_bug_report_include_screenshot">"Kirim tangkapan layar"</string>
<string name="screen_bug_report_logs_description">"Log akan disertakan dengan pesan Anda untuk memastikan bahwa semuanya berfungsi dengan baik. Untuk mengirimkan pesan Anda tanpa log, matikan pengaturan ini."</string>
<string name="screen_bug_report_rash_logs_alert_title">"%1$s mengalami kemogokan saat terakhir kali digunakan. Apakah Anda ingin berbagi laporan kerusakan dengan kami?"</string>
</resources>

View file

@ -59,6 +59,10 @@ class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Succes
override fun logDirectory(): File {
return File("fake")
}
override fun setCurrentTracingFilter(tracingFilter: String) {
// No op
}
}
enum class FakeBugReporterMode {

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 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.rageshake.impl.logs
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
@RunWith(RobolectricTestRunner::class)
class VectorFileLoggerTest {
@Test
fun `init VectorFileLogger log debug`() = runTest {
val sut = createVectorFileLogger()
sut.d("A debug log")
}
@Test
fun `init VectorFileLogger log error`() = runTest {
val sut = createVectorFileLogger()
sut.e(A_THROWABLE, "A debug log")
}
@Test
fun `reset VectorFileLogger`() = runTest {
val sut = createVectorFileLogger()
sut.reset()
}
@Test
fun `check getFromTimber`() {
assertThat(VectorFileLogger.getFromTimber()).isNull()
}
private fun TestScope.createVectorFileLogger() = VectorFileLogger(
context = RuntimeEnvironment.getApplication(),
dispatcher = testCoroutineDispatchers().io,
)
}

View file

@ -22,6 +22,7 @@ import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.parcelize.Parcelize
@ -42,6 +43,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
interface Callback : Plugin {
fun onOpenGlobalNotificationSettings()
fun onOpenRoom(roomId: RoomId)
}
interface NodeBuilder {

Some files were not shown because too many files have changed in this diff Show more