Merge branch 'develop' into separate_import_error
This commit is contained in:
commit
e5ff66e827
679 changed files with 12323 additions and 1753 deletions
|
|
@ -913,3 +913,8 @@ ij_yaml_sequence_on_new_line = false
|
||||||
ij_yaml_space_before_colon = false
|
ij_yaml_space_before_colon = false
|
||||||
ij_yaml_spaces_within_braces = true
|
ij_yaml_spaces_within_braces = true
|
||||||
ij_yaml_spaces_within_brackets = true
|
ij_yaml_spaces_within_brackets = true
|
||||||
|
|
||||||
|
[**/generated/**]
|
||||||
|
generated_code = true
|
||||||
|
ij_formatter_enabled = false
|
||||||
|
ktlint = disabled
|
||||||
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
screenshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
screenshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
libraries/compound/screenshots/** filter=lfs diff=lfs merge=lfs -text
|
||||||
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
**/docs/images-lfs/*.png filter=lfs diff=lfs merge=lfs -text
|
**/docs/images-lfs/*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
libraries/mediaupload/impl/src/test/assets/* filter=lfs diff=lfs merge=lfs -text
|
libraries/mediaupload/impl/src/test/assets/* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
|
||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug APKs
|
- name: Assemble debug APKs
|
||||||
|
|
|
||||||
2
.github/workflows/build_enterprise.yml
vendored
2
.github/workflows/build_enterprise.yml
vendored
|
|
@ -44,7 +44,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug Gplay Enterprise APK
|
- name: Assemble debug Gplay Enterprise APK
|
||||||
|
|
|
||||||
2
.github/workflows/generate_github_pages.yml
vendored
2
.github/workflows/generate_github_pages.yml
vendored
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
|
||||||
2
.github/workflows/maestro-local.yml
vendored
2
.github/workflows/maestro-local.yml
vendored
|
|
@ -34,7 +34,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug APK
|
- name: Assemble debug APK
|
||||||
|
|
|
||||||
4
.github/workflows/nightlyReports.yml
vendored
4
.github/workflows/nightlyReports.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: false
|
cache-read-only: false
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Dependency analysis
|
- name: Dependency analysis
|
||||||
|
|
|
||||||
12
.github/workflows/quality.yml
vendored
12
.github/workflows/quality.yml
vendored
|
|
@ -52,7 +52,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
@ -90,7 +90,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Konsist tests
|
- name: Run Konsist tests
|
||||||
|
|
@ -130,7 +130,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Build Gplay Debug
|
- name: Build Gplay Debug
|
||||||
|
|
@ -174,7 +174,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Detekt
|
- name: Run Detekt
|
||||||
|
|
@ -214,7 +214,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Ktlint check
|
- name: Run Ktlint check
|
||||||
|
|
@ -254,7 +254,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Knit
|
- name: Run Knit
|
||||||
|
|
|
||||||
2
.github/workflows/recordScreenshots.yml
vendored
2
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -40,7 +40,7 @@ jobs:
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
# Add gradle cache, this should speed up the process
|
# Add gradle cache, this should speed up the process
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Record screenshots
|
- name: Record screenshots
|
||||||
|
|
|
||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
- name: Create app bundle
|
- name: Create app bundle
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
@ -66,7 +66,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
- name: Create Enterprise app bundle
|
- name: Create Enterprise app bundle
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
@ -94,7 +94,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
- name: Create APKs
|
- name: Create APKs
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,12 @@ echo "Deleting previous screenshots"
|
||||||
echo "Record screenshots"
|
echo "Record screenshots"
|
||||||
./gradlew recordPaparazziDebug --stacktrace $GRADLE_ARGS
|
./gradlew recordPaparazziDebug --stacktrace $GRADLE_ARGS
|
||||||
|
|
||||||
|
echo "Deleting previous screenshots"
|
||||||
|
./gradlew removeOldScreenshots --stacktrace --warn $GRADLE_ARGS
|
||||||
|
|
||||||
|
echo "Record screenshots (Compound)"
|
||||||
|
./gradlew :libraries:compound:recordRoborazziDebug --stacktrace -PpreDexEnable=false --max-workers 4 --warn $GRADLE_ARGS
|
||||||
|
|
||||||
echo "Committing changes"
|
echo "Committing changes"
|
||||||
git config http.sslVerify false
|
git config http.sslVerify false
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/sonar.yml
vendored
2
.github/workflows/sonar.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Build debug code and test fixtures
|
- name: Build debug code and test fixtures
|
||||||
|
|
|
||||||
2
.github/workflows/sync-localazy.yml
vendored
2
.github/workflows/sync-localazy.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
|
||||||
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
|
|
@ -52,7 +52,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v5
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
|
||||||
|
|
@ -78,6 +78,7 @@ jobs:
|
||||||
name: tests-and-screenshot-tests-results
|
name: tests-and-screenshot-tests-results
|
||||||
path: |
|
path: |
|
||||||
**/build/paparazzi/failures/
|
**/build/paparazzi/failures/
|
||||||
|
**/build/roborazzi/failures/
|
||||||
**/build/reports/tests/*UnitTest/
|
**/build/reports/tests/*UnitTest/
|
||||||
|
|
||||||
# https://github.com/codecov/codecov-action
|
# https://github.com/codecov/codecov-action
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ dependencies {
|
||||||
|
|
||||||
implementation(libs.coil)
|
implementation(libs.coil)
|
||||||
|
|
||||||
|
implementation(projects.features.announcement.api)
|
||||||
implementation(projects.features.ftue.api)
|
implementation(projects.features.ftue.api)
|
||||||
implementation(projects.features.share.api)
|
implementation(projects.features.share.api)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,8 @@ class LoggedInFlowNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
private val loggedInFlowProcessor = LoggedInEventProcessor(
|
private val loggedInFlowProcessor = LoggedInEventProcessor(
|
||||||
snackbarDispatcher,
|
snackbarDispatcher = snackbarDispatcher,
|
||||||
matrixClient.roomMembershipObserver(),
|
roomMembershipObserver = matrixClient.roomMembershipObserver,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val verificationListener = object : SessionVerificationServiceListener {
|
private val verificationListener = object : SessionVerificationServiceListener {
|
||||||
|
|
@ -189,7 +189,7 @@ class LoggedInFlowNode(
|
||||||
// TODO We do not support Space yet, so directly navigate to main space
|
// TODO We do not support Space yet, so directly navigate to main space
|
||||||
appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE)
|
appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE)
|
||||||
loggedInFlowProcessor.observeEvents(sessionCoroutineScope)
|
loggedInFlowProcessor.observeEvents(sessionCoroutineScope)
|
||||||
matrixClient.sessionVerificationService().setListener(verificationListener)
|
matrixClient.sessionVerificationService.setListener(verificationListener)
|
||||||
mediaPreviewConfigMigration()
|
mediaPreviewConfigMigration()
|
||||||
|
|
||||||
sessionCoroutineScope.launch {
|
sessionCoroutineScope.launch {
|
||||||
|
|
@ -218,7 +218,7 @@ class LoggedInFlowNode(
|
||||||
appNavigationStateService.onLeavingSpace(id)
|
appNavigationStateService.onLeavingSpace(id)
|
||||||
appNavigationStateService.onLeavingSession(id)
|
appNavigationStateService.onLeavingSession(id)
|
||||||
loggedInFlowProcessor.stopObserving()
|
loggedInFlowProcessor.stopObserving()
|
||||||
matrixClient.sessionVerificationService().setListener(null)
|
matrixClient.sessionVerificationService.setListener(null)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
setupSendingQueue()
|
setupSendingQueue()
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import io.element.android.appnav.intent.ResolvedIntent
|
||||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||||
import io.element.android.appnav.root.RootPresenter
|
import io.element.android.appnav.root.RootPresenter
|
||||||
import io.element.android.appnav.root.RootView
|
import io.element.android.appnav.root.RootView
|
||||||
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.features.login.api.LoginParams
|
import io.element.android.features.login.api.LoginParams
|
||||||
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
||||||
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
||||||
|
|
@ -64,7 +65,9 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ContributesNode(AppScope::class) @AssistedInject class RootFlowNode(
|
@ContributesNode(AppScope::class)
|
||||||
|
@AssistedInject
|
||||||
|
class RootFlowNode(
|
||||||
@Assisted val buildContext: BuildContext,
|
@Assisted val buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
|
|
@ -79,6 +82,7 @@ import timber.log.Timber
|
||||||
private val oidcActionFlow: OidcActionFlow,
|
private val oidcActionFlow: OidcActionFlow,
|
||||||
private val bugReporter: BugReporter,
|
private val bugReporter: BugReporter,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
|
private val announcementService: AnnouncementService,
|
||||||
) : BaseFlowNode<RootFlowNode.NavTarget>(
|
) : BaseFlowNode<RootFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.SplashScreen,
|
initialElement = NavTarget.SplashScreen,
|
||||||
|
|
@ -185,6 +189,7 @@ import timber.log.Timber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BackstackView(transitionHandler = transitionHandler)
|
BackstackView(transitionHandler = transitionHandler)
|
||||||
|
announcementService.Render(Modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,10 @@ class MatrixSessionCache(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onNewMatrixClient(matrixClient: MatrixClient) {
|
private fun onNewMatrixClient(matrixClient: MatrixClient) {
|
||||||
val syncOrchestrator = syncOrchestratorFactory.create(matrixClient)
|
val syncOrchestrator = syncOrchestratorFactory.create(
|
||||||
|
syncService = matrixClient.syncService,
|
||||||
|
sessionCoroutineScope = matrixClient.sessionCoroutineScope,
|
||||||
|
)
|
||||||
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
|
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
syncOrchestrator = syncOrchestrator,
|
syncOrchestrator = syncOrchestrator,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,10 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.coroutine.childScope
|
import io.element.android.libraries.core.coroutine.childScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||||
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
|
@ -32,21 +33,23 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class SyncOrchestrator(
|
class SyncOrchestrator(
|
||||||
@Assisted matrixClient: MatrixClient,
|
@Assisted private val syncService: SyncService,
|
||||||
|
@Assisted sessionCoroutineScope: CoroutineScope,
|
||||||
private val appForegroundStateService: AppForegroundStateService,
|
private val appForegroundStateService: AppForegroundStateService,
|
||||||
private val networkMonitor: NetworkMonitor,
|
private val networkMonitor: NetworkMonitor,
|
||||||
dispatchers: CoroutineDispatchers,
|
dispatchers: CoroutineDispatchers,
|
||||||
) {
|
) {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(matrixClient: MatrixClient): SyncOrchestrator
|
fun create(
|
||||||
|
syncService: SyncService,
|
||||||
|
sessionCoroutineScope: CoroutineScope,
|
||||||
|
): SyncOrchestrator
|
||||||
}
|
}
|
||||||
|
|
||||||
private val syncService = matrixClient.syncService()
|
|
||||||
|
|
||||||
private val tag = "SyncOrchestrator"
|
private val tag = "SyncOrchestrator"
|
||||||
|
|
||||||
private val coroutineScope = matrixClient.sessionCoroutineScope.childScope(dispatchers.io, tag)
|
private val coroutineScope = sessionCoroutineScope.childScope(dispatchers.io, tag)
|
||||||
|
|
||||||
private val started = AtomicBoolean(false)
|
private val started = AtomicBoolean(false)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import io.element.android.appnav.di.SyncOrchestrator
|
||||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
|
||||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||||
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
|
@ -385,7 +384,8 @@ class SyncOrchestratorTest {
|
||||||
networkMonitor: FakeNetworkMonitor = FakeNetworkMonitor(),
|
networkMonitor: FakeNetworkMonitor = FakeNetworkMonitor(),
|
||||||
appForegroundStateService: FakeAppForegroundStateService = FakeAppForegroundStateService(),
|
appForegroundStateService: FakeAppForegroundStateService = FakeAppForegroundStateService(),
|
||||||
) = SyncOrchestrator(
|
) = SyncOrchestrator(
|
||||||
matrixClient = FakeMatrixClient(syncService = syncService, sessionCoroutineScope = backgroundScope),
|
syncService = syncService,
|
||||||
|
sessionCoroutineScope = backgroundScope,
|
||||||
networkMonitor = networkMonitor,
|
networkMonitor = networkMonitor,
|
||||||
appForegroundStateService = appForegroundStateService,
|
appForegroundStateService = appForegroundStateService,
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,13 @@ package io.element.android.appnav.di
|
||||||
import com.bumble.appyx.core.state.MutableSavedStateMapImpl
|
import com.bumble.appyx.core.state.MutableSavedStateMapImpl
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||||
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -117,9 +118,13 @@ class MatrixSessionCacheTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TestScope.createSyncOrchestratorFactory() = object : SyncOrchestrator.Factory {
|
private fun TestScope.createSyncOrchestratorFactory() = object : SyncOrchestrator.Factory {
|
||||||
override fun create(matrixClient: MatrixClient): SyncOrchestrator {
|
override fun create(
|
||||||
|
syncService: SyncService,
|
||||||
|
sessionCoroutineScope: CoroutineScope,
|
||||||
|
): SyncOrchestrator {
|
||||||
return SyncOrchestrator(
|
return SyncOrchestrator(
|
||||||
matrixClient,
|
syncService = syncService,
|
||||||
|
sessionCoroutineScope = sessionCoroutineScope,
|
||||||
appForegroundStateService = FakeAppForegroundStateService(),
|
appForegroundStateService = FakeAppForegroundStateService(),
|
||||||
networkMonitor = FakeNetworkMonitor(),
|
networkMonitor = FakeNetworkMonitor(),
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ plugins {
|
||||||
alias(libs.plugins.compose.compiler) apply false
|
alias(libs.plugins.compose.compiler) apply false
|
||||||
alias(libs.plugins.ksp) apply false
|
alias(libs.plugins.ksp) apply false
|
||||||
alias(libs.plugins.dependencycheck) apply false
|
alias(libs.plugins.dependencycheck) apply false
|
||||||
|
alias(libs.plugins.roborazzi) apply false
|
||||||
alias(libs.plugins.dependencyanalysis)
|
alias(libs.plugins.dependencyanalysis)
|
||||||
alias(libs.plugins.detekt)
|
alias(libs.plugins.detekt)
|
||||||
alias(libs.plugins.ktlint)
|
alias(libs.plugins.ktlint)
|
||||||
|
|
@ -192,6 +193,21 @@ subprojects {
|
||||||
tasks.findByName("recordPaparazziRelease")?.dependsOn(removeOldScreenshotsTask)
|
tasks.findByName("recordPaparazziRelease")?.dependsOn(removeOldScreenshotsTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure to delete old snapshot before recording new ones
|
||||||
|
subprojects {
|
||||||
|
val screenshotsDir = File("${project.projectDir}/screenshots")
|
||||||
|
val removeOldScreenshotsTask = tasks.register("removeOldScreenshots") {
|
||||||
|
onlyIf { screenshotsDir.exists() }
|
||||||
|
doFirst {
|
||||||
|
println("Delete previous screenshots located at $screenshotsDir\n")
|
||||||
|
screenshotsDir.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tasks.findByName("recordRoborazzi")?.dependsOn(removeOldScreenshotsTask)
|
||||||
|
tasks.findByName("recordRoborazziDebug")?.dependsOn(removeOldScreenshotsTask)
|
||||||
|
tasks.findByName("recordRoborazziRelease")?.dependsOn(removeOldScreenshotsTask)
|
||||||
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 95789d40119499eba8a79284df9dd2306405b099
|
Subproject commit ffc02b8d0f35188c3ef8a876dc1532bfe3e533da
|
||||||
13
features/announcement/api/build.gradle.kts
Normal file
13
features/announcement/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
plugins {
|
||||||
|
id("io.element.android-compose-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "io.element.android.features.announcement.api"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.api
|
||||||
|
|
||||||
|
enum class Announcement {
|
||||||
|
Space,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.api
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
interface AnnouncementService {
|
||||||
|
suspend fun showAnnouncement(announcement: Announcement)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Render(
|
||||||
|
modifier: Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
37
features/announcement/impl/build.gradle.kts
Normal file
37
features/announcement/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import extension.setupDependencyInjection
|
||||||
|
import extension.testCommonDependencies
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("io.element.android-compose-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "io.element.android.features.announcement.impl"
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDependencyInjection()
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.designsystem)
|
||||||
|
implementation(projects.libraries.preferences.api)
|
||||||
|
implementation(projects.libraries.uiStrings)
|
||||||
|
api(projects.features.announcement.api)
|
||||||
|
implementation(libs.androidx.datastore.preferences)
|
||||||
|
|
||||||
|
testCommonDependencies(libs, true)
|
||||||
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
class AnnouncementPresenter(
|
||||||
|
private val announcementStore: AnnouncementStore,
|
||||||
|
) : Presenter<AnnouncementState> {
|
||||||
|
@Composable
|
||||||
|
override fun present(): AnnouncementState {
|
||||||
|
val showSpaceAnnouncement by remember {
|
||||||
|
announcementStore.spaceAnnouncementFlow().map {
|
||||||
|
it == AnnouncementStore.SpaceAnnouncement.Show
|
||||||
|
}
|
||||||
|
}.collectAsState(false)
|
||||||
|
return AnnouncementState(
|
||||||
|
showSpaceAnnouncement = showSpaceAnnouncement,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
|
data class AnnouncementState(
|
||||||
|
val showSpaceAnnouncement: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun anAnnouncementState(
|
||||||
|
showSpaceAnnouncement: Boolean = false,
|
||||||
|
) = AnnouncementState(
|
||||||
|
showSpaceAnnouncement = showSpaceAnnouncement,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
|
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
||||||
|
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementView
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
@Inject
|
||||||
|
class DefaultAnnouncementService(
|
||||||
|
private val announcementStore: AnnouncementStore,
|
||||||
|
private val announcementPresenter: Presenter<AnnouncementState>,
|
||||||
|
private val spaceAnnouncementPresenter: Presenter<SpaceAnnouncementState>,
|
||||||
|
) : AnnouncementService {
|
||||||
|
override suspend fun showAnnouncement(announcement: Announcement) {
|
||||||
|
when (announcement) {
|
||||||
|
Announcement.Space -> showSpaceAnnouncement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun showSpaceAnnouncement() {
|
||||||
|
val currentValue = announcementStore.spaceAnnouncementFlow().first()
|
||||||
|
if (currentValue == AnnouncementStore.SpaceAnnouncement.NeverShown) {
|
||||||
|
announcementStore.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Show)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Render(modifier: Modifier) {
|
||||||
|
val announcementState = announcementPresenter.present()
|
||||||
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = announcementState.showSpaceAnnouncement,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
) {
|
||||||
|
val spaceAnnouncementState = spaceAnnouncementPresenter.present()
|
||||||
|
SpaceAnnouncementView(
|
||||||
|
state = spaceAnnouncementState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.di
|
||||||
|
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.BindingContainer
|
||||||
|
import dev.zacsweers.metro.Binds
|
||||||
|
import dev.zacsweers.metro.ContributesTo
|
||||||
|
import io.element.android.features.announcement.impl.AnnouncementPresenter
|
||||||
|
import io.element.android.features.announcement.impl.AnnouncementState
|
||||||
|
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementPresenter
|
||||||
|
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
||||||
|
@ContributesTo(AppScope::class)
|
||||||
|
@BindingContainer
|
||||||
|
interface AnnouncementModule {
|
||||||
|
@Binds
|
||||||
|
fun bindAnnouncementPresenter(presenter: AnnouncementPresenter): Presenter<AnnouncementState>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindSpaceAnnouncementPresenter(presenter: SpaceAnnouncementPresenter): Presenter<SpaceAnnouncementState>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.spaces
|
||||||
|
|
||||||
|
sealed interface SpaceAnnouncementEvents {
|
||||||
|
data object Continue : SpaceAnnouncementEvents
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.spaces
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStore.SpaceAnnouncement
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
class SpaceAnnouncementPresenter(
|
||||||
|
private val announcementStore: AnnouncementStore,
|
||||||
|
) : Presenter<SpaceAnnouncementState> {
|
||||||
|
@Composable
|
||||||
|
override fun present(): SpaceAnnouncementState {
|
||||||
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
fun handleEvents(event: SpaceAnnouncementEvents) {
|
||||||
|
when (event) {
|
||||||
|
SpaceAnnouncementEvents.Continue -> localCoroutineScope.launch {
|
||||||
|
announcementStore.setSpaceAnnouncementValue(SpaceAnnouncement.Shown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpaceAnnouncementState(
|
||||||
|
eventSink = ::handleEvents
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.spaces
|
||||||
|
|
||||||
|
data class SpaceAnnouncementState(
|
||||||
|
val eventSink: (SpaceAnnouncementEvents) -> Unit
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.spaces
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
|
||||||
|
open class SpaceAnnouncementStateProvider : PreviewParameterProvider<SpaceAnnouncementState> {
|
||||||
|
override val values: Sequence<SpaceAnnouncementState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aSpaceAnnouncementState(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aSpaceAnnouncementState(
|
||||||
|
eventSink: (SpaceAnnouncementEvents) -> Unit = {},
|
||||||
|
) = SpaceAnnouncementState(
|
||||||
|
eventSink = eventSink,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.spaces
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
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
|
||||||
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
import io.element.android.features.announcement.impl.R
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
|
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||||
|
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||||
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
|
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.Text
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=4593-40181
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun SpaceAnnouncementView(
|
||||||
|
state: SpaceAnnouncementState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val eventSink = state.eventSink
|
||||||
|
|
||||||
|
fun onContinue() {
|
||||||
|
eventSink(SpaceAnnouncementEvents.Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(onBack = ::onContinue)
|
||||||
|
HeaderFooterPage(
|
||||||
|
modifier = modifier,
|
||||||
|
isScrollable = true,
|
||||||
|
contentPadding = PaddingValues(top = 24.dp, start = 16.dp, end = 16.dp, bottom = 24.dp),
|
||||||
|
header = {
|
||||||
|
SpaceAnnouncementHeader()
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
SpaceAnnouncementContent(
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer = {
|
||||||
|
SpaceAnnouncementFooter(
|
||||||
|
onContinue = ::onContinue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SpaceAnnouncementHeader(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
IconTitleSubtitleMolecule(
|
||||||
|
modifier = modifier.padding(top = 16.dp, bottom = 16.dp),
|
||||||
|
title = stringResource(id = R.string.screen_space_announcement_title),
|
||||||
|
showBetaLabel = true,
|
||||||
|
subTitle = stringResource(id = R.string.screen_space_announcement_subtitle),
|
||||||
|
iconStyle = BigIcon.Style.Default(
|
||||||
|
vectorIcon = CompoundIcons.WorkspaceSolid(),
|
||||||
|
usePrimaryTint = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SpaceAnnouncementContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
InfoListOrganism(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
items = persistentListOf(
|
||||||
|
InfoListItem(
|
||||||
|
message = stringResource(id = R.string.screen_space_announcement_item1),
|
||||||
|
iconVector = CompoundIcons.VisibilityOn(),
|
||||||
|
),
|
||||||
|
InfoListItem(
|
||||||
|
message = stringResource(id = R.string.screen_space_announcement_item2),
|
||||||
|
iconVector = CompoundIcons.Email(),
|
||||||
|
),
|
||||||
|
InfoListItem(
|
||||||
|
message = stringResource(id = R.string.screen_space_announcement_item3),
|
||||||
|
iconVector = CompoundIcons.Search(),
|
||||||
|
),
|
||||||
|
InfoListItem(
|
||||||
|
message = stringResource(id = R.string.screen_space_announcement_item4),
|
||||||
|
iconVector = CompoundIcons.Explore(),
|
||||||
|
),
|
||||||
|
InfoListItem(
|
||||||
|
message = stringResource(id = R.string.screen_space_announcement_item5),
|
||||||
|
iconVector = CompoundIcons.Leave(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textStyle = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
iconTint = ElementTheme.colors.iconSecondary,
|
||||||
|
iconSize = 24.dp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
text = stringResource(id = R.string.screen_space_announcement_notice),
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SpaceAnnouncementFooter(
|
||||||
|
onContinue: () -> Unit,
|
||||||
|
) {
|
||||||
|
ButtonColumnMolecule(
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = CommonStrings.action_continue),
|
||||||
|
onClick = onContinue,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun SpaceAnnouncementViewPreview(@PreviewParameter(SpaceAnnouncementStateProvider::class) state: SpaceAnnouncementState) = ElementPreview {
|
||||||
|
SpaceAnnouncementView(
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.store
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface AnnouncementStore {
|
||||||
|
suspend fun setSpaceAnnouncementValue(value: SpaceAnnouncement)
|
||||||
|
fun spaceAnnouncementFlow(): Flow<SpaceAnnouncement>
|
||||||
|
|
||||||
|
suspend fun reset()
|
||||||
|
|
||||||
|
enum class SpaceAnnouncement {
|
||||||
|
NeverShown,
|
||||||
|
Show,
|
||||||
|
Shown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.store
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
private val spaceAnnouncementKey = intPreferencesKey("spaceAnnouncement")
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
@Inject
|
||||||
|
class DefaultAnnouncementStore(
|
||||||
|
preferenceDataStoreFactory: PreferenceDataStoreFactory,
|
||||||
|
) : AnnouncementStore {
|
||||||
|
private val store = preferenceDataStoreFactory.create("elementx_announcement")
|
||||||
|
|
||||||
|
override suspend fun setSpaceAnnouncementValue(value: AnnouncementStore.SpaceAnnouncement) {
|
||||||
|
store.edit {
|
||||||
|
it[spaceAnnouncementKey] = value.ordinal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun spaceAnnouncementFlow(): Flow<AnnouncementStore.SpaceAnnouncement> {
|
||||||
|
return store.data.map { prefs ->
|
||||||
|
val ordinal = prefs[spaceAnnouncementKey] ?: AnnouncementStore.SpaceAnnouncement.NeverShown.ordinal
|
||||||
|
AnnouncementStore.SpaceAnnouncement.entries.getOrElse(ordinal) { AnnouncementStore.SpaceAnnouncement.NeverShown }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun reset() {
|
||||||
|
store.edit { it.clear() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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_space_announcement_item1">"Se klynger, du har oprettet eller tilmeldt dig"</string>
|
||||||
|
<string name="screen_space_announcement_item2">"Acceptere eller afvise invitationer til klynger"</string>
|
||||||
|
<string name="screen_space_announcement_item3">"Finde alle rum, du kan deltage i, i dine klynger"</string>
|
||||||
|
<string name="screen_space_announcement_item4">"Deltage i offentlige klynger"</string>
|
||||||
|
<string name="screen_space_announcement_item5">"Forlade de klynger, du har tilsluttet dig"</string>
|
||||||
|
<string name="screen_space_announcement_notice">"Oprettelse og administration af klynger kommer snart."</string>
|
||||||
|
<string name="screen_space_announcement_subtitle">"Velkommen til betaversionen af Klynger! Med denne første version kan du:"</string>
|
||||||
|
<string name="screen_space_announcement_title">"Introduktion til Klynger"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_space_announcement_item1">"Von dir erstellte oder beigetretene Spaces anzeigen"</string>
|
||||||
|
<string name="screen_space_announcement_item2">"Einladungen zu Spaces annehmen oder ablehnen"</string>
|
||||||
|
<string name="screen_space_announcement_item3">"Chats innerhalb deiner Spaces entdecken, um ihnen beizutreten"</string>
|
||||||
|
<string name="screen_space_announcement_item4">"Öffentlichen Spaces beitreten"</string>
|
||||||
|
<string name="screen_space_announcement_item5">"Spaces verlassen, bei denen du Mitglied bist"</string>
|
||||||
|
<string name="screen_space_announcement_notice">"Das Erstellen und Verwalten von Spaces ist bald verfügbar."</string>
|
||||||
|
<string name="screen_space_announcement_subtitle">"Willkommen bei der Beta-Version von Spaces! Mit dieser ersten Version kannst du:"</string>
|
||||||
|
<string name="screen_space_announcement_title">"Einführung in Spaces"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_space_announcement_item1">"Voir les espaces que vous avez créés ou rejoints"</string>
|
||||||
|
<string name="screen_space_announcement_item2">"Accepter ou refuser les invitations aux espaces"</string>
|
||||||
|
<string name="screen_space_announcement_item3">"Découvrir les salons que vous pouvez joindre depuis vos espaces"</string>
|
||||||
|
<string name="screen_space_announcement_item4">"Rejoindre les espaces publics"</string>
|
||||||
|
<string name="screen_space_announcement_item5">"Quitter les espaces dont vous êtes membre."</string>
|
||||||
|
<string name="screen_space_announcement_notice">"La création et la gestion des espaces seront bientôt disponibles."</string>
|
||||||
|
<string name="screen_space_announcement_subtitle">"Bienvenue dans la version bêta des espaces! Avec cette première version, vous pourrez :"</string>
|
||||||
|
<string name="screen_space_announcement_title">"Ajout des espaces"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_space_announcement_item1">"Se områder du har opprettet eller blitt med i"</string>
|
||||||
|
<string name="screen_space_announcement_item2">"Godta eller avslå invitasjoner til områder"</string>
|
||||||
|
<string name="screen_space_announcement_item3">"Oppdag alle rom du kan bli med i i dine områder"</string>
|
||||||
|
<string name="screen_space_announcement_item4">"Bli med i offentlige områder"</string>
|
||||||
|
<string name="screen_space_announcement_item5">"Forlat områder du har blitt med i"</string>
|
||||||
|
<string name="screen_space_announcement_notice">"Oppretting og administrasjon av områder kommer snart."</string>
|
||||||
|
<string name="screen_space_announcement_subtitle">"Velkommen til betaversjonen av Områder! Med denne første versjonen kan du:"</string>
|
||||||
|
<string name="screen_space_announcement_title">"Vi introduserer Områder"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_space_announcement_item1">"Vizualizați spațiile pe care le-ați creat sau la care v-ați alăturat"</string>
|
||||||
|
<string name="screen_space_announcement_item2">"Acceptați sau refuzați invitațiile la spații"</string>
|
||||||
|
<string name="screen_space_announcement_item3">"Descoperiți toate camerele la care vă puteți alătura în spațiile dumneavoastră."</string>
|
||||||
|
<string name="screen_space_announcement_item4">"Alăturați-vă spațiilor publice"</string>
|
||||||
|
<string name="screen_space_announcement_item5">"Părăsiți spațiile la care v-ați alăturat."</string>
|
||||||
|
<string name="screen_space_announcement_notice">"Crearea și gestionarea spațiilor vor fi disponibile în curând."</string>
|
||||||
|
<string name="screen_space_announcement_subtitle">"Bun venit la versiunea beta a Spațiilor! Cu această primă versiune puteți:"</string>
|
||||||
|
<string name="screen_space_announcement_title">"Vă prezentăm Spații"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_space_announcement_item1">"Просмотр пространств, которые вы создали или к которым присоединились"</string>
|
||||||
|
<string name="screen_space_announcement_item2">"Принимать или отклонять приглашения в пространства"</string>
|
||||||
|
<string name="screen_space_announcement_item3">"Откройте для себя все комнаты, к которым вы можете присоединиться в своих пространствах."</string>
|
||||||
|
<string name="screen_space_announcement_item4">"Присоединиться к публичному пространству"</string>
|
||||||
|
<string name="screen_space_announcement_item5">"Покинуть все пространства, к которым вы присоединились"</string>
|
||||||
|
<string name="screen_space_announcement_notice">"Создание и управление пространствами станет доступно в ближайшее время."</string>
|
||||||
|
<string name="screen_space_announcement_subtitle">"Добро пожаловать в бета-версию Spaces! В этой первой версии вы сможете:"</string>
|
||||||
|
<string name="screen_space_announcement_title">"Знакомство с пространствами"</string>
|
||||||
|
</resources>
|
||||||
11
features/announcement/impl/src/main/res/values/localazy.xml
Normal file
11
features/announcement/impl/src/main/res/values/localazy.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_space_announcement_item1">"View spaces you\'ve created or joined"</string>
|
||||||
|
<string name="screen_space_announcement_item2">"Accept or decline invites to spaces"</string>
|
||||||
|
<string name="screen_space_announcement_item3">"Discover any rooms you can join in your spaces"</string>
|
||||||
|
<string name="screen_space_announcement_item4">"Join public spaces"</string>
|
||||||
|
<string name="screen_space_announcement_item5">"Leave any spaces you’ve joined"</string>
|
||||||
|
<string name="screen_space_announcement_notice">"Creating and managing spaces is coming soon."</string>
|
||||||
|
<string name="screen_space_announcement_subtitle">"Welcome to the beta version of Spaces! With this first version you can:"</string>
|
||||||
|
<string name="screen_space_announcement_title">"Introducing Spaces"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
|
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||||
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class AnnouncementPresenterTest {
|
||||||
|
@Test
|
||||||
|
fun `present - initial state`() = runTest {
|
||||||
|
val presenter = createAnnouncementPresenter()
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state.showSpaceAnnouncement).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - showSpaceAnnouncement value depends on the value in the store`() = runTest {
|
||||||
|
val store = InMemoryAnnouncementStore()
|
||||||
|
val presenter = createAnnouncementPresenter(
|
||||||
|
announcementStore = store,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state.showSpaceAnnouncement).isFalse()
|
||||||
|
store.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Show)
|
||||||
|
val updatedState = awaitItem()
|
||||||
|
assertThat(updatedState.showSpaceAnnouncement).isTrue()
|
||||||
|
store.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Shown)
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.showSpaceAnnouncement).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAnnouncementPresenter(
|
||||||
|
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||||
|
) = AnnouncementPresenter(
|
||||||
|
announcementStore = announcementStore,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
||||||
|
import io.element.android.features.announcement.impl.spaces.aSpaceAnnouncementState
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
|
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class DefaultAnnouncementServiceTest {
|
||||||
|
@Test
|
||||||
|
fun `when showing Space announcement, space announcement is set to show only if it was never shown`() = runTest {
|
||||||
|
val announcementStore = InMemoryAnnouncementStore()
|
||||||
|
val sut = createDefaultAnnouncementService(
|
||||||
|
announcementStore = announcementStore,
|
||||||
|
)
|
||||||
|
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.NeverShown)
|
||||||
|
sut.showAnnouncement(Announcement.Space)
|
||||||
|
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Show)
|
||||||
|
// Simulate user close the announcement
|
||||||
|
announcementStore.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Shown)
|
||||||
|
// Entering again the space tab should not change the value
|
||||||
|
sut.showAnnouncement(Announcement.Space)
|
||||||
|
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Shown)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDefaultAnnouncementService(
|
||||||
|
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||||
|
announcementPresenter: Presenter<AnnouncementState> = Presenter { anAnnouncementState() },
|
||||||
|
spaceAnnouncementPresenter: Presenter<SpaceAnnouncementState> = Presenter { aSpaceAnnouncementState() },
|
||||||
|
) = DefaultAnnouncementService(
|
||||||
|
announcementStore = announcementStore,
|
||||||
|
announcementPresenter = announcementPresenter,
|
||||||
|
spaceAnnouncementPresenter = spaceAnnouncementPresenter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.spaces
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
|
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||||
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class SpaceAnnouncementPresenterTest {
|
||||||
|
@Test
|
||||||
|
fun `present - when user continues, the store is updated`() = runTest {
|
||||||
|
val store = InMemoryAnnouncementStore()
|
||||||
|
val presenter = createSpaceAnnouncementPresenter(
|
||||||
|
announcementStore = store,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
assertThat(store.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.NeverShown)
|
||||||
|
val state = awaitItem()
|
||||||
|
state.eventSink(SpaceAnnouncementEvents.Continue)
|
||||||
|
assertThat(store.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Shown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSpaceAnnouncementPresenter(
|
||||||
|
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||||
|
) = SpaceAnnouncementPresenter(
|
||||||
|
announcementStore = announcementStore,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.spaces
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class SpaceAnnouncementViewTest {
|
||||||
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on back sends a SpaceAnnouncementEvents`() {
|
||||||
|
val eventsRecorder = EventsRecorder<SpaceAnnouncementEvents>()
|
||||||
|
rule.setSpaceAnnouncementView(
|
||||||
|
aSpaceAnnouncementState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rule.pressBackKey()
|
||||||
|
eventsRecorder.assertSingle(SpaceAnnouncementEvents.Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on Continue sends a SpaceAnnouncementEvents`() {
|
||||||
|
val eventsRecorder = EventsRecorder<SpaceAnnouncementEvents>()
|
||||||
|
rule.setSpaceAnnouncementView(
|
||||||
|
aSpaceAnnouncementState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_continue)
|
||||||
|
eventsRecorder.assertSingle(SpaceAnnouncementEvents.Continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceAnnouncementView(
|
||||||
|
state: SpaceAnnouncementState,
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
SpaceAnnouncementView(
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.announcement.impl.store
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
class InMemoryAnnouncementStore(
|
||||||
|
initialSpaceAnnouncement: AnnouncementStore.SpaceAnnouncement = AnnouncementStore.SpaceAnnouncement.NeverShown,
|
||||||
|
) : AnnouncementStore {
|
||||||
|
private val spaceAnnouncement = MutableStateFlow(initialSpaceAnnouncement)
|
||||||
|
override suspend fun setSpaceAnnouncementValue(value: AnnouncementStore.SpaceAnnouncement) {
|
||||||
|
spaceAnnouncement.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun spaceAnnouncementFlow(): Flow<AnnouncementStore.SpaceAnnouncement> {
|
||||||
|
return spaceAnnouncement.asStateFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun reset() {
|
||||||
|
spaceAnnouncement.value = AnnouncementStore.SpaceAnnouncement.NeverShown
|
||||||
|
}
|
||||||
|
}
|
||||||
19
features/announcement/test/build.gradle.kts
Normal file
19
features/announcement/test/build.gradle.kts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
plugins {
|
||||||
|
id("io.element.android-compose-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "io.element.android.features.announcement.test"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.features.announcement.api)
|
||||||
|
implementation(libs.coroutines.core)
|
||||||
|
implementation(projects.tests.testutils)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.rageshake.test.logs
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
|
class FakeAnnouncementService(
|
||||||
|
val showAnnouncementResult: (Announcement) -> Unit = { lambdaError() },
|
||||||
|
val renderResult: (Modifier) -> Unit = { lambdaError() },
|
||||||
|
) : AnnouncementService {
|
||||||
|
override suspend fun showAnnouncement(announcement: Announcement) {
|
||||||
|
showAnnouncementResult(announcement)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Render(modifier: Modifier) {
|
||||||
|
renderResult(modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -242,7 +242,7 @@ class CallScreenPresenter(
|
||||||
}
|
}
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
Timber.d("Observing sync state in-call for sessionId: ${roomCallType.sessionId}")
|
Timber.d("Observing sync state in-call for sessionId: ${roomCallType.sessionId}")
|
||||||
client.syncService().syncState
|
client.syncService.syncState
|
||||||
.collect { state ->
|
.collect { state ->
|
||||||
if (state != SyncState.Running) {
|
if (state != SyncState.Running) {
|
||||||
appForegroundStateService.updateIsInCallState(true)
|
appForegroundStateService.updateIsInCallState(true)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<string name="screen_room_change_role_administrators_title">"Rediger administratorer"</string>
|
<string name="screen_room_change_role_administrators_title">"Rediger administratorer"</string>
|
||||||
<string name="screen_room_change_role_confirm_add_admin_description">"Du vil ikke kunne angre denne handlingen. Du forfremmer brukeren til å ha samme rettighetsnivå som deg."</string>
|
<string name="screen_room_change_role_confirm_add_admin_description">"Du vil ikke kunne angre denne handlingen. Du forfremmer brukeren til å ha samme rettighetsnivå som deg."</string>
|
||||||
<string name="screen_room_change_role_confirm_add_admin_title">"Legg til administrator?"</string>
|
<string name="screen_room_change_role_confirm_add_admin_title">"Legg til administrator?"</string>
|
||||||
|
<string name="screen_room_change_role_confirm_change_owners_description">"Du kan ikke angre denne handlingen. Du overfører eierskapet til de valgte brukerne. Når du forlater siden, vil dette være permanent."</string>
|
||||||
<string name="screen_room_change_role_confirm_change_owners_title">"Overføre eierskapet?"</string>
|
<string name="screen_room_change_role_confirm_change_owners_title">"Overføre eierskapet?"</string>
|
||||||
<string name="screen_room_change_role_confirm_demote_self_action">"Degradere"</string>
|
<string name="screen_room_change_role_confirm_demote_self_action">"Degradere"</string>
|
||||||
<string name="screen_room_change_role_confirm_demote_self_description">"Du vil ikke kunne angre denne endringen ettersom du degraderer deg selv, og hvis du er den siste privilegerte brukeren i rommet, vil det være umulig å få tilbake privilegiene."</string>
|
<string name="screen_room_change_role_confirm_demote_self_description">"Du vil ikke kunne angre denne endringen ettersom du degraderer deg selv, og hvis du er den siste privilegerte brukeren i rommet, vil det være umulig å få tilbake privilegiene."</string>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.compound)
|
implementation(projects.libraries.compound)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ android {
|
||||||
setupDependencyInjection()
|
setupDependencyInjection()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.compound)
|
implementation(projects.libraries.compound)
|
||||||
api(projects.features.enterprise.api)
|
api(projects.features.enterprise.api)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.features.enterprise.api)
|
api(projects.features.enterprise.api)
|
||||||
implementation(libs.compound)
|
implementation(projects.libraries.compound)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class ChooseSelfVerificationModePresenter(
|
||||||
) : Presenter<ChooseSelfVerificationModeState> {
|
) : Presenter<ChooseSelfVerificationModeState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ChooseSelfVerificationModeState {
|
override fun present(): ChooseSelfVerificationModeState {
|
||||||
val isLastDevice by encryptionService.isLastDevice.collectAsState()
|
val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState()
|
||||||
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
|
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
|
||||||
val canEnterRecoveryKey by remember { derivedStateOf { recoveryState == RecoveryState.INCOMPLETE } }
|
val canEnterRecoveryKey by remember { derivedStateOf { recoveryState == RecoveryState.INCOMPLETE } }
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ class ChooseSelfVerificationModePresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChooseSelfVerificationModeState(
|
return ChooseSelfVerificationModeState(
|
||||||
isLastDevice = isLastDevice,
|
canUseAnotherDevice = hasDevicesToVerifyAgainst,
|
||||||
canEnterRecoveryKey = canEnterRecoveryKey,
|
canEnterRecoveryKey = canEnterRecoveryKey,
|
||||||
directLogoutState = directLogoutState,
|
directLogoutState = directLogoutState,
|
||||||
eventSink = ::eventHandler,
|
eventSink = ::eventHandler,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||||
|
|
||||||
data class ChooseSelfVerificationModeState(
|
data class ChooseSelfVerificationModeState(
|
||||||
val isLastDevice: Boolean,
|
val canUseAnotherDevice: Boolean,
|
||||||
val canEnterRecoveryKey: Boolean,
|
val canEnterRecoveryKey: Boolean,
|
||||||
val directLogoutState: DirectLogoutState,
|
val directLogoutState: DirectLogoutState,
|
||||||
val eventSink: (ChooseSelfVerificationModeEvent) -> Unit,
|
val eventSink: (ChooseSelfVerificationModeEvent) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,18 @@ import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||||
class ChooseSelfVerificationModeStateProvider :
|
class ChooseSelfVerificationModeStateProvider :
|
||||||
PreviewParameterProvider<ChooseSelfVerificationModeState> {
|
PreviewParameterProvider<ChooseSelfVerificationModeState> {
|
||||||
override val values = sequenceOf(
|
override val values = sequenceOf(
|
||||||
aChooseSelfVerificationModeState(isLastDevice = true, canEnterRecoveryKey = true),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = true),
|
||||||
aChooseSelfVerificationModeState(isLastDevice = true, canEnterRecoveryKey = false),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = false),
|
||||||
aChooseSelfVerificationModeState(isLastDevice = false, canEnterRecoveryKey = true),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = true),
|
||||||
aChooseSelfVerificationModeState(isLastDevice = false, canEnterRecoveryKey = false),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aChooseSelfVerificationModeState(
|
fun aChooseSelfVerificationModeState(
|
||||||
isLastDevice: Boolean = false,
|
canUseAnotherDevice: Boolean = true,
|
||||||
canEnterRecoveryKey: Boolean = true,
|
canEnterRecoveryKey: Boolean = true,
|
||||||
) = ChooseSelfVerificationModeState(
|
) = ChooseSelfVerificationModeState(
|
||||||
isLastDevice = isLastDevice,
|
canUseAnotherDevice = canUseAnotherDevice,
|
||||||
canEnterRecoveryKey = canEnterRecoveryKey,
|
canEnterRecoveryKey = canEnterRecoveryKey,
|
||||||
directLogoutState = aDirectLogoutState(),
|
directLogoutState = aDirectLogoutState(),
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ fun ChooseSelfVerificationModeView(
|
||||||
ButtonColumnMolecule(
|
ButtonColumnMolecule(
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
) {
|
) {
|
||||||
if (state.isLastDevice.not()) {
|
if (state.canUseAnotherDevice) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.screen_identity_use_another_device),
|
text = stringResource(R.string.screen_identity_use_another_device),
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,15 @@ class ChooseSessionVerificationModePresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `initial state - is relayed from EncryptionService`() = runTest {
|
fun `initial state - is relayed from EncryptionService`() = runTest {
|
||||||
val encryptionService = FakeEncryptionService().apply {
|
val encryptionService = FakeEncryptionService().apply {
|
||||||
// Is last device
|
// Has device to verify against
|
||||||
emitIsLastDevice(true)
|
emitHasDevicesToVerifyAgainst(false)
|
||||||
// Can enter recovery key
|
// Can enter recovery key
|
||||||
emitRecoveryState(RecoveryState.INCOMPLETE)
|
emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||||
}
|
}
|
||||||
val presenter = createPresenter(encryptionService = encryptionService)
|
val presenter = createPresenter(encryptionService = encryptionService)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(isLastDevice).isTrue()
|
assertThat(canUseAnotherDevice).isFalse()
|
||||||
assertThat(canEnterRecoveryKey).isTrue()
|
assertThat(canEnterRecoveryKey).isTrue()
|
||||||
assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue()
|
assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class ChooseSessionVerificationModeViewTest {
|
||||||
fun `clicking on use another device calls the callback`() {
|
fun `clicking on use another device calls the callback`() {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setChooseSelfVerificationModeView(
|
rule.setChooseSelfVerificationModeView(
|
||||||
aChooseSelfVerificationModeState(isLastDevice = false),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = true),
|
||||||
onUseAnotherDevice = callback,
|
onUseAnotherDevice = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_identity_use_another_device)
|
rule.clickOn(R.string.screen_identity_use_another_device)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ dependencies {
|
||||||
implementation(projects.libraries.permissions.noop)
|
implementation(projects.libraries.permissions.noop)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
implementation(projects.libraries.push.api)
|
implementation(projects.libraries.push.api)
|
||||||
|
implementation(projects.features.announcement.api)
|
||||||
implementation(projects.features.invite.api)
|
implementation(projects.features.invite.api)
|
||||||
implementation(projects.features.networkmonitor.api)
|
implementation(projects.features.networkmonitor.api)
|
||||||
implementation(projects.features.logout.api)
|
implementation(projects.features.logout.api)
|
||||||
|
|
@ -60,6 +61,7 @@ dependencies {
|
||||||
api(projects.features.home.api)
|
api(projects.features.home.api)
|
||||||
|
|
||||||
testCommonDependencies(libs, true)
|
testCommonDependencies(libs, true)
|
||||||
|
testImplementation(projects.features.announcement.test)
|
||||||
testImplementation(projects.features.invite.test)
|
testImplementation(projects.features.invite.test)
|
||||||
testImplementation(projects.features.logout.test)
|
testImplementation(projects.features.logout.test)
|
||||||
testImplementation(projects.features.networkmonitor.test)
|
testImplementation(projects.features.networkmonitor.test)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||||
|
|
@ -47,6 +49,7 @@ class HomePresenter(
|
||||||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
|
private val announcementService: AnnouncementService,
|
||||||
) : Presenter<HomeState> {
|
) : Presenter<HomeState> {
|
||||||
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
|
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
|
||||||
|
|
||||||
|
|
@ -84,7 +87,10 @@ class HomePresenter(
|
||||||
|
|
||||||
fun handleEvents(event: HomeEvents) {
|
fun handleEvents(event: HomeEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is HomeEvents.SelectHomeNavigationBarItem -> {
|
is HomeEvents.SelectHomeNavigationBarItem -> coroutineState.launch {
|
||||||
|
if (event.item == HomeNavigationBarItem.Spaces) {
|
||||||
|
announcementService.showAnnouncement(Announcement.Space)
|
||||||
|
}
|
||||||
currentHomeNavigationBarItemOrdinal = event.item.ordinal
|
currentHomeNavigationBarItemOrdinal = event.item.ordinal
|
||||||
}
|
}
|
||||||
is HomeEvents.SwitchToAccount -> coroutineState.launch {
|
is HomeEvents.SwitchToAccount -> coroutineState.launch {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.home.impl.components
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import io.element.android.features.home.impl.R
|
||||||
|
import io.element.android.libraries.designsystem.components.Announcement
|
||||||
|
import io.element.android.libraries.designsystem.components.AnnouncementType
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun NewNotificationSoundBanner(
|
||||||
|
onDismissClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Announcement(
|
||||||
|
modifier = modifier.roomListBannerPadding(),
|
||||||
|
title = stringResource(R.string.banner_new_sound_title),
|
||||||
|
description = stringResource(R.string.banner_new_sound_message),
|
||||||
|
type = AnnouncementType.Actionable(
|
||||||
|
actionText = stringResource(CommonStrings.action_ok),
|
||||||
|
onActionClick = onDismissClick,
|
||||||
|
onDismissClick = onDismissClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun NewNotificationSoundBannerPreview() = ElementPreview {
|
||||||
|
NewNotificationSoundBanner(
|
||||||
|
onDismissClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -251,6 +251,12 @@ private fun RoomsViewList(
|
||||||
item {
|
item {
|
||||||
BatteryOptimizationBanner(state = state.batteryOptimizationState)
|
BatteryOptimizationBanner(state = state.batteryOptimizationState)
|
||||||
}
|
}
|
||||||
|
} else if (state.showNewNotificationSoundBanner) {
|
||||||
|
item {
|
||||||
|
NewNotificationSoundBanner(
|
||||||
|
onDismissClick = { updatedEventSink(RoomListEvents.DismissNewNotificationSoundBanner) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,10 +189,14 @@ private fun RoomSummaryScaffoldRow(
|
||||||
) {
|
) {
|
||||||
Avatar(
|
Avatar(
|
||||||
avatarData = room.avatarData,
|
avatarData = room.avatarData,
|
||||||
avatarType = AvatarType.Room(
|
avatarType = if (room.isSpace) {
|
||||||
heroes = room.heroes,
|
AvatarType.Space(isTombstoned = room.isTombstoned)
|
||||||
isTombstoned = room.isTombstoned,
|
} else {
|
||||||
),
|
AvatarType.Room(
|
||||||
|
heroes = room.heroes,
|
||||||
|
isTombstoned = room.isTombstoned,
|
||||||
|
)
|
||||||
|
},
|
||||||
hideImage = hideAvatarImage,
|
hideImage = hideAvatarImage,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ class RoomListRoomSummaryFactory(
|
||||||
user.getAvatarData(size = AvatarSize.RoomListItem)
|
user.getAvatarData(size = AvatarSize.RoomListItem)
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
isTombstoned = roomInfo.successorRoom != null,
|
isTombstoned = roomInfo.successorRoom != null,
|
||||||
|
isSpace = roomInfo.isSpace,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ data class RoomListRoomSummary(
|
||||||
val inviteSender: InviteSender?,
|
val inviteSender: InviteSender?,
|
||||||
val isTombstoned: Boolean,
|
val isTombstoned: Boolean,
|
||||||
val heroes: ImmutableList<AvatarData>,
|
val heroes: ImmutableList<AvatarData>,
|
||||||
|
val isSpace: Boolean,
|
||||||
) {
|
) {
|
||||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,15 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSu
|
||||||
displayName = "Bob",
|
displayName = "Bob",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
aRoomListRoomSummary(
|
||||||
|
name = "A space invite",
|
||||||
|
displayType = RoomSummaryDisplayType.INVITE,
|
||||||
|
inviteSender = anInviteSender(
|
||||||
|
userId = UserId("@bob:matrix.org"),
|
||||||
|
displayName = "Bob",
|
||||||
|
),
|
||||||
|
isSpace = true
|
||||||
|
),
|
||||||
aRoomListRoomSummary(
|
aRoomListRoomSummary(
|
||||||
name = "A knocked room",
|
name = "A knocked room",
|
||||||
displayType = RoomSummaryDisplayType.KNOCKED,
|
displayType = RoomSummaryDisplayType.KNOCKED,
|
||||||
|
|
@ -151,6 +160,7 @@ internal fun aRoomListRoomSummary(
|
||||||
canonicalAlias: RoomAlias? = null,
|
canonicalAlias: RoomAlias? = null,
|
||||||
heroes: List<AvatarData> = emptyList(),
|
heroes: List<AvatarData> = emptyList(),
|
||||||
isTombstoned: Boolean = false,
|
isTombstoned: Boolean = false,
|
||||||
|
isSpace: Boolean = false,
|
||||||
) = RoomListRoomSummary(
|
) = RoomListRoomSummary(
|
||||||
id = id,
|
id = id,
|
||||||
roomId = RoomId(id),
|
roomId = RoomId(id),
|
||||||
|
|
@ -172,4 +182,5 @@ internal fun aRoomListRoomSummary(
|
||||||
canonicalAlias = canonicalAlias,
|
canonicalAlias = canonicalAlias,
|
||||||
heroes = heroes.toImmutableList(),
|
heroes = heroes.toImmutableList(),
|
||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
|
isSpace = isSpace
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,22 @@ open class RoomListContentStateProvider : PreviewParameterProvider<RoomListConte
|
||||||
aSkeletonContentState(),
|
aSkeletonContentState(),
|
||||||
anEmptyContentState(),
|
anEmptyContentState(),
|
||||||
anEmptyContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
|
anEmptyContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
|
||||||
|
aRoomsContentState(
|
||||||
|
showNewNotificationSoundBanner = true,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aRoomsContentState(
|
internal fun aRoomsContentState(
|
||||||
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
||||||
|
showNewNotificationSoundBanner: Boolean = false,
|
||||||
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
||||||
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
||||||
batteryOptimizationState: BatteryOptimizationState = aBatteryOptimizationState(),
|
batteryOptimizationState: BatteryOptimizationState = aBatteryOptimizationState(),
|
||||||
seenRoomInvites: Set<RoomId> = emptySet(),
|
seenRoomInvites: Set<RoomId> = emptySet(),
|
||||||
) = RoomListContentState.Rooms(
|
) = RoomListContentState.Rooms(
|
||||||
securityBannerState = securityBannerState,
|
securityBannerState = securityBannerState,
|
||||||
|
showNewNotificationSoundBanner = showNewNotificationSoundBanner,
|
||||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
|
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
|
||||||
batteryOptimizationState = batteryOptimizationState,
|
batteryOptimizationState = batteryOptimizationState,
|
||||||
summaries = summaries,
|
summaries = summaries,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ sealed interface RoomListEvents {
|
||||||
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents
|
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents
|
||||||
data object DismissRequestVerificationPrompt : RoomListEvents
|
data object DismissRequestVerificationPrompt : RoomListEvents
|
||||||
data object DismissBanner : RoomListEvents
|
data object DismissBanner : RoomListEvents
|
||||||
|
data object DismissNewNotificationSoundBanner : RoomListEvents
|
||||||
data object ToggleSearchResults : RoomListEvents
|
data object ToggleSearchResults : RoomListEvents
|
||||||
data class ShowContextMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents
|
data class ShowContextMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
|
||||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||||
|
|
@ -84,7 +83,7 @@ class RoomListPresenter(
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val seenInvitesStore: SeenInvitesStore,
|
private val seenInvitesStore: SeenInvitesStore,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
private val encryptionService: EncryptionService = client.encryptionService()
|
private val encryptionService = client.encryptionService
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomListState {
|
override fun present(): RoomListState {
|
||||||
|
|
@ -99,6 +98,7 @@ class RoomListPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
|
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val showNewNotificationSoundBanner by appPreferencesStore.showNewNotificationSoundBanner().collectAsState(false)
|
||||||
|
|
||||||
// Avatar indicator
|
// Avatar indicator
|
||||||
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
||||||
|
|
@ -113,6 +113,9 @@ class RoomListPresenter(
|
||||||
}
|
}
|
||||||
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true
|
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true
|
||||||
RoomListEvents.DismissBanner -> securityBannerDismissed = true
|
RoomListEvents.DismissBanner -> securityBannerDismissed = true
|
||||||
|
RoomListEvents.DismissNewNotificationSoundBanner -> coroutineScope.launch {
|
||||||
|
appPreferencesStore.setShowNewNotificationSoundBanner(false)
|
||||||
|
}
|
||||||
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||||
is RoomListEvents.ShowContextMenu -> {
|
is RoomListEvents.ShowContextMenu -> {
|
||||||
coroutineScope.showContextMenu(event, contextMenu)
|
coroutineScope.showContextMenu(event, contextMenu)
|
||||||
|
|
@ -142,7 +145,10 @@ class RoomListPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val contentState = roomListContentState(securityBannerDismissed)
|
val contentState = roomListContentState(
|
||||||
|
securityBannerDismissed,
|
||||||
|
showNewNotificationSoundBanner,
|
||||||
|
)
|
||||||
|
|
||||||
val canReportRoom by produceState(false) { value = client.canReportRoom() }
|
val canReportRoom by produceState(false) { value = client.canReportRoom() }
|
||||||
|
|
||||||
|
|
@ -198,6 +204,7 @@ class RoomListPresenter(
|
||||||
@Composable
|
@Composable
|
||||||
private fun roomListContentState(
|
private fun roomListContentState(
|
||||||
securityBannerDismissed: Boolean,
|
securityBannerDismissed: Boolean,
|
||||||
|
showNewNotificationSoundBanner: Boolean,
|
||||||
): RoomListContentState {
|
): RoomListContentState {
|
||||||
val roomSummaries by produceState(initialValue = AsyncData.Loading()) {
|
val roomSummaries by produceState(initialValue = AsyncData.Loading()) {
|
||||||
roomListDataSource.allRooms.collect { value = AsyncData.Success(it) }
|
roomListDataSource.allRooms.collect { value = AsyncData.Success(it) }
|
||||||
|
|
@ -216,11 +223,14 @@ class RoomListPresenter(
|
||||||
val seenRoomInvites by remember { seenInvitesStore.seenRoomIds() }.collectAsState(emptySet())
|
val seenRoomInvites by remember { seenInvitesStore.seenRoomIds() }.collectAsState(emptySet())
|
||||||
val securityBannerState by rememberSecurityBannerState(securityBannerDismissed)
|
val securityBannerState by rememberSecurityBannerState(securityBannerDismissed)
|
||||||
return when {
|
return when {
|
||||||
showEmpty -> RoomListContentState.Empty(securityBannerState = securityBannerState)
|
showEmpty -> RoomListContentState.Empty(
|
||||||
|
securityBannerState = securityBannerState,
|
||||||
|
)
|
||||||
showSkeleton -> RoomListContentState.Skeleton(count = 16)
|
showSkeleton -> RoomListContentState.Skeleton(count = 16)
|
||||||
else -> {
|
else -> {
|
||||||
RoomListContentState.Rooms(
|
RoomListContentState.Rooms(
|
||||||
securityBannerState = securityBannerState,
|
securityBannerState = securityBannerState,
|
||||||
|
showNewNotificationSoundBanner = showNewNotificationSoundBanner,
|
||||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
|
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
|
||||||
batteryOptimizationState = batteryOptimizationPresenter.present(),
|
batteryOptimizationState = batteryOptimizationPresenter.present(),
|
||||||
summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList(),
|
summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList(),
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ sealed interface RoomListContentState {
|
||||||
val securityBannerState: SecurityBannerState,
|
val securityBannerState: SecurityBannerState,
|
||||||
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
|
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
|
||||||
val batteryOptimizationState: BatteryOptimizationState,
|
val batteryOptimizationState: BatteryOptimizationState,
|
||||||
|
val showNewNotificationSoundBanner: Boolean,
|
||||||
val summaries: ImmutableList<RoomListRoomSummary>,
|
val summaries: ImmutableList<RoomListRoomSummary>,
|
||||||
val seenRoomInvites: ImmutableSet<RoomId>,
|
val seenRoomInvites: ImmutableSet<RoomId>,
|
||||||
) : RoomListContentState
|
) : RoomListContentState
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,7 @@ fun HomeSpacesView(
|
||||||
when (space) {
|
when (space) {
|
||||||
CurrentSpace.Root -> {
|
CurrentSpace.Root -> {
|
||||||
item {
|
item {
|
||||||
SpaceHeaderRootView(
|
SpaceHeaderRootView(numberOfSpaces = state.spaceRooms.size)
|
||||||
numberOfSpaces = state.spaceRooms.size,
|
|
||||||
// TODO
|
|
||||||
numberOfRooms = 0,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CurrentSpace.Space -> item {
|
is CurrentSpace.Space -> item {
|
||||||
|
|
@ -45,10 +41,9 @@ fun HomeSpacesView(
|
||||||
avatarData = space.spaceRoom.getAvatarData(AvatarSize.SpaceHeader),
|
avatarData = space.spaceRoom.getAvatarData(AvatarSize.SpaceHeader),
|
||||||
name = space.spaceRoom.name,
|
name = space.spaceRoom.name,
|
||||||
topic = space.spaceRoom.topic,
|
topic = space.spaceRoom.topic,
|
||||||
joinRule = space.spaceRoom.joinRule,
|
visibility = space.spaceRoom.visibility,
|
||||||
heroes = space.spaceRoom.heroes.toImmutableList(),
|
heroes = space.spaceRoom.heroes.toImmutableList(),
|
||||||
numberOfMembers = space.spaceRoom.numJoinedMembers,
|
numberOfMembers = space.spaceRoom.numJoinedMembers,
|
||||||
numberOfRooms = space.spaceRoom.childrenCount,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class SpaceRoomProvider : PreviewParameterProvider<SpaceRoom> {
|
||||||
roomId = RoomId("!spaceId1:example.com"),
|
roomId = RoomId("!spaceId1:example.com"),
|
||||||
),
|
),
|
||||||
aSpaceRoom(
|
aSpaceRoom(
|
||||||
name = null,
|
rawName = null,
|
||||||
numJoinedMembers = 5,
|
numJoinedMembers = 5,
|
||||||
childrenCount = 10,
|
childrenCount = 10,
|
||||||
worldReadable = true,
|
worldReadable = true,
|
||||||
|
|
@ -39,7 +39,7 @@ class SpaceRoomProvider : PreviewParameterProvider<SpaceRoom> {
|
||||||
state = CurrentUserMembership.INVITED,
|
state = CurrentUserMembership.INVITED,
|
||||||
),
|
),
|
||||||
aSpaceRoom(
|
aSpaceRoom(
|
||||||
name = null,
|
rawName = null,
|
||||||
numJoinedMembers = 5,
|
numJoinedMembers = 5,
|
||||||
childrenCount = 10,
|
childrenCount = 10,
|
||||||
worldReadable = true,
|
worldReadable = true,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<string name="banner_battery_optimization_content_android">"Deaktiver batterioptimering for denne app for at sikre, at alle notifikationer dukker op."</string>
|
<string name="banner_battery_optimization_content_android">"Deaktiver batterioptimering for denne app for at sikre, at alle notifikationer dukker op."</string>
|
||||||
<string name="banner_battery_optimization_submit_android">"Deaktivér optimering"</string>
|
<string name="banner_battery_optimization_submit_android">"Deaktivér optimering"</string>
|
||||||
<string name="banner_battery_optimization_title_android">"Modtager du ikke notifikationer?"</string>
|
<string name="banner_battery_optimization_title_android">"Modtager du ikke notifikationer?"</string>
|
||||||
|
<string name="banner_new_sound_message">"Dit notifikationsping er blevet opdateret – tydeligere, hurtigere og mindre forstyrrende."</string>
|
||||||
|
<string name="banner_new_sound_title">"Vi har opdateret dine lyde"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Gendan din kryptografiske identitet og meddelelseshistorik med en gendannelsesnøgle, hvis du har mistet alle dine eksisterende enheder."</string>
|
<string name="banner_set_up_recovery_content">"Gendan din kryptografiske identitet og meddelelseshistorik med en gendannelsesnøgle, hvis du har mistet alle dine eksisterende enheder."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Opsæt gendannelse"</string>
|
<string name="banner_set_up_recovery_submit">"Opsæt gendannelse"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Konfigurer gendannelse for at beskytte din konto"</string>
|
<string name="banner_set_up_recovery_title">"Konfigurer gendannelse for at beskytte din konto"</string>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<string name="banner_battery_optimization_content_android">"Deaktiviere die Batterieoptimierung für diese App, um sicherzustellen, dass alle Benachrichtigungen empfangen werden."</string>
|
<string name="banner_battery_optimization_content_android">"Deaktiviere die Batterieoptimierung für diese App, um sicherzustellen, dass alle Benachrichtigungen empfangen werden."</string>
|
||||||
<string name="banner_battery_optimization_submit_android">"Optimierung deaktivieren"</string>
|
<string name="banner_battery_optimization_submit_android">"Optimierung deaktivieren"</string>
|
||||||
<string name="banner_battery_optimization_title_android">"Kommen die Benachrichtigungen nicht an?"</string>
|
<string name="banner_battery_optimization_title_android">"Kommen die Benachrichtigungen nicht an?"</string>
|
||||||
|
<string name="banner_new_sound_message">"Dein Benachrichtigungs-Ping wurde aktualisiert – klarer, schneller und weniger störend."</string>
|
||||||
|
<string name="banner_new_sound_title">"Wir haben deine Sounds aktualisiert"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Stelle Deine kryptographische Identität und Deinen Nachrichtenverlauf mit Hilfe eines Wiederherstellungsschlüssels wieder her, falls du alle deine Geräte verloren haben solltest"</string>
|
<string name="banner_set_up_recovery_content">"Stelle Deine kryptographische Identität und Deinen Nachrichtenverlauf mit Hilfe eines Wiederherstellungsschlüssels wieder her, falls du alle deine Geräte verloren haben solltest"</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Wiederherstellung einrichten"</string>
|
<string name="banner_set_up_recovery_submit">"Wiederherstellung einrichten"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Wiederherstellung einrichten"</string>
|
<string name="banner_set_up_recovery_title">"Wiederherstellung einrichten"</string>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<string name="banner_battery_optimization_content_android">"Désactivez l’optimisation de la batterie pour cette application afin de vous assurer que toutes les notifications sont reçues."</string>
|
<string name="banner_battery_optimization_content_android">"Désactivez l’optimisation de la batterie pour cette application afin de vous assurer que toutes les notifications sont reçues."</string>
|
||||||
<string name="banner_battery_optimization_submit_android">"Désactiver l’optimisation"</string>
|
<string name="banner_battery_optimization_submit_android">"Désactiver l’optimisation"</string>
|
||||||
<string name="banner_battery_optimization_title_android">"Ils vous manque des notifications?"</string>
|
<string name="banner_battery_optimization_title_android">"Ils vous manque des notifications?"</string>
|
||||||
|
<string name="banner_new_sound_message">"Le son des notifications a été modifié: plus clair, plus court et moins perturbateur."</string>
|
||||||
|
<string name="banner_new_sound_title">"Nous avons rafraîchi les sons"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Générez une nouvelle clé de récupération qui peut être utilisée pour restaurer l’historique de vos messages chiffrés au cas où vous perdriez l’accès à vos appareils."</string>
|
<string name="banner_set_up_recovery_content">"Générez une nouvelle clé de récupération qui peut être utilisée pour restaurer l’historique de vos messages chiffrés au cas où vous perdriez l’accès à vos appareils."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Configurer la sauvegarde"</string>
|
<string name="banner_set_up_recovery_submit">"Configurer la sauvegarde"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Configurer la récupération"</string>
|
<string name="banner_set_up_recovery_title">"Configurer la récupération"</string>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<string name="banner_battery_optimization_content_android">"Deaktiver batterioptimalisering for denne appen for å sikre at alle varsler mottas."</string>
|
<string name="banner_battery_optimization_content_android">"Deaktiver batterioptimalisering for denne appen for å sikre at alle varsler mottas."</string>
|
||||||
<string name="banner_battery_optimization_submit_android">"Deaktiver optimalisering"</string>
|
<string name="banner_battery_optimization_submit_android">"Deaktiver optimalisering"</string>
|
||||||
<string name="banner_battery_optimization_title_android">"Kommer ikke varslene frem?"</string>
|
<string name="banner_battery_optimization_title_android">"Kommer ikke varslene frem?"</string>
|
||||||
|
<string name="banner_new_sound_message">"Varslingssignalet ditt er oppdatert – tydeligere, raskere og mindre forstyrrende."</string>
|
||||||
|
<string name="banner_new_sound_title">"Vi har oppdatert lydene dine"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Gjenopprett din kryptografiske identitet og meldingshistorikk med en gjenopprettingsnøkkel hvis du har mistet alle dine brukte enheter."</string>
|
<string name="banner_set_up_recovery_content">"Gjenopprett din kryptografiske identitet og meldingshistorikk med en gjenopprettingsnøkkel hvis du har mistet alle dine brukte enheter."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Konfigurer gjenoppretting"</string>
|
<string name="banner_set_up_recovery_submit">"Konfigurer gjenoppretting"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Konfigurer gjenoppretting for å beskytte kontoen din"</string>
|
<string name="banner_set_up_recovery_title">"Konfigurer gjenoppretting for å beskytte kontoen din"</string>
|
||||||
|
|
@ -33,6 +35,7 @@ Inntil videre kan du velge bort filtre for å se de andre chattene dine"</string
|
||||||
<string name="screen_roomlist_filter_invites">"Invitasjoner"</string>
|
<string name="screen_roomlist_filter_invites">"Invitasjoner"</string>
|
||||||
<string name="screen_roomlist_filter_invites_empty_state_title">"Du har ingen ventende invitasjoner."</string>
|
<string name="screen_roomlist_filter_invites_empty_state_title">"Du har ingen ventende invitasjoner."</string>
|
||||||
<string name="screen_roomlist_filter_low_priority">"Lav prioritet"</string>
|
<string name="screen_roomlist_filter_low_priority">"Lav prioritet"</string>
|
||||||
|
<string name="screen_roomlist_filter_low_priority_empty_state_title">"Du har ingen lavprioriterte chatter ennå"</string>
|
||||||
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Du kan velge bort filtre for å se de andre chattene dine"</string>
|
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Du kan velge bort filtre for å se de andre chattene dine"</string>
|
||||||
<string name="screen_roomlist_filter_mixed_empty_state_title">"Du har ikke chatter for dette utvalget"</string>
|
<string name="screen_roomlist_filter_mixed_empty_state_title">"Du har ikke chatter for dette utvalget"</string>
|
||||||
<string name="screen_roomlist_filter_people">"Personer"</string>
|
<string name="screen_roomlist_filter_people">"Personer"</string>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<string name="banner_battery_optimization_content_android">"Dezactivați optimizarea bateriei pentru această aplicație, pentru a vă asigura că toate notificările sunt primite."</string>
|
<string name="banner_battery_optimization_content_android">"Dezactivați optimizarea bateriei pentru această aplicație, pentru a vă asigura că toate notificările sunt primite."</string>
|
||||||
<string name="banner_battery_optimization_submit_android">"Dezactivați optimizarea"</string>
|
<string name="banner_battery_optimization_submit_android">"Dezactivați optimizarea"</string>
|
||||||
<string name="banner_battery_optimization_title_android">"Nu primiți notificări?"</string>
|
<string name="banner_battery_optimization_title_android">"Nu primiți notificări?"</string>
|
||||||
|
<string name="banner_new_sound_message">"Sunetul pentru notificări a fost actualizat — mai clar, mai rapid și mai puțin perturbatoar."</string>
|
||||||
|
<string name="banner_new_sound_title">"Am reîmprospătat sunetele"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Recuperați-vă identitatea criptografică și mesajele anterioare cu o cheie de recuperare dacă ați pierdut toate dispozitivele existente."</string>
|
<string name="banner_set_up_recovery_content">"Recuperați-vă identitatea criptografică și mesajele anterioare cu o cheie de recuperare dacă ați pierdut toate dispozitivele existente."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Configurați recuperarea"</string>
|
<string name="banner_set_up_recovery_submit">"Configurați recuperarea"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Configurați recuperarea pentru a vă proteja contul"</string>
|
<string name="banner_set_up_recovery_title">"Configurați recuperarea pentru a vă proteja contul"</string>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<string name="banner_battery_optimization_content_android">"Выключите оптимизацию расхода батареи, чтобы убедиться, что все уведомления будут поступать."</string>
|
<string name="banner_battery_optimization_content_android">"Выключите оптимизацию расхода батареи, чтобы убедиться, что все уведомления будут поступать."</string>
|
||||||
<string name="banner_battery_optimization_submit_android">"Выключить оптимизацию"</string>
|
<string name="banner_battery_optimization_submit_android">"Выключить оптимизацию"</string>
|
||||||
<string name="banner_battery_optimization_title_android">"Уведомления не поступают?"</string>
|
<string name="banner_battery_optimization_title_android">"Уведомления не поступают?"</string>
|
||||||
|
<string name="banner_new_sound_message">"Ваши уведомления были обновлены — теперь они понятнее, быстрее и менее отвлекающие."</string>
|
||||||
|
<string name="banner_new_sound_title">"Мы обновили ваши звуки"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Создайте новый ключ восстановления, который можно использовать для восстановления зашифрованной истории сообщений в случае потери доступа к своим устройствам."</string>
|
<string name="banner_set_up_recovery_content">"Создайте новый ключ восстановления, который можно использовать для восстановления зашифрованной истории сообщений в случае потери доступа к своим устройствам."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Настроить восстановление"</string>
|
<string name="banner_set_up_recovery_submit">"Настроить восстановление"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Для защиты вашего аккаунта рекомендуется настроить восстановление"</string>
|
<string name="banner_set_up_recovery_title">"Для защиты вашего аккаунта рекомендуется настроить восстановление"</string>
|
||||||
|
|
@ -13,6 +15,7 @@
|
||||||
<string name="full_screen_intent_banner_message">"Чтобы больше не пропускать важные звонки, разрешите приложению показывать полноэкранные уведомления на заблокированном экране телефона."</string>
|
<string name="full_screen_intent_banner_message">"Чтобы больше не пропускать важные звонки, разрешите приложению показывать полноэкранные уведомления на заблокированном экране телефона."</string>
|
||||||
<string name="full_screen_intent_banner_title">"Улучшите качество звонков"</string>
|
<string name="full_screen_intent_banner_title">"Улучшите качество звонков"</string>
|
||||||
<string name="screen_home_tab_chats">"Все чаты"</string>
|
<string name="screen_home_tab_chats">"Все чаты"</string>
|
||||||
|
<string name="screen_home_tab_spaces">"Пространства"</string>
|
||||||
<string name="screen_invites_decline_chat_message">"Вы уверены, что хотите отклонить приглашение в %1$s?"</string>
|
<string name="screen_invites_decline_chat_message">"Вы уверены, что хотите отклонить приглашение в %1$s?"</string>
|
||||||
<string name="screen_invites_decline_chat_title">"Отклонить приглашение"</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_message">"Вы уверены, что хотите отказаться от личного общения с %1$s?"</string>
|
||||||
|
|
@ -32,6 +35,7 @@
|
||||||
<string name="screen_roomlist_filter_invites">"Приглашения"</string>
|
<string name="screen_roomlist_filter_invites">"Приглашения"</string>
|
||||||
<string name="screen_roomlist_filter_invites_empty_state_title">"У вас нет отложенных приглашений."</string>
|
<string name="screen_roomlist_filter_invites_empty_state_title">"У вас нет отложенных приглашений."</string>
|
||||||
<string name="screen_roomlist_filter_low_priority">"Низкий приоритет"</string>
|
<string name="screen_roomlist_filter_low_priority">"Низкий приоритет"</string>
|
||||||
|
<string name="screen_roomlist_filter_low_priority_empty_state_title">"У вас пока нет чатов с низким приоритетом."</string>
|
||||||
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</string>
|
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</string>
|
||||||
<string name="screen_roomlist_filter_mixed_empty_state_title">"У вас нет чатов для этой подборки"</string>
|
<string name="screen_roomlist_filter_mixed_empty_state_title">"У вас нет чатов для этой подборки"</string>
|
||||||
<string name="screen_roomlist_filter_people">"Пользователи"</string>
|
<string name="screen_roomlist_filter_people">"Пользователи"</string>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<string name="banner_battery_optimization_content_android">"Disable battery optimisation for this app, to make sure all notifications are received."</string>
|
<string name="banner_battery_optimization_content_android">"Disable battery optimisation for this app, to make sure all notifications are received."</string>
|
||||||
<string name="banner_battery_optimization_submit_android">"Disable optimisation"</string>
|
<string name="banner_battery_optimization_submit_android">"Disable optimisation"</string>
|
||||||
<string name="banner_battery_optimization_title_android">"Notifications not arriving?"</string>
|
<string name="banner_battery_optimization_title_android">"Notifications not arriving?"</string>
|
||||||
|
<string name="banner_new_sound_message">"Your notification ping has been updated—clearer, quicker, and less disruptive."</string>
|
||||||
|
<string name="banner_new_sound_title">"We’ve refreshed your sounds"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices."</string>
|
<string name="banner_set_up_recovery_content">"Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Set up recovery"</string>
|
<string name="banner_set_up_recovery_submit">"Set up recovery"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Set up recovery to protect your account"</string>
|
<string name="banner_set_up_recovery_title">"Set up recovery to protect your account"</string>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,14 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.features.home.impl.roomlist.aRoomListState
|
import io.element.android.features.home.impl.roomlist.aRoomListState
|
||||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||||
import io.element.android.features.home.impl.spaces.aHomeSpacesState
|
import io.element.android.features.home.impl.spaces.aHomeSpacesState
|
||||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||||
|
import io.element.android.features.rageshake.test.logs.FakeAnnouncementService
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
|
@ -31,12 +34,15 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||||
import io.element.android.tests.testutils.MutablePresenter
|
import io.element.android.tests.testutils.MutablePresenter
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -47,6 +53,8 @@ class HomePresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
private val isSpaceEnabled = FeatureFlags.Space.defaultValue(aBuildMeta())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - should start with no user and then load user with success`() = runTest {
|
fun `present - should start with no user and then load user with success`() = runTest {
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
|
|
@ -70,6 +78,7 @@ class HomePresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
if (isSpaceEnabled) skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(
|
assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(
|
||||||
MatrixUser(A_USER_ID, null, null)
|
MatrixUser(A_USER_ID, null, null)
|
||||||
|
|
@ -81,8 +90,8 @@ class HomePresenterTest {
|
||||||
MatrixUser(A_USER_ID, A_USER_NAME, AN_AVATAR_URL)
|
MatrixUser(A_USER_ID, A_USER_NAME, AN_AVATAR_URL)
|
||||||
)
|
)
|
||||||
assertThat(withUserState.showAvatarIndicator).isFalse()
|
assertThat(withUserState.showAvatarIndicator).isFalse()
|
||||||
assertThat(withUserState.isSpaceFeatureEnabled).isFalse()
|
assertThat(withUserState.isSpaceFeatureEnabled).isEqualTo(isSpaceEnabled)
|
||||||
assertThat(withUserState.showNavigationBar).isFalse()
|
assertThat(withUserState.showNavigationBar).isEqualTo(isSpaceEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,6 +142,7 @@ class HomePresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
if (isSpaceEnabled) skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.showAvatarIndicator).isFalse()
|
assertThat(initialState.showAvatarIndicator).isFalse()
|
||||||
indicatorService.setShowRoomListTopBarIndicator(true)
|
indicatorService.setShowRoomListTopBarIndicator(true)
|
||||||
|
|
@ -157,6 +167,7 @@ class HomePresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
if (isSpaceEnabled) skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(MatrixUser(matrixClient.sessionId))
|
assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(MatrixUser(matrixClient.sessionId))
|
||||||
// No new state is coming
|
// No new state is coming
|
||||||
|
|
@ -165,19 +176,26 @@ class HomePresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - NavigationBar change`() = runTest {
|
fun `present - NavigationBar change`() = runTest {
|
||||||
|
val showAnnouncementResult = lambdaRecorder<Announcement, Unit> { }
|
||||||
val presenter = createHomePresenter(
|
val presenter = createHomePresenter(
|
||||||
sessionStore = InMemorySessionStore(
|
sessionStore = InMemorySessionStore(
|
||||||
updateUserProfileResult = { _, _, _ -> },
|
updateUserProfileResult = { _, _, _ -> },
|
||||||
),
|
),
|
||||||
|
announcementService = FakeAnnouncementService(
|
||||||
|
showAnnouncementResult = showAnnouncementResult,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
if (isSpaceEnabled) skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats)
|
assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats)
|
||||||
initialState.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
|
initialState.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
|
assertThat(finalState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
|
||||||
|
showAnnouncementResult.assertions().isCalledOnce()
|
||||||
|
.with(value(Announcement.Space))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +210,9 @@ class HomePresenterTest {
|
||||||
initialState = mapOf(FeatureFlags.Space.key to true),
|
initialState = mapOf(FeatureFlags.Space.key to true),
|
||||||
),
|
),
|
||||||
homeSpacesPresenter = homeSpacesPresenter,
|
homeSpacesPresenter = homeSpacesPresenter,
|
||||||
|
announcementService = FakeAnnouncementService(
|
||||||
|
showAnnouncementResult = {},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
@ -222,6 +243,7 @@ internal fun createHomePresenter(
|
||||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||||
homeSpacesPresenter: Presenter<HomeSpacesState> = Presenter { aHomeSpacesState() },
|
homeSpacesPresenter: Presenter<HomeSpacesState> = Presenter { aHomeSpacesState() },
|
||||||
sessionStore: SessionStore = InMemorySessionStore(),
|
sessionStore: SessionStore = InMemorySessionStore(),
|
||||||
|
announcementService: AnnouncementService = FakeAnnouncementService(),
|
||||||
) = HomePresenter(
|
) = HomePresenter(
|
||||||
client = client,
|
client = client,
|
||||||
syncService = syncService,
|
syncService = syncService,
|
||||||
|
|
@ -233,4 +255,5 @@ internal fun createHomePresenter(
|
||||||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
sessionStore = sessionStore,
|
sessionStore = sessionStore,
|
||||||
|
announcementService = announcementService,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ internal fun createRoomListRoomSummary(
|
||||||
heroes: List<AvatarData> = emptyList(),
|
heroes: List<AvatarData> = emptyList(),
|
||||||
timestamp: String? = null,
|
timestamp: String? = null,
|
||||||
isTombstoned: Boolean = false,
|
isTombstoned: Boolean = false,
|
||||||
|
isSpace: Boolean = false,
|
||||||
) = RoomListRoomSummary(
|
) = RoomListRoomSummary(
|
||||||
id = A_ROOM_ID.value,
|
id = A_ROOM_ID.value,
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -106,4 +107,5 @@ internal fun createRoomListRoomSummary(
|
||||||
isDm = false,
|
isDm = false,
|
||||||
heroes = heroes.toPersistentList(),
|
heroes = heroes.toPersistentList(),
|
||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
|
isSpace = isSpace
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.advanceTimeBy
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -593,6 +594,38 @@ class RoomListPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - notification sound banner`() = runTest {
|
||||||
|
val subscribeToVisibleRoomsLambda = lambdaRecorder { _: List<RoomId> -> }
|
||||||
|
val roomListService = FakeRoomListService(subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda)
|
||||||
|
val matrixClient = FakeMatrixClient(
|
||||||
|
roomListService = roomListService,
|
||||||
|
)
|
||||||
|
val roomSummary = aRoomSummary(
|
||||||
|
currentUserMembership = CurrentUserMembership.INVITED
|
||||||
|
)
|
||||||
|
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||||
|
roomListService.postAllRooms(listOf(roomSummary))
|
||||||
|
val store = InMemoryAppPreferencesStore()
|
||||||
|
val presenter = createRoomListPresenter(
|
||||||
|
client = matrixClient,
|
||||||
|
appPreferencesStore = store,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
||||||
|
skipItems(1)
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
||||||
|
store.setShowNewNotificationSoundBanner(true)
|
||||||
|
assertThat(store.showNewNotificationSoundBanner().first()).isTrue()
|
||||||
|
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isTrue()
|
||||||
|
state.eventSink(RoomListEvents.DismissNewNotificationSoundBanner)
|
||||||
|
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
||||||
|
// Ensure store has been updated
|
||||||
|
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun TestScope.createRoomListPresenter(
|
private fun TestScope.createRoomListPresenter(
|
||||||
client: MatrixClient = FakeMatrixClient(),
|
client: MatrixClient = FakeMatrixClient(),
|
||||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||||
|
|
@ -616,7 +649,7 @@ class RoomListPresenterTest {
|
||||||
roomLastMessageFormatter = roomLastMessageFormatter,
|
roomLastMessageFormatter = roomLastMessageFormatter,
|
||||||
),
|
),
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
notificationSettingsService = client.notificationSettingsService(),
|
notificationSettingsService = client.notificationSettingsService,
|
||||||
sessionCoroutineScope = backgroundScope,
|
sessionCoroutineScope = backgroundScope,
|
||||||
dateTimeObserver = FakeDateTimeObserver(),
|
dateTimeObserver = FakeDateTimeObserver(),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.theme.placeholderBackground
|
import io.element.android.libraries.designsystem.theme.placeholderBackground
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoomVisibility
|
||||||
import io.element.android.libraries.matrix.ui.components.SpaceInfoRow
|
import io.element.android.libraries.matrix.ui.components.SpaceInfoRow
|
||||||
import io.element.android.libraries.matrix.ui.components.SpaceMembersView
|
import io.element.android.libraries.matrix.ui.components.SpaceMembersView
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
|
|
@ -567,10 +567,7 @@ private fun DefaultLoadedContent(
|
||||||
subtitle = {
|
subtitle = {
|
||||||
when {
|
when {
|
||||||
contentState.details is LoadedDetails.Space -> {
|
contentState.details is LoadedDetails.Space -> {
|
||||||
SpaceInfoRow(
|
SpaceInfoRow(visibility = SpaceRoomVisibility.fromJoinRule(contentState.joinRule))
|
||||||
joinRule = contentState.joinRule ?: JoinRule.Public,
|
|
||||||
numberOfRooms = contentState.details.childrenCount,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
contentState.alias != null -> {
|
contentState.alias != null -> {
|
||||||
RoomPreviewSubtitleAtom(contentState.alias.value)
|
RoomPreviewSubtitleAtom(contentState.alias.value)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_join_room_ban_by_message">"Du blev spærret fra dette rum af %1$s."</string>
|
<string name="screen_join_room_ban_by_message">"Du blev spærret af %1$s."</string>
|
||||||
<string name="screen_join_room_ban_message">"Du blev spærret fra dette rum"</string>
|
<string name="screen_join_room_ban_message">"Du blev spærret"</string>
|
||||||
<string name="screen_join_room_ban_reason">"Årsag: %1$s."</string>
|
<string name="screen_join_room_ban_reason">"Årsag: %1$s."</string>
|
||||||
<string name="screen_join_room_cancel_knock_action">"Annuller anmodning"</string>
|
<string name="screen_join_room_cancel_knock_action">"Annuller anmodning"</string>
|
||||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ja, annullér"</string>
|
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ja, annullér"</string>
|
||||||
|
|
@ -11,12 +11,12 @@
|
||||||
<string name="screen_join_room_decline_and_block_alert_message">"Er du sikker på, at du vil afvise invitationen til at deltage i dette rum? Dette forhindrer også %1$s i at kontakte dig eller invitere dig til andre rum."</string>
|
<string name="screen_join_room_decline_and_block_alert_message">"Er du sikker på, at du vil afvise invitationen til at deltage i dette rum? Dette forhindrer også %1$s i at kontakte dig eller invitere dig til andre rum."</string>
|
||||||
<string name="screen_join_room_decline_and_block_alert_title">"Afvis invitation og blokér"</string>
|
<string name="screen_join_room_decline_and_block_alert_title">"Afvis invitation og blokér"</string>
|
||||||
<string name="screen_join_room_decline_and_block_button_title">"Afvis og blokér"</string>
|
<string name="screen_join_room_decline_and_block_button_title">"Afvis og blokér"</string>
|
||||||
<string name="screen_join_room_fail_message">"Deltagelse i rummet fejlede."</string>
|
<string name="screen_join_room_fail_message">"Deltagelse fejlede."</string>
|
||||||
<string name="screen_join_room_fail_reason">"Dette rum er enten kun for gæster, eller der kan være sat begrænsninger for adgangen på klyngeniveau."</string>
|
<string name="screen_join_room_fail_reason">"Du skal enten inviteres til at deltage, eller der kan være adgangsbegrænsninger."</string>
|
||||||
<string name="screen_join_room_forget_action">"Glem dette rum"</string>
|
<string name="screen_join_room_forget_action">"Glem"</string>
|
||||||
<string name="screen_join_room_invite_required_message">"Du har brug for en invitation for at deltage i dette rum"</string>
|
<string name="screen_join_room_invite_required_message">"Du har brug for en invitation for at deltage"</string>
|
||||||
<string name="screen_join_room_invited_by">"Inviteret af"</string>
|
<string name="screen_join_room_invited_by">"Inviteret af"</string>
|
||||||
<string name="screen_join_room_join_action">"Deltag i rummet"</string>
|
<string name="screen_join_room_join_action">"Deltag"</string>
|
||||||
<string name="screen_join_room_join_restricted_message">"Du skal muligvis være inviteret eller være medlem af en klynge for at deltage."</string>
|
<string name="screen_join_room_join_restricted_message">"Du skal muligvis være inviteret eller være medlem af en klynge for at deltage."</string>
|
||||||
<string name="screen_join_room_knock_action">"Send anmodning om at deltage"</string>
|
<string name="screen_join_room_knock_action">"Send anmodning om at deltage"</string>
|
||||||
<string name="screen_join_room_knock_message_characters_count">"Tilladte tegn %1$d af %2$d"</string>
|
<string name="screen_join_room_knock_message_characters_count">"Tilladte tegn %1$d af %2$d"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_join_room_ban_by_message">"Du wurdest von %1$s für diesen Chat gesperrt."</string>
|
<string name="screen_join_room_ban_by_message">"Du wurdest von %1$s gesperrt."</string>
|
||||||
<string name="screen_join_room_ban_message">"Du wurdest für diesen Chat gesperrt"</string>
|
<string name="screen_join_room_ban_message">"Du wurdest gesperrt"</string>
|
||||||
<string name="screen_join_room_ban_reason">"Grund:%1$s."</string>
|
<string name="screen_join_room_ban_reason">"Grund:%1$s."</string>
|
||||||
<string name="screen_join_room_cancel_knock_action">"Anfrage abbrechen"</string>
|
<string name="screen_join_room_cancel_knock_action">"Anfrage abbrechen"</string>
|
||||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ja, abbrechen"</string>
|
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ja, abbrechen"</string>
|
||||||
|
|
@ -11,12 +11,12 @@
|
||||||
<string name="screen_join_room_decline_and_block_alert_message">"Bist du sicher, dass du die Einladung zu diesem Chat ablehnen möchtest? Dadurch wird auch jede weitere Kontaktaufnahme oder Chat Einladung von %1$s blockiert."</string>
|
<string name="screen_join_room_decline_and_block_alert_message">"Bist du sicher, dass du die Einladung zu diesem Chat ablehnen möchtest? Dadurch wird auch jede weitere Kontaktaufnahme oder Chat Einladung von %1$s blockiert."</string>
|
||||||
<string name="screen_join_room_decline_and_block_alert_title">"Einladung ablehnen & Nutzer blockieren"</string>
|
<string name="screen_join_room_decline_and_block_alert_title">"Einladung ablehnen & Nutzer blockieren"</string>
|
||||||
<string name="screen_join_room_decline_and_block_button_title">"Ablehnen und blockieren"</string>
|
<string name="screen_join_room_decline_and_block_button_title">"Ablehnen und blockieren"</string>
|
||||||
<string name="screen_join_room_fail_message">"Der Beitritt zum Chat schlug fehl."</string>
|
<string name="screen_join_room_fail_message">"Beitritt fehlgeschlagen"</string>
|
||||||
<string name="screen_join_room_fail_reason">"Dieser Chat ist entweder nur auf Einladung zugänglich oder es gibt andere Zugangsbeschränkungen durch Spaces."</string>
|
<string name="screen_join_room_fail_reason">"Du musst entweder eingeladen werden, um beizutreten, oder es gibt möglicherweise Zugriffsbeschränkungen."</string>
|
||||||
<string name="screen_join_room_forget_action">"Vergiss diesen Chat"</string>
|
<string name="screen_join_room_forget_action">"Vergessen"</string>
|
||||||
<string name="screen_join_room_invite_required_message">"Du benötigst eine Einladung, um diesem Chat beizutreten"</string>
|
<string name="screen_join_room_invite_required_message">"Du benötigst eine Einladung, um beizutreten"</string>
|
||||||
<string name="screen_join_room_invited_by">"Eingeladen von"</string>
|
<string name="screen_join_room_invited_by">"Eingeladen von"</string>
|
||||||
<string name="screen_join_room_join_action">"Chat beitreten"</string>
|
<string name="screen_join_room_join_action">"Beitreten"</string>
|
||||||
<string name="screen_join_room_join_restricted_message">"Möglicherweise musst du eingeladen werden oder ein Mitglied eines Spaces sein, um beitreten zu können."</string>
|
<string name="screen_join_room_join_restricted_message">"Möglicherweise musst du eingeladen werden oder ein Mitglied eines Spaces sein, um beitreten zu können."</string>
|
||||||
<string name="screen_join_room_knock_action">"Anklopfen"</string>
|
<string name="screen_join_room_knock_action">"Anklopfen"</string>
|
||||||
<string name="screen_join_room_knock_message_characters_count">"%1$d von %2$d erlaubte Zeichen"</string>
|
<string name="screen_join_room_knock_message_characters_count">"%1$d von %2$d erlaubte Zeichen"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_join_room_ban_by_message">"Du ble utestengt fra dette rommet av %1$s."</string>
|
<string name="screen_join_room_ban_by_message">"Du ble utestengt av %1$s."</string>
|
||||||
<string name="screen_join_room_ban_message">"Du ble utestengt fra dette rommet"</string>
|
<string name="screen_join_room_ban_message">"Du ble utestengt"</string>
|
||||||
<string name="screen_join_room_ban_reason">"Årsak: %1$s."</string>
|
<string name="screen_join_room_ban_reason">"Årsak: %1$s."</string>
|
||||||
<string name="screen_join_room_cancel_knock_action">"Avbryt forespørsel"</string>
|
<string name="screen_join_room_cancel_knock_action">"Avbryt forespørsel"</string>
|
||||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ja, avbryt"</string>
|
<string name="screen_join_room_cancel_knock_alert_confirmation">"Ja, avbryt"</string>
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
<string name="screen_join_room_decline_and_block_alert_message">"Er du sikker på at du vil avslå invitasjonen til å bli med i dette rommet? Dette vil også forhindre %1$s fra å kontakte deg eller invitere deg til rom."</string>
|
<string name="screen_join_room_decline_and_block_alert_message">"Er du sikker på at du vil avslå invitasjonen til å bli med i dette rommet? Dette vil også forhindre %1$s fra å kontakte deg eller invitere deg til rom."</string>
|
||||||
<string name="screen_join_room_decline_and_block_alert_title">"Avslå invitasjon og blokker"</string>
|
<string name="screen_join_room_decline_and_block_alert_title">"Avslå invitasjon og blokker"</string>
|
||||||
<string name="screen_join_room_decline_and_block_button_title">"Avslå og blokker"</string>
|
<string name="screen_join_room_decline_and_block_button_title">"Avslå og blokker"</string>
|
||||||
<string name="screen_join_room_fail_message">"Å bli med i rommet mislyktes."</string>
|
<string name="screen_join_room_fail_message">"Kunne ikke bli med"</string>
|
||||||
<string name="screen_join_room_fail_reason">"Dette rommet er enten kun for inviterte, eller det kan være begrensninger for tilgang på områdenivå."</string>
|
<string name="screen_join_room_fail_reason">"Du må enten bli invitert til å bli med, eller så kan det være begrensninger på tilgangen."</string>
|
||||||
<string name="screen_join_room_forget_action">"Glem dette rommet"</string>
|
<string name="screen_join_room_forget_action">"Glem"</string>
|
||||||
<string name="screen_join_room_invite_required_message">"Du trenger en invitasjon for å bli med i dette rommet"</string>
|
<string name="screen_join_room_invite_required_message">"Du trenger en invitasjon for å bli med"</string>
|
||||||
<string name="screen_join_room_join_action">"Bli med i rommet"</string>
|
<string name="screen_join_room_invited_by">"Invitert av"</string>
|
||||||
|
<string name="screen_join_room_join_action">"Bli med"</string>
|
||||||
<string name="screen_join_room_join_restricted_message">"Du må kanskje bli invitert eller være medlem av et område for å bli med."</string>
|
<string name="screen_join_room_join_restricted_message">"Du må kanskje bli invitert eller være medlem av et område for å bli med."</string>
|
||||||
<string name="screen_join_room_knock_action">"Send forespørsel om å bli med"</string>
|
<string name="screen_join_room_knock_action">"Send forespørsel om å bli med"</string>
|
||||||
<string name="screen_join_room_knock_message_characters_count">"Tillatte tegn %1$d av %2$d"</string>
|
<string name="screen_join_room_knock_message_characters_count">"Tillatte tegn %1$d av %2$d"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_join_room_ban_by_message">"Ai fost exclus din această cameră de către %1$s."</string>
|
<string name="screen_join_room_ban_by_message">"Ați fost exclus din această cameră de către %1$s."</string>
|
||||||
<string name="screen_join_room_ban_message">"Ați fost exclus din această cameră."</string>
|
<string name="screen_join_room_ban_message">"Ați fost exclus din această cameră."</string>
|
||||||
<string name="screen_join_room_ban_reason">"Motiv: %1$s."</string>
|
<string name="screen_join_room_ban_reason">"Motiv: %1$s."</string>
|
||||||
<string name="screen_join_room_cancel_knock_action">"Anulați cererea"</string>
|
<string name="screen_join_room_cancel_knock_action">"Anulați cererea"</string>
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
<string name="screen_join_room_fail_reason">"Această cameră este fie accesibilă numai pe bază de invitație, fie exista restricții de acces la nivel de spațiu."</string>
|
<string name="screen_join_room_fail_reason">"Această cameră este fie accesibilă numai pe bază de invitație, fie exista restricții de acces la nivel de spațiu."</string>
|
||||||
<string name="screen_join_room_forget_action">"Uitați această cameră"</string>
|
<string name="screen_join_room_forget_action">"Uitați această cameră"</string>
|
||||||
<string name="screen_join_room_invite_required_message">"Aveți nevoie de o invitație pentru a vă alătura acestei camere."</string>
|
<string name="screen_join_room_invite_required_message">"Aveți nevoie de o invitație pentru a vă alătura acestei camere."</string>
|
||||||
<string name="screen_join_room_join_action">"Alăturați-vă camerei"</string>
|
<string name="screen_join_room_invited_by">"Invitat de"</string>
|
||||||
|
<string name="screen_join_room_join_action">"Alăturați-vă"</string>
|
||||||
<string name="screen_join_room_join_restricted_message">"Este posibil să fie necesar să fiți invitat sau să fiți membru al unui spațiu pentru a vă alătura."</string>
|
<string name="screen_join_room_join_restricted_message">"Este posibil să fie necesar să fiți invitat sau să fiți membru al unui spațiu pentru a vă alătura."</string>
|
||||||
<string name="screen_join_room_knock_action">"Trimiteți o cerere de alăturare"</string>
|
<string name="screen_join_room_knock_action">"Trimiteți o cerere de alăturare"</string>
|
||||||
<string name="screen_join_room_knock_message_characters_count">"Caractere permise %1$d din %2$d"</string>
|
<string name="screen_join_room_knock_message_characters_count">"Caractere permise %1$d din %2$d"</string>
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@
|
||||||
<string name="screen_join_room_fail_reason">"Доступ в эту комнату возможен только по приглашениям или может быть ограничен на уровне пространства."</string>
|
<string name="screen_join_room_fail_reason">"Доступ в эту комнату возможен только по приглашениям или может быть ограничен на уровне пространства."</string>
|
||||||
<string name="screen_join_room_forget_action">"Забыть эту комнату"</string>
|
<string name="screen_join_room_forget_action">"Забыть эту комнату"</string>
|
||||||
<string name="screen_join_room_invite_required_message">"Вам необходимо приглашение для того, чтобы присоединиться к этой комнате"</string>
|
<string name="screen_join_room_invite_required_message">"Вам необходимо приглашение для того, чтобы присоединиться к этой комнате"</string>
|
||||||
|
<string name="screen_join_room_invited_by">"Приглашен"</string>
|
||||||
<string name="screen_join_room_join_action">"Присоединиться к комнате"</string>
|
<string name="screen_join_room_join_action">"Присоединиться к комнате"</string>
|
||||||
<string name="screen_join_room_join_restricted_message">"Чтобы присоединиться, вам необходимо приглашение или быть участником сообщества."</string>
|
<string name="screen_join_room_join_restricted_message">"Чтобы присоединиться, вам необходимо приглашение или быть участником сообщества."</string>
|
||||||
<string name="screen_join_room_knock_action">"Отправить запрос на присоединение"</string>
|
<string name="screen_join_room_knock_action">"Отправить запрос на присоединение"</string>
|
||||||
|
<string name="screen_join_room_knock_message_characters_count">"Разрешенные символы %1$d %2$d"</string>
|
||||||
<string name="screen_join_room_knock_message_description">"Сообщение (опционально)"</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_description">"Вы получите приглашение присоединиться к комнате, как только ваш запрос будет принят."</string>
|
||||||
<string name="screen_join_room_knock_sent_title">"Запрос на присоединение отправлен"</string>
|
<string name="screen_join_room_knock_sent_title">"Запрос на присоединение отправлен"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_join_room_ban_by_message">"You were banned from this room by %1$s."</string>
|
<string name="screen_join_room_ban_by_message">"You were banned by %1$s."</string>
|
||||||
<string name="screen_join_room_ban_message">"You were banned from this room"</string>
|
<string name="screen_join_room_ban_message">"You were banned"</string>
|
||||||
<string name="screen_join_room_ban_reason">"Reason: %1$s."</string>
|
<string name="screen_join_room_ban_reason">"Reason: %1$s."</string>
|
||||||
<string name="screen_join_room_cancel_knock_action">"Cancel request"</string>
|
<string name="screen_join_room_cancel_knock_action">"Cancel request"</string>
|
||||||
<string name="screen_join_room_cancel_knock_alert_confirmation">"Yes, cancel"</string>
|
<string name="screen_join_room_cancel_knock_alert_confirmation">"Yes, cancel"</string>
|
||||||
|
|
@ -11,12 +11,12 @@
|
||||||
<string name="screen_join_room_decline_and_block_alert_message">"Are you sure you want to decline the invite to join this room? This will also prevent %1$s from contacting you or inviting you to rooms."</string>
|
<string name="screen_join_room_decline_and_block_alert_message">"Are you sure you want to decline the invite to join this room? This will also prevent %1$s from contacting you or inviting you to rooms."</string>
|
||||||
<string name="screen_join_room_decline_and_block_alert_title">"Decline invite & block"</string>
|
<string name="screen_join_room_decline_and_block_alert_title">"Decline invite & block"</string>
|
||||||
<string name="screen_join_room_decline_and_block_button_title">"Decline and block"</string>
|
<string name="screen_join_room_decline_and_block_button_title">"Decline and block"</string>
|
||||||
<string name="screen_join_room_fail_message">"Joining the room failed."</string>
|
<string name="screen_join_room_fail_message">"Joining failed"</string>
|
||||||
<string name="screen_join_room_fail_reason">"This room is either invite-only or there might be restrictions to access at space level."</string>
|
<string name="screen_join_room_fail_reason">"You either need to be invited to join or there might be restrictions to access."</string>
|
||||||
<string name="screen_join_room_forget_action">"Forget this room"</string>
|
<string name="screen_join_room_forget_action">"Forget"</string>
|
||||||
<string name="screen_join_room_invite_required_message">"You need an invite in order to join this room"</string>
|
<string name="screen_join_room_invite_required_message">"You need an invite in order to join"</string>
|
||||||
<string name="screen_join_room_invited_by">"Invited by"</string>
|
<string name="screen_join_room_invited_by">"Invited by"</string>
|
||||||
<string name="screen_join_room_join_action">"Join room"</string>
|
<string name="screen_join_room_join_action">"Join"</string>
|
||||||
<string name="screen_join_room_join_restricted_message">"You may need to be invited or be a member of a space in order to join."</string>
|
<string name="screen_join_room_join_restricted_message">"You may need to be invited or be a member of a space in order to join."</string>
|
||||||
<string name="screen_join_room_knock_action">"Send request to join"</string>
|
<string name="screen_join_room_knock_action">"Send request to join"</string>
|
||||||
<string name="screen_join_room_knock_message_characters_count">"Allowed characters %1$d of %2$d"</string>
|
<string name="screen_join_room_knock_message_characters_count">"Allowed characters %1$d of %2$d"</string>
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class CreateAccountPresenter(
|
||||||
tryOrNull {
|
tryOrNull {
|
||||||
// Wait until the session is verified
|
// Wait until the session is verified
|
||||||
val client = clientProvider.getOrRestore(sessionId).getOrThrow()
|
val client = clientProvider.getOrRestore(sessionId).getOrThrow()
|
||||||
val sessionVerificationService = client.sessionVerificationService()
|
val sessionVerificationService = client.sessionVerificationService
|
||||||
withTimeout(10.seconds) { sessionVerificationService.sessionVerifiedStatus.first { it.isVerified() } }
|
withTimeout(10.seconds) { sessionVerificationService.sessionVerifiedStatus.first { it.isVerified() } }
|
||||||
}
|
}
|
||||||
loggedInState.value = AsyncAction.Success(sessionId)
|
loggedInState.value = AsyncAction.Success(sessionId)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
<string name="screen_change_account_provider_other">"Другое"</string>
|
<string name="screen_change_account_provider_other">"Другое"</string>
|
||||||
<string name="screen_change_account_provider_subtitle">"Используйте другого поставщика учетных записей, например, собственный частный сервер или рабочую учетную запись."</string>
|
<string name="screen_change_account_provider_subtitle">"Используйте другого поставщика учетных записей, например, собственный частный сервер или рабочую учетную запись."</string>
|
||||||
<string name="screen_change_account_provider_title">"Сменить поставщика учетной записи"</string>
|
<string name="screen_change_account_provider_title">"Сменить поставщика учетной записи"</string>
|
||||||
|
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
|
||||||
<string name="screen_change_server_error_element_pro_required_message">"Требуется приложение Element Pro для %1$s. Пожалуйста, загрузите его из магазина."</string>
|
<string name="screen_change_server_error_element_pro_required_message">"Требуется приложение Element Pro для %1$s. Пожалуйста, загрузите его из магазина."</string>
|
||||||
<string name="screen_change_server_error_element_pro_required_title">"Требуется Element Pro"</string>
|
<string name="screen_change_server_error_element_pro_required_title">"Требуется Element Pro"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Нам не удалось связаться с этим домашним сервером. Убедитесь, что вы правильно ввели URL-адрес домашнего сервера. Если URL-адрес указан правильно, обратитесь к администратору домашнего сервера за дополнительной помощью."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Нам не удалось связаться с этим домашним сервером. Убедитесь, что вы правильно ввели URL-адрес домашнего сервера. Если URL-адрес указан правильно, обратитесь к администратору домашнего сервера за дополнительной помощью."</string>
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,9 @@ import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
import io.element.android.libraries.dateformatter.api.toHumanReadableDuration
|
import io.element.android.libraries.dateformatter.api.toHumanReadableDuration
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
|
|
@ -73,6 +73,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||||
import io.element.android.libraries.matrix.api.room.alias.matches
|
import io.element.android.libraries.matrix.api.room.alias.matches
|
||||||
import io.element.android.libraries.matrix.api.room.joinedRoomMembers
|
import io.element.android.libraries.matrix.api.room.joinedRoomMembers
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||||
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
||||||
|
|
@ -95,7 +96,8 @@ import kotlinx.parcelize.Parcelize
|
||||||
class MessagesFlowNode(
|
class MessagesFlowNode(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val matrixClient: MatrixClient,
|
private val roomListService: RoomListService,
|
||||||
|
private val sessionId: SessionId,
|
||||||
private val sendLocationEntryPoint: SendLocationEntryPoint,
|
private val sendLocationEntryPoint: SendLocationEntryPoint,
|
||||||
private val showLocationEntryPoint: ShowLocationEntryPoint,
|
private val showLocationEntryPoint: ShowLocationEntryPoint,
|
||||||
private val createPollEntryPoint: CreatePollEntryPoint,
|
private val createPollEntryPoint: CreatePollEntryPoint,
|
||||||
|
|
@ -194,7 +196,7 @@ class MessagesFlowNode(
|
||||||
}
|
}
|
||||||
.launchIn(lifecycleScope)
|
.launchIn(lifecycleScope)
|
||||||
|
|
||||||
matrixClient.roomListService
|
roomListService
|
||||||
.allRooms
|
.allRooms
|
||||||
.summaries
|
.summaries
|
||||||
.onEach {
|
.onEach {
|
||||||
|
|
@ -221,11 +223,13 @@ class MessagesFlowNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?) {
|
override fun onPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?) {
|
||||||
backstack.push(NavTarget.AttachmentPreview(
|
backstack.push(
|
||||||
attachment = attachments.first(),
|
NavTarget.AttachmentPreview(
|
||||||
timelineMode = Timeline.Mode.Live,
|
attachment = attachments.first(),
|
||||||
inReplyToEventId = inReplyToEventId,
|
timelineMode = Timeline.Mode.Live,
|
||||||
))
|
inReplyToEventId = inReplyToEventId,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserDataClick(userId: UserId) {
|
override fun onUserDataClick(userId: UserId) {
|
||||||
|
|
@ -262,7 +266,7 @@ class MessagesFlowNode(
|
||||||
|
|
||||||
override fun onJoinCallClick(roomId: RoomId) {
|
override fun onJoinCallClick(roomId: RoomId) {
|
||||||
val callType = CallType.RoomCall(
|
val callType = CallType.RoomCall(
|
||||||
sessionId = matrixClient.sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
)
|
)
|
||||||
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
||||||
|
|
@ -348,18 +352,20 @@ class MessagesFlowNode(
|
||||||
}
|
}
|
||||||
is NavTarget.CreatePoll -> {
|
is NavTarget.CreatePoll -> {
|
||||||
createPollEntryPoint.nodeBuilder(this, buildContext)
|
createPollEntryPoint.nodeBuilder(this, buildContext)
|
||||||
.params(CreatePollEntryPoint.Params(
|
.params(
|
||||||
timelineMode = navTarget.timelineMode,
|
CreatePollEntryPoint.Params(
|
||||||
mode = CreatePollMode.NewPoll
|
timelineMode = navTarget.timelineMode,
|
||||||
))
|
mode = CreatePollMode.NewPoll
|
||||||
|
)
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
is NavTarget.EditPoll -> {
|
is NavTarget.EditPoll -> {
|
||||||
createPollEntryPoint.nodeBuilder(this, buildContext)
|
createPollEntryPoint.nodeBuilder(this, buildContext)
|
||||||
.params(
|
.params(
|
||||||
CreatePollEntryPoint.Params(
|
CreatePollEntryPoint.Params(
|
||||||
timelineMode = navTarget.timelineMode,
|
timelineMode = navTarget.timelineMode,
|
||||||
mode = CreatePollMode.EditPoll(eventId = navTarget.eventId)
|
mode = CreatePollMode.EditPoll(eventId = navTarget.eventId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -412,11 +418,13 @@ class MessagesFlowNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?) {
|
override fun onPreviewAttachments(attachments: ImmutableList<Attachment>, inReplyToEventId: EventId?) {
|
||||||
backstack.push(NavTarget.AttachmentPreview(
|
backstack.push(
|
||||||
attachment = attachments.first(),
|
NavTarget.AttachmentPreview(
|
||||||
timelineMode = Timeline.Mode.Thread(navTarget.threadRootId),
|
attachment = attachments.first(),
|
||||||
inReplyToEventId = inReplyToEventId,
|
timelineMode = Timeline.Mode.Thread(navTarget.threadRootId),
|
||||||
))
|
inReplyToEventId = inReplyToEventId,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserDataClick(userId: UserId) {
|
override fun onUserDataClick(userId: UserId) {
|
||||||
|
|
@ -453,7 +461,7 @@ class MessagesFlowNode(
|
||||||
|
|
||||||
override fun onJoinCallClick(roomId: RoomId) {
|
override fun onJoinCallClick(roomId: RoomId) {
|
||||||
val callType = CallType.RoomCall(
|
val callType = CallType.RoomCall(
|
||||||
sessionId = matrixClient.sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
)
|
)
|
||||||
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,17 @@
|
||||||
<string name="emoji_picker_category_objects">"Gjenstander"</string>
|
<string name="emoji_picker_category_objects">"Gjenstander"</string>
|
||||||
<string name="emoji_picker_category_people">"Smilefjes og mennesker"</string>
|
<string name="emoji_picker_category_people">"Smilefjes og mennesker"</string>
|
||||||
<string name="emoji_picker_category_places">"Reising og steder"</string>
|
<string name="emoji_picker_category_places">"Reising og steder"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Nylige emojier"</string>
|
||||||
<string name="emoji_picker_category_symbols">"Symboler"</string>
|
<string name="emoji_picker_category_symbols">"Symboler"</string>
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Teksting er kanskje ikke synlig for personer som bruker eldre apper."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Teksting er kanskje ikke synlig for personer som bruker eldre apper."</string>
|
||||||
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Trykk for å endre kvaliteten på videoopplastingen"</string>
|
||||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunne ikke lastes opp."</string>
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunne ikke lastes opp."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Kunne ikke behandle medier for opplasting, vennligst prøv igjen."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Kunne ikke behandle medier for opplasting, vennligst prøv igjen."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Opplasting av medier mislyktes, vennligst prøv igjen."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Opplasting av medier mislyktes, vennligst prøv igjen."</string>
|
||||||
<string name="screen_media_upload_preview_error_too_large_message">"Maksimal tillatt filstørrelse er %1$s."</string>
|
<string name="screen_media_upload_preview_error_too_large_message">"Maksimal tillatt filstørrelse er %1$s."</string>
|
||||||
<string name="screen_media_upload_preview_error_too_large_title">"Filen er for stor til å lastes opp"</string>
|
<string name="screen_media_upload_preview_error_too_large_title">"Filen er for stor til å lastes opp"</string>
|
||||||
|
<string name="screen_media_upload_preview_optimize_image_quality_title">"Optimaliser bildekvaliteten"</string>
|
||||||
|
<string name="screen_media_upload_preview_processing">"Behandler…"</string>
|
||||||
<string name="screen_report_content_block_user">"Blokker bruker"</string>
|
<string name="screen_report_content_block_user">"Blokker bruker"</string>
|
||||||
<string name="screen_report_content_block_user_hint">"Kryss av for om du vil skjule alle nåværende og fremtidige meldinger fra denne brukeren"</string>
|
<string name="screen_report_content_block_user_hint">"Kryss av for om du vil skjule alle nåværende og fremtidige meldinger fra denne brukeren"</string>
|
||||||
<string name="screen_report_content_explanation">"Denne meldingen vil bli rapportert til hjemmeserverens administratorer. De vil ikke kunne lese noen krypterte meldinger."</string>
|
<string name="screen_report_content_explanation">"Denne meldingen vil bli rapportert til hjemmeserverens administratorer. De vil ikke kunne lese noen krypterte meldinger."</string>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<string name="emoji_picker_category_objects">"Obiecte"</string>
|
<string name="emoji_picker_category_objects">"Obiecte"</string>
|
||||||
<string name="emoji_picker_category_people">"Fețe zâmbitoare & Oameni"</string>
|
<string name="emoji_picker_category_people">"Fețe zâmbitoare & Oameni"</string>
|
||||||
<string name="emoji_picker_category_places">"Călătorii & Locuri"</string>
|
<string name="emoji_picker_category_places">"Călătorii & Locuri"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Emoticoane recente"</string>
|
||||||
<string name="emoji_picker_category_symbols">"Simboluri"</string>
|
<string name="emoji_picker_category_symbols">"Simboluri"</string>
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Este posibil ca descrierile să nu fie vizibile pentru persoanele care folosesc aplicații mai vechi."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Este posibil ca descrierile să nu fie vizibile pentru persoanele care folosesc aplicații mai vechi."</string>
|
||||||
<string name="screen_media_upload_preview_change_video_quality_prompt">"Atingeți pentru a modifica calitatea încărcării videoclipului"</string>
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Atingeți pentru a modifica calitatea încărcării videoclipului"</string>
|
||||||
|
|
@ -15,6 +16,7 @@
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Încărcarea fișierelor media a eșuat, încercați din nou."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Încărcarea fișierelor media a eșuat, încercați din nou."</string>
|
||||||
<string name="screen_media_upload_preview_error_too_large_message">"Dimensiunea maximă permisă pentru fișiere este de %1$s."</string>
|
<string name="screen_media_upload_preview_error_too_large_message">"Dimensiunea maximă permisă pentru fișiere este de %1$s."</string>
|
||||||
<string name="screen_media_upload_preview_error_too_large_title">"Fișierul este prea mare pentru a fi încărcat."</string>
|
<string name="screen_media_upload_preview_error_too_large_title">"Fișierul este prea mare pentru a fi încărcat."</string>
|
||||||
|
<string name="screen_media_upload_preview_item_count">"Elementul %1$d din %2$d"</string>
|
||||||
<string name="screen_media_upload_preview_optimize_image_quality_title">"Optimizați calitatea imaginii"</string>
|
<string name="screen_media_upload_preview_optimize_image_quality_title">"Optimizați calitatea imaginii"</string>
|
||||||
<string name="screen_media_upload_preview_processing">"Se procesează…"</string>
|
<string name="screen_media_upload_preview_processing">"Se procesează…"</string>
|
||||||
<string name="screen_report_content_block_user">"Blocați utilizatorul"</string>
|
<string name="screen_report_content_block_user">"Blocați utilizatorul"</string>
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,18 @@
|
||||||
<string name="emoji_picker_category_objects">"Объекты"</string>
|
<string name="emoji_picker_category_objects">"Объекты"</string>
|
||||||
<string name="emoji_picker_category_people">"Улыбки и люди"</string>
|
<string name="emoji_picker_category_people">"Улыбки и люди"</string>
|
||||||
<string name="emoji_picker_category_places">"Путешествия и места"</string>
|
<string name="emoji_picker_category_places">"Путешествия и места"</string>
|
||||||
|
<string name="emoji_picker_category_recent">"Недавние эмодзи"</string>
|
||||||
<string name="emoji_picker_category_symbols">"Символы"</string>
|
<string name="emoji_picker_category_symbols">"Символы"</string>
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Подпись может быть не видна пользователям старых приложений."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Подпись может быть не видна пользователям старых приложений."</string>
|
||||||
|
<string name="screen_media_upload_preview_change_video_quality_prompt">"Нажмите, чтобы изменить качество загружаемого видео."</string>
|
||||||
|
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Файл не может быть загружен."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_sending">"Не удалось загрузить медиафайлы, попробуйте еще раз."</string>
|
<string name="screen_media_upload_preview_error_failed_sending">"Не удалось загрузить медиафайлы, попробуйте еще раз."</string>
|
||||||
|
<string name="screen_media_upload_preview_error_too_large_message">"Максимальный размер файла: %1$s."</string>
|
||||||
|
<string name="screen_media_upload_preview_error_too_large_title">"Файл слишком большой для загрузки."</string>
|
||||||
|
<string name="screen_media_upload_preview_item_count">"Элемент %1$d из %2$d"</string>
|
||||||
|
<string name="screen_media_upload_preview_optimize_image_quality_title">"Оптимизировать качество изображения"</string>
|
||||||
|
<string name="screen_media_upload_preview_processing">"Обработка…"</string>
|
||||||
<string name="screen_report_content_block_user">"Заблокировать пользователя"</string>
|
<string name="screen_report_content_block_user">"Заблокировать пользователя"</string>
|
||||||
<string name="screen_report_content_block_user_hint">"Отметьте, хотите ли вы скрыть все текущие и будущие сообщения от этого пользователя"</string>
|
<string name="screen_report_content_block_user_hint">"Отметьте, хотите ли вы скрыть все текущие и будущие сообщения от этого пользователя"</string>
|
||||||
<string name="screen_report_content_explanation">"Это сообщение будет передано администратору вашего домашнего сервера. Они не смогут прочитать зашифрованные сообщения."</string>
|
<string name="screen_report_content_explanation">"Это сообщение будет передано администратору вашего домашнего сервера. Они не смогут прочитать зашифрованные сообщения."</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