Merge branch 'release/0.7.4' into main
This commit is contained in:
commit
e56777600a
715 changed files with 5113 additions and 3469 deletions
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [debug, release, nightly, samples]
|
||||
variant: [debug, release, nightly]
|
||||
fail-fast: false
|
||||
# Allow all jobs on develop. Just one per PR.
|
||||
concurrency:
|
||||
|
|
@ -82,6 +82,3 @@ jobs:
|
|||
- name: Compile nightly sources
|
||||
if: ${{ matrix.variant == 'nightly' }}
|
||||
run: ./gradlew compileGplayNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Compile samples minimal
|
||||
if: ${{ matrix.variant == 'samples' }}
|
||||
run: ./gradlew :samples:minimal:assemble $CI_GRADLE_ARG_PROPERTIES
|
||||
|
|
|
|||
2
.github/workflows/maestro.yml
vendored
2
.github/workflows/maestro.yml
vendored
|
|
@ -79,7 +79,7 @@ jobs:
|
|||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: elementx-apk-maestro
|
||||
- uses: mobile-dev-inc/action-maestro-cloud@v1.9.4
|
||||
- uses: mobile-dev-inc/action-maestro-cloud@v1.9.6
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
|
||||
|
|
|
|||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -82,7 +82,7 @@ jobs:
|
|||
|
||||
# https://github.com/codecov/codecov-action
|
||||
- name: ☂️ Upload coverage reports to codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
|
|||
62
CHANGES.md
62
CHANGES.md
|
|
@ -1,3 +1,65 @@
|
|||
Changes in Element X v0.7.3 (2024-11-08)
|
||||
========================================
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Incoming session verification by @bmarty in https://github.com/element-hq/element-x-android/pull/3733
|
||||
* Remove all GPS metadata from images uploaded as media by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3781
|
||||
* Send caption with image and video by @bmarty in https://github.com/element-hq/element-x-android/pull/3803
|
||||
### 🙌 Improvements
|
||||
* UI iteration on the encryption settings by @bmarty in https://github.com/element-hq/element-x-android/pull/3750
|
||||
* Rotate firebase token in case of error by @bmarty in https://github.com/element-hq/element-x-android/pull/3755
|
||||
* Optimize media upload by @bmarty in https://github.com/element-hq/element-x-android/pull/3779
|
||||
* Iteration on caption by @bmarty in https://github.com/element-hq/element-x-android/pull/3816
|
||||
* Hide join call button when the user is already in the call by @bmarty in https://github.com/element-hq/element-x-android/pull/3815
|
||||
* Disable button during the "verifying" step. by @bmarty in https://github.com/element-hq/element-x-android/pull/3832
|
||||
### 🐛 Bugfixes
|
||||
* Fix oversize padding on captioned images/videos by @frebib in https://github.com/element-hq/element-x-android/pull/3732
|
||||
* Fix the onboarding flow getting stuck in some cases by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3778
|
||||
* bugfix: do not remove logs after sending them by @ganfra in https://github.com/element-hq/element-x-android/pull/3780
|
||||
* Use in-memory thumbnail APIs when possible by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3817
|
||||
* ElementCall: allow user to switch to another call. by @bmarty in https://github.com/element-hq/element-x-android/pull/3833
|
||||
* Do not delete the original file if it's not a temporary file when sending it to a room. by @bmarty in https://github.com/element-hq/element-x-android/pull/3819
|
||||
* Fix verification failed issue, simplify verification logic by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3830
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3798
|
||||
### 🧱 Build
|
||||
* Target api 35 by @bmarty in https://github.com/element-hq/element-x-android/pull/3776
|
||||
### 🚧 In development 🚧
|
||||
* Knocking : update create room flow by @ganfra in https://github.com/element-hq/element-x-android/pull/3804
|
||||
### Dependency upgrades
|
||||
* Update dependency io.nlopez.compose.rules:detekt to v0.4.17 by @renovate in https://github.com/element-hq/element-x-android/pull/3746
|
||||
* Update dependency com.posthog:posthog-android to v3.8.3 - autoclosed by @renovate in https://github.com/element-hq/element-x-android/pull/3742
|
||||
* Update dependency org.maplibre.gl:android-plugin-annotation-v9 to v3.0.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3702
|
||||
* Update dependency com.posthog:posthog-android to v3.9.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3754
|
||||
* Update kotlin by @renovate in https://github.com/element-hq/element-x-android/pull/3283
|
||||
* Update camera to v1.4.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3765
|
||||
* Update dependencyAnalysis to v2.4.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3773
|
||||
* Update kotlin to v2.0.21-1.0.26 by @renovate in https://github.com/element-hq/element-x-android/pull/3774
|
||||
* Update dependency androidx.annotation:annotation-jvm to v1.9.1 - autoclosed by @renovate in https://github.com/element-hq/element-x-android/pull/3762
|
||||
* chore(deps): update dependencyanalysis to v2.4.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3791
|
||||
* fix(deps): update dependency androidx.compose:compose-bom to v2024.10.01 by @renovate in https://github.com/element-hq/element-x-android/pull/3782
|
||||
* Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3770
|
||||
* fix(deps): update dependency androidx.constraintlayout:constraintlayout to v2.2.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3784
|
||||
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v0.2.59 by @renovate in https://github.com/element-hq/element-x-android/pull/3809
|
||||
* Update mobile-dev-inc/action-maestro-cloud action to v1.9.4 by @renovate in https://github.com/element-hq/element-x-android/pull/3820
|
||||
* Update dependency com.otaliastudios:transcoder to v0.11.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3805
|
||||
* Update plugin paparazzi to v1.3.5 by @renovate in https://github.com/element-hq/element-x-android/pull/3826
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.60 by @renovate in https://github.com/element-hq/element-x-android/pull/3827
|
||||
### Others
|
||||
* Change wording to "Verify identity" by @bmarty in https://github.com/element-hq/element-x-android/pull/3751
|
||||
* Improve FakeMatrixRoom to be able to check all the parameters. by @bmarty in https://github.com/element-hq/element-x-android/pull/3761
|
||||
* Editor state fixture and preview improvement by @bmarty in https://github.com/element-hq/element-x-android/pull/3758
|
||||
* Enable identity pinning violation notifications unconditionally by @andybalaam in https://github.com/element-hq/element-x-android/pull/3745
|
||||
* Enable predictive back gesture by @frebib in https://github.com/element-hq/element-x-android/pull/3797
|
||||
* Update project status by @mxandreas in https://github.com/element-hq/element-x-android/pull/3806
|
||||
* Remove code duplication - no behavior change. by @bmarty in https://github.com/element-hq/element-x-android/pull/3823
|
||||
* Verification UI / UX iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/3829
|
||||
|
||||
## New Contributors
|
||||
* @andybalaam made their first contribution in https://github.com/element-hq/element-x-android/pull/3745
|
||||
* @mxandreas made their first contribution in https://github.com/element-hq/element-x-android/pull/3806
|
||||
|
||||
Changes in Element X v0.7.2 (2024-10-29)
|
||||
========================================
|
||||
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@ Please ensure that you're using the project formatting rules (which are in the p
|
|||
|
||||
This project should compile without any special action. Just clone it and open it with Android Studio, or compile from command line using `gradlew`.
|
||||
|
||||
Note: please make sure that the configuration is `app` and not `samples.minimal`.
|
||||
|
||||
## Strings
|
||||
|
||||
The strings of the project are managed externally using [https://localazy.com](https://localazy.com) and shared with Element X iOS.
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class MainActivity : NodeActivity() {
|
|||
|
||||
@Composable
|
||||
private fun MainNodeHost() {
|
||||
NodeHost(integrationPoint = appyxIntegrationPoint) {
|
||||
NodeHost(integrationPoint = appyxV1IntegrationPoint) {
|
||||
MainNode(
|
||||
it,
|
||||
plugins = listOf(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<locale android:name="es"/>
|
||||
<locale android:name="et"/>
|
||||
<locale android:name="fa"/>
|
||||
<locale android:name="fi"/>
|
||||
<locale android:name="fr"/>
|
||||
<locale android:name="hu"/>
|
||||
<locale android:name="in"/>
|
||||
|
|
|
|||
|
|
@ -142,12 +142,12 @@ class LoggedInPresenter @Inject constructor(
|
|||
.also { Timber.tag(pusherTag.value).w("No distributors available") }
|
||||
.also {
|
||||
// In this case, consider the push provider is chosen.
|
||||
pushService.selectPushProvider(matrixClient, pushProvider)
|
||||
pushService.selectPushProvider(matrixClient.sessionId, pushProvider)
|
||||
}
|
||||
.also { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable()) }
|
||||
pushService.registerWith(matrixClient, pushProvider, distributor)
|
||||
} else {
|
||||
val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient)
|
||||
val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient.sessionId)
|
||||
if (currentPushDistributor == null) {
|
||||
Timber.tag(pusherTag.value).d("Register with the first available distributor")
|
||||
val distributor = currentPushProvider.getDistributors().firstOrNull()
|
||||
|
|
|
|||
5
appnav/src/main/res/values-fi/translations.xml
Normal file
5
appnav/src/main/res/values-fi/translations.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Kirjaudu Ulos & Päivitä"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Kotipalvelimesi ei enää tue vanhaa protokollaa. Kirjaudu ulos ja takaisin sisään jatkaaksesi sovelluksen käyttöä."</string>
|
||||
</resources>
|
||||
5
appnav/src/main/res/values-uk/translations.xml
Normal file
5
appnav/src/main/res/values-uk/translations.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Вийти та оновити"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Ваш домашній сервер більше не підтримує старий протокол. Будь ласка, вийдіть і увійдіть знову, щоб продовжити використання програми."</string>
|
||||
</resources>
|
||||
|
|
@ -378,7 +378,7 @@ class LoggedInPresenterTest {
|
|||
val lambda = lambdaRecorder<MatrixClient, PushProvider, Distributor, Result<Unit>> { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val selectPushProviderLambda = lambdaRecorder<MatrixClient, PushProvider, Unit> { _, _ -> }
|
||||
val selectPushProviderLambda = lambdaRecorder<SessionId, PushProvider, Unit> { _, _ -> }
|
||||
val sessionVerificationService = FakeSessionVerificationService(
|
||||
initialSessionVerifiedStatus = SessionVerifiedStatus.Verified
|
||||
)
|
||||
|
|
@ -408,8 +408,8 @@ class LoggedInPresenterTest {
|
|||
selectPushProviderLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
// MatrixClient
|
||||
any(),
|
||||
// SessionId
|
||||
value(A_SESSION_ID),
|
||||
// PushProvider
|
||||
value(pushProvider),
|
||||
)
|
||||
|
|
@ -481,7 +481,7 @@ class LoggedInPresenterTest {
|
|||
registerWithLambda: (MatrixClient, PushProvider, Distributor) -> Result<Unit> = { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
},
|
||||
selectPushProviderLambda: (MatrixClient, PushProvider) -> Unit = { _, _ -> lambdaError() },
|
||||
selectPushProviderLambda: (SessionId, PushProvider) -> Unit = { _, _ -> lambdaError() },
|
||||
currentPushProvider: () -> PushProvider? = { null },
|
||||
setIgnoreRegistrationErrorLambda: (SessionId, Boolean) -> Unit = { _, _ -> lambdaError() },
|
||||
): PushService {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ allprojects {
|
|||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||
}
|
||||
dependencies {
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.17")
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.19")
|
||||
}
|
||||
|
||||
// KtLint
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ We want:
|
|||
|
||||
The CI checks that:
|
||||
|
||||
1. The code is compiling, without any warnings, for all the app build types and variants and for the minimal app
|
||||
1. The code is compiling, without any warnings, for all the app build types and variants
|
||||
2. The tests are passing
|
||||
3. The code quality is good (detekt, ktlint, lint)
|
||||
4. The code is running and smoke tests are passing (maestro)
|
||||
|
|
|
|||
2
fastlane/metadata/android/en-US/changelogs/40007040.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40007040.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: bug fixes.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"Jaa anonyymejä käyttötietoja auttaaksesi meitä tunnistamaan ongelmat."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Voit lukea kaikki ehtomme %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"täällä"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Jaa analytiikkatietoja"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Emme tallenna tai profiloi henkilötietoja"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Jaa anonyymejä käyttötietoja auttaaksesi meitä tunnistamaan ongelmat."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Voit lukea kaikki ehtomme %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"täällä"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Voit poistaa tämän käytöstä milloin tahansa"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Emme jaa tietojasi kolmansien osapuolien kanssa"</string>
|
||||
<string name="screen_analytics_prompt_title">"Auta parantamaan %1$s -sovellusta"</string>
|
||||
</resources>
|
||||
|
|
@ -5,6 +5,6 @@
|
|||
<string name="screen_analytics_prompt_read_terms">"Ви можете прочитати всі наші умови %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"тут"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Ви можете вимкнути цю функцію в будь-який час"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Ми не передаватимемо Ваші дані третім особам"</string>
|
||||
<string name="screen_analytics_prompt_title">"Допоможіть покращити %1$s"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Ми не передаватимемо ваші дані третім особам"</string>
|
||||
<string name="screen_analytics_prompt_title">"Допоможіть вдосконалити %1$s"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.call.impl.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
|
|
@ -56,11 +57,6 @@ interface ActiveCallManager {
|
|||
*/
|
||||
fun registerIncomingCall(notificationData: CallNotificationData)
|
||||
|
||||
/**
|
||||
* Called when the incoming call timed out. It will remove the active call and remove any associated UI, adding a 'missed call' notification.
|
||||
*/
|
||||
fun incomingCallTimedOut()
|
||||
|
||||
/**
|
||||
* Called when the active call has been hung up. It will remove any existing UI and the active call.
|
||||
* @param callType The type of call that the user hung up, either an external url one or a room one.
|
||||
|
|
@ -113,18 +109,24 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
|
||||
// Wait for the ringing call to time out
|
||||
delay(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds)
|
||||
incomingCallTimedOut()
|
||||
incomingCallTimedOut(displayMissedCallNotification = true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun incomingCallTimedOut() {
|
||||
/**
|
||||
* Called when the incoming call timed out. It will remove the active call and remove any associated UI, adding a 'missed call' notification.
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
fun incomingCallTimedOut(displayMissedCallNotification: Boolean) {
|
||||
val previousActiveCall = activeCall.value ?: return
|
||||
val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return
|
||||
activeCall.value = null
|
||||
|
||||
cancelIncomingCallNotification()
|
||||
|
||||
displayMissedCallNotification(notificationData)
|
||||
if (displayMissedCallNotification) {
|
||||
displayMissedCallNotification(notificationData)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hungUpCall(callType: CallType) {
|
||||
|
|
@ -186,28 +188,35 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun observeRingingCall() {
|
||||
// This will observe ringing calls and ensure they're terminated if the room call is cancelled
|
||||
// This will observe ringing calls and ensure they're terminated if the room call is cancelled or if the user
|
||||
// has joined the call from another session.
|
||||
activeCall
|
||||
.filterNotNull()
|
||||
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
|
||||
.flatMapLatest { activeCall ->
|
||||
val callType = activeCall.callType as CallType.RoomCall
|
||||
// Get a flow of updated `hasRoomCall` values for the room
|
||||
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
|
||||
matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()
|
||||
?.getRoom(callType.roomId)
|
||||
?.roomInfoFlow
|
||||
?.map { it.hasRoomCall }
|
||||
?.map {
|
||||
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
|
||||
}
|
||||
?: flowOf()
|
||||
}
|
||||
// We only want to check if the room active call status changes
|
||||
.distinctUntilChanged()
|
||||
// Skip the first one, we're not interested in it (if the check below passes, it had to be active anyway)
|
||||
.drop(1)
|
||||
.onEach { roomHasActiveCall ->
|
||||
.onEach { (roomHasActiveCall, userIsInTheCall) ->
|
||||
if (!roomHasActiveCall) {
|
||||
// The call was cancelled
|
||||
timedOutCallJob?.cancel()
|
||||
incomingCallTimedOut()
|
||||
incomingCallTimedOut(displayMissedCallNotification = true)
|
||||
} else if (userIsInTheCall) {
|
||||
// The user joined the call from another session
|
||||
timedOutCallJob?.cancel()
|
||||
incomingCallTimedOut(displayMissedCallNotification = false)
|
||||
}
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
<?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">"Käynnissä oleva puhelu"</string>
|
||||
<string name="call_foreground_service_message_android">"Palaa puheluun napauttamalla"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Puhelu käynnissä"</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Saapuva Element Call -puhelu"</string>
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="call_foreground_service_channel_title_android">"Поточний дзвінок"</string>
|
||||
<string name="call_foreground_service_message_android">"Натисніть, щоб повернутися до виклику"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Триває дзвінок"</string>
|
||||
<string name="call_foreground_service_channel_title_android">"Поточний виклик"</string>
|
||||
<string name="call_foreground_service_message_android">"Торкніться, щоб повернутися до виклику"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Триває виклик"</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Вхідний виклик Element"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class DefaultActiveCallManagerTest {
|
|||
onMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(addMissedCallNotificationLambda = addMissedCallNotificationLambda)
|
||||
)
|
||||
|
||||
manager.incomingCallTimedOut()
|
||||
manager.incomingCallTimedOut(displayMissedCallNotification = true)
|
||||
|
||||
addMissedCallNotificationLambda.assertions().isNeverCalled()
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ class DefaultActiveCallManagerTest {
|
|||
manager.registerIncomingCall(aCallNotificationData())
|
||||
assertThat(manager.activeCall.value).isNotNull()
|
||||
|
||||
manager.incomingCallTimedOut()
|
||||
manager.incomingCallTimedOut(displayMissedCallNotification = true)
|
||||
advanceTimeBy(1)
|
||||
|
||||
assertThat(manager.activeCall.value).isNull()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
|
||||
class FakeActiveCallManager(
|
||||
var registerIncomingCallResult: (CallNotificationData) -> Unit = {},
|
||||
var incomingCallTimedOutResult: () -> Unit = {},
|
||||
var hungUpCallResult: (CallType) -> Unit = {},
|
||||
var joinedCallResult: (CallType) -> Unit = {},
|
||||
) : ActiveCallManager {
|
||||
|
|
@ -25,10 +24,6 @@ class FakeActiveCallManager(
|
|||
registerIncomingCallResult(notificationData)
|
||||
}
|
||||
|
||||
override fun incomingCallTimedOut() {
|
||||
incomingCallTimedOutResult()
|
||||
}
|
||||
|
||||
override fun hungUpCall(callType: CallType) {
|
||||
hungUpCallResult(callType)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,4 @@ data class CreateRoomConfig(
|
|||
val avatarUri: Uri? = null,
|
||||
val invites: ImmutableList<MatrixUser> = persistentListOf(),
|
||||
val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private,
|
||||
) {
|
||||
val isValid = roomName.isNullOrEmpty().not() && roomVisibility.isValid()
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ import android.net.Uri
|
|||
import io.element.android.features.createroom.impl.configureroom.RoomAccess
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomAccessItem
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomAddress
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomAddressErrorState
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
|
||||
import io.element.android.features.createroom.impl.di.CreateRoomScope
|
||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -29,6 +29,7 @@ import javax.inject.Inject
|
|||
@SingleIn(CreateRoomScope::class)
|
||||
class CreateRoomDataStore @Inject constructor(
|
||||
val selectedUserListDataStore: UserListDataStore,
|
||||
private val roomAliasHelper: RoomAliasHelper,
|
||||
) {
|
||||
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
|
||||
private var cachedAvatarUri: Uri? = null
|
||||
|
|
@ -46,13 +47,13 @@ class CreateRoomDataStore @Inject constructor(
|
|||
|
||||
fun setRoomName(roomName: String) {
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
/*
|
||||
val newVisibility = when (config.roomVisibility) {
|
||||
is RoomVisibilityState.Public -> {
|
||||
val roomAddress = config.roomVisibility.roomAddress
|
||||
if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) {
|
||||
val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(roomName)
|
||||
config.roomVisibility.copy(
|
||||
roomAddress = RoomAddress.AutoFilled(roomName),
|
||||
roomAddress = RoomAddress.AutoFilled(roomAliasName),
|
||||
)
|
||||
} else {
|
||||
config.roomVisibility
|
||||
|
|
@ -60,9 +61,9 @@ class CreateRoomDataStore @Inject constructor(
|
|||
}
|
||||
else -> config.roomVisibility
|
||||
}
|
||||
*/
|
||||
config.copy(
|
||||
roomName = roomName.takeIf { it.isNotEmpty() },
|
||||
roomVisibility = newVisibility,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -85,11 +86,13 @@ class CreateRoomDataStore @Inject constructor(
|
|||
config.copy(
|
||||
roomVisibility = when (visibility) {
|
||||
RoomVisibilityItem.Private -> RoomVisibilityState.Private
|
||||
RoomVisibilityItem.Public -> RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled(config.roomName.orEmpty()),
|
||||
roomAddressErrorState = RoomAddressErrorState.None,
|
||||
roomAccess = RoomAccess.Anyone,
|
||||
)
|
||||
RoomVisibilityItem.Public -> {
|
||||
val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(config.roomName.orEmpty())
|
||||
RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled(roomAliasName),
|
||||
roomAccess = RoomAccess.Anyone,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
||||
|
|
@ -31,6 +32,8 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
|||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||
import io.element.android.libraries.matrix.api.roomAliasFromName
|
||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||
|
|
@ -39,9 +42,12 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter
|
|||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Optional
|
||||
import javax.inject.Inject
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class ConfigureRoomPresenter @Inject constructor(
|
||||
private val dataStore: CreateRoomDataStore,
|
||||
|
|
@ -51,6 +57,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
private val analyticsService: AnalyticsService,
|
||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val roomAliasHelper: RoomAliasHelper,
|
||||
) : Presenter<ConfigureRoomState> {
|
||||
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
|
||||
private var pendingPermissionRequest = false
|
||||
|
|
@ -58,9 +65,12 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
@Composable
|
||||
override fun present(): ConfigureRoomState {
|
||||
val cameraPermissionState = cameraPermissionPresenter.present()
|
||||
val createRoomConfig = dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig())
|
||||
val createRoomConfig by dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig())
|
||||
val homeserverName = remember { matrixClient.userIdServerName() }
|
||||
val isKnockFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(initial = false)
|
||||
val roomAddressValidity = remember {
|
||||
mutableStateOf<RoomAddressValidity>(RoomAddressValidity.Unknown)
|
||||
}
|
||||
|
||||
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
|
||||
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) },
|
||||
|
|
@ -69,12 +79,12 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri) }
|
||||
)
|
||||
|
||||
val avatarActions by remember(createRoomConfig.value.avatarUri) {
|
||||
val avatarActions by remember(createRoomConfig.avatarUri) {
|
||||
derivedStateOf {
|
||||
listOfNotNull(
|
||||
AvatarAction.TakePhoto,
|
||||
AvatarAction.ChoosePhoto,
|
||||
AvatarAction.Remove.takeIf { createRoomConfig.value.avatarUri != null },
|
||||
AvatarAction.Remove.takeIf { createRoomConfig.avatarUri != null },
|
||||
).toImmutableList()
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +96,10 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
RoomAddressValidityEffect(createRoomConfig.roomVisibility.roomAddress()) { newRoomAddressValidity ->
|
||||
roomAddressValidity.value = newRoomAddressValidity
|
||||
}
|
||||
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val createRoomAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
|
||||
|
|
@ -102,7 +116,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
is ConfigureRoomEvents.RemoveUserFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
|
||||
is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess)
|
||||
is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress)
|
||||
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig.value)
|
||||
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig)
|
||||
is ConfigureRoomEvents.HandleAvatarAction -> {
|
||||
when (event.action) {
|
||||
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
|
||||
|
|
@ -122,15 +136,49 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
|
||||
return ConfigureRoomState(
|
||||
isKnockFeatureEnabled = isKnockFeatureEnabled,
|
||||
config = createRoomConfig.value,
|
||||
config = createRoomConfig,
|
||||
avatarActions = avatarActions,
|
||||
createRoomAction = createRoomAction.value,
|
||||
cameraPermissionState = cameraPermissionState,
|
||||
homeserverName = homeserverName,
|
||||
roomAddressValidity = roomAddressValidity.value,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomAddressValidityEffect(
|
||||
roomAddress: Optional<String>,
|
||||
onRoomAddressValidityChange: (RoomAddressValidity) -> Unit,
|
||||
) {
|
||||
val onChange by rememberUpdatedState(onRoomAddressValidityChange)
|
||||
LaunchedEffect(roomAddress) {
|
||||
val roomAliasName = roomAddress.getOrNull().orEmpty()
|
||||
if (roomAliasName.isEmpty()) {
|
||||
onChange(RoomAddressValidity.Unknown)
|
||||
return@LaunchedEffect
|
||||
}
|
||||
// debounce the room address validation
|
||||
delay(300)
|
||||
val roomAlias = matrixClient.roomAliasFromName(roomAliasName).getOrNull()
|
||||
if (roomAlias == null || !roomAliasHelper.isRoomAliasValid(roomAlias)) {
|
||||
onChange(RoomAddressValidity.InvalidSymbols)
|
||||
} else {
|
||||
matrixClient.resolveRoomAlias(roomAlias)
|
||||
.onSuccess { resolved ->
|
||||
if (resolved.isPresent) {
|
||||
onChange(RoomAddressValidity.NotAvailable)
|
||||
} else {
|
||||
onChange(RoomAddressValidity.Valid)
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
onChange(RoomAddressValidity.Valid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.createRoom(
|
||||
config: CreateRoomConfig,
|
||||
createRoomAction: MutableState<AsyncAction<RoomId>>
|
||||
|
|
@ -148,7 +196,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
preset = RoomPreset.PUBLIC_CHAT,
|
||||
invite = config.invites.map { it.userId },
|
||||
avatar = avatarUrl,
|
||||
canonicalAlias = config.roomVisibility.roomAddress()
|
||||
roomAliasName = config.roomVisibility.roomAddress()
|
||||
)
|
||||
} else {
|
||||
CreateRoomParameters(
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ data class ConfigureRoomState(
|
|||
val avatarActions: ImmutableList<AvatarAction>,
|
||||
val createRoomAction: AsyncAction<RoomId>,
|
||||
val cameraPermissionState: PermissionsState,
|
||||
val roomAddressValidity: RoomAddressValidity,
|
||||
val homeserverName: String,
|
||||
val eventSink: (ConfigureRoomEvents) -> Unit
|
||||
)
|
||||
) {
|
||||
val isValid: Boolean = config.roomName?.isNotEmpty() == true &&
|
||||
(config.roomVisibility is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,8 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
|||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
invites = aMatrixUserList().toImmutableList(),
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Room 101"),
|
||||
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||
roomAccess = RoomAccess.Knocking,
|
||||
roomAddressErrorState = RoomAddressErrorState.None,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -40,12 +39,44 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
|||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
invites = aMatrixUserList().toImmutableList(),
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Room 101"),
|
||||
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||
roomAccess = RoomAccess.Knocking,
|
||||
roomAddressErrorState = RoomAddressErrorState.None,
|
||||
),
|
||||
),
|
||||
),
|
||||
aConfigureRoomState(
|
||||
config = CreateRoomConfig(
|
||||
roomName = "Room 101",
|
||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||
roomAccess = RoomAccess.Knocking,
|
||||
),
|
||||
),
|
||||
roomAddressValidity = RoomAddressValidity.NotAvailable,
|
||||
),
|
||||
aConfigureRoomState(
|
||||
config = CreateRoomConfig(
|
||||
roomName = "Room 101",
|
||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||
roomAccess = RoomAccess.Knocking,
|
||||
),
|
||||
),
|
||||
roomAddressValidity = RoomAddressValidity.InvalidSymbols,
|
||||
),
|
||||
aConfigureRoomState(
|
||||
config = CreateRoomConfig(
|
||||
roomName = "Room 101",
|
||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Room-101"),
|
||||
roomAccess = RoomAccess.Knocking,
|
||||
),
|
||||
),
|
||||
roomAddressValidity = RoomAddressValidity.Valid,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -56,6 +87,7 @@ fun aConfigureRoomState(
|
|||
createRoomAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false),
|
||||
homeserverName: String = "matrix.org",
|
||||
roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Valid,
|
||||
eventSink: (ConfigureRoomEvents) -> Unit = { },
|
||||
) = ConfigureRoomState(
|
||||
config = config,
|
||||
|
|
@ -64,5 +96,6 @@ fun aConfigureRoomState(
|
|||
createRoomAction = createRoomAction,
|
||||
cameraPermissionState = cameraPermissionState,
|
||||
homeserverName = homeserverName,
|
||||
roomAddressValidity = roomAddressValidity,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
|
|
@ -23,7 +24,6 @@ import androidx.compose.foundation.selection.selectableGroup
|
|||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -36,16 +36,17 @@ 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.createroom.impl.R
|
||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
|
||||
import io.element.android.libraries.designsystem.components.LabelledTextField
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
|
|
@ -79,7 +80,7 @@ fun ConfigureRoomView(
|
|||
modifier = modifier.clearFocusOnTap(focusManager),
|
||||
topBar = {
|
||||
ConfigureRoomToolbar(
|
||||
isNextActionEnabled = state.config.isValid,
|
||||
isNextActionEnabled = state.isValid,
|
||||
onBackClick = onBackClick,
|
||||
onNextClick = {
|
||||
focusManager.clearFocus()
|
||||
|
|
@ -143,8 +144,10 @@ fun ConfigureRoomView(
|
|||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
address = state.config.roomVisibility.roomAddress,
|
||||
homeserverName = state.homeserverName,
|
||||
addressValidity = state.roomAddressValidity,
|
||||
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
|
||||
)
|
||||
Spacer(Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -217,7 +220,7 @@ private fun RoomNameWithAvatar(
|
|||
modifier = Modifier.clickable(onClick = onAvatarClick),
|
||||
)
|
||||
|
||||
LabelledTextField(
|
||||
TextField(
|
||||
label = stringResource(R.string.screen_create_room_room_name_label),
|
||||
value = roomName,
|
||||
placeholder = stringResource(CommonStrings.common_room_name_placeholder),
|
||||
|
|
@ -233,7 +236,7 @@ private fun RoomTopic(
|
|||
onTopicChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LabelledTextField(
|
||||
TextField(
|
||||
modifier = modifier,
|
||||
label = stringResource(R.string.screen_create_room_topic_label),
|
||||
value = topic,
|
||||
|
|
@ -319,54 +322,56 @@ private fun RoomAccessOptions(
|
|||
private fun RoomAddressField(
|
||||
address: RoomAddress,
|
||||
homeserverName: String,
|
||||
addressValidity: RoomAddressValidity,
|
||||
onAddressChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = stringResource(R.string.screen_create_room_room_address_section_title),
|
||||
)
|
||||
|
||||
TextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = address.value,
|
||||
leadingIcon = {
|
||||
Text(
|
||||
text = "#",
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Text(
|
||||
text = homeserverName,
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
modifier = Modifier.padding(end = 16.dp)
|
||||
)
|
||||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_create_room_room_address_section_footer),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
},
|
||||
onValueChange = onAddressChange,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
TextField(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
value = address.value,
|
||||
label = stringResource(R.string.screen_create_room_room_address_section_title),
|
||||
leadingIcon = {
|
||||
Text(
|
||||
text = "#",
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Text(
|
||||
text = homeserverName,
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
},
|
||||
supportingText = when (addressValidity) {
|
||||
RoomAddressValidity.InvalidSymbols -> {
|
||||
stringResource(R.string.screen_create_room_room_address_invalid_symbols_error_description)
|
||||
}
|
||||
RoomAddressValidity.NotAvailable -> {
|
||||
stringResource(R.string.screen_create_room_room_address_not_available_error_description)
|
||||
}
|
||||
else -> stringResource(R.string.screen_create_room_room_address_section_footer)
|
||||
},
|
||||
isError = addressValidity.isError(),
|
||||
onValueChange = onAddressChange,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@PreviewWithLargeHeight
|
||||
@Composable
|
||||
internal fun ConfigureRoomViewPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) = ElementPreview {
|
||||
internal fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
|
||||
ElementPreviewLight { ContentToPreview(state) }
|
||||
|
||||
@PreviewWithLargeHeight
|
||||
@Composable
|
||||
internal fun ConfigureRoomViewDarkPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
|
||||
ElementPreviewDark { ContentToPreview(state) }
|
||||
|
||||
@ExcludeFromCoverage
|
||||
@Composable
|
||||
private fun ContentToPreview(state: ConfigureRoomState) {
|
||||
ConfigureRoomView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
/**
|
||||
* Represents the error state of a room address.
|
||||
*/
|
||||
sealed interface RoomAddressErrorState {
|
||||
data object InvalidCharacters : RoomAddressErrorState
|
||||
data object AlreadyExists : RoomAddressErrorState
|
||||
data object None : RoomAddressErrorState
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
/**
|
||||
* Represents the validity state of a room address.
|
||||
* ie. whether it contains invalid characters, is already taken, or is valid.
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface RoomAddressValidity {
|
||||
data object Unknown : RoomAddressValidity
|
||||
data object InvalidSymbols : RoomAddressValidity
|
||||
data object NotAvailable : RoomAddressValidity
|
||||
data object Valid : RoomAddressValidity
|
||||
|
||||
fun isError(): Boolean {
|
||||
return this is InvalidSymbols || this is NotAvailable
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ sealed interface RoomVisibilityState {
|
|||
|
||||
data class Public(
|
||||
val roomAddress: RoomAddress,
|
||||
val roomAddressErrorState: RoomAddressErrorState,
|
||||
val roomAccess: RoomAccess,
|
||||
) : RoomVisibilityState
|
||||
|
||||
|
|
@ -24,11 +23,4 @@ sealed interface RoomVisibilityState {
|
|||
is Public -> Optional.of(roomAddress.value)
|
||||
}
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
return when (this) {
|
||||
is Private -> true
|
||||
is Public -> roomAddressErrorState is RoomAddressErrorState.None && roomAddress.value.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ To můžete kdykoli změnit v nastavení místnosti."</string>
|
|||
<string name="screen_create_room_room_access_section_header">"Přístup do místnosti"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Kdokoli může požádat o vstup do místnosti, ale správce nebo moderátor bude muset žádost přijmout"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Požádat o připojení"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Některé znaky nejsou povoleny. Podporovány jsou pouze písmena, číslice a následující symboly ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Tato adresa místnosti již existuje, zkuste prosím upravit pole adresy místnosti nebo změnit název místnosti"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Aby byla tato místnost viditelná v adresáři veřejných místností, budete potřebovat adresu místnosti."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adresa místnosti"</string>
|
||||
<string name="screen_create_room_room_name_label">"Název místnosti"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ Sa võid seda jututoa seadistustest alati muuta."</string>
|
|||
<string name="screen_create_room_room_access_section_header">"Ligipääs jututoale"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Kõik võivad paluda selle jututoaga liitumist, kuid peakasutaja või moderaator peavad selle kinnitama"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Küsi võimalust liitumiseks"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Mõned tähemärgid pole lubatud. Kasuta vaid tähti, numbreid ja neid kirjavahemärke ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Selline jututoa aadress on juba olemas. Palun proovi muuta kas aadressi või jututoa nime"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Selleks, et see jututuba oleks nähtav jututubade avalikus kataloogis, sa vajad jututoa aadressi."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Jututoa aadress"</string>
|
||||
<string name="screen_create_room_room_name_label">"Jututoa nimi"</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<?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">"Uusi huone"</string>
|
||||
<string name="screen_create_room_add_people_title">"Kutsu ihmisiä"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Huoneen luomisessa tapahtui virhe"</string>
|
||||
<string name="screen_create_room_private_option_description">"Vain kutsutut henkilöt pääsevät tähän huoneeseen. Kaikki viestit ovat päästä päähän salattuja."</string>
|
||||
<string name="screen_create_room_private_option_title">"Yksityinen huone"</string>
|
||||
<string name="screen_create_room_public_option_description">"Kuka tahansa voi löytää tämän huoneen.
|
||||
Voit muuttaa tämän milloin tahansa huoneen asetuksista."</string>
|
||||
<string name="screen_create_room_public_option_title">"Julkinen huone"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"Kuka tahansa voi liittyä tähän huoneeseen"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"Kuka tahansa"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"Huoneeseen Pääsy"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Kuka tahansa voi pyytää saada liittyä huoneeseen, mutta ylläpitäjän tai valvojan on hyväksyttävä pyyntö"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Pyydä liittymistä"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Jotkin merkit eivät ole sallittuja. Vain kirjaimet, numerot ja seuraavat symbolit ovat tuettuja ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Tämä huoneen osoite on jo käytössä, yritä muokata huoneen osoitekenttää tai muuta huoneen nimeä"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Jotta tämä huone näkyisi julkisessa huonehakemistossa, tarvitset huoneen osoitteen."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Huoneen osoite"</string>
|
||||
<string name="screen_create_room_room_name_label">"Huoneen nimi"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Huoneen näkyvyys"</string>
|
||||
<string name="screen_create_room_title">"Luo huone"</string>
|
||||
<string name="screen_create_room_topic_label">"Aihe (valinnainen)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Keskustelun aloituksessa tapahtui virhe"</string>
|
||||
</resources>
|
||||
|
|
@ -13,6 +13,8 @@ Vous pouvez modifier cela à tout moment dans les paramètres du salon."</string
|
|||
<string name="screen_create_room_room_access_section_header">"Accès au salon"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Tout le monde peut demander à rejoindre le salon, mais un administrateur ou un modérateur devra accepter la demande"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Demander à rejoindre"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Certains caractères ne sont pas autorisés. Seuls les lettres, les chiffres et les symboles suivants sont utilisables ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Cette adresse de salon existe déjà, veuillez essayer de modifier le champ d’adresse de salon ou de modifier le nom du salon"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Pour que ce salon soit visible dans le répertoire des salons publics, vous aurez besoin d’une adresse de salon."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adresse du salon"</string>
|
||||
<string name="screen_create_room_room_name_label">"Nom du salon"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ Anda dapat mengubah ini kapan pun dalam pengaturan ruangan."</string>
|
|||
<string name="screen_create_room_room_access_section_header">"Akses Ruangan"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Siapa pun dapat meminta untuk bergabung dengan ruangan tetapi administrator atau moderator harus menerima permintaan tersebut"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Minta untuk bergabung"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Beberapa karakter tidak diperbolehkan. Hanya huruf, angka, dan simbol berikut didukung ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Alamat ruangan sudah ada, silakan coba sunting kolom alamat ruangan atau ubah nama ruangan"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Supaya ruangan ini terlihat di direktori ruangan publik, Anda memerlukan alamat ruangan."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Alamat ruangan"</string>
|
||||
<string name="screen_create_room_room_name_label">"Nama ruangan"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ Pode alterar esta opção nas definições da sala."</string>
|
|||
<string name="screen_create_room_room_access_section_header">"Acesso à sala"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Qualquer pessoa pode pedir para entrar na sala, mas um administrador ou um moderador terá de aceitar o pedido"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Pedir para participar"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Alguns caracteres não são permitidos. Apenas letras, dígitos e os seguintes símbolos são suportados! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Este endereço de sala já existe, tente editar o campo de endereço da sala ou altere o nome da sala"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Para que esta sala seja visível no diretório público de salas, precisas de um endereço de sala."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Endereço da sala"</string>
|
||||
<string name="screen_create_room_room_name_label">"Nome da sala"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
<string name="screen_create_room_room_access_section_header">"Доступ в комнату"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Любой желающий может подать заявку на присоединение к комнате, но администратор или модератор должен будет принять запрос."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Попросить присоединиться"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Некоторые символы не допускаются. Поддерживаются только буквы, цифры и следующие символы! $ & \'() * +/; =? @ [] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Такой адрес комнаты уже существует, попробуйте отредактировать поле адреса комнаты или изменить название комнаты"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Чтобы эта комната была видна в каталоге общедоступных, вам необходим ее адрес"</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Адрес комнаты"</string>
|
||||
<string name="screen_create_room_room_name_label">"Название комнаты"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ Môžete to kedykoľvek zmeniť v nastaveniach miestnosti."</string>
|
|||
<string name="screen_create_room_room_access_section_header">"Prístup do miestnosti"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Ktokoľvek môže požiadať o pripojenie sa k miestnosti, ale administrátor alebo moderátor bude musieť žiadosť schváliť"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Požiadať o pripojenie"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Niektoré znaky nie sú povolené. Podporované sú iba písmená, číslice a nasledujúce symboly ! $ & \'() * +/; =? @ [] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Táto adresa miestnosti už existuje, skúste upraviť pole adresy miestnosti alebo zmeňte názov miestnosti"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Aby bola táto miestnosť viditeľná v adresári verejných miestností, budete potrebovať adresu miestnosti."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adresa miestnosti"</string>
|
||||
<string name="screen_create_room_room_name_label">"Názov miestnosti"</string>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,23 @@
|
|||
<string name="screen_create_room_action_create_room">"Нова кімната"</string>
|
||||
<string name="screen_create_room_add_people_title">"Запросити людей"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Під час створення кімнати сталася помилка"</string>
|
||||
<string name="screen_create_room_private_option_description">"Повідомлення в цій кімнаті зашифровані. Пізніше шифрування вимкнути не можна."</string>
|
||||
<string name="screen_create_room_private_option_description">"Лише запрошені люди мають доступ до цієї кімнати. Усі повідомлення захищені наскрізним шифруванням."</string>
|
||||
<string name="screen_create_room_private_option_title">"Приватна кімната (тільки за запрошенням)"</string>
|
||||
<string name="screen_create_room_public_option_description">"Повідомлення не шифруються, і будь-хто може їх прочитати. Шифрування можна ввімкнути пізніше."</string>
|
||||
<string name="screen_create_room_public_option_title">"Загальна кімната (будь-хто)"</string>
|
||||
<string name="screen_create_room_public_option_description">"Будь-хто може знайти цю кімнату.
|
||||
Ви можете змінити це в будь-який час у налаштуваннях кімнати."</string>
|
||||
<string name="screen_create_room_public_option_title">"Публічна кімната"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"Будь-хто може приєднатися до цієї кімнати"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"Кожний"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"Доступ до кімнати"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Будь-хто може попросити приєднатися до кімнати, але адміністратор або модератор повинен буде прийняти запит"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Запросити приєднатися"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Деякі символи не допускаються. Підтримуються тільки букви, цифри і наступні символи! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"Ця адреса кімнати вже існує, будь ласка, спробуйте відредагувати поле адреси кімнати або змінити назву кімнати"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Щоб цю кімнату було видно в каталозі загальнодоступних кімнат, вам знадобиться її адреса."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Адреса кімнати"</string>
|
||||
<string name="screen_create_room_room_name_label">"Назва кімнати"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Видимість кімнати"</string>
|
||||
<string name="screen_create_room_title">"Створити кімнату"</string>
|
||||
<string name="screen_create_room_topic_label">"Тема (необов\'язково)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Під час спроби почати чат сталася помилка"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Під час спроби почати бесіду сталася помилка"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,22 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"新聊天室"</string>
|
||||
<string name="screen_create_room_add_people_title">"邀请朋友"</string>
|
||||
<string name="screen_create_room_error_creating_room">"创建房间时出错"</string>
|
||||
<string name="screen_create_room_private_option_description">"此聊天室中的消息已加密。加密无法禁用。"</string>
|
||||
<string name="screen_create_room_private_option_title">"私人房间(仅限受邀者)"</string>
|
||||
<string name="screen_create_room_public_option_description">"消息未加密,任何人都可以查看。可以稍后启用加密。"</string>
|
||||
<string name="screen_create_room_public_option_title">"公共房间(任何人)"</string>
|
||||
<string name="screen_create_room_room_name_label">"房间名称"</string>
|
||||
<string name="screen_create_room_title">"创建房间"</string>
|
||||
<string name="screen_create_room_error_creating_room">"创建聊天室时出错"</string>
|
||||
<string name="screen_create_room_private_option_description">"只有受邀用户才能访问此房间。所有消息均经过端到端加密。"</string>
|
||||
<string name="screen_create_room_private_option_title">"私有房间"</string>
|
||||
<string name="screen_create_room_public_option_description">"任何人都能找到此房间。
|
||||
你可以随时在房间设置中更改。"</string>
|
||||
<string name="screen_create_room_public_option_title">"公开房间"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"任何人都可以加入此房间"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"任何人"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"房间访问权限"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"任何人都可以请求加入房间,但必须由管理员或审核人接受"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"请求加入"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"要使该房间在公开房间目录中可见,您需要一个房间地址。"</string>
|
||||
<string name="screen_create_room_room_address_section_title">"房间地址"</string>
|
||||
<string name="screen_create_room_room_name_label">"聊天室名称"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"房间可见性"</string>
|
||||
<string name="screen_create_room_title">"创建聊天室"</string>
|
||||
<string name="screen_create_room_topic_label">"主题(可选)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"在开始聊天时发生了错误"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ You can change this anytime in room settings."</string>
|
|||
<string name="screen_create_room_room_access_section_header">"Room Access"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Anyone can ask to join the room but an administrator or a moderator will have to accept the request"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Ask to join"</string>
|
||||
<string name="screen_create_room_room_address_invalid_symbols_error_description">"Some characters are not allowed. Only letters, digits and the following symbols are supported ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</string>
|
||||
<string name="screen_create_room_room_address_not_available_error_description">"This room address already exists, please try editing the room address field or change the room name"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"In order for this room to be visible in the public room directory, you will need a room address."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Room address"</string>
|
||||
<string name="screen_create_room_room_name_label">"Room name"</string>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
||||
import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory
|
||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||
import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper
|
||||
import io.element.android.libraries.usersearch.test.FakeUserRepository
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -32,7 +33,7 @@ class AddPeoplePresenterTest {
|
|||
presenter = AddPeoplePresenter(
|
||||
FakeUserListPresenterFactory(),
|
||||
FakeUserRepository(),
|
||||
CreateRoomDataStore(UserListDataStore())
|
||||
CreateRoomDataStore(UserListDataStore(), FakeRoomAliasHelper())
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,15 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
|
|||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||
|
|
@ -44,6 +48,8 @@ import io.mockk.mockkStatic
|
|||
import io.mockk.unmockkAll
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
|
|
@ -52,6 +58,7 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import java.io.File
|
||||
import java.util.Optional
|
||||
|
||||
private const val AN_URI_FROM_CAMERA = "content://uri_from_camera"
|
||||
private const val AN_URI_FROM_CAMERA_2 = "content://uri_from_camera_2"
|
||||
|
|
@ -95,21 +102,21 @@ class ConfigureRoomPresenterTest {
|
|||
presenter.test {
|
||||
val initialState = initialState()
|
||||
var config = initialState.config
|
||||
assertThat(initialState.config.isValid).isFalse()
|
||||
assertThat(initialState.isValid).isFalse()
|
||||
|
||||
// Room name not empty
|
||||
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
|
||||
var newState: ConfigureRoomState = awaitItem()
|
||||
config = config.copy(roomName = A_ROOM_NAME)
|
||||
assertThat(newState.config).isEqualTo(config)
|
||||
assertThat(newState.config.isValid).isTrue()
|
||||
assertThat(newState.isValid).isTrue()
|
||||
|
||||
// Clear room name
|
||||
newState.eventSink(ConfigureRoomEvents.RoomNameChanged(""))
|
||||
newState = awaitItem()
|
||||
config = config.copy(roomName = null)
|
||||
assertThat(newState.config).isEqualTo(config)
|
||||
assertThat(newState.config.isValid).isFalse()
|
||||
assertThat(newState.isValid).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,8 +125,9 @@ class ConfigureRoomPresenterTest {
|
|||
val userListDataStore = UserListDataStore()
|
||||
val pickerProvider = FakePickerProvider()
|
||||
val permissionsPresenter = FakePermissionsPresenter()
|
||||
val roomAliasHelper = FakeRoomAliasHelper()
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
createRoomDataStore = CreateRoomDataStore(userListDataStore),
|
||||
createRoomDataStore = CreateRoomDataStore(userListDataStore, roomAliasHelper),
|
||||
pickerProvider = pickerProvider,
|
||||
permissionsPresenter = permissionsPresenter,
|
||||
)
|
||||
|
|
@ -191,8 +199,7 @@ class ConfigureRoomPresenterTest {
|
|||
newState = awaitItem()
|
||||
expectedConfig = expectedConfig.copy(
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled(expectedConfig.roomName ?: ""),
|
||||
roomAddressErrorState = RoomAddressErrorState.None,
|
||||
roomAddress = RoomAddress.AutoFilled(roomAliasHelper.roomAliasNameFromRoomDisplayName(expectedConfig.roomName ?: "")),
|
||||
roomAccess = RoomAccess.Anyone,
|
||||
)
|
||||
)
|
||||
|
|
@ -254,7 +261,7 @@ class ConfigureRoomPresenterTest {
|
|||
val matrixClient = createMatrixClient()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val mediaPreProcessor = FakeMediaPreProcessor()
|
||||
val createRoomDataStore = CreateRoomDataStore(UserListDataStore())
|
||||
val createRoomDataStore = CreateRoomDataStore(UserListDataStore(), FakeRoomAliasHelper())
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
createRoomDataStore = createRoomDataStore,
|
||||
mediaPreProcessor = mediaPreProcessor,
|
||||
|
|
@ -315,17 +322,88 @@ class ConfigureRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `present - address is invalid when format is invalid`() = runTest {
|
||||
val aliasHelper = FakeRoomAliasHelper(
|
||||
isRoomAliasValidLambda = { false }
|
||||
)
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
roomAliasHelper = aliasHelper
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public))
|
||||
skipItems(1)
|
||||
initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("invalid address"))
|
||||
skipItems(1)
|
||||
advanceUntilIdle()
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.InvalidSymbols)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `present - address is not available when alias is not available`() = runTest {
|
||||
val fakeMatrixClient = createMatrixClient(isAliasAvailable = false)
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
matrixClient = fakeMatrixClient,
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public))
|
||||
skipItems(1)
|
||||
initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address"))
|
||||
skipItems(1)
|
||||
advanceUntilIdle()
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.NotAvailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `present - address is valid when alias is available and format is valid`() = runTest {
|
||||
val fakeMatrixClient = createMatrixClient(isAliasAvailable = true)
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
matrixClient = fakeMatrixClient,
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public))
|
||||
skipItems(1)
|
||||
initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address"))
|
||||
skipItems(1)
|
||||
advanceUntilIdle()
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.Valid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun TurbineTestContext<ConfigureRoomState>.initialState(): ConfigureRoomState {
|
||||
skipItems(1)
|
||||
return awaitItem()
|
||||
}
|
||||
|
||||
private fun createMatrixClient() = FakeMatrixClient(
|
||||
private fun createMatrixClient(isAliasAvailable: Boolean = true) = FakeMatrixClient(
|
||||
userIdServerNameLambda = { "matrix.org" },
|
||||
resolveRoomAliasResult = {
|
||||
val resolvedRoomAlias = if (isAliasAvailable) {
|
||||
Optional.empty()
|
||||
} else {
|
||||
Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList()))
|
||||
}
|
||||
Result.success(resolvedRoomAlias)
|
||||
}
|
||||
)
|
||||
|
||||
private fun createConfigureRoomPresenter(
|
||||
createRoomDataStore: CreateRoomDataStore = CreateRoomDataStore(UserListDataStore()),
|
||||
roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(),
|
||||
createRoomDataStore: CreateRoomDataStore = CreateRoomDataStore(UserListDataStore(), roomAliasHelper),
|
||||
matrixClient: MatrixClient = createMatrixClient(),
|
||||
pickerProvider: PickerProvider = FakePickerProvider(),
|
||||
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
|
||||
|
|
@ -339,6 +417,7 @@ class ConfigureRoomPresenterTest {
|
|||
mediaPreProcessor = mediaPreProcessor,
|
||||
analyticsService = analyticsService,
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
|
||||
roomAliasHelper = roomAliasHelper,
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
mapOf(FeatureFlags.Knock.key to isKnockFeatureEnabled)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
|
||||
package io.element.android.features.logout.impl
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -52,19 +54,18 @@ import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrgani
|
|||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||
import io.element.android.libraries.designsystem.components.list.SwitchListItem
|
||||
import io.element.android.libraries.designsystem.modifiers.autofill
|
||||
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.components.autofill
|
||||
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -257,26 +258,21 @@ private fun Content(
|
|||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(CommonStrings.action_confirm_password),
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
if (isLoading) {
|
||||
// Ensure password is hidden when user submits the form
|
||||
passwordVisible = false
|
||||
}
|
||||
OutlinedTextField(
|
||||
TextField(
|
||||
value = passwordFieldState,
|
||||
label = stringResource(CommonStrings.action_confirm_password),
|
||||
readOnly = isLoading,
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.onTabOrEnterKeyFocusNext(focusManager)
|
||||
.testTag(TestTags.loginPassword)
|
||||
|
|
@ -293,9 +289,7 @@ private fun Content(
|
|||
passwordFieldState = sanitized
|
||||
eventSink(AccountDeactivationEvents.SetPassword(sanitized))
|
||||
},
|
||||
placeholder = {
|
||||
Text(text = stringResource(CommonStrings.common_password))
|
||||
},
|
||||
placeholder = stringResource(CommonStrings.common_password),
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
val image =
|
||||
|
|
@ -303,7 +297,7 @@ private fun Content(
|
|||
val description =
|
||||
if (passwordVisible) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
|
||||
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Box(modifier = Modifier.clickable { passwordVisible = !passwordVisible }) {
|
||||
Icon(imageVector = image, description)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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_deactivate_account_confirmation_dialog_content">"Vahvista, että haluat deaktivoida tilisi. Tätä ei voi perua."</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages">"Poista kaikki viestini"</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Varoitus: Tulevaisuudessa muut voivat nähdä puutteellisia keskusteluja."</string>
|
||||
<string name="screen_deactivate_account_description">"Tilisi deaktivointia %1$s. Jos teet sen:"</string>
|
||||
<string name="screen_deactivate_account_description_bold_part">"ei voi peruuttaa"</string>
|
||||
<string name="screen_deactivate_account_list_item_1">"Tilisi %1$s (et voi kirjautua takaisin sisään, eikä tunnustasi voi käyttää uudelleen)."</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"poistetaan käytöstä pysyvästi"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Sinut poistetaan kaikista keskusteluhuoneista."</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"Tilitietosi poistetaan identiteettipalvelimeltamme."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"Viestisi näkyvät edelleen rekisteröityneille käyttäjille, mutta ne eivät ole uusien tai rekisteröimättömien käyttäjien saatavilla, jos päätät poistaa ne."</string>
|
||||
<string name="screen_deactivate_account_title">"Deaktivoi tili"</string>
|
||||
</resources>
|
||||
|
|
@ -9,6 +9,6 @@
|
|||
<string name="screen_deactivate_account_list_item_1_bold_part">"Desativar permanentemente"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Removê-lo de todas as salas de chat."</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"Exclua as informações da sua conta do nosso servidor de identidade."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"Suas mensagens ainda estarão visíveis para usuários registrados, mas não estarão disponíveis para usuários novos ou não registrados se você optar por excluí-las."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"As tuas mensagens continuarão a ser visíveis para os utilizadores registados, mas não estarão disponíveis para os utilizadores novos ou não registados se optares por as apagar."</string>
|
||||
<string name="screen_deactivate_account_title">"Desativar conta"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<string name="screen_deactivate_account_description">"Отключение вашей учетной записи %1$s и означает следующее:"</string>
|
||||
<string name="screen_deactivate_account_description_bold_part">"необратимо"</string>
|
||||
<string name="screen_deactivate_account_list_item_1">"Ваша учётная запись будет %1$s (вы не сможете войти в неё снова, и ваш ID не может быть использован повторно)."</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Отключить навсегда"</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"отключена навсегда"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Вы будете удалены из всех чатов."</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"Данные вашей учётной записи будут удалены с нашего сервера идентификации."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"Ваши сообщения по-прежнему будут видны зарегистрированным пользователям, но не будут доступны новым или незарегистрированным пользователям, если вы решите удалить их."</string>
|
||||
|
|
|
|||
|
|
@ -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_deactivate_account_confirmation_dialog_content">"Будь ласка, підтвердіть, що ви хочете деактивувати свій обліковий запис. Ця дія не може бути скасована."</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages">"Видалити всі мої повідомлення"</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Попередження: майбутні користувачі можуть бачити неповні розмови."</string>
|
||||
<string name="screen_deactivate_account_description">"Деактивація вашого облікового запису%1$s , це буде:"</string>
|
||||
<string name="screen_deactivate_account_description_bold_part">"незворотні"</string>
|
||||
<string name="screen_deactivate_account_list_item_1">"%1$sваш обліковий запис (ви не можете знову увійти, а ваш ідентифікатор не може бути використаний повторно)."</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Назавжди відключити"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Видалити вас з усіх чатів."</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"Видаліть інформацію свого облікового запису з нашого сервера ідентифікації."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"Ваші повідомлення залишатимуться видимими для зареєстрованих користувачів, але недоступними для нових або незареєстрованих користувачів, якщо ви вирішите їх видалити."</string>
|
||||
<string name="screen_deactivate_account_title">"Відключити обліковий запис"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_confirmation_dialog_content">"请确认您要停用您的账户。此操作无法撤消。"</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages">"删除我的所有消息"</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages_notice">"警告:未来的用户可能会看到不完整的对话。"</string>
|
||||
<string name="screen_deactivate_account_description">"停用您的帐户是%1$s,它将:"</string>
|
||||
<string name="screen_deactivate_account_description_bold_part">"不可逆转的"</string>
|
||||
<string name="screen_deactivate_account_list_item_1">"%1$s您的账户(您无法登录回来,并且您的ID无法重复使用)。"</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"永久禁用"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"将您从所有聊天房间中移除。"</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"从我们的身份服务器中删除您的账户信息。"</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"注册用户仍可看到您的消息,但如果您选择删除它们,新用户或未注册用户将无法看到您的消息。"</string>
|
||||
<string name="screen_deactivate_account_title">"停用账户"</string>
|
||||
</resources>
|
||||
11
features/ftue/impl/src/main/res/values-fi/translations.xml
Normal file
11
features/ftue/impl/src/main/res/values-fi/translations.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_notification_optin_subtitle">"Voit muuttaa asetuksia myöhemmin."</string>
|
||||
<string name="screen_notification_optin_title">"Salli ilmoitukset ja älä koskaan missaa viestejä"</string>
|
||||
<string name="screen_welcome_bullet_1">"Puhelut, kyselyt, haku ja paljon muuta lisätään myöhemmin tänä vuonna."</string>
|
||||
<string name="screen_welcome_bullet_2">"Salattujen huoneiden viestihistoria ei ole vielä käytettävissä."</string>
|
||||
<string name="screen_welcome_bullet_3">"Haluaisimme kuulla mielipiteesi, kerro mitä mieltä olet asetuksien kautta."</string>
|
||||
<string name="screen_welcome_button">"Mennään!"</string>
|
||||
<string name="screen_welcome_subtitle">"Tässä on mitä sinun tarvitsee tietää:"</string>
|
||||
<string name="screen_welcome_title">"Tervetuloa %1$s -sovellukseen!"</string>
|
||||
</resources>
|
||||
|
|
@ -2,10 +2,10 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_notification_optin_subtitle">"Ви можете змінити свої налаштування пізніше."</string>
|
||||
<string name="screen_notification_optin_title">"Дозволити сповіщення і ніколи не пропускати повідомлення"</string>
|
||||
<string name="screen_welcome_bullet_1">"Дзвінки, опитування, пошук тощо будуть додані пізніше цього року."</string>
|
||||
<string name="screen_welcome_bullet_1">"Виклики, опитування, пошук тощо будуть додані пізніше цього року."</string>
|
||||
<string name="screen_welcome_bullet_2">"Історія повідомлень для зашифрованих кімнат ще недоступна."</string>
|
||||
<string name="screen_welcome_bullet_3">"Ми хотіли б почути вас, розкажіть нам ваші враження та ідеї щодо застосунку на сторінці налаштувань."</string>
|
||||
<string name="screen_welcome_button">"Пішли!"</string>
|
||||
<string name="screen_welcome_button">"Уперед!"</string>
|
||||
<string name="screen_welcome_subtitle">"Ось що вам потрібно знати:"</string>
|
||||
<string name="screen_welcome_title">"Ласкаво просимо до %1$s!"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<string name="screen_notification_optin_subtitle">"您可以稍后更改设置。"</string>
|
||||
<string name="screen_notification_optin_title">"允许通知,绝不错过任何消息"</string>
|
||||
<string name="screen_welcome_bullet_1">"今年晚些时候将增加通话、投票、搜索等功能。"</string>
|
||||
<string name="screen_welcome_bullet_2">"加密房间的消息历史记录尚不可用。"</string>
|
||||
<string name="screen_welcome_bullet_2">"加密聊天室的消息历史记录尚不可用。"</string>
|
||||
<string name="screen_welcome_bullet_3">"我们很乐意听取您的意见,请通过设置页面告诉我们您的想法。"</string>
|
||||
<string name="screen_welcome_button">"开始吧!"</string>
|
||||
<string name="screen_welcome_subtitle">"以下是您需要了解的内容:"</string>
|
||||
|
|
|
|||
|
|
@ -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">"Haluatko varmasti hylätä kutsun liittyä %1$s -huoneeseen?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Hylkää kutsu"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Haluatko varmasti hylätä kutsun yksityiseen keskusteluun käyttäjän %1$s kanssa?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"Hylkää keskustelu"</string>
|
||||
<string name="screen_invites_empty_list">"Ei kutsuja"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) kutsui sinut"</string>
|
||||
</resources>
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_invites_decline_chat_message">"Ви впевнені, що хочете відхилити запрошення приєднатися до %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Відхилити запрошення"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Ви дійсно хочете відмовитися від приватного чату з %1$s?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"Відхилити чат"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Ви дійсно хочете відмовитися від приватної бесіди з %1$s?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"Відхилити бесіду"</string>
|
||||
<string name="screen_invites_empty_list">"Немає запрошень"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) запросив (-ла) Вас"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) запрошує вас"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
|||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -96,10 +96,10 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
}
|
||||
else -> {
|
||||
value = ContentState.Loading(roomIdOrAlias)
|
||||
val result = matrixClient.getRoomPreview(roomIdOrAlias, serverNames)
|
||||
val result = matrixClient.getRoomPreviewInfo(roomIdOrAlias, serverNames)
|
||||
value = result.fold(
|
||||
onSuccess = { roomPreview ->
|
||||
roomPreview.toContentState()
|
||||
onSuccess = { previewInfo ->
|
||||
previewInfo.toContentState()
|
||||
},
|
||||
onFailure = { throwable ->
|
||||
if (throwable.message?.contains("403") == true) {
|
||||
|
|
@ -184,7 +184,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun RoomPreview.toContentState(): ContentState {
|
||||
private fun RoomPreviewInfo.toContentState(): ContentState {
|
||||
return ContentState.Loaded(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
|
|
@ -390,19 +390,13 @@ private fun DefaultLoadedContent(
|
|||
)
|
||||
} else if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
OutlinedTextField(
|
||||
TextField(
|
||||
value = knockMessage,
|
||||
onValueChange = onKnockMessageUpdate,
|
||||
maxLines = 3,
|
||||
minLines = 3,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.screen_join_room_knock_message_description),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textPlaceholder,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
supportingText = stringResource(R.string.screen_join_room_knock_message_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_cancel_knock_action">"Ακύρωση αιτήματος"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ναι, ακύρωση"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_description">"Σίγουρα θες να ακυρώσεις το αίτημά σου για συμμετοχή σε αυτό το δωμάτιο;"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_title">"Ακύρωση αίτησης συμμετοχής"</string>
|
||||
<string name="screen_join_room_join_action">"Συμμετοχή στο δωμάτιο"</string>
|
||||
<string name="screen_join_room_knock_action">"Χτύπα για συμμετοχή"</string>
|
||||
<string name="screen_join_room_knock_message_description">"Μήνυμα (προαιρετικό)"</string>
|
||||
|
|
|
|||
|
|
@ -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_join_room_cancel_knock_action">"Peruuta pyyntö"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Kyllä, peruuta"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_description">"Haluatko varmasti peruuttaa pyyntösi liittyä tähän huoneeseen?"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_title">"Peruuta liittymispyyntö"</string>
|
||||
<string name="screen_join_room_join_action">"Liity huoneeseen"</string>
|
||||
<string name="screen_join_room_knock_action">"Lähetä liittymispyyntö"</string>
|
||||
<string name="screen_join_room_knock_message_description">"Viesti (valinnainen)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"Saat kutsun liittyä huoneeseen, jos pyyntösi hyväksytään."</string>
|
||||
<string name="screen_join_room_knock_sent_title">"Liittymispyyntö lähetetty"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s ei tue vielä tiloja. Voit käyttää tiloja selainversiolla."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Tiloja ei vielä tueta"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"Paina alla olevaa nappia ja huoneen ylläpitäjä saa ilmoituksen. Voit liittyä keskusteluun kun pyyntösi on hyväksytty."</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"Sinun on oltava tämän huoneen jäsen, jotta voit nähdä viestihistorian."</string>
|
||||
<string name="screen_join_room_title_knock">"Haluatko liittyä tähän huoneeseen?"</string>
|
||||
<string name="screen_join_room_title_no_preview">"Esikatselu ei ole saatavilla"</string>
|
||||
</resources>
|
||||
|
|
@ -1,8 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_cancel_knock_action">"Скасувати запит"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Так, скасувати"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_description">"Ви впевнені, що бажаєте скасувати свій запит на приєднання до цієї кімнати?"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_title">"Скасувати запит на приєднання"</string>
|
||||
<string name="screen_join_room_join_action">"Приєднатися до кімнати"</string>
|
||||
<string name="screen_join_room_knock_action">"Постукати, щоб приєднатися"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s ще не підтримує простори. Ви можете отримати доступ до них в вебверсії."</string>
|
||||
<string name="screen_join_room_knock_message_description">"Повідомлення (необов\'язково)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"Ви отримаєте запрошення приєднатися до кімнати, якщо ваш запит буде прийнятий."</string>
|
||||
<string name="screen_join_room_knock_sent_title">"Запит на приєднання надіслано"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s ще не підтримує простори. Ви можете отримати доступ до них у вебверсії."</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"Простори поки що не підтримуються"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"Натисніть кнопку нижче, і адміністратор кімнати отримає сповіщення. Ви зможете приєднатися до розмови після схвалення."</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"Ви мусите бути учасником цієї кімнати, щоб переглядати історію повідомлень."</string>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_join_room_cancel_knock_action">"取消请求"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"是的,取消"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_description">"您确定要取消加入此房间的请求吗?"</string>
|
||||
<string name="screen_join_room_cancel_knock_alert_title">"取消加入申请"</string>
|
||||
<string name="screen_join_room_join_action">"加入聊天室"</string>
|
||||
<string name="screen_join_room_knock_action">"加入房间"</string>
|
||||
<string name="screen_join_room_knock_action">"加入聊天室"</string>
|
||||
<string name="screen_join_room_knock_message_description">"消息(可选)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"如果您的请求被接受,您将收到加入房间的邀请。"</string>
|
||||
<string name="screen_join_room_knock_sent_title">"加入请求已发送"</string>
|
||||
<string name="screen_join_room_space_not_supported_description">"%1$s 尚不支持空间。您可以通过 Web 端访问空间"</string>
|
||||
<string name="screen_join_room_space_not_supported_title">"空间尚不支持"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"点击下面的按钮,系统将通知房间管理员。获得批准后,您将能够加入对话。"</string>
|
||||
<string name="screen_join_room_subtitle_knock">"点击下面的按钮,系统将通知聊天室管理员。获得批准后将能够加入对话。"</string>
|
||||
<string name="screen_join_room_subtitle_no_preview">"只有聊天室成员才能查看消息历史记录。"</string>
|
||||
<string name="screen_join_room_title_knock">"想加入这个房间吗?"</string>
|
||||
<string name="screen_join_room_title_knock">"想加入这个聊天室吗?"</string>
|
||||
<string name="screen_join_room_title_no_preview">"预览不可用"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
|
|
@ -408,9 +408,9 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = { _, _ ->
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.success(
|
||||
RoomPreview(
|
||||
RoomPreviewInfo(
|
||||
roomId = A_ROOM_ID,
|
||||
canonicalAlias = RoomAlias("#alias:matrix.org"),
|
||||
name = "Room name",
|
||||
|
|
@ -453,7 +453,7 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = { _, _ ->
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.failure(AN_EXCEPTION)
|
||||
}
|
||||
)
|
||||
|
|
@ -491,7 +491,7 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = { _, _ ->
|
||||
getRoomPreviewInfoResult = { _, _ ->
|
||||
Result.failure(Exception("403"))
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_conversation_alert_subtitle">"Haluatko varmasti poistua keskustelusta? Tämä keskustelu ei ole julkinen ja et voi liittyä takaisin ilman kutsua."</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"Haluatko varmasti poistua huoneesta? Olet huoneen ainoa jäsen. Jos poistut, kukaan ei voi liittyä takaisin, et edes sinä."</string>
|
||||
<string name="leave_room_alert_private_subtitle">"Haluatko varmasti poistua huoneesta? Tämä huone ei ole julkinen ja et voi liittyä takaisin ilman kutsua."</string>
|
||||
<string name="leave_room_alert_subtitle">"Haluatko varmasti poistua huoneesta?"</string>
|
||||
</resources>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_conversation_alert_subtitle">"Ви впевнені, що хочете залишити цю розмову? Ця розмова не є загальнодоступною, і ви не зможете знову приєднатися без запрошення."</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"Ви впевнені, що хочете вийти з цієї кімнати? Ви тут єдина людина. Якщо Ви вийдете, ніхто в майбутньому не зможе приєднатися, у тому числі і Ви."</string>
|
||||
<string name="leave_room_alert_private_subtitle">"Ви впевнені, що хочете вийти з цієї кімнати? Ця кімната не є публічною, і ви не зможете повернутися до неї без запрошення."</string>
|
||||
<string name="leave_conversation_alert_subtitle">"Ви впевнені, що хочете залишити цю розмову? Ця розмова не загальнодоступна, і ви не зможете знову приєднатися без запрошення."</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"Ви впевнені, що хочете вийти з цієї кімнати? Ви тут єдина людина. Якщо ви вийдете, ніхто в майбутньому не зможе приєднатися, у тому числі й ви."</string>
|
||||
<string name="leave_room_alert_private_subtitle">"Ви впевнені, що хочете вийти з цієї кімнати? Ця кімната не загальнодоступна, і ви не зможете повернутися до неї без запрошення."</string>
|
||||
<string name="leave_room_alert_subtitle">"Ви впевнені, що хочете вийти з кімнати?"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_conversation_alert_subtitle">"您確定要離開對話嗎?此對話不是公開的,如果沒有收到邀請,您無法重新加入。"</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"您確定要離開聊天室嗎?這裡只有您一個人。如果您離開了,包含您在內的所有人都無法再進入此聊天室。"</string>
|
||||
<string name="leave_room_alert_private_subtitle">"您確定要離開聊天室嗎?此聊天室不是公開的,如果沒有收到邀請,您無法重新加入。"</string>
|
||||
<string name="leave_room_alert_subtitle">"您確定要離開聊天室嗎?"</string>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_conversation_alert_subtitle">"您确定要离开此对话吗?此对话不公开,未经邀请您将无法重新加入。"</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"确定要离开这个房间吗?这里只有你一个人。如果你离开此房间,包括你在内的所有人都将无法进入。"</string>
|
||||
<string name="leave_room_alert_private_subtitle">"确定要离开这个房间吗?此房间不公开,没有邀请你将无法重新加入。"</string>
|
||||
<string name="leave_room_alert_subtitle">"确定要离开房间吗?"</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"确定要离开此聊天室吗?此处只有你一个人。如果离开此聊天室,包括你在内的所有人都将无法进入。"</string>
|
||||
<string name="leave_room_alert_private_subtitle">"确定要离开此聊天室吗?此聊天室不公开,没有邀请你将无法重新加入。"</string>
|
||||
<string name="leave_room_alert_subtitle">"确定要离开聊天室吗?"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_app_lock_biometric_authentication">"biometrinen tunnistus"</string>
|
||||
<string name="screen_app_lock_biometric_unlock">"biometrinen tunnistus"</string>
|
||||
<string name="screen_app_lock_biometric_unlock_title_android">"Avaa biometrisellä"</string>
|
||||
<string name="screen_app_lock_forgot_pin">"Unohtuiko PIN-koodi?"</string>
|
||||
<string name="screen_app_lock_settings_change_pin">"Vaihda PIN-koodi"</string>
|
||||
<string name="screen_app_lock_settings_enable_biometric_unlock">"Salli biometrinen tunnistus"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin">"Poista PIN-koodi"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_message">"Haluatko varmasti poistaa PIN-koodin?"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_title">"Poista PIN-koodi?"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Salli %1$s"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_skip">"Käytän mieluummin PIN-koodia"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Säästä aikaa ja ota käyttöön %1$s"</string>
|
||||
<string name="screen_app_lock_setup_choose_pin">"Valitse PIN-koodi"</string>
|
||||
<string name="screen_app_lock_setup_confirm_pin">"Vahvista PIN-koodi"</string>
|
||||
<string name="screen_app_lock_setup_pin_context">"Lukitse %1$s -sovellus lisätäksesi turvaa keskusteluihisi.
|
||||
|
||||
Valitse PIN-koodi, jonka muistat. Jos unohdat sen, joudut kirjautumaan ulos."</string>
|
||||
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Et voi valita tätä PIN-koodia turvallisuussyistä"</string>
|
||||
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Valitse toinen PIN-koodi"</string>
|
||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Anna sama PIN-koodi kahdesti"</string>
|
||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN-koodit eivät täsmää"</string>
|
||||
<string name="screen_app_lock_signout_alert_message">"Sinun on kirjauduttava sisään uudelleen ja luotava uusi PIN-koodi jatkaaksesi"</string>
|
||||
<string name="screen_app_lock_signout_alert_title">"Sinut kirjataan ulos"</string>
|
||||
<plurals name="screen_app_lock_subtitle">
|
||||
<item quantity="one">"Sinulla on %1$d yritys"</item>
|
||||
<item quantity="other">"Sinulla on %1$d yritystä"</item>
|
||||
</plurals>
|
||||
<plurals name="screen_app_lock_subtitle_wrong_pin">
|
||||
<item quantity="one">"Väärä PIN-koodi. Sinulla on %1$d yritys jäljellä"</item>
|
||||
<item quantity="other">"Väärä PIN-koodi. Sinulla on %1$d yritystä jäljellä"</item>
|
||||
</plurals>
|
||||
<string name="screen_app_lock_use_biometric_android">"Käytä biometristä"</string>
|
||||
<string name="screen_app_lock_use_pin_android">"Käytä PIN-koodia"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Kirjaudutaan ulos…"</string>
|
||||
</resources>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_app_lock_biometric_authentication">"біометрична аутентифікація"</string>
|
||||
<string name="screen_app_lock_biometric_authentication">"біометрична автентифікація"</string>
|
||||
<string name="screen_app_lock_biometric_unlock">"біометричне розблокування"</string>
|
||||
<string name="screen_app_lock_biometric_unlock_title_android">"Розблокуйте за допомогою біометрії"</string>
|
||||
<string name="screen_app_lock_biometric_unlock_title_android">"Розблокувати за допомогою біометрії"</string>
|
||||
<string name="screen_app_lock_forgot_pin">"Забули PIN-код?"</string>
|
||||
<string name="screen_app_lock_settings_change_pin">"Змінити PIN-код"</string>
|
||||
<string name="screen_app_lock_settings_enable_biometric_unlock">"Дозволити біометричне розблокування"</string>
|
||||
|
|
@ -10,18 +10,18 @@
|
|||
<string name="screen_app_lock_settings_remove_pin_alert_message">"Ви впевнені, що хочете видалити PIN-код?"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_title">"Видалити PIN-код?"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Дозволити %1$s"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_skip">"Я б краще використав PIN-код"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_skip">"Мені краще використати PIN-код"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Заощаджуйте час і використовуйте %1$s для розблокування застосунку щоразу"</string>
|
||||
<string name="screen_app_lock_setup_choose_pin">"Виберіть PIN-код"</string>
|
||||
<string name="screen_app_lock_setup_confirm_pin">"Підтвердити PIN-код"</string>
|
||||
<string name="screen_app_lock_setup_pin_context">"Заблокуйте %1$s, щоб додати додаткову безпеку вашим чатам.
|
||||
|
||||
Виберіть щось, що запам\'ятовується. Але якщо ви забудете PIN-код, ви вийдете з застосунку."</string>
|
||||
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Ви не можете вибрати його як свій PIN-код з міркувань безпеки"</string>
|
||||
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Ви не можете вибрати його своїм PIN-кодом з міркувань безпеки"</string>
|
||||
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Виберіть інший PIN-код"</string>
|
||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Будь ласка, введіть один і той самий PIN-код двічі"</string>
|
||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN-коди не збігаються"</string>
|
||||
<string name="screen_app_lock_signout_alert_message">"Щоб продовжити, вам потрібно буде повторно увійти та створити новий PIN-код"</string>
|
||||
<string name="screen_app_lock_signout_alert_message">"Щоб продовжити, вам потрібно повторно ввійти та створити новий PIN-код"</string>
|
||||
<string name="screen_app_lock_signout_alert_title">"Ви виходите з системи"</string>
|
||||
<plurals name="screen_app_lock_subtitle">
|
||||
<item quantity="one">"Ви маєте %1$d спробу"</item>
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<item quantity="few">"Хибний PIN-код. Ви маєте ще %1$d шанси"</item>
|
||||
<item quantity="many">"Хибний PIN-код. Ви маєте ще %1$d шансів"</item>
|
||||
</plurals>
|
||||
<string name="screen_app_lock_use_biometric_android">"Використовуйте біометрію"</string>
|
||||
<string name="screen_app_lock_use_pin_android">"Використовуйте PIN-код"</string>
|
||||
<string name="screen_app_lock_use_biometric_android">"Використати біометрію"</string>
|
||||
<string name="screen_app_lock_use_pin_android">"Використати PIN-код"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Вихід…"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.features.login.impl.screens.loginpassword
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -52,17 +53,15 @@ import io.element.android.libraries.designsystem.components.BigIcon
|
|||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||
import io.element.android.libraries.designsystem.modifiers.autofill
|
||||
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.components.autofill
|
||||
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -101,12 +100,12 @@ fun LoginPasswordView(
|
|||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.verticalScroll(state = scrollState)
|
||||
.padding(start = 20.dp, end = 20.dp, bottom = 20.dp),
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.verticalScroll(state = scrollState)
|
||||
.padding(start = 20.dp, end = 20.dp, bottom = 20.dp),
|
||||
) {
|
||||
// Title
|
||||
IconTitleSubtitleMolecule(
|
||||
|
|
@ -140,8 +139,8 @@ fun LoginPasswordView(
|
|||
onClick = ::submit,
|
||||
enabled = state.submitEnabled || isLoading,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.loginContinue)
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.loginContinue)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
}
|
||||
|
|
@ -170,16 +169,10 @@ private fun LoginForm(
|
|||
val eventSink = state.eventSink
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_login_form_header),
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
TextField(
|
||||
label = stringResource(R.string.screen_login_form_header),
|
||||
value = loginFieldState,
|
||||
readOnly = isLoading,
|
||||
enabled = !isLoading,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onTabOrEnterKeyFocusNext(focusManager)
|
||||
|
|
@ -192,9 +185,7 @@ private fun LoginForm(
|
|||
eventSink(LoginPasswordEvents.SetLogin(sanitized))
|
||||
}
|
||||
),
|
||||
placeholder = {
|
||||
Text(text = stringResource(CommonStrings.common_username))
|
||||
},
|
||||
placeholder = stringResource(CommonStrings.common_username),
|
||||
onValueChange = {
|
||||
val sanitized = it.sanitize()
|
||||
loginFieldState = sanitized
|
||||
|
|
@ -210,10 +201,14 @@ private fun LoginForm(
|
|||
singleLine = true,
|
||||
trailingIcon = if (loginFieldState.isNotEmpty()) {
|
||||
{
|
||||
IconButton(onClick = {
|
||||
Box(Modifier.clickable {
|
||||
loginFieldState = ""
|
||||
}) {
|
||||
Icon(imageVector = CompoundIcons.Close(), contentDescription = stringResource(CommonStrings.action_clear))
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Close(),
|
||||
contentDescription = stringResource(CommonStrings.action_clear),
|
||||
tint = ElementTheme.colors.iconSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -226,9 +221,9 @@ private fun LoginForm(
|
|||
passwordVisible = false
|
||||
}
|
||||
Spacer(Modifier.height(20.dp))
|
||||
OutlinedTextField(
|
||||
TextField(
|
||||
value = passwordFieldState,
|
||||
readOnly = isLoading,
|
||||
enabled = !isLoading,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onTabOrEnterKeyFocusNext(focusManager)
|
||||
|
|
@ -246,18 +241,18 @@ private fun LoginForm(
|
|||
passwordFieldState = sanitized
|
||||
eventSink(LoginPasswordEvents.SetPassword(sanitized))
|
||||
},
|
||||
placeholder = {
|
||||
Text(text = stringResource(CommonStrings.common_password))
|
||||
},
|
||||
placeholder = stringResource(CommonStrings.common_password),
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
val image =
|
||||
if (passwordVisible) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff()
|
||||
val description =
|
||||
if (passwordVisible) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
|
||||
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(imageVector = image, description)
|
||||
Box(Modifier.clickable { passwordVisible = !passwordVisible }) {
|
||||
Icon(
|
||||
imageVector = image,
|
||||
contentDescription = description,
|
||||
)
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
package io.element.android.features.login.impl.screens.searchaccountprovider
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
|
|
@ -23,7 +24,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -51,14 +51,12 @@ import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubti
|
|||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -86,10 +84,10 @@ fun SearchAccountProviderView(
|
|||
) { padding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) {
|
||||
item {
|
||||
|
|
@ -104,7 +102,7 @@ fun SearchAccountProviderView(
|
|||
// TextInput
|
||||
var userInputState by textFieldState(stateValue = state.userInput)
|
||||
val focusManager = LocalFocusManager.current
|
||||
OutlinedTextField(
|
||||
TextField(
|
||||
value = userInputState,
|
||||
// readOnly = isLoading,
|
||||
modifier = Modifier
|
||||
|
|
@ -126,7 +124,7 @@ fun SearchAccountProviderView(
|
|||
singleLine = true,
|
||||
trailingIcon = if (userInputState.isNotEmpty()) {
|
||||
{
|
||||
IconButton(onClick = {
|
||||
Box(Modifier.clickable {
|
||||
userInputState = ""
|
||||
eventSink(SearchAccountProviderEvents.UserInput(""))
|
||||
}) {
|
||||
|
|
@ -139,9 +137,7 @@ fun SearchAccountProviderView(
|
|||
} else {
|
||||
null
|
||||
},
|
||||
supportingText = {
|
||||
Text(text = stringResource(id = R.string.screen_account_provider_form_notice), color = MaterialTheme.colorScheme.secondary)
|
||||
}
|
||||
supportingText = stringResource(id = R.string.screen_account_provider_form_notice),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
83
features/login/impl/src/main/res/values-fi/translations.xml
Normal file
83
features/login/impl/src/main/res/values-fi/translations.xml
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_account_provider_change">"Vaihda palveluntarjoajaa"</string>
|
||||
<string name="screen_account_provider_form_hint">"Kotipalvelimen osoite"</string>
|
||||
<string name="screen_account_provider_form_notice">"Kirjoita hakutermi tai osoite."</string>
|
||||
<string name="screen_account_provider_form_subtitle">"Hae yritystä, yhteisöä tai yksityistä palvelinta."</string>
|
||||
<string name="screen_account_provider_form_title">"Etsi palveluntarjoajaa"</string>
|
||||
<string name="screen_account_provider_signin_subtitle">"Keskustelusi asuvat täällä — aivan kuten aivan kuten käyttäisit sähköpostipalveluntarjoajaa sähköpostiesi säilyttämiseen."</string>
|
||||
<string name="screen_account_provider_signin_title">"Olet kirjautumassa sisään %s-palvelimelle"</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"Keskustelusi asuvat täällä — aivan kuten aivan kuten käyttäisit sähköpostipalveluntarjoajaa sähköpostiesi säilyttämiseen."</string>
|
||||
<string name="screen_account_provider_signup_title">"Olet luomassa tiliä %s-palvelimelle"</string>
|
||||
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org on suuri, ilmainen palvelin julkisessa Matrix-verkossa turvalliseen, hajautettuun viestintään, jota ylläpitää Matrix.org-säätiö."</string>
|
||||
<string name="screen_change_account_provider_other">"Muu"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Käytä toista palveluntarjoajaa, kuten omaa yksityistä palvelintasi tai työpaikkaasi."</string>
|
||||
<string name="screen_change_account_provider_title">"Vaihda palveluntarjoajaa"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Kotipalvelimeen ei saatu yhteyttä. Varmista, että olet syöttänyt osoitteen oikein. Jos osoite on oikein, ota yhteyttä palvelimesi ylläpitäjään."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Sliding sync ei ole saatavilla well-known tiedostossa olevan ongelman vuoksi:
|
||||
%1$s"</string>
|
||||
<string name="screen_change_server_error_no_sliding_sync_message">"Tämä palvelin ei tällä hetkellä tue sliding syncia."</string>
|
||||
<string name="screen_change_server_form_header">"Kotipalvelimen osoite"</string>
|
||||
<string name="screen_change_server_form_notice">"Voit yhdistää vain olemassa olevaan palvelimeen, joka tukee sliding syncia. Kotipalvelimesi ylläpitäjän on otettava se käyttöön. %1$s"</string>
|
||||
<string name="screen_change_server_subtitle">"Mikä on palvelimesi osoite?"</string>
|
||||
<string name="screen_change_server_title">"Valitse palvelimesi"</string>
|
||||
<string name="screen_create_account_title">"Luo tili"</string>
|
||||
<string name="screen_login_error_deactivated_account">"Tämä tili on deaktivoitu."</string>
|
||||
<string name="screen_login_error_invalid_credentials">"Väärä käyttäjänimi ja/tai salasana"</string>
|
||||
<string name="screen_login_error_invalid_user_id">"Tämä ei ole kelvollinen käyttäjätunnus. Odotettu muoto: \'@käyttäjä:kotipalvelin.fi\'"</string>
|
||||
<string name="screen_login_error_refresh_tokens">"Tämä palvelin on määritetty käyttämään refresh tokeneja. Näitä ei tueta salasanapohjaisen kirjautumisen kanssa."</string>
|
||||
<string name="screen_login_error_unsupported_authentication">"Valitsemasi kotipalvelin ei tue salasana- tai OIDC-kirjautumista. Ota yhteyttä palvelimesi ylläpitäjään tai valitse toinen kotipalvelin."</string>
|
||||
<string name="screen_login_form_header">"Syötä tietosi"</string>
|
||||
<string name="screen_login_subtitle">"Matrix on avoin verkko turvallista, hajautettua viestintää varten."</string>
|
||||
<string name="screen_login_title">"Tervetuloa takaisin!"</string>
|
||||
<string name="screen_login_title_with_homeserver">"Kirjaudu sisään %1$s -palvelimelle"</string>
|
||||
<string name="screen_qr_code_login_connecting_subtitle">"Muodostetaan turvallista yhteyttä"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Turvallista yhteyttä uuteen laitteeseen ei voitu muodostaa. Olemassa olevat laitteesi ovat edelleen turvassa, eikä sinun tarvitse huolehtia niistä."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Mitä nyt?"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Yritä kirjautua sisään uudelleen QR-koodilla, jos kyseessä oli verkko-ongelma"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Jos kohtaat saman ongelman, kokeile toista wifi-verkkoa tai käytä mobiilidataa wifi-yhteyden sijaan"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Jos tämä ei auta, kirjaudu sisään manuaalisesti"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Yhteys ei ole turvallinen"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Sinua pyydetään antamaan tässä laitteessa näkyvät kaksi numeroa."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Kirjoita alla oleva numero toisella laitteellasi"</string>
|
||||
<string name="screen_qr_code_login_device_not_signed_in_scan_state_description">"Kirjaudu sisään toisella laitteellasi ja yritä sitten uudelleen tai käytä toista laitetta, joka on jo kirjautunut sisään."</string>
|
||||
<string name="screen_qr_code_login_device_not_signed_in_scan_state_subtitle">"Toinen laitteesi ei ole kirjautuneena"</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"Kirjautuminen peruutettiin toisella laitteella."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Kirjautumispyyntö peruutettu"</string>
|
||||
<string name="screen_qr_code_login_error_declined_subtitle">"Kirjautuminen hylättiin toisella laitteella."</string>
|
||||
<string name="screen_qr_code_login_error_declined_title">"Kirjautuminen hylätty"</string>
|
||||
<string name="screen_qr_code_login_error_expired_subtitle">"Kirjautuminen vanhentui. Yritä uudelleen."</string>
|
||||
<string name="screen_qr_code_login_error_expired_title">"Kirjautumista ei suoritettu ajoissa"</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Toinen laitteesi ei tue kirjautumista %s -sovellukseen QR-koodilla.
|
||||
|
||||
Yritä kirjautua sisään manuaalisesti tai skannaa QR-koodi toisella laitteella."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR-koodia ei tueta"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Palveluntarjoajasi ei tue %1$s -sovellusta"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s -sovellusta ei tueta"</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"Valmis skannaamaan"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Avaa %1$s tietokoneella"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Napsauta avatariasi"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Valitse %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Yhdistä uusi laite”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Skannaa QR-koodi tällä laitteella"</string>
|
||||
<string name="screen_qr_code_login_initial_state_subtitle">"Saatavilla vain, jos palveluntarjoajasi tukee sitä."</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Avaa %1$s toisella laitteella saadaksesi QR-koodin"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Käytä toisessa laitteessa näkyvää QR-koodia."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Yritä uudelleen"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Väärä QR-koodi"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_button">"Siirry kameran asetuksiin"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_description">"Jatkaaksesi sinun on annettava lupa %1$s -sovellukselle käyttää laitteesi kameraa."</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_title">"Salli lupa kameraan QR-koodin skannaamiseksi"</string>
|
||||
<string name="screen_qr_code_login_scanning_state_title">"Skannaa QR-koodi"</string>
|
||||
<string name="screen_qr_code_login_start_over_button">"Aloita alusta"</string>
|
||||
<string name="screen_qr_code_login_unknown_error_description">"Tapahtui odottamaton virhe. Yritä uudelleen."</string>
|
||||
<string name="screen_qr_code_login_verify_code_loading">"Odotetaan toista laitettasi"</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"Palveluntarjoajasi saattaa kysyä seuraavaa koodia kirjautumisen vahvistamiseksi."</string>
|
||||
<string name="screen_qr_code_login_verify_code_title">"Vahvistuskoodisi"</string>
|
||||
<string name="screen_server_confirmation_change_server">"Vaihda palveluntarjoajaa"</string>
|
||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Yksityinen palvelin Elementin työntekijöille."</string>
|
||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix on avoin verkko turvallista, hajautettua viestintää varten."</string>
|
||||
<string name="screen_server_confirmation_message_register">"Keskustelusi asuvat täällä — aivan kuten aivan kuten käyttäisit sähköpostipalveluntarjoajaa sähköpostiesi säilyttämiseen."</string>
|
||||
<string name="screen_server_confirmation_title_login">"Olet kirjautumassa sisään %1$s-palvelimelle"</string>
|
||||
<string name="screen_server_confirmation_title_register">"Olet luomassa tiliä %1$s-palvelimelle"</string>
|
||||
</resources>
|
||||
|
|
@ -5,42 +5,43 @@
|
|||
<string name="screen_account_provider_form_notice">"Уведіть пошуковий термін або адресу домену."</string>
|
||||
<string name="screen_account_provider_form_subtitle">"Пошук компанії, спільноти або приватного сервера."</string>
|
||||
<string name="screen_account_provider_form_title">"Знайти провайдера облікового запису"</string>
|
||||
<string name="screen_account_provider_signin_subtitle">"Тут будуть зберігатися Ваші розмови - так само, як Ви використовуєте поштову скриньку для зберігання своїх електронних листів."</string>
|
||||
<string name="screen_account_provider_signin_subtitle">"Тут розміщуватимуться ваші розмови — так само як у поштовій скриньці для зберігання своїх електронних листів."</string>
|
||||
<string name="screen_account_provider_signin_title">"Ви збираєтесь увійти в %s"</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"Тут будуть зберігатися Ваші розмови - так само, як Ви використовуєте поштову скриньку для зберігання своїх електронних листів."</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"Тут розміщуватимуться ваші розмови — так само як у поштовій скриньці для зберігання своїх електронних листів."</string>
|
||||
<string name="screen_account_provider_signup_title">"Ви збираєтеся створити обліковий запис на %s"</string>
|
||||
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org — це великий безплатний сервер у загальнодоступній мережі Matrix для безпечного децентралізованого зв’язку, яким керує Matrix.org Foundation."</string>
|
||||
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org — це великий безплатний сервер у загальнодоступній мережі Matrix для безпечного децентралізованого спілкування, яким керує Matrix.org Foundation."</string>
|
||||
<string name="screen_change_account_provider_other">"Інше"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Використати іншого провайдера облікових записів, наприклад, власний приватний сервер або робочий обліковий запис."</string>
|
||||
<string name="screen_change_account_provider_title">"Змінити провайдера облікового запису"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Не вдалося підключитися до цього домашнього сервера. Будь ласка, перевірте, чи правильно Ви ввели URL-адресу домашнього сервера. Якщо URL-адреса правильна, зверніться за додатковою допомогою до адміністратора домашнього сервера."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Sliding sync недоступний через проблему у well-known файлі:
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Не вдалося під\'єднатися до цього домашнього сервера. Перевірте правильність введеної URL-адреси домашнього сервера. Якщо URL-адреса правильна, зверніться по додаткову допомогу до адміністратора домашнього сервера."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Sliding sync недоступний через проблему у файлі well-known:
|
||||
%1$s"</string>
|
||||
<string name="screen_change_server_error_no_sliding_sync_message">"Наразі цей сервер не підтримує sliding sync."</string>
|
||||
<string name="screen_change_server_form_header">"URL-адреса домашнього сервера"</string>
|
||||
<string name="screen_change_server_form_notice">"Ви можете підключитися лише до наявного сервера, який підтримує sliding sync. Ваш адміністратор домашнього сервера повинен буде налаштувати його. %1$s"</string>
|
||||
<string name="screen_change_server_subtitle">"Яка адреса Вашого сервера?"</string>
|
||||
<string name="screen_change_server_form_notice">"Ви можете під\'єднатися лише до наявного сервера, який підтримує sliding sync. Адміністратор вашого домашнього сервера повинен буде налаштувати його. %1$s"</string>
|
||||
<string name="screen_change_server_subtitle">"Яка адреса вашого сервера?"</string>
|
||||
<string name="screen_change_server_title">"Виберіть свій сервер"</string>
|
||||
<string name="screen_create_account_title">"Створити обліковий запис"</string>
|
||||
<string name="screen_login_error_deactivated_account">"Цей обліковий запис було деактивовано."</string>
|
||||
<string name="screen_login_error_invalid_credentials">"Неправильне ім\'я користувача та/або пароль"</string>
|
||||
<string name="screen_login_error_invalid_user_id">"Це недійсний ідентифікатор користувача. Очікуваний формат: \'@user:homeserver.org\'"</string>
|
||||
<string name="screen_login_error_refresh_tokens">"Цей сервер налаштований на використання токенів оновлення. Вони не підтримуються при використанні входу на основі пароля."</string>
|
||||
<string name="screen_login_error_refresh_tokens">"Цей сервер налаштований на використання оновлюваних токенів. Вони не підтримуються, якщо використовується вхід за допомогою основі пароля."</string>
|
||||
<string name="screen_login_error_unsupported_authentication">"Обраний домашній сервер не підтримує вхід за допомогою пароля або OIDC. Зверніться до адміністратора або виберіть інший домашній сервер."</string>
|
||||
<string name="screen_login_form_header">"Введіть свої дані"</string>
|
||||
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
||||
<string name="screen_login_title">"З поверненням!"</string>
|
||||
<string name="screen_login_title_with_homeserver">"Увійти в %1$s"</string>
|
||||
<string name="screen_qr_code_login_connecting_subtitle">"Встановлення безпечного з\'єднання"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Не вдалося встановити безпечне з\'єднання з новим пристроєм. Ваші існуючі пристрої все ще в безпеці, і вам не потрібно про них турбуватися."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Не вдалося встановити безпечне з\'єднання з новим пристроєм. Ваші наявні пристрої досі в безпеці, і вам не потрібно про них турбуватися."</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Що тепер?"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Спробуйте увійти ще раз за допомогою QR-коду, якщо це була проблема з мережею"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Якщо ви зіткнулися з тією ж проблемою, спробуйте іншу мережу Wi-Fi або використовуйте мобільний інтернет замість Wi-Fi"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Якщо це не спрацює, увійдіть вручну"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"З\'єднання не є безпечним"</string>
|
||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"З\'єднання не безпечне"</string>
|
||||
<string name="screen_qr_code_login_device_code_subtitle">"Вас попросять ввести дві цифри, показані на цьому пристрої."</string>
|
||||
<string name="screen_qr_code_login_device_code_title">"Введіть номер нижче на іншому пристрої"</string>
|
||||
<string name="screen_qr_code_login_device_not_signed_in_scan_state_description">"Увійдіть на іншому пристрої та спробуйте ще раз або скористайтеся іншим пристроєм, що вже в обліковому записі."</string>
|
||||
<string name="screen_qr_code_login_device_not_signed_in_scan_state_subtitle">"Інший пристрій не ввійшов"</string>
|
||||
<string name="screen_qr_code_login_device_not_signed_in_scan_state_description">"Увійдіть на іншому пристрої та спробуйте ще раз або скористайтеся іншим пристроєм, на якому ви вже ввійшли."</string>
|
||||
<string name="screen_qr_code_login_device_not_signed_in_scan_state_subtitle">"Вхід на іншому пристрої не виконано"</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"Вхід було скасовано на іншому пристрої."</string>
|
||||
<string name="screen_qr_code_login_error_cancelled_title">"Запит на вхід скасовано"</string>
|
||||
<string name="screen_qr_code_login_error_declined_subtitle">"Вхід був відхилений на іншому пристрої."</string>
|
||||
|
|
@ -56,9 +57,10 @@
|
|||
<string name="screen_qr_code_login_initial_state_button_title">"Готовий до сканування"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"Відкрийте %1$s на комп\'ютері"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Натисніть на свою аватарку"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Оберіть %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Підключити новий пристрій”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Відскануйте QR-код цим пристроєм"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Виберіть %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Під\'єднати новий пристрій”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Зіскануйте QR-код цим пристроєм"</string>
|
||||
<string name="screen_qr_code_login_initial_state_subtitle">"Доступно лише в тому випадку, якщо ваш постачальник облікового запису підтримує цю функцію."</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Відкрийте %1$s на іншому пристрої, щоб отримати QR-код"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Використовуйте QR-код, показаний на іншому пристрої."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Спробуйте ще раз"</string>
|
||||
|
|
@ -66,7 +68,7 @@
|
|||
<string name="screen_qr_code_login_no_camera_permission_button">"Перейти до налаштувань камери"</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_description">"Вам потрібно дати дозвіл %1$s на використання камери вашого пристрою, щоб продовжити."</string>
|
||||
<string name="screen_qr_code_login_no_camera_permission_state_title">"Надайте доступ до камери, щоб сканувати QR-код"</string>
|
||||
<string name="screen_qr_code_login_scanning_state_title">"Відскануйте QR-код"</string>
|
||||
<string name="screen_qr_code_login_scanning_state_title">"Зіскануйте QR-код"</string>
|
||||
<string name="screen_qr_code_login_start_over_button">"Почати спочатку"</string>
|
||||
<string name="screen_qr_code_login_unknown_error_description">"Сталася несподівана помилка. Будь ласка, спробуйте ще раз."</string>
|
||||
<string name="screen_qr_code_login_verify_code_loading">"Чекаємо на ваш інший пристрій"</string>
|
||||
|
|
@ -75,7 +77,7 @@
|
|||
<string name="screen_server_confirmation_change_server">"Змінити провайдера облікового запису"</string>
|
||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Приватний сервер для співробітників Element."</string>
|
||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
||||
<string name="screen_server_confirmation_message_register">"Тут будуть зберігатися Ваші розмови - так само, як Ви використовуєте поштову скриньку для зберігання своїх електронних листів."</string>
|
||||
<string name="screen_server_confirmation_message_register">"Тут розміщуватимуться ваші розмови — так само як у поштовій скриньці для зберігання своїх електронних листів."</string>
|
||||
<string name="screen_server_confirmation_title_login">"Ви збираєтесь увійти в %1$s"</string>
|
||||
<string name="screen_server_confirmation_title_register">"Ви збираєтеся створити обліковий запис на %1$s"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_account_provider_change">"更改账户提供者"</string>
|
||||
<string name="screen_account_provider_change">"更改账户提供方"</string>
|
||||
<string name="screen_account_provider_form_hint">"服务器地址"</string>
|
||||
<string name="screen_account_provider_form_notice">"输入搜索词或域名地址。"</string>
|
||||
<string name="screen_account_provider_form_subtitle">"搜索公司、社区或私人服务器。"</string>
|
||||
<string name="screen_account_provider_form_title">"查找账户提供者"</string>
|
||||
<string name="screen_account_provider_signin_subtitle">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
|
||||
<string name="screen_account_provider_signin_title">"您即将登录%s"</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
|
||||
<string name="screen_account_provider_form_title">"寻找账户提供方"</string>
|
||||
<string name="screen_account_provider_signin_subtitle">"这是您的对话将存在的地方,就像您使用电子邮件提供方来保存电子邮件一样。"</string>
|
||||
<string name="screen_account_provider_signin_title">"您即将登录 %s"</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"这是您的对话将存在的地方,就像您使用电子邮件提供方来保存电子邮件一样。"</string>
|
||||
<string name="screen_account_provider_signup_title">"您即将在 %s 上创建一个帐户"</string>
|
||||
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org 由 Matrix.org 基金会运营,是用于安全、去中心化的通信的公共 Matrix 网络上的大型免费服务器。"</string>
|
||||
<string name="screen_change_account_provider_other">"其他"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"使用其他帐户提供者,例如您自己的私人服务器或工作帐户。"</string>
|
||||
<string name="screen_change_account_provider_title">"更改账户提供者"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"使用其他账户提供商,例如您自己的私人服务器或工作账户。"</string>
|
||||
<string name="screen_change_account_provider_title">"更改账户提供方"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"我们无法访问此服务器。请检查您输入的服务器网址是否正确。如果 URL 正确,请联系您的服务器管理员寻求进一步帮助。"</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"由于 Well Known 文件中的问题,Sliding Sync 不可用:
|
||||
%1$s"</string>
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
<string name="screen_change_server_form_notice">"您只能连接到支持 Sliding Sync 的现有服务器。您的服务器管理员需要对其进行配置。%1$s"</string>
|
||||
<string name="screen_change_server_subtitle">"您的服务器地址是什么?"</string>
|
||||
<string name="screen_change_server_title">"选择服务器"</string>
|
||||
<string name="screen_create_account_title">"创建账户"</string>
|
||||
<string name="screen_login_error_deactivated_account">"该账户已被停用。"</string>
|
||||
<string name="screen_login_error_invalid_credentials">"错误的用户名和/或密码"</string>
|
||||
<string name="screen_login_error_invalid_user_id">"这不是合法的用户 ID。期望格式:‘@user:homeserver.org’。"</string>
|
||||
|
|
@ -51,7 +52,7 @@
|
|||
|
||||
尝试手动或使用另一个设备扫描二维码."</string>
|
||||
<string name="screen_qr_code_login_error_linking_not_suported_title">"不支持二维码"</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"账户提供者不支持 %1$s."</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"账户提供方不支持 %1$s."</string>
|
||||
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"不支持 %1$s."</string>
|
||||
<string name="screen_qr_code_login_initial_state_button_title">"准备进行扫描"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_1">"在桌面设备上打开 %1$s"</string>
|
||||
|
|
@ -59,6 +60,7 @@
|
|||
<string name="screen_qr_code_login_initial_state_item_3">"选择 %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"「连接新设备」"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"使用此设备扫描二维码"</string>
|
||||
<string name="screen_qr_code_login_initial_state_subtitle">"仅在您的账户提供方支持时才可用。"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"在另一台设备上打开 %1$s 以获取二维码"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"使用其他设备上显示的二维码。"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"再试一次"</string>
|
||||
|
|
@ -70,12 +72,12 @@
|
|||
<string name="screen_qr_code_login_start_over_button">"重新开始"</string>
|
||||
<string name="screen_qr_code_login_unknown_error_description">"发生了意外错误。请再试一次。"</string>
|
||||
<string name="screen_qr_code_login_verify_code_loading">"等着您的其他设备"</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"您的账户提供商可能会要求您提供以下代码来验证登录。"</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"您的账户提供方可能会要求您提供以下代码来验证登录。"</string>
|
||||
<string name="screen_qr_code_login_verify_code_title">"您的验证码"</string>
|
||||
<string name="screen_server_confirmation_change_server">"更改账户提供者"</string>
|
||||
<string name="screen_server_confirmation_change_server">"更改账户提供方"</string>
|
||||
<string name="screen_server_confirmation_message_login_element_dot_io">"专为 Element 员工提供的私人服务器。"</string>
|
||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
|
||||
<string name="screen_server_confirmation_message_register">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
|
||||
<string name="screen_server_confirmation_message_register">"这是您的对话将存在的地方,就像您使用电子邮件提供方来保存电子邮件一样。"</string>
|
||||
<string name="screen_server_confirmation_title_login">"即将登录 %1$s"</string>
|
||||
<string name="screen_server_confirmation_title_register">"即将在 %1$s 上创建一个账户"</string>
|
||||
</resources>
|
||||
|
|
|
|||
18
features/logout/impl/src/main/res/values-fi/translations.xml
Normal file
18
features/logout/impl/src/main/res/values-fi/translations.xml
Normal 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">"Haluatko varmasti kirjautua ulos?"</string>
|
||||
<string name="screen_signout_confirmation_dialog_submit">"Kirjaudu ulos"</string>
|
||||
<string name="screen_signout_confirmation_dialog_title">"Kirjaudu ulos"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Kirjaudutaan ulos…"</string>
|
||||
<string name="screen_signout_key_backup_disabled_subtitle">"Olet kirjautumassa ulos viimeisestä istunnostasi. Jos kirjaudut ulos nyt, menetät pääsyn salattuihin viesteihisi."</string>
|
||||
<string name="screen_signout_key_backup_disabled_title">"Olet poistanut varmuuskopioinnin käytöstä"</string>
|
||||
<string name="screen_signout_key_backup_offline_subtitle">"Avaimiasi varmuuskopioitiin vielä, kun menit offline-tilaan. Muodosta yhteys uudelleen, jotta avaimesi voidaan varmuuskopioida ennen uloskirjautumista."</string>
|
||||
<string name="screen_signout_key_backup_offline_title">"Avaimiasi varmuuskopioidaan vielä"</string>
|
||||
<string name="screen_signout_key_backup_ongoing_subtitle">"Odota, että tämä on valmis ennen uloskirjautumista."</string>
|
||||
<string name="screen_signout_key_backup_ongoing_title">"Avaimiasi varmuuskopioidaan vielä"</string>
|
||||
<string name="screen_signout_preference_item">"Kirjaudu ulos"</string>
|
||||
<string name="screen_signout_recovery_disabled_subtitle">"Olet kirjautumassa ulos viimeisestä istunnostasi. Jos kirjaudut ulos nyt, menetät pääsyn salattuihin viesteihisi."</string>
|
||||
<string name="screen_signout_recovery_disabled_title">"Palautus ei ole käytössä"</string>
|
||||
<string name="screen_signout_save_recovery_key_subtitle">"Olet kirjautumassa ulos viimeisestä istunnostasi. Jos kirjaudut ulos nyt, saatat menettää pääsyn salattuihin viesteihisi."</string>
|
||||
<string name="screen_signout_save_recovery_key_title">"Oletko tallentanut palautusavaimesi?"</string>
|
||||
</resources>
|
||||
|
|
@ -6,9 +6,9 @@
|
|||
<string name="screen_signout_in_progress_dialog_content">"Вихід…"</string>
|
||||
<string name="screen_signout_key_backup_disabled_subtitle">"Ви збираєтеся вийти зі свого останнього сеансу. Якщо ви вийдете зараз, ви втратите доступ до своїх зашифрованих повідомлень."</string>
|
||||
<string name="screen_signout_key_backup_disabled_title">"Ви вимкнули резервне копіювання"</string>
|
||||
<string name="screen_signout_key_backup_offline_subtitle">"Коли ви вийшли з мережі, резервна копія ваших ключів все ще створювалася. Повторно підключіться, щоб зберегти резервну копію ключів перед виходом з системи."</string>
|
||||
<string name="screen_signout_key_backup_offline_subtitle">"Коли ви вийшли з мережі, резервна копія ваших ключів все ще створювалася. Повторно під\'єднайтеся, щоб зберегти резервну копію ключів перед виходом."</string>
|
||||
<string name="screen_signout_key_backup_offline_title">"Резервне копіювання ваших ключів ще триває"</string>
|
||||
<string name="screen_signout_key_backup_ongoing_subtitle">"Зачекайте, поки це завершиться, перш ніж вийти."</string>
|
||||
<string name="screen_signout_key_backup_ongoing_subtitle">"Дочекайтеся завершення процесу, перш ніж вийти."</string>
|
||||
<string name="screen_signout_key_backup_ongoing_title">"Резервне копіювання ваших ключів ще триває"</string>
|
||||
<string name="screen_signout_preference_item">"Вийти"</string>
|
||||
<string name="screen_signout_recovery_disabled_subtitle">"Ви збираєтеся вийти зі свого останнього сеансу. Якщо ви вийдете зараз, ви втратите доступ до своїх зашифрованих повідомлень."</string>
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ class MessagesNode @AssistedInject constructor(
|
|||
state = state,
|
||||
onBackClick = this::navigateUp,
|
||||
onRoomDetailsClick = this::onRoomDetailsClick,
|
||||
onEventClick = this::onEventClick,
|
||||
onEventContentClick = this::onEventClick,
|
||||
onPreviewAttachments = this::onPreviewAttachments,
|
||||
onUserDataClick = this::onUserDataClick,
|
||||
onLinkClick = { url -> onLinkClick(activity, isDark, url, state.timelineState.eventSink) },
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ fun MessagesView(
|
|||
state: MessagesState,
|
||||
onBackClick: () -> Unit,
|
||||
onRoomDetailsClick: () -> Unit,
|
||||
onEventClick: (event: TimelineItem.Event) -> Boolean,
|
||||
onEventContentClick: (event: TimelineItem.Event) -> Boolean,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit,
|
||||
|
|
@ -142,9 +142,14 @@ fun MessagesView(
|
|||
// This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose
|
||||
val localView = LocalView.current
|
||||
|
||||
fun onMessageClick(event: TimelineItem.Event) {
|
||||
fun hidingKeyboard(block: () -> Unit) {
|
||||
localView.hideKeyboard()
|
||||
block()
|
||||
}
|
||||
|
||||
fun onContentClick(event: TimelineItem.Event) {
|
||||
Timber.v("onMessageClick= ${event.id}")
|
||||
val hideKeyboard = onEventClick(event)
|
||||
val hideKeyboard = onEventContentClick(event)
|
||||
if (hideKeyboard) {
|
||||
localView.hideKeyboard()
|
||||
}
|
||||
|
|
@ -152,13 +157,14 @@ fun MessagesView(
|
|||
|
||||
fun onMessageLongClick(event: TimelineItem.Event) {
|
||||
Timber.v("OnMessageLongClicked= ${event.id}")
|
||||
localView.hideKeyboard()
|
||||
state.actionListState.eventSink(
|
||||
ActionListEvents.ComputeForMessage(
|
||||
event = event,
|
||||
userEventPermissions = state.userEventPermissions,
|
||||
hidingKeyboard {
|
||||
state.actionListState.eventSink(
|
||||
ActionListEvents.ComputeForMessage(
|
||||
event = event,
|
||||
userEventPermissions = state.userEventPermissions,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) {
|
||||
|
|
@ -189,13 +195,8 @@ fun MessagesView(
|
|||
roomAvatar = state.roomAvatar.dataOrNull(),
|
||||
heroes = state.heroes,
|
||||
roomCallState = state.roomCallState,
|
||||
onBackClick = {
|
||||
// Since the textfield is now based on an Android view, this is no longer done automatically.
|
||||
// We need to hide the keyboard when navigating out of this screen.
|
||||
localView.hideKeyboard()
|
||||
onBackClick()
|
||||
},
|
||||
onRoomDetailsClick = onRoomDetailsClick,
|
||||
onBackClick = { hidingKeyboard { onBackClick() } },
|
||||
onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } },
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
)
|
||||
}
|
||||
|
|
@ -206,9 +207,9 @@ fun MessagesView(
|
|||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
onMessageClick = ::onMessageClick,
|
||||
onContentClick = ::onContentClick,
|
||||
onMessageLongClick = ::onMessageLongClick,
|
||||
onUserDataClick = onUserDataClick,
|
||||
onUserDataClick = { hidingKeyboard { onUserDataClick(it) } },
|
||||
onLinkClick = onLinkClick,
|
||||
onReactionClick = ::onEmojiReactionClick,
|
||||
onReactionLongClick = ::onEmojiReactionLongClick,
|
||||
|
|
@ -306,7 +307,7 @@ private fun AttachmentStateView(
|
|||
@Composable
|
||||
private fun MessagesViewContent(
|
||||
state: MessagesState,
|
||||
onMessageClick: (TimelineItem.Event) -> Unit,
|
||||
onContentClick: (TimelineItem.Event) -> Unit,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onReactionClick: (key: String, TimelineItem.Event) -> Unit,
|
||||
|
|
@ -382,7 +383,7 @@ private fun MessagesViewContent(
|
|||
timelineProtectionState = state.timelineProtectionState,
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = onLinkClick,
|
||||
onMessageClick = onMessageClick,
|
||||
onContentClick = onContentClick,
|
||||
onMessageLongClick = onMessageLongClick,
|
||||
onSwipeToReply = onSwipeToReply,
|
||||
onReactionClick = onReactionClick,
|
||||
|
|
@ -568,7 +569,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
|
|||
state = state,
|
||||
onBackClick = {},
|
||||
onRoomDetailsClick = {},
|
||||
onEventClick = { false },
|
||||
onEventContentClick = { false },
|
||||
onUserDataClick = {},
|
||||
onLinkClick = {},
|
||||
onPreviewAttachments = {},
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ internal fun MessagesViewWithIdentityChangePreview(
|
|||
),
|
||||
onBackClick = {},
|
||||
onRoomDetailsClick = {},
|
||||
onEventClick = { false },
|
||||
onEventContentClick = { false },
|
||||
onUserDataClick = {},
|
||||
onLinkClick = {},
|
||||
onPreviewAttachments = {},
|
||||
|
|
|
|||
|
|
@ -51,10 +51,12 @@ class ResolveVerifiedUserSendFailurePresenter @Inject constructor(
|
|||
is ResolveVerifiedUserSendFailureEvents.ComputeForMessage -> {
|
||||
val sendState = event.messageEvent.localSendState as? LocalEventSendState.Failed.VerifiedUser
|
||||
val transactionId = event.messageEvent.transactionId
|
||||
resolver = if (sendState != null && transactionId != null) {
|
||||
val sendHandle = event.messageEvent.sendhandle
|
||||
resolver = if (sendState != null && transactionId != null && sendHandle != null) {
|
||||
VerifiedUserSendFailureResolver(
|
||||
room = room,
|
||||
transactionId = transactionId,
|
||||
sendHandle = sendHandle,
|
||||
iterator = VerifiedUserSendFailureIterator.from(sendState)
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.messages.impl.crypto.sendfailure.resolve
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import io.element.android.libraries.matrix.api.core.SendHandle
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
|
|
@ -22,6 +23,7 @@ import timber.log.Timber
|
|||
class VerifiedUserSendFailureResolver(
|
||||
private val room: MatrixRoom,
|
||||
private val transactionId: TransactionId,
|
||||
private val sendHandle: SendHandle,
|
||||
private val iterator: VerifiedUserSendFailureIterator,
|
||||
) {
|
||||
val currentSendFailure = mutableStateOf<LocalEventSendState.Failed.VerifiedUser?>(null)
|
||||
|
|
@ -33,7 +35,7 @@ class VerifiedUserSendFailureResolver(
|
|||
}
|
||||
|
||||
suspend fun resend(): Result<Unit> {
|
||||
return room.retrySendMessage(transactionId)
|
||||
return sendHandle.retry()
|
||||
.onSuccess {
|
||||
Timber.d("Succeed to resend message with transactionId: $transactionId")
|
||||
currentSendFailure.value = null
|
||||
|
|
@ -46,10 +48,10 @@ class VerifiedUserSendFailureResolver(
|
|||
suspend fun resolveAndResend(): Result<Unit> {
|
||||
return when (val failure = currentSendFailure.value) {
|
||||
is LocalEventSendState.Failed.VerifiedUserHasUnsignedDevice -> {
|
||||
room.ignoreDeviceTrustAndResend(failure.devices, transactionId)
|
||||
room.ignoreDeviceTrustAndResend(failure.devices, sendHandle)
|
||||
}
|
||||
is LocalEventSendState.Failed.VerifiedUserChangedIdentity -> {
|
||||
room.withdrawVerificationAndResend(failure.users, transactionId)
|
||||
room.withdrawVerificationAndResend(failure.users, sendHandle)
|
||||
}
|
||||
else -> {
|
||||
Result.failure(IllegalStateException("Unknown send failure type"))
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ private fun PinnedMessagesListLoaded(
|
|||
focusedEventId = null,
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = onLinkClick,
|
||||
onClick = onEventClick,
|
||||
onContentClick = onEventClick,
|
||||
onLongClick = ::onMessageLongClick,
|
||||
inReplyToClick = {},
|
||||
onReactionClick = { _, _ -> },
|
||||
|
|
@ -230,6 +230,7 @@ private fun PinnedMessagesListLoaded(
|
|||
TimelineItemEventContentViewWrapper(
|
||||
event = event,
|
||||
timelineProtectionState = state.timelineProtectionState,
|
||||
onContentClick = { onEventClick(event) },
|
||||
onLinkClick = onLinkClick,
|
||||
modifier = contentModifier,
|
||||
onContentLayoutChange = onContentLayoutChange
|
||||
|
|
@ -244,6 +245,7 @@ private fun PinnedMessagesListLoaded(
|
|||
private fun TimelineItemEventContentViewWrapper(
|
||||
event: TimelineItem.Event,
|
||||
timelineProtectionState: TimelineProtectionState,
|
||||
onContentClick: () -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -258,10 +260,12 @@ private fun TimelineItemEventContentViewWrapper(
|
|||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
|
||||
onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onLinkClick = onLinkClick,
|
||||
eventSink = { },
|
||||
modifier = modifier,
|
||||
onContentClick = onContentClick,
|
||||
onLongClick = null,
|
||||
onContentLayoutChange = onContentLayoutChange
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
|
|
@ -39,9 +38,9 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
|||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
|
|
@ -89,21 +88,16 @@ fun ReportMessageView(
|
|||
) {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
TextField(
|
||||
value = state.reason,
|
||||
onValueChange = { state.eventSink(ReportMessageEvents.UpdateReason(it)) },
|
||||
placeholder = { Text(stringResource(R.string.screen_report_content_hint)) },
|
||||
placeholder = stringResource(R.string.screen_report_content_hint),
|
||||
minLines = 3,
|
||||
enabled = !isSending,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 90.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.screen_report_content_explanation),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 24.dp, start = 16.dp, end = 16.dp)
|
||||
.heightIn(min = 90.dp),
|
||||
supportingText = stringResource(R.string.screen_report_content_explanation),
|
||||
)
|
||||
|
||||
Row(
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ internal fun aTimelineItemEvent(
|
|||
origin = null,
|
||||
timelineItemDebugInfoProvider = { debugInfo },
|
||||
messageShieldProvider = { messageShield },
|
||||
sendHandleProvider = { null }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ fun TimelineView(
|
|||
timelineProtectionState: TimelineProtectionState,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onMessageClick: (TimelineItem.Event) -> Unit,
|
||||
onContentClick: (TimelineItem.Event) -> Unit,
|
||||
onMessageLongClick: (TimelineItem.Event) -> Unit,
|
||||
onSwipeToReply: (TimelineItem.Event) -> Unit,
|
||||
onReactionClick: (emoji: String, TimelineItem.Event) -> Unit,
|
||||
|
|
@ -141,7 +141,7 @@ fun TimelineView(
|
|||
focusedEventId = state.focusedEventId,
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = onLinkClick,
|
||||
onClick = onMessageClick,
|
||||
onContentClick = onContentClick,
|
||||
onLongClick = onMessageLongClick,
|
||||
inReplyToClick = ::inReplyToClick,
|
||||
onReactionClick = onReactionClick,
|
||||
|
|
@ -322,7 +322,7 @@ internal fun TimelineViewPreview(
|
|||
timelineProtectionState = aTimelineProtectionState(),
|
||||
onUserDataClick = {},
|
||||
onLinkClick = {},
|
||||
onMessageClick = {},
|
||||
onContentClick = {},
|
||||
onMessageLongClick = {},
|
||||
onSwipeToReply = {},
|
||||
onReactionClick = { _, _ -> },
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ internal fun TimelineViewMessageShieldPreview() = ElementPreview {
|
|||
timelineProtectionState = aTimelineProtectionState(),
|
||||
onUserDataClick = {},
|
||||
onLinkClick = {},
|
||||
onMessageClick = {},
|
||||
onContentClick = {},
|
||||
onMessageLongClick = {},
|
||||
onSwipeToReply = {},
|
||||
onReactionClick = { _, _ -> },
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ internal fun ATimelineItemEventRow(
|
|||
timelineProtectionState = timelineProtectionState,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
isHighlighted = isHighlighted,
|
||||
onClick = {},
|
||||
onEventClick = {},
|
||||
onLongClick = {},
|
||||
onLinkClick = {},
|
||||
onUserDataClick = {},
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ fun TimelineItemEventRow(
|
|||
renderReadReceipts: Boolean,
|
||||
isLastOutgoingMessage: Boolean,
|
||||
isHighlighted: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onEventClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
|
|
@ -127,10 +127,15 @@ fun TimelineItemEventRow(
|
|||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { contentModifier, onContentLayoutChange ->
|
||||
// Only pass down a custom clickable lambda if the content can be clicked separately
|
||||
val onContentClick = onEventClick.takeUnless { event.isWholeContentClickable }
|
||||
|
||||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
|
||||
onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onContentClick = onContentClick,
|
||||
onLongClick = onLongClick,
|
||||
onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onLinkClick = onLinkClick,
|
||||
eventSink = eventSink,
|
||||
modifier = contentModifier,
|
||||
|
|
@ -173,7 +178,7 @@ fun TimelineItemEventRow(
|
|||
isHighlighted = isHighlighted,
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
interactionSource = interactionSource,
|
||||
onClick = onClick,
|
||||
onContentClick = onEventClick,
|
||||
onLongClick = onLongClick,
|
||||
inReplyToClick = ::inReplyToClick,
|
||||
onUserDataClick = ::onUserDataClick,
|
||||
|
|
@ -207,7 +212,7 @@ fun TimelineItemEventRow(
|
|||
isHighlighted = isHighlighted,
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
interactionSource = interactionSource,
|
||||
onClick = onClick,
|
||||
onContentClick = onEventClick,
|
||||
onLongClick = onLongClick,
|
||||
inReplyToClick = ::inReplyToClick,
|
||||
onUserDataClick = ::onUserDataClick,
|
||||
|
|
@ -263,7 +268,7 @@ private fun TimelineItemEventRowContent(
|
|||
isHighlighted: Boolean,
|
||||
timelineRoomInfo: TimelineRoomInfo,
|
||||
interactionSource: MutableInteractionSource,
|
||||
onClick: () -> Unit,
|
||||
onContentClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
inReplyToClick: () -> Unit,
|
||||
onUserDataClick: () -> Unit,
|
||||
|
|
@ -340,7 +345,7 @@ private fun TimelineItemEventRowContent(
|
|||
},
|
||||
state = bubbleState,
|
||||
interactionSource = interactionSource,
|
||||
onClick = onClick,
|
||||
onClick = onContentClick,
|
||||
onLongClick = onLongClick,
|
||||
) {
|
||||
MessageEventBubbleContent(
|
||||
|
|
|
|||
|
|
@ -57,10 +57,12 @@ fun TimelineItemGroupedEventsRow(
|
|||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
|
||||
onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onLinkClick = onLinkClick,
|
||||
eventSink = eventSink,
|
||||
modifier = contentModifier,
|
||||
onContentClick = null,
|
||||
onLongClick = null,
|
||||
onContentLayoutChange = onContentLayoutChange
|
||||
)
|
||||
},
|
||||
|
|
@ -121,10 +123,12 @@ private fun TimelineItemGroupedEventsRowContent(
|
|||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
|
||||
onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onLinkClick = onLinkClick,
|
||||
eventSink = eventSink,
|
||||
modifier = contentModifier,
|
||||
onContentClick = null,
|
||||
onLongClick = null,
|
||||
onContentLayoutChange = onContentLayoutChange
|
||||
)
|
||||
},
|
||||
|
|
@ -152,7 +156,7 @@ private fun TimelineItemGroupedEventsRowContent(
|
|||
focusedEventId = focusedEventId,
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = onLinkClick,
|
||||
onClick = onClick,
|
||||
onContentClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
inReplyToClick = inReplyToClick,
|
||||
onReactionClick = onReactionClick,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
import io.element.android.features.messages.impl.timeline.protection.mustBeProtected
|
||||
import io.element.android.libraries.designsystem.text.toPx
|
||||
import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
|
|
@ -44,7 +43,7 @@ internal fun TimelineItemRow(
|
|||
focusedEventId: EventId?,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onClick: (TimelineItem.Event) -> Unit,
|
||||
onContentClick: (TimelineItem.Event) -> Unit,
|
||||
onLongClick: (TimelineItem.Event) -> Unit,
|
||||
inReplyToClick: (EventId) -> Unit,
|
||||
onReactionClick: (key: String, TimelineItem.Event) -> Unit,
|
||||
|
|
@ -60,7 +59,9 @@ internal fun TimelineItemRow(
|
|||
TimelineItemEventContentView(
|
||||
content = event.content,
|
||||
hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
|
||||
onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
|
||||
onContentClick = { onContentClick(event) },
|
||||
onLongClick = { onLongClick(event) },
|
||||
onLinkClick = onLinkClick,
|
||||
eventSink = eventSink,
|
||||
modifier = contentModifier,
|
||||
|
|
@ -95,7 +96,7 @@ internal fun TimelineItemRow(
|
|||
renderReadReceipts = renderReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
isHighlighted = timelineItem.isEvent(focusedEventId),
|
||||
onClick = { onClick(timelineItem) },
|
||||
onClick = { onContentClick(timelineItem) },
|
||||
onReadReceiptsClick = onReadReceiptClick,
|
||||
onLongClick = { onLongClick(timelineItem) },
|
||||
eventSink = eventSink,
|
||||
|
|
@ -118,11 +119,7 @@ internal fun TimelineItemRow(
|
|||
timelineProtectionState = timelineProtectionState,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
isHighlighted = timelineItem.isEvent(focusedEventId),
|
||||
onClick = if (timelineProtectionState.hideMediaContent(timelineItem.eventId) && timelineItem.mustBeProtected()) {
|
||||
{}
|
||||
} else {
|
||||
{ onClick(timelineItem) }
|
||||
},
|
||||
onEventClick = { onContentClick(timelineItem) },
|
||||
onLongClick = { onLongClick(timelineItem) },
|
||||
onLinkClick = onLinkClick,
|
||||
onUserDataClick = onUserDataClick,
|
||||
|
|
@ -148,7 +145,7 @@ internal fun TimelineItemRow(
|
|||
renderReadReceipts = renderReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
focusedEventId = focusedEventId,
|
||||
onClick = onClick,
|
||||
onClick = onContentClick,
|
||||
onLongClick = onLongClick,
|
||||
inReplyToClick = inReplyToClick,
|
||||
onUserDataClick = onUserDataClick,
|
||||
|
|
|
|||
|
|
@ -72,8 +72,10 @@ fun TimelineItemStateEventRow(
|
|||
content = event.content,
|
||||
onLinkClick = {},
|
||||
hideMediaContent = false,
|
||||
onShowClick = {},
|
||||
onShowContentClick = {},
|
||||
eventSink = eventSink,
|
||||
onContentClick = null,
|
||||
onLongClick = null,
|
||||
modifier = Modifier.defaultTimelineContentPadding()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ import io.element.android.libraries.architecture.Presenter
|
|||
fun TimelineItemEventContentView(
|
||||
content: TimelineItemEventContent,
|
||||
hideMediaContent: Boolean,
|
||||
onShowClick: () -> Unit,
|
||||
onContentClick: (() -> Unit)?,
|
||||
onLongClick: (() -> Unit)?,
|
||||
onShowContentClick: () -> Unit,
|
||||
onLinkClick: (url: String) -> Unit,
|
||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -72,20 +74,28 @@ fun TimelineItemEventContentView(
|
|||
is TimelineItemImageContent -> TimelineItemImageView(
|
||||
content = content,
|
||||
hideMediaContent = hideMediaContent,
|
||||
onShowClick = onShowClick,
|
||||
onContentClick = onContentClick,
|
||||
onLongClick = onLongClick,
|
||||
onShowContentClick = onShowContentClick,
|
||||
onLinkClick = onLinkClick,
|
||||
onContentLayoutChange = onContentLayoutChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
is TimelineItemStickerContent -> TimelineItemStickerView(
|
||||
content = content,
|
||||
hideMediaContent = hideMediaContent,
|
||||
onShowClick = onShowClick,
|
||||
onContentClick = onContentClick,
|
||||
onLongClick = onLongClick,
|
||||
onShowClick = onShowContentClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
is TimelineItemVideoContent -> TimelineItemVideoView(
|
||||
content = content,
|
||||
hideMediaContent = hideMediaContent,
|
||||
onShowClick = onShowClick,
|
||||
onContentClick = onContentClick,
|
||||
onLongClick = onLongClick,
|
||||
onShowContentClick = onShowContentClick,
|
||||
onLinkClick = onLinkClick,
|
||||
onContentLayoutChange = onContentLayoutChange,
|
||||
modifier = modifier
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
package io.element.android.features.messages.impl.timeline.components.event
|
||||
|
||||
import android.text.SpannedString
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -50,16 +52,19 @@ import io.element.android.features.messages.impl.timeline.protection.ProtectedVi
|
|||
import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
|
||||
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.wysiwyg.compose.EditorStyledText
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun TimelineItemImageView(
|
||||
content: TimelineItemImageContent,
|
||||
hideMediaContent: Boolean,
|
||||
onShowClick: () -> Unit,
|
||||
onContentClick: (() -> Unit)?,
|
||||
onLongClick: (() -> Unit)?,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onShowContentClick: () -> Unit,
|
||||
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -78,13 +83,14 @@ fun TimelineItemImageView(
|
|||
) {
|
||||
ProtectedView(
|
||||
hideContent = hideMediaContent,
|
||||
onShowClick = onShowClick,
|
||||
onShowClick = onShowContentClick,
|
||||
) {
|
||||
var isLoaded by remember { mutableStateOf(false) }
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(if (isLoaded) Modifier.background(Color.White) else Modifier),
|
||||
.then(if (isLoaded) Modifier.background(Color.White) else Modifier)
|
||||
.then(if (onContentClick != null) Modifier.combinedClickable(onClick = onContentClick, onLongClick = onLongClick) else Modifier),
|
||||
model = content.thumbnailMediaRequestData,
|
||||
contentScale = ContentScale.Fit,
|
||||
alignment = Alignment.Center,
|
||||
|
|
@ -99,9 +105,7 @@ fun TimelineItemImageView(
|
|||
val caption = if (LocalInspectionMode.current) {
|
||||
SpannedString(content.caption)
|
||||
} else {
|
||||
content.formattedCaption?.body
|
||||
?.takeIf { content.formattedCaption.format == MessageFormat.HTML }
|
||||
?: SpannedString(content.caption)
|
||||
content.formattedCaption ?: SpannedString(content.caption)
|
||||
}
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides ElementTheme.colors.textPrimary,
|
||||
|
|
@ -114,6 +118,7 @@ fun TimelineItemImageView(
|
|||
.widthIn(min = MIN_HEIGHT_IN_DP.dp * aspectRatio, max = MAX_HEIGHT_IN_DP.dp * aspectRatio),
|
||||
text = caption,
|
||||
style = ElementRichTextEditorStyle.textStyle(),
|
||||
onLinkClickedListener = onLinkClick,
|
||||
releaseOnDetach = false,
|
||||
onTextLayout = ContentAvoidingLayout.measureLegacyLastTextLine(onContentLayoutChange = onContentLayoutChange),
|
||||
)
|
||||
|
|
@ -128,7 +133,10 @@ internal fun TimelineItemImageViewPreview(@PreviewParameter(TimelineItemImageCon
|
|||
TimelineItemImageView(
|
||||
content = content,
|
||||
hideMediaContent = false,
|
||||
onShowClick = {},
|
||||
onShowContentClick = {},
|
||||
onContentClick = {},
|
||||
onLongClick = {},
|
||||
onLinkClick = {},
|
||||
onContentLayoutChange = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -139,7 +147,10 @@ internal fun TimelineItemImageViewHideMediaContentPreview() = ElementPreview {
|
|||
TimelineItemImageView(
|
||||
content = aTimelineItemImageContent(),
|
||||
hideMediaContent = true,
|
||||
onShowClick = {},
|
||||
onShowContentClick = {},
|
||||
onContentClick = {},
|
||||
onLongClick = {},
|
||||
onLinkClick = {},
|
||||
onContentLayoutChange = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,24 +7,21 @@
|
|||
|
||||
package io.element.android.features.messages.impl.timeline.components.event
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.messages.impl.R
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun TimelineItemLegacyCallInviteView(
|
||||
|
|
@ -32,20 +29,18 @@ fun TimelineItemLegacyCallInviteView(
|
|||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.VoiceCall(),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
tint = ElementTheme.colors.iconSecondary,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = stringResource(CommonStrings.common_call_invite),
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(R.string.screen_room_timeline_legacy_call),
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,5 +51,7 @@ fun TimelineItemLocationView(
|
|||
@Composable
|
||||
internal fun TimelineItemLocationViewPreview(@PreviewParameter(TimelineItemLocationContentProvider::class) content: TimelineItemLocationContent) =
|
||||
ElementPreview {
|
||||
TimelineItemLocationView(content)
|
||||
TimelineItemLocationView(
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
package io.element.android.features.messages.impl.timeline.components.event
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -36,10 +38,13 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
|
||||
private const val STICKER_SIZE_IN_DP = 128
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun TimelineItemStickerView(
|
||||
content: TimelineItemStickerContent,
|
||||
hideMediaContent: Boolean,
|
||||
onContentClick: (() -> Unit)?,
|
||||
onLongClick: (() -> Unit)?,
|
||||
onShowClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -61,7 +66,8 @@ fun TimelineItemStickerView(
|
|||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.then(if (isLoaded) Modifier.background(Color.White) else Modifier),
|
||||
.then(if (isLoaded) Modifier.background(Color.White) else Modifier)
|
||||
.then(if (onContentClick != null) Modifier.combinedClickable(onClick = onContentClick, onLongClick = onLongClick) else Modifier),
|
||||
model = MediaRequestData(
|
||||
source = content.preferredMediaSource,
|
||||
kind = MediaRequestData.Kind.File(
|
||||
|
|
@ -85,6 +91,8 @@ internal fun TimelineItemStickerViewPreview(@PreviewParameter(TimelineItemSticke
|
|||
TimelineItemStickerView(
|
||||
content = content,
|
||||
hideMediaContent = false,
|
||||
onContentClick = {},
|
||||
onLongClick = {},
|
||||
onShowClick = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@
|
|||
package io.element.android.features.messages.impl.timeline.components.event
|
||||
|
||||
import android.text.SpannedString
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -56,7 +58,6 @@ import io.element.android.libraries.designsystem.components.blurhash.blurHashBac
|
|||
import io.element.android.libraries.designsystem.modifiers.roundedBackground
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
|
||||
import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_HEIGHT
|
||||
import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_WIDTH
|
||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||
|
|
@ -64,11 +65,15 @@ import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
|
|||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.wysiwyg.compose.EditorStyledText
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun TimelineItemVideoView(
|
||||
content: TimelineItemVideoContent,
|
||||
hideMediaContent: Boolean,
|
||||
onShowClick: () -> Unit,
|
||||
onContentClick: (() -> Unit)?,
|
||||
onLongClick: (() -> Unit)?,
|
||||
onShowContentClick: () -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -90,13 +95,14 @@ fun TimelineItemVideoView(
|
|||
) {
|
||||
ProtectedView(
|
||||
hideContent = hideMediaContent,
|
||||
onShowClick = onShowClick,
|
||||
onShowClick = onShowContentClick,
|
||||
) {
|
||||
var isLoaded by remember { mutableStateOf(false) }
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(if (isLoaded) Modifier.background(Color.White) else Modifier),
|
||||
.then(if (isLoaded) Modifier.background(Color.White) else Modifier)
|
||||
.then(if (onContentClick != null) Modifier.combinedClickable(onClick = onContentClick, onLongClick = onLongClick) else Modifier),
|
||||
model = MediaRequestData(
|
||||
source = content.thumbnailSource,
|
||||
kind = MediaRequestData.Kind.Thumbnail(
|
||||
|
|
@ -128,9 +134,7 @@ fun TimelineItemVideoView(
|
|||
val caption = if (LocalInspectionMode.current) {
|
||||
SpannedString(content.caption)
|
||||
} else {
|
||||
content.formattedCaption?.body
|
||||
?.takeIf { content.formattedCaption.format == MessageFormat.HTML }
|
||||
?: SpannedString(content.caption)
|
||||
content.formattedCaption ?: SpannedString(content.caption)
|
||||
}
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides ElementTheme.colors.textPrimary,
|
||||
|
|
@ -142,6 +146,7 @@ fun TimelineItemVideoView(
|
|||
.padding(horizontal = 4.dp) // This is (12.dp - 8.dp) contentPadding from CommonLayout
|
||||
.widthIn(min = MIN_HEIGHT_IN_DP.dp * aspectRatio, max = MAX_HEIGHT_IN_DP.dp * aspectRatio),
|
||||
text = caption,
|
||||
onLinkClickedListener = onLinkClick,
|
||||
style = ElementRichTextEditorStyle.textStyle(),
|
||||
releaseOnDetach = false,
|
||||
onTextLayout = ContentAvoidingLayout.measureLegacyLastTextLine(onContentLayoutChange = onContentLayoutChange),
|
||||
|
|
@ -157,7 +162,10 @@ internal fun TimelineItemVideoViewPreview(@PreviewParameter(TimelineItemVideoCon
|
|||
TimelineItemVideoView(
|
||||
content = content,
|
||||
hideMediaContent = false,
|
||||
onShowClick = {},
|
||||
onShowContentClick = {},
|
||||
onContentClick = {},
|
||||
onLongClick = {},
|
||||
onLinkClick = {},
|
||||
onContentLayoutChange = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -168,7 +176,10 @@ internal fun TimelineItemVideoViewHideMediaContentPreview() = ElementPreview {
|
|||
TimelineItemVideoView(
|
||||
content = aTimelineItemVideoContent(),
|
||||
hideMediaContent = true,
|
||||
onShowClick = {},
|
||||
onShowContentClick = {},
|
||||
onContentClick = {},
|
||||
onLongClick = {},
|
||||
onLinkClick = {},
|
||||
onContentLayoutChange = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
TimelineItemImageContent(
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
mediaSource = messageType.source,
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
|
|
@ -105,7 +105,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
TimelineItemStickerContent(
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
mediaSource = messageType.source,
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
|
|
@ -142,7 +142,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
TimelineItemVideoContent(
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
videoSource = messageType.source,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
|
|
@ -161,7 +161,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
TimelineItemAudioContent(
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
mediaSource = messageType.source,
|
||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
|
|
@ -176,7 +176,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
eventId = eventId,
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
mediaSource = messageType.source,
|
||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
|
|
@ -187,7 +187,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
TimelineItemAudioContent(
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
mediaSource = messageType.source,
|
||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
|
|
@ -202,7 +202,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
TimelineItemFileContent(
|
||||
filename = messageType.filename,
|
||||
caption = messageType.caption?.trimEnd(),
|
||||
formattedCaption = messageType.formattedCaption,
|
||||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
fileSource = messageType.source,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension),
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ class TimelineItemEventFactory @AssistedInject constructor(
|
|||
origin = currentTimelineItem.event.origin,
|
||||
timelineItemDebugInfoProvider = currentTimelineItem.event.timelineItemDebugInfoProvider,
|
||||
messageShieldProvider = currentTimelineItem.event.messageShieldProvider,
|
||||
sendHandleProvider = currentTimelineItem.event.sendHandleProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ package io.element.android.features.messages.impl.timeline.model
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.SendHandle
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
|
@ -23,6 +26,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSen
|
|||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShieldProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.SendHandleProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemDebugInfoProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
||||
|
|
@ -80,6 +84,7 @@ sealed interface TimelineItem {
|
|||
val origin: TimelineItemEventOrigin?,
|
||||
val timelineItemDebugInfoProvider: TimelineItemDebugInfoProvider,
|
||||
val messageShieldProvider: MessageShieldProvider,
|
||||
val sendHandleProvider: SendHandleProvider,
|
||||
) : TimelineItem {
|
||||
val showSenderInformation = groupPosition.isNew() && !isMine
|
||||
|
||||
|
|
@ -93,6 +98,17 @@ sealed interface TimelineItem {
|
|||
|
||||
val isRemote = eventId != null
|
||||
|
||||
/** Whether a click on any part of the event bubble should trigger the 'onContentClick' callback.
|
||||
*
|
||||
* This is `true` for all events except for visual media events with a caption or formatted caption.
|
||||
*/
|
||||
val isWholeContentClickable = when (content) {
|
||||
is TimelineItemStickerContent -> content.formattedCaption == null && content.caption == null
|
||||
is TimelineItemImageContent -> content.formattedCaption == null && content.caption == null
|
||||
is TimelineItemVideoContent -> content.formattedCaption == null && content.caption == null
|
||||
else -> true
|
||||
}
|
||||
|
||||
val eventOrTransactionId: EventOrTransactionId
|
||||
get() = EventOrTransactionId.from(eventId = eventId, transactionId = transactionId)
|
||||
|
||||
|
|
@ -101,6 +117,8 @@ sealed interface TimelineItem {
|
|||
|
||||
val debugInfo: TimelineItemDebugInfo
|
||||
get() = timelineItemDebugInfoProvider()
|
||||
|
||||
val sendhandle: SendHandle? get() = sendHandleProvider()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@
|
|||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class TimelineItemAudioContent(
|
||||
override val filename: String,
|
||||
override val caption: String?,
|
||||
override val formattedCaption: FormattedBody?,
|
||||
override val formattedCaption: CharSequence?,
|
||||
val duration: Duration,
|
||||
val mediaSource: MediaSource,
|
||||
val mimeType: String,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue