Merge branch 'develop' into feature/fga/space_ui_tweaks
This commit is contained in:
commit
91eeb7b773
174 changed files with 2905 additions and 1323 deletions
3
.github/renovate.json
vendored
3
.github/renovate.json
vendored
|
|
@ -22,7 +22,8 @@
|
||||||
{
|
{
|
||||||
"versioning": "semver",
|
"versioning": "semver",
|
||||||
"matchPackageNames": [
|
"matchPackageNames": [
|
||||||
"/^org.maplibre/"
|
"/^org.maplibre/",
|
||||||
|
"/^org.jetbrains.kotlinx:kotlinx-datetime/"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
22
.github/workflows/stale-issues.yml
vendored
Normal file
22
.github/workflows/stale-issues.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
name: Close stale issues that are missing info.
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v10
|
||||||
|
with:
|
||||||
|
only-labels: "X-Needs-Info"
|
||||||
|
days-before-issue-stale: 30
|
||||||
|
days-before-issue-close: 7
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
labels-to-remove-when-unstale: "X-Needs-Info"
|
||||||
|
stale-issue-message: "This issue has been awaiting further information for the past 30 days so will now be marked as stale. Please provide the requested information within the next 7 days to keep it open."
|
||||||
|
close-issue-message: "This issue is being closed due to inactivity after further information was requested."
|
||||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="2.3.0" />
|
<option name="version" value="2.3.10" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -9,29 +9,35 @@ appId: ${MAESTRO_APP_ID}
|
||||||
id: "login-continue"
|
id: "login-continue"
|
||||||
## MAS page
|
## MAS page
|
||||||
## Conditional workflow to pass the Chrome first launch welcome page.
|
## Conditional workflow to pass the Chrome first launch welcome page.
|
||||||
- runFlow:
|
- retry:
|
||||||
when:
|
maxRetries: 3
|
||||||
visible: 'Use without an account'
|
|
||||||
commands:
|
commands:
|
||||||
- tapOn: "Use without an account"
|
- runFlow:
|
||||||
## For older chrome versions
|
when:
|
||||||
- runFlow:
|
visible: 'Use without an account'
|
||||||
when:
|
commands:
|
||||||
visible: 'Accept & continue'
|
- tapOn: "Use without an account"
|
||||||
commands:
|
## For older chrome versions
|
||||||
- tapOn: "Accept & continue"
|
- runFlow:
|
||||||
- runFlow:
|
when:
|
||||||
when:
|
visible: 'Accept & continue'
|
||||||
visible: 'No thanks'
|
commands:
|
||||||
commands:
|
- tapOn: "Accept & continue"
|
||||||
- tapOn: "No thanks"
|
- runFlow:
|
||||||
|
when:
|
||||||
|
visible: 'No thanks'
|
||||||
|
commands:
|
||||||
|
- tapOn: "No thanks"
|
||||||
## Working when running Maestro locally, but not on the CI yet.
|
## Working when running Maestro locally, but not on the CI yet.
|
||||||
- extendedWaitUntil:
|
- retry:
|
||||||
visible:
|
maxRetries: 3
|
||||||
id: "form-1"
|
commands:
|
||||||
timeout: 10000
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "form-1"
|
||||||
|
timeout: 10000
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "form-1"
|
id: "form-1"
|
||||||
- inputText: ${MAESTRO_USERNAME}
|
- inputText: ${MAESTRO_USERNAME}
|
||||||
- pressKey: Enter
|
- pressKey: Enter
|
||||||
- tapOn:
|
- tapOn:
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,8 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- scroll
|
- scroll
|
||||||
- tapOn: "Leave room"
|
- tapOn: "Leave room"
|
||||||
- tapOn: "Leave"
|
- tapOn: "Leave"
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
visible: 'You need an invite in order to join'
|
||||||
|
commands:
|
||||||
|
- tapOn: "Back"
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ appId: ${MAESTRO_APP_ID}
|
||||||
# Purpose: Test the creation and deletion of a room
|
# Purpose: Test the creation and deletion of a room
|
||||||
- tapOn: "Create a new conversation or room"
|
- tapOn: "Create a new conversation or room"
|
||||||
- tapOn: "New room"
|
- tapOn: "New room"
|
||||||
- tapOn: "e.g. your project name"
|
- tapOn: "Add name…"
|
||||||
- inputText: "aRoomName"
|
- inputText: "aRoomName"
|
||||||
- tapOn: "What is this room about?"
|
- tapOn: "Add description…"
|
||||||
- inputText: "aRoomTopic"
|
- inputText: "aRoomTopic"
|
||||||
- tapOn: "Create"
|
- tapOn: "Create"
|
||||||
- takeScreenshot: build/maestro/320-createAndDeleteRoom
|
- takeScreenshot: build/maestro/320-createAndDeleteRoom
|
||||||
|
|
@ -37,3 +37,8 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- scroll
|
- scroll
|
||||||
- tapOn: "Leave room"
|
- tapOn: "Leave room"
|
||||||
- tapOn: "Leave"
|
- tapOn: "Leave"
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
visible: 'You need an invite in order to join'
|
||||||
|
commands:
|
||||||
|
- tapOn: "Back"
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,33 @@
|
||||||
<string name="screen_create_room_action_create_room">"Nová místnost"</string>
|
<string name="screen_create_room_action_create_room">"Nová místnost"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Pozvat přátele"</string>
|
<string name="screen_create_room_add_people_title">"Pozvat přátele"</string>
|
||||||
<string name="screen_create_room_error_creating_room">"Při vytváření místnosti došlo k chybě"</string>
|
<string name="screen_create_room_error_creating_room">"Při vytváření místnosti došlo k chybě"</string>
|
||||||
<string name="screen_create_room_private_option_description">"Do této místnosti mají přístup pouze pozvaní lidé. Všechny zprávy jsou koncově šifrovány."</string>
|
<string name="screen_create_room_error_creating_space">"Prostor se nepodařilo vytvořit kvůli neznámé chybě. Zkuste to znovu později."</string>
|
||||||
|
<string name="screen_create_room_name_placeholder">"Přidat název…"</string>
|
||||||
|
<string name="screen_create_room_new_room_title">"Nová místnost"</string>
|
||||||
|
<string name="screen_create_room_new_space_title">"Nový prostor"</string>
|
||||||
|
<string name="screen_create_room_private_option_description">"Do této místnosti mohou vstoupit pouze pozvaní."</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"Soukromý"</string>
|
||||||
<string name="screen_create_room_public_option_description">"Tuto místnost může najít kdokoli.
|
<string name="screen_create_room_public_option_description">"Tuto místnost může najít kdokoli.
|
||||||
To můžete kdykoli změnit v nastavení místnosti."</string>
|
To můžete kdykoli změnit v nastavení místnosti."</string>
|
||||||
|
<string name="screen_create_room_public_option_short_description">"Vstoupit může kdokoli."</string>
|
||||||
<string name="screen_create_room_public_option_title">"Veřejná místnost"</string>
|
<string name="screen_create_room_public_option_title">"Veřejná místnost"</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Kdokoli může požádat o vstup do místnosti, ale správce nebo moderátor bude muset žádost přijmout"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_description">"Kdokoli může požádat o vstup do místnosti, ale správce nebo moderátor bude muset žádost přijmout."</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Požádat o připojení"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_title">"Povolit žádost o vstup"</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_description">"Do této místnosti může vstoupit kdokoli"</string>
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Kdokoli v %1$s může vstoupit, ale všichni ostatní si musí o přístup požádat."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Požádat o vstup"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_description">"Vstoupit mohou pouze pozvaní lidé."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_title">"Soukromý"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_public_option_description">"Vstoupit může kdokoli."</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_title">"Kdokoliv"</string>
|
<string name="screen_create_room_room_access_section_public_option_title">"Kdokoliv"</string>
|
||||||
<string name="screen_create_room_room_address_section_footer">"Aby byla tato místnost viditelná v adresáři veřejných místností, budete potřebovat adresu místnosti."</string>
|
<string name="screen_create_room_room_access_section_restricted_option_description">"Kdokoli může vstoupit do %1$s."</string>
|
||||||
<string name="screen_create_room_room_address_section_title">"Adresa místnosti"</string>
|
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_title">"Kdo má přístup"</string>
|
||||||
|
<string name="screen_create_room_room_address_section_footer">"Budete potřebovat adresu, aby se zobrazovala ve veřejném adresáři."</string>
|
||||||
|
<string name="screen_create_room_room_address_section_title">"Adresa"</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Viditelnost místnosti"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Viditelnost místnosti"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_description">"(bez prostoru)"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_title">"Domov"</string>
|
||||||
|
<string name="screen_create_room_space_selection_sheet_title">"Přidat do prostoru"</string>
|
||||||
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
|
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
|
||||||
|
<string name="screen_create_room_topic_placeholder">"Přidat popis…"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,31 @@
|
||||||
<string name="screen_create_room_action_create_room">"Uus jututuba"</string>
|
<string name="screen_create_room_action_create_room">"Uus jututuba"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Kutsu osalejaid"</string>
|
<string name="screen_create_room_add_people_title">"Kutsu osalejaid"</string>
|
||||||
<string name="screen_create_room_error_creating_room">"Jututoa loomisel tekkis viga"</string>
|
<string name="screen_create_room_error_creating_room">"Jututoa loomisel tekkis viga"</string>
|
||||||
|
<string name="screen_create_room_error_creating_space">"Kogukonda polnud tundmatu vea tõttu võimalik luua. Palun proovi hiljem uuesti."</string>
|
||||||
<string name="screen_create_room_name_placeholder">"Sisesta nimi…"</string>
|
<string name="screen_create_room_name_placeholder">"Sisesta nimi…"</string>
|
||||||
<string name="screen_create_room_new_room_title">"Uus jututuba"</string>
|
<string name="screen_create_room_new_room_title">"Uus jututuba"</string>
|
||||||
<string name="screen_create_room_new_space_title">"Uus kogukond"</string>
|
<string name="screen_create_room_new_space_title">"Uus kogukond"</string>
|
||||||
<string name="screen_create_room_private_option_description">"Ligipääs siia jututuppa on vaid kutse alusel."</string>
|
<string name="screen_create_room_private_option_description">"Ligipääs siia jututuppa on vaid kutse alusel."</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"Privaatne"</string>
|
||||||
<string name="screen_create_room_public_option_description">"Kõik saavad seda jututuba leida.
|
<string name="screen_create_room_public_option_description">"Kõik saavad seda jututuba leida.
|
||||||
Sa võid seda jututoa seadistustest alati muuta."</string>
|
Sa võid seda jututoa seadistustest alati muuta."</string>
|
||||||
|
<string name="screen_create_room_public_option_short_description">"Kõik võivad selle jututoaga liituda."</string>
|
||||||
<string name="screen_create_room_public_option_title">"Avalik jututuba"</string>
|
<string name="screen_create_room_public_option_title">"Avalik jututuba"</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Kõik võivad paluda selle jututoaga liitumist, kuid peakasutaja või moderaator peavad selle kinnitama."</string>
|
<string name="screen_create_room_room_access_section_knocking_option_description">"Kõik võivad paluda selle jututoaga liitumist, kuid peakasutaja või moderaator peavad selle kinnitama."</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Luba küsida liitumisvõimalust"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_title">"Luba küsida liitumisvõimalust"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Kõik „%1$s“ kogukonna liikmed võivad liituda, kuid kõik teised peavad liitumiseks küsima luba."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Küsi võimalust liitumiseks"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_description">"Ligipääs siia jututuppa on vaid kutse alusel."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_title">"Privaatne"</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_description">"Kõik võivad selle jututoaga liituda."</string>
|
<string name="screen_create_room_room_access_section_public_option_description">"Kõik võivad selle jututoaga liituda."</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_title">"Kõik kasutajad"</string>
|
<string name="screen_create_room_room_access_section_public_option_title">"Kõik kasutajad"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_restricted_option_description">"Liituda võivad kõik „%1$s“ kogukonna liikmed."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_restricted_option_title">"Standardne"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_title">"Kellel on ligipääs"</string>
|
||||||
<string name="screen_create_room_room_address_section_footer">"Selleks, et jututuba oleks nähtav jututubade avalikus kataloogis, vajab ta aadressi."</string>
|
<string name="screen_create_room_room_address_section_footer">"Selleks, et jututuba oleks nähtav jututubade avalikus kataloogis, vajab ta aadressi."</string>
|
||||||
<string name="screen_create_room_room_address_section_title">"Aadress"</string>
|
<string name="screen_create_room_room_address_section_title">"Aadress"</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Jututoa nähtavus"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Jututoa nähtavus"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_description">"(kogukonda pole)"</string>
|
||||||
<string name="screen_create_room_space_selection_no_space_title">"Avaleht"</string>
|
<string name="screen_create_room_space_selection_no_space_title">"Avaleht"</string>
|
||||||
<string name="screen_create_room_space_selection_sheet_title">"Lisa kogukonda"</string>
|
<string name="screen_create_room_space_selection_sheet_title">"Lisa kogukonda"</string>
|
||||||
<string name="screen_create_room_topic_label">"Teema (kui soovid lisada)"</string>
|
<string name="screen_create_room_topic_label">"Teema (kui soovid lisada)"</string>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Vous pouvez modifier cela à tout moment dans les paramètres du salon."</string
|
||||||
<string name="screen_create_room_room_access_section_private_option_description">"Seules les personnes invitées peuvent joindre."</string>
|
<string name="screen_create_room_room_access_section_private_option_description">"Seules les personnes invitées peuvent joindre."</string>
|
||||||
<string name="screen_create_room_room_access_section_private_option_title">"Privé"</string>
|
<string name="screen_create_room_room_access_section_private_option_title">"Privé"</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_description">"Tout le monde peut joindre"</string>
|
<string name="screen_create_room_room_access_section_public_option_description">"Tout le monde peut joindre"</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_title">"Tout le monde"</string>
|
<string name="screen_create_room_room_access_section_public_option_title">"Public"</string>
|
||||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Toute membre de %1$s peut joindre le salon."</string>
|
<string name="screen_create_room_room_access_section_restricted_option_description">"Toute membre de %1$s peut joindre le salon."</string>
|
||||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
|
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
|
||||||
<string name="screen_create_room_room_access_section_title">"Qui a accès"</string>
|
<string name="screen_create_room_room_access_section_title">"Qui a accès"</string>
|
||||||
|
|
@ -28,7 +28,8 @@ Vous pouvez modifier cela à tout moment dans les paramètres du salon."</string
|
||||||
<string name="screen_create_room_room_address_section_title">"Adresse"</string>
|
<string name="screen_create_room_room_address_section_title">"Adresse"</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Visibilité du salon"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Visibilité du salon"</string>
|
||||||
<string name="screen_create_room_space_selection_no_space_description">"(pas d’espace)"</string>
|
<string name="screen_create_room_space_selection_no_space_description">"(pas d’espace)"</string>
|
||||||
<string name="screen_create_room_space_selection_no_space_title">"Accueil"</string>
|
<string name="screen_create_room_space_selection_no_space_option">"Ne pas ajouter à un espace"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_title">"Aucun espace sélectionné"</string>
|
||||||
<string name="screen_create_room_space_selection_sheet_title">"Ajouter à l’espace"</string>
|
<string name="screen_create_room_space_selection_sheet_title">"Ajouter à l’espace"</string>
|
||||||
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
|
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
|
||||||
<string name="screen_create_room_topic_placeholder">"Ajouter une description…"</string>
|
<string name="screen_create_room_topic_placeholder">"Ajouter une description…"</string>
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,25 @@
|
||||||
<string name="screen_create_room_action_create_room">"Nytt rom"</string>
|
<string name="screen_create_room_action_create_room">"Nytt rom"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Inviter folk"</string>
|
<string name="screen_create_room_add_people_title">"Inviter folk"</string>
|
||||||
<string name="screen_create_room_error_creating_room">"Det oppsto en feil under opprettelsen av rommet"</string>
|
<string name="screen_create_room_error_creating_room">"Det oppsto en feil under opprettelsen av rommet"</string>
|
||||||
|
<string name="screen_create_room_name_placeholder">"Legg til navn…"</string>
|
||||||
|
<string name="screen_create_room_new_room_title">"Nytt rom"</string>
|
||||||
|
<string name="screen_create_room_new_space_title">"Nytt område"</string>
|
||||||
<string name="screen_create_room_private_option_description">"Bare inviterte personer kan bli med."</string>
|
<string name="screen_create_room_private_option_description">"Bare inviterte personer kan bli med."</string>
|
||||||
<string name="screen_create_room_private_option_title">"Privat"</string>
|
<string name="screen_create_room_private_option_title">"Privat"</string>
|
||||||
<string name="screen_create_room_public_option_description">"Alle kan finne dette rommet.
|
<string name="screen_create_room_public_option_description">"Alle kan finne dette rommet.
|
||||||
Du kan endre dette når som helst i rominnstillingene."</string>
|
Du kan endre dette når som helst i rominnstillingene."</string>
|
||||||
|
<string name="screen_create_room_public_option_short_description">"Alle kan bli med."</string>
|
||||||
<string name="screen_create_room_public_option_title">"Offentlig rom"</string>
|
<string name="screen_create_room_public_option_title">"Offentlig rom"</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan be om å få bli med, men en administrator eller moderator må godta forespørselen."</string>
|
<string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan be om å få bli med, men en administrator eller moderator må godta forespørselen."</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Be om å bli med"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_title">"Be om å bli med"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_description">"Bare inviterte personer kan bli med."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_title">"Privat"</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_description">"Alle kan bli med."</string>
|
<string name="screen_create_room_room_access_section_public_option_description">"Alle kan bli med."</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_title">"Alle"</string>
|
<string name="screen_create_room_room_access_section_public_option_title">"Alle"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_title">"Hvem har tilgang"</string>
|
||||||
<string name="screen_create_room_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
<string name="screen_create_room_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
||||||
<string name="screen_create_room_room_address_section_title">"Adresse"</string>
|
<string name="screen_create_room_room_address_section_title">"Adresse"</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Romsynlighet"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Romsynlighet"</string>
|
||||||
<string name="screen_create_room_topic_label">"Emne (valgfritt)"</string>
|
<string name="screen_create_room_topic_label">"Emne (valgfritt)"</string>
|
||||||
|
<string name="screen_create_room_topic_placeholder">"Legg til beskrivelse…"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@
|
||||||
<string name="screen_create_room_public_option_description">"Vem som helst kan hitta det här rummet.
|
<string name="screen_create_room_public_option_description">"Vem som helst kan hitta det här rummet.
|
||||||
Du kan ändra detta när som helst i rumsinställningarna."</string>
|
Du kan ändra detta när som helst i rumsinställningarna."</string>
|
||||||
<string name="screen_create_room_public_option_title">"Offentligt rum"</string>
|
<string name="screen_create_room_public_option_title">"Offentligt rum"</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Vem som helst kan be om att gå med i rummet men en administratör eller en moderator måste acceptera begäran"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_description">"Vem som helst kan be om att gå med men en administratör eller en moderator måste acceptera begäran"</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Tillåt att be om att gå med"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_title">"Tillåt att be om att gå med"</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_description">"Vem som helst kan gå med i det här rummet"</string>
|
<string name="screen_create_room_room_access_section_public_option_description">"Vem som helst kan gå med."</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_title">"Offentligt"</string>
|
<string name="screen_create_room_room_access_section_public_option_title">"Offentligt"</string>
|
||||||
<string name="screen_create_room_room_address_section_footer">"För att detta rum ska vara synligt i den allmänna rumskatalogen behöver du en rumsadress."</string>
|
<string name="screen_create_room_room_address_section_footer">"Du behöver en adress för att den ska synas i den offentliga katalogen."</string>
|
||||||
<string name="screen_create_room_room_address_section_title">"Rumsadress"</string>
|
<string name="screen_create_room_room_address_section_title">"Adress"</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Rumssynlighet"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Rumssynlighet"</string>
|
||||||
<string name="screen_create_room_topic_label">"Ämne (valfritt)"</string>
|
<string name="screen_create_room_topic_label">"Ämne (valfritt)"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
package io.element.android.features.home.impl
|
package io.element.android.features.home.impl
|
||||||
|
|
||||||
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.spacefilters.SpaceFiltersState
|
||||||
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
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||||
|
|
@ -31,6 +32,7 @@ data class HomeState(
|
||||||
val directLogoutState: DirectLogoutState,
|
val directLogoutState: DirectLogoutState,
|
||||||
val eventSink: (HomeEvent) -> Unit,
|
val eventSink: (HomeEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val isBackHandlerEnabled = currentHomeNavigationBarItem != HomeNavigationBarItem.Chats || roomListState.spaceFiltersState is SpaceFiltersState.Selected
|
||||||
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
|
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
|
||||||
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters
|
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters
|
||||||
val showNavigationBar = homeSpacesState.canCreateSpaces || homeSpacesState.spaceRooms.isNotEmpty()
|
val showNavigationBar = homeSpacesState.canCreateSpaces || homeSpacesState.spaceRooms.isNotEmpty()
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,9 @@ import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu
|
||||||
import io.element.android.features.home.impl.roomlist.RoomListEvent
|
import io.element.android.features.home.impl.roomlist.RoomListEvent
|
||||||
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.search.RoomListSearchView
|
import io.element.android.features.home.impl.search.RoomListSearchView
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersEvent
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersView
|
||||||
import io.element.android.features.home.impl.spaces.HomeSpacesView
|
import io.element.android.features.home.impl.spaces.HomeSpacesView
|
||||||
import io.element.android.libraries.androidutils.throttler.FirstThrottler
|
import io.element.android.libraries.androidutils.throttler.FirstThrottler
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -153,10 +156,15 @@ private fun HomeScaffold(
|
||||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||||
val roomListState: RoomListState = state.roomListState
|
val roomListState: RoomListState = state.roomListState
|
||||||
|
|
||||||
BackHandler(
|
BackHandler(enabled = state.isBackHandlerEnabled) {
|
||||||
enabled = state.currentHomeNavigationBarItem != HomeNavigationBarItem.Chats,
|
if (state.currentHomeNavigationBarItem != HomeNavigationBarItem.Chats) {
|
||||||
) {
|
state.eventSink(HomeEvent.SelectHomeNavigationBarItem(HomeNavigationBarItem.Chats))
|
||||||
state.eventSink(HomeEvent.SelectHomeNavigationBarItem(HomeNavigationBarItem.Chats))
|
} else {
|
||||||
|
val spaceFiltersState = state.roomListState.spaceFiltersState
|
||||||
|
if (spaceFiltersState is SpaceFiltersState.Selected) {
|
||||||
|
spaceFiltersState.eventSink(SpaceFiltersEvent.Selected.ClearSelection)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val hazeState = rememberHazeState()
|
val hazeState = rememberHazeState()
|
||||||
|
|
@ -168,7 +176,6 @@ private fun HomeScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
HomeTopBar(
|
HomeTopBar(
|
||||||
selectedNavigationItem = state.currentHomeNavigationBarItem,
|
selectedNavigationItem = state.currentHomeNavigationBarItem,
|
||||||
title = stringResource(state.currentHomeNavigationBarItem.labelRes),
|
|
||||||
currentUserAndNeighbors = state.currentUserAndNeighbors,
|
currentUserAndNeighbors = state.currentUserAndNeighbors,
|
||||||
showAvatarIndicator = state.showAvatarIndicator,
|
showAvatarIndicator = state.showAvatarIndicator,
|
||||||
areSearchResultsDisplayed = roomListState.searchState.isSearchActive,
|
areSearchResultsDisplayed = roomListState.searchState.isSearchActive,
|
||||||
|
|
@ -182,6 +189,7 @@ private fun HomeScaffold(
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
displayFilters = state.displayRoomListFilters,
|
displayFilters = state.displayRoomListFilters,
|
||||||
filtersState = roomListState.filtersState,
|
filtersState = roomListState.filtersState,
|
||||||
|
spaceFiltersState = roomListState.spaceFiltersState,
|
||||||
canCreateSpaces = state.homeSpacesState.canCreateSpaces,
|
canCreateSpaces = state.homeSpacesState.canCreateSpaces,
|
||||||
canReportBug = state.canReportBug,
|
canReportBug = state.canReportBug,
|
||||||
modifier = Modifier.hazeEffect(
|
modifier = Modifier.hazeEffect(
|
||||||
|
|
@ -227,6 +235,7 @@ private fun HomeScaffold(
|
||||||
RoomListContentView(
|
RoomListContentView(
|
||||||
contentState = roomListState.contentState,
|
contentState = roomListState.contentState,
|
||||||
filtersState = roomListState.filtersState,
|
filtersState = roomListState.filtersState,
|
||||||
|
spaceFiltersState = roomListState.spaceFiltersState,
|
||||||
lazyListState = roomsLazyListState,
|
lazyListState = roomsLazyListState,
|
||||||
hideInvitesAvatars = roomListState.hideInvitesAvatars,
|
hideInvitesAvatars = roomListState.hideInvitesAvatars,
|
||||||
eventSink = roomListState.eventSink,
|
eventSink = roomListState.eventSink,
|
||||||
|
|
@ -256,6 +265,7 @@ private fun HomeScaffold(
|
||||||
.consumeWindowInsets(padding)
|
.consumeWindowInsets(padding)
|
||||||
.hazeSource(state = hazeState)
|
.hazeSource(state = hazeState)
|
||||||
)
|
)
|
||||||
|
SpaceFiltersView(roomListState.spaceFiltersState)
|
||||||
}
|
}
|
||||||
HomeNavigationBarItem.Spaces -> {
|
HomeNavigationBarItem.Spaces -> {
|
||||||
HomeSpacesView(
|
HomeSpacesView(
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
import androidx.compose.foundation.pager.VerticalPager
|
import androidx.compose.foundation.pager.VerticalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
|
@ -44,6 +45,10 @@ import io.element.android.features.home.impl.R
|
||||||
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
||||||
import io.element.android.features.home.impl.filters.RoomListFiltersView
|
import io.element.android.features.home.impl.filters.RoomListFiltersView
|
||||||
import io.element.android.features.home.impl.filters.aRoomListFiltersState
|
import io.element.android.features.home.impl.filters.aRoomListFiltersState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersEvent
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.aSelectedSpaceFiltersState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.anUnselectedSpaceFiltersState
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
||||||
import io.element.android.libraries.designsystem.components.TopAppBarScrollBehaviorLayout
|
import io.element.android.libraries.designsystem.components.TopAppBarScrollBehaviorLayout
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
|
@ -75,7 +80,6 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeTopBar(
|
fun HomeTopBar(
|
||||||
selectedNavigationItem: HomeNavigationBarItem,
|
selectedNavigationItem: HomeNavigationBarItem,
|
||||||
title: String,
|
|
||||||
currentUserAndNeighbors: ImmutableList<MatrixUser>,
|
currentUserAndNeighbors: ImmutableList<MatrixUser>,
|
||||||
showAvatarIndicator: Boolean,
|
showAvatarIndicator: Boolean,
|
||||||
areSearchResultsDisplayed: Boolean,
|
areSearchResultsDisplayed: Boolean,
|
||||||
|
|
@ -89,6 +93,7 @@ fun HomeTopBar(
|
||||||
canReportBug: Boolean,
|
canReportBug: Boolean,
|
||||||
displayFilters: Boolean,
|
displayFilters: Boolean,
|
||||||
filtersState: RoomListFiltersState,
|
filtersState: RoomListFiltersState,
|
||||||
|
spaceFiltersState: SpaceFiltersState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(modifier) {
|
Column(modifier) {
|
||||||
|
|
@ -103,12 +108,21 @@ fun HomeTopBar(
|
||||||
scrolledContainerColor = Color.Transparent,
|
scrolledContainerColor = Color.Transparent,
|
||||||
),
|
),
|
||||||
title = {
|
title = {
|
||||||
|
val displayTitle = when (selectedNavigationItem) {
|
||||||
|
HomeNavigationBarItem.Chats -> {
|
||||||
|
when (spaceFiltersState) {
|
||||||
|
is SpaceFiltersState.Selected -> spaceFiltersState.selectedFilter.spaceRoom.displayName
|
||||||
|
else -> stringResource(selectedNavigationItem.labelRes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HomeNavigationBarItem.Spaces -> stringResource(selectedNavigationItem.labelRes)
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.semantics {
|
modifier = Modifier.semantics {
|
||||||
heading()
|
heading()
|
||||||
},
|
},
|
||||||
style = ElementTheme.typography.aliasScreenTitle,
|
style = ElementTheme.typography.aliasScreenTitle,
|
||||||
text = title,
|
text = displayTitle,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
|
|
@ -124,7 +138,8 @@ fun HomeTopBar(
|
||||||
HomeNavigationBarItem.Chats -> RoomListMenuItems(
|
HomeNavigationBarItem.Chats -> RoomListMenuItems(
|
||||||
onToggleSearch = onToggleSearch,
|
onToggleSearch = onToggleSearch,
|
||||||
onMenuActionClick = onMenuActionClick,
|
onMenuActionClick = onMenuActionClick,
|
||||||
canReportBug = canReportBug
|
canReportBug = canReportBug,
|
||||||
|
spaceFiltersState = spaceFiltersState,
|
||||||
)
|
)
|
||||||
HomeNavigationBarItem.Spaces -> SpacesMenuItems(
|
HomeNavigationBarItem.Spaces -> SpacesMenuItems(
|
||||||
canCreateSpaces = canCreateSpaces,
|
canCreateSpaces = canCreateSpaces,
|
||||||
|
|
@ -154,6 +169,7 @@ private fun RoomListMenuItems(
|
||||||
onToggleSearch: () -> Unit,
|
onToggleSearch: () -> Unit,
|
||||||
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
||||||
canReportBug: Boolean,
|
canReportBug: Boolean,
|
||||||
|
spaceFiltersState: SpaceFiltersState,
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onToggleSearch,
|
onClick = onToggleSearch,
|
||||||
|
|
@ -163,6 +179,7 @@ private fun RoomListMenuItems(
|
||||||
contentDescription = stringResource(CommonStrings.action_search),
|
contentDescription = stringResource(CommonStrings.action_search),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
SpaceFilterButton(spaceFiltersState = spaceFiltersState)
|
||||||
if (RoomListConfig.HAS_DROP_DOWN_MENU) {
|
if (RoomListConfig.HAS_DROP_DOWN_MENU) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
@ -228,6 +245,38 @@ private fun SpacesMenuItems(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SpaceFilterButton(
|
||||||
|
spaceFiltersState: SpaceFiltersState,
|
||||||
|
) {
|
||||||
|
if (spaceFiltersState == SpaceFiltersState.Disabled) return
|
||||||
|
|
||||||
|
fun onClick() {
|
||||||
|
when (spaceFiltersState) {
|
||||||
|
is SpaceFiltersState.Unselected -> spaceFiltersState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
is SpaceFiltersState.Selected -> spaceFiltersState.eventSink(SpaceFiltersEvent.Selected.ClearSelection)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val isSelected = spaceFiltersState is SpaceFiltersState.Selected
|
||||||
|
IconButton(
|
||||||
|
onClick = ::onClick,
|
||||||
|
colors = if (isSelected) {
|
||||||
|
IconButtonDefaults.iconButtonColors(
|
||||||
|
containerColor = ElementTheme.colors.bgAccentRest,
|
||||||
|
contentColor = ElementTheme.colors.iconOnSolidPrimary,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
IconButtonDefaults.iconButtonColors()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = CompoundIcons.Filter(),
|
||||||
|
contentDescription = stringResource(R.string.screen_roomlist_your_spaces),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NavigationIcon(
|
private fun NavigationIcon(
|
||||||
currentUserAndNeighbors: ImmutableList<MatrixUser>,
|
currentUserAndNeighbors: ImmutableList<MatrixUser>,
|
||||||
|
|
@ -309,7 +358,6 @@ private fun AccountIcon(
|
||||||
internal fun HomeTopBarPreview() = ElementPreview {
|
internal fun HomeTopBarPreview() = ElementPreview {
|
||||||
HomeTopBar(
|
HomeTopBar(
|
||||||
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
||||||
title = stringResource(R.string.screen_roomlist_main_space_title),
|
|
||||||
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
||||||
showAvatarIndicator = false,
|
showAvatarIndicator = false,
|
||||||
areSearchResultsDisplayed = false,
|
areSearchResultsDisplayed = false,
|
||||||
|
|
@ -322,6 +370,30 @@ internal fun HomeTopBarPreview() = ElementPreview {
|
||||||
canReportBug = true,
|
canReportBug = true,
|
||||||
displayFilters = true,
|
displayFilters = true,
|
||||||
filtersState = aRoomListFiltersState(),
|
filtersState = aRoomListFiltersState(),
|
||||||
|
spaceFiltersState = anUnselectedSpaceFiltersState(),
|
||||||
|
onMenuActionClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun HomeTopBarSpaceFiltersSelectedPreview() = ElementPreview {
|
||||||
|
HomeTopBar(
|
||||||
|
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
||||||
|
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
||||||
|
showAvatarIndicator = false,
|
||||||
|
areSearchResultsDisplayed = false,
|
||||||
|
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
|
||||||
|
onOpenSettings = {},
|
||||||
|
onAccountSwitch = {},
|
||||||
|
onToggleSearch = {},
|
||||||
|
onCreateSpace = {},
|
||||||
|
canCreateSpaces = true,
|
||||||
|
canReportBug = true,
|
||||||
|
displayFilters = true,
|
||||||
|
filtersState = aRoomListFiltersState(),
|
||||||
|
spaceFiltersState = aSelectedSpaceFiltersState(),
|
||||||
onMenuActionClick = {},
|
onMenuActionClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -332,7 +404,6 @@ internal fun HomeTopBarPreview() = ElementPreview {
|
||||||
internal fun HomeTopBarSpacesPreview() = ElementPreview {
|
internal fun HomeTopBarSpacesPreview() = ElementPreview {
|
||||||
HomeTopBar(
|
HomeTopBar(
|
||||||
selectedNavigationItem = HomeNavigationBarItem.Spaces,
|
selectedNavigationItem = HomeNavigationBarItem.Spaces,
|
||||||
title = stringResource(R.string.screen_home_tab_spaces),
|
|
||||||
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
||||||
showAvatarIndicator = false,
|
showAvatarIndicator = false,
|
||||||
areSearchResultsDisplayed = false,
|
areSearchResultsDisplayed = false,
|
||||||
|
|
@ -345,6 +416,7 @@ internal fun HomeTopBarSpacesPreview() = ElementPreview {
|
||||||
canReportBug = true,
|
canReportBug = true,
|
||||||
displayFilters = false,
|
displayFilters = false,
|
||||||
filtersState = aRoomListFiltersState(),
|
filtersState = aRoomListFiltersState(),
|
||||||
|
spaceFiltersState = anUnselectedSpaceFiltersState(),
|
||||||
onMenuActionClick = {},
|
onMenuActionClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -355,7 +427,6 @@ internal fun HomeTopBarSpacesPreview() = ElementPreview {
|
||||||
internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
|
internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
|
||||||
HomeTopBar(
|
HomeTopBar(
|
||||||
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
||||||
title = stringResource(R.string.screen_roomlist_main_space_title),
|
|
||||||
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
||||||
showAvatarIndicator = true,
|
showAvatarIndicator = true,
|
||||||
areSearchResultsDisplayed = false,
|
areSearchResultsDisplayed = false,
|
||||||
|
|
@ -368,6 +439,7 @@ internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
|
||||||
canReportBug = true,
|
canReportBug = true,
|
||||||
displayFilters = true,
|
displayFilters = true,
|
||||||
filtersState = aRoomListFiltersState(),
|
filtersState = aRoomListFiltersState(),
|
||||||
|
spaceFiltersState = anUnselectedSpaceFiltersState(),
|
||||||
onMenuActionClick = {},
|
onMenuActionClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -378,7 +450,6 @@ internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
|
||||||
internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
|
internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
|
||||||
HomeTopBar(
|
HomeTopBar(
|
||||||
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
||||||
title = stringResource(R.string.screen_roomlist_main_space_title),
|
|
||||||
currentUserAndNeighbors = aMatrixUserList().take(3).toImmutableList(),
|
currentUserAndNeighbors = aMatrixUserList().take(3).toImmutableList(),
|
||||||
showAvatarIndicator = false,
|
showAvatarIndicator = false,
|
||||||
areSearchResultsDisplayed = false,
|
areSearchResultsDisplayed = false,
|
||||||
|
|
@ -391,6 +462,7 @@ internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
|
||||||
canReportBug = true,
|
canReportBug = true,
|
||||||
displayFilters = true,
|
displayFilters = true,
|
||||||
filtersState = aRoomListFiltersState(),
|
filtersState = aRoomListFiltersState(),
|
||||||
|
spaceFiltersState = anUnselectedSpaceFiltersState(),
|
||||||
onMenuActionClick = {},
|
onMenuActionClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ import io.element.android.features.home.impl.roomlist.RoomListContentState
|
||||||
import io.element.android.features.home.impl.roomlist.RoomListContentStateProvider
|
import io.element.android.features.home.impl.roomlist.RoomListContentStateProvider
|
||||||
import io.element.android.features.home.impl.roomlist.RoomListEvent
|
import io.element.android.features.home.impl.roomlist.RoomListEvent
|
||||||
import io.element.android.features.home.impl.roomlist.SecurityBannerState
|
import io.element.android.features.home.impl.roomlist.SecurityBannerState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.anUnselectedSpaceFiltersState
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
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.Button
|
||||||
|
|
@ -59,6 +61,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
fun RoomListContentView(
|
fun RoomListContentView(
|
||||||
contentState: RoomListContentState,
|
contentState: RoomListContentState,
|
||||||
filtersState: RoomListFiltersState,
|
filtersState: RoomListFiltersState,
|
||||||
|
spaceFiltersState: SpaceFiltersState,
|
||||||
lazyListState: LazyListState,
|
lazyListState: LazyListState,
|
||||||
hideInvitesAvatars: Boolean,
|
hideInvitesAvatars: Boolean,
|
||||||
eventSink: (RoomListEvent) -> Unit,
|
eventSink: (RoomListEvent) -> Unit,
|
||||||
|
|
@ -93,6 +96,7 @@ fun RoomListContentView(
|
||||||
state = contentState,
|
state = contentState,
|
||||||
hideInvitesAvatars = hideInvitesAvatars,
|
hideInvitesAvatars = hideInvitesAvatars,
|
||||||
filtersState = filtersState,
|
filtersState = filtersState,
|
||||||
|
spaceFiltersState = spaceFiltersState,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||||
|
|
@ -172,6 +176,7 @@ private fun RoomsView(
|
||||||
state: RoomListContentState.Rooms,
|
state: RoomListContentState.Rooms,
|
||||||
hideInvitesAvatars: Boolean,
|
hideInvitesAvatars: Boolean,
|
||||||
filtersState: RoomListFiltersState,
|
filtersState: RoomListFiltersState,
|
||||||
|
spaceFiltersState: SpaceFiltersState,
|
||||||
eventSink: (RoomListEvent) -> Unit,
|
eventSink: (RoomListEvent) -> Unit,
|
||||||
onSetUpRecoveryClick: () -> Unit,
|
onSetUpRecoveryClick: () -> Unit,
|
||||||
onConfirmRecoveryKeyClick: () -> Unit,
|
onConfirmRecoveryKeyClick: () -> Unit,
|
||||||
|
|
@ -180,9 +185,12 @@ private fun RoomsView(
|
||||||
lazyListState: LazyListState,
|
lazyListState: LazyListState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (state.summaries.isEmpty() && filtersState.hasAnyFilterSelected) {
|
val isSpaceFilterSelected = spaceFiltersState is SpaceFiltersState.Selected
|
||||||
|
val hasAnyFilterSelected = filtersState.hasAnyFilterSelected || isSpaceFilterSelected
|
||||||
|
if (state.summaries.isEmpty() && hasAnyFilterSelected) {
|
||||||
EmptyViewForFilterStates(
|
EmptyViewForFilterStates(
|
||||||
selectedFilters = filtersState.selectedFilters(),
|
selectedFilters = filtersState.selectedFilters(),
|
||||||
|
isSpaceFilterSelected = isSpaceFilterSelected,
|
||||||
modifier = modifier.fillMaxSize()
|
modifier = modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -278,9 +286,10 @@ private fun RoomsViewList(
|
||||||
@Composable
|
@Composable
|
||||||
private fun EmptyViewForFilterStates(
|
private fun EmptyViewForFilterStates(
|
||||||
selectedFilters: ImmutableList<RoomListFilter>,
|
selectedFilters: ImmutableList<RoomListFilter>,
|
||||||
|
isSpaceFilterSelected: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val emptyStateResources = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters) ?: return
|
val emptyStateResources = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected) ?: return
|
||||||
EmptyScaffold(
|
EmptyScaffold(
|
||||||
title = emptyStateResources.title,
|
title = emptyStateResources.title,
|
||||||
subtitle = emptyStateResources.subtitle,
|
subtitle = emptyStateResources.subtitle,
|
||||||
|
|
@ -331,6 +340,7 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
spaceFiltersState = anUnselectedSpaceFiltersState(),
|
||||||
hideInvitesAvatars = false,
|
hideInvitesAvatars = false,
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
onSetUpRecoveryClick = {},
|
onSetUpRecoveryClick = {},
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ class RoomListRoomSummaryFactory(
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is LatestEventValue.RoomInvite -> LatestEvent.None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import io.element.android.features.home.impl.roomlist.RoomListPresenter
|
||||||
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.search.RoomListSearchPresenter
|
import io.element.android.features.home.impl.search.RoomListSearchPresenter
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchState
|
import io.element.android.features.home.impl.search.RoomListSearchState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersPresenter
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
|
|
@ -31,4 +33,7 @@ interface RoomListModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
|
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindSpaceFiltersPresenter(presenter: SpaceFiltersPresenter): Presenter<SpaceFiltersState>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
package io.element.android.features.home.impl.filters
|
package io.element.android.features.home.impl.filters
|
||||||
|
|
||||||
import io.element.android.features.home.impl.R
|
import io.element.android.features.home.impl.R
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum class representing the different filters that can be applied to the room list.
|
* Enum class representing the different filters that can be applied to the room list.
|
||||||
|
|
@ -30,3 +31,13 @@ enum class RoomListFilter(val stringResource: Int) {
|
||||||
Invites -> setOf(Rooms, People, Unread, Favourites)
|
Invites -> setOf(Rooms, People, Unread, Favourites)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun RoomListFilter.into(): MatrixRoomListFilter {
|
||||||
|
return when (this) {
|
||||||
|
RoomListFilter.Rooms -> MatrixRoomListFilter.Category.Group
|
||||||
|
RoomListFilter.People -> MatrixRoomListFilter.Category.People
|
||||||
|
RoomListFilter.Unread -> MatrixRoomListFilter.Unread
|
||||||
|
RoomListFilter.Favourites -> MatrixRoomListFilter.Favorite
|
||||||
|
RoomListFilter.Invites -> MatrixRoomListFilter.Invite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,12 @@ data class RoomListFiltersEmptyStateResources(
|
||||||
/**
|
/**
|
||||||
* Create a [RoomListFiltersEmptyStateResources] from a list of selected filters.
|
* Create a [RoomListFiltersEmptyStateResources] from a list of selected filters.
|
||||||
*/
|
*/
|
||||||
fun fromSelectedFilters(selectedFilters: List<RoomListFilter>): RoomListFiltersEmptyStateResources? {
|
fun fromSelectedFilters(selectedFilters: List<RoomListFilter>, isSpaceFilterSelected: Boolean): RoomListFiltersEmptyStateResources? {
|
||||||
return when {
|
return when {
|
||||||
|
isSpaceFilterSelected -> RoomListFiltersEmptyStateResources(
|
||||||
|
title = R.string.screen_roomlist_filter_mixed_empty_state_title,
|
||||||
|
subtitle = R.string.screen_roomlist_filter_mixed_empty_state_subtitle
|
||||||
|
)
|
||||||
selectedFilters.isEmpty() -> null
|
selectedFilters.isEmpty() -> null
|
||||||
selectedFilters.size == 1 -> {
|
selectedFilters.size == 1 -> {
|
||||||
when (selectedFilters.first()) {
|
when (selectedFilters.first()) {
|
||||||
|
|
|
||||||
|
|
@ -9,24 +9,17 @@
|
||||||
package io.element.android.features.home.impl.filters
|
package io.element.android.features.home.impl.filters
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
|
||||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionStrategy
|
import io.element.android.features.home.impl.filters.selection.FilterSelectionStrategy
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class RoomListFiltersPresenter(
|
class RoomListFiltersPresenter(
|
||||||
private val roomListDataSource: RoomListDataSource,
|
|
||||||
private val filterSelectionStrategy: FilterSelectionStrategy,
|
private val filterSelectionStrategy: FilterSelectionStrategy,
|
||||||
) : Presenter<RoomListFiltersState> {
|
) : Presenter<RoomListFiltersState> {
|
||||||
private val initialFilters = filterSelectionStrategy.filterSelectionStates.value.toImmutableList()
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomListFiltersState {
|
override fun present(): RoomListFiltersState {
|
||||||
fun handleEvent(event: RoomListFiltersEvent) {
|
fun handleEvent(event: RoomListFiltersEvent) {
|
||||||
|
|
@ -40,31 +33,9 @@ class RoomListFiltersPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val filters by produceState(initialValue = initialFilters) {
|
val filters by filterSelectionStrategy.filterSelectionStates.collectAsState()
|
||||||
filterSelectionStrategy.filterSelectionStates
|
|
||||||
.map { filters ->
|
|
||||||
value = filters.toImmutableList()
|
|
||||||
filters.mapNotNull { filterState ->
|
|
||||||
if (!filterState.isSelected) {
|
|
||||||
return@mapNotNull null
|
|
||||||
}
|
|
||||||
when (filterState.filter) {
|
|
||||||
RoomListFilter.Rooms -> MatrixRoomListFilter.Category.Group
|
|
||||||
RoomListFilter.People -> MatrixRoomListFilter.Category.People
|
|
||||||
RoomListFilter.Unread -> MatrixRoomListFilter.Unread
|
|
||||||
RoomListFilter.Favourites -> MatrixRoomListFilter.Favorite
|
|
||||||
RoomListFilter.Invites -> MatrixRoomListFilter.Invite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.collectLatest { filters ->
|
|
||||||
val result = MatrixRoomListFilter.All(filters)
|
|
||||||
roomListDataSource.updateFilter(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return RoomListFiltersState(
|
return RoomListFiltersState(
|
||||||
filterSelectionStates = filters,
|
filterSelectionStates = filters.toImmutableList(),
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@ContributesBinding(SessionScope::class)
|
@ContributesBinding(SessionScope::class)
|
||||||
class DefaultFilterSelectionStrategy : FilterSelectionStrategy {
|
class DefaultFilterSelectionStrategy : FilterSelectionStrategy {
|
||||||
private val selectedFilters = LinkedHashSet<RoomListFilter>()
|
private val selectedFilters = LinkedHashSet<RoomListFilter>()
|
||||||
|
private val availableFilters
|
||||||
|
get() = RoomListFilter.entries.toSet()
|
||||||
|
|
||||||
override val filterSelectionStates = MutableStateFlow(buildFilters())
|
override val filterSelectionStates = MutableStateFlow(buildFilters())
|
||||||
|
|
||||||
|
|
@ -45,7 +47,7 @@ class DefaultFilterSelectionStrategy : FilterSelectionStrategy {
|
||||||
isSelected = true
|
isSelected = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val unselectedFilters = RoomListFilter.entries - selectedFilters - selectedFilters.flatMap { it.incompatibleFilters }.toSet()
|
val unselectedFilters = availableFilters - selectedFilters - selectedFilters.flatMap { it.incompatibleFilters }.toSet()
|
||||||
val unselectedFilterStates = unselectedFilters.map {
|
val unselectedFilterStates = unselectedFilters.map {
|
||||||
FilterSelectionState(
|
FilterSelectionState(
|
||||||
filter = it,
|
filter = it,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
interface FilterSelectionStrategy {
|
interface FilterSelectionStrategy {
|
||||||
val filterSelectionStates: StateFlow<Set<FilterSelectionState>>
|
val filterSelectionStates: StateFlow<Set<FilterSelectionState>>
|
||||||
|
|
||||||
fun select(filter: RoomListFilter)
|
fun select(filter: RoomListFilter)
|
||||||
fun deselect(filter: RoomListFilter)
|
fun deselect(filter: RoomListFilter)
|
||||||
fun isSelected(filter: RoomListFilter): Boolean
|
fun isSelected(filter: RoomListFilter): Boolean
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,14 @@ import im.vector.app.features.analytics.plan.Interaction
|
||||||
import io.element.android.features.announcement.api.Announcement
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.features.announcement.api.AnnouncementService
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
||||||
|
import io.element.android.features.home.impl.filters.RoomListFilter.Rooms
|
||||||
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
||||||
|
import io.element.android.features.home.impl.filters.into
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchEvent
|
import io.element.android.features.home.impl.search.RoomListSearchEvent
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchState
|
import io.element.android.features.home.impl.search.RoomListSearchState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.into
|
||||||
|
import io.element.android.features.home.impl.spacefilters.selectedFilter
|
||||||
import io.element.android.features.invite.api.SeenInvitesStore
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.AcceptInvite
|
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.AcceptInvite
|
||||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.DeclineInvite
|
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.DeclineInvite
|
||||||
|
|
@ -44,6 +49,7 @@ 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.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.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||||
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
|
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
|
|
@ -83,6 +89,7 @@ class RoomListPresenter(
|
||||||
private val seenInvitesStore: SeenInvitesStore,
|
private val seenInvitesStore: SeenInvitesStore,
|
||||||
private val announcementService: AnnouncementService,
|
private val announcementService: AnnouncementService,
|
||||||
private val coldStartWatcher: AnalyticsColdStartWatcher,
|
private val coldStartWatcher: AnalyticsColdStartWatcher,
|
||||||
|
private val spaceFiltersPresenter: Presenter<SpaceFiltersState>,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
private val encryptionService = client.encryptionService
|
private val encryptionService = client.encryptionService
|
||||||
|
|
||||||
|
|
@ -92,6 +99,7 @@ class RoomListPresenter(
|
||||||
val leaveRoomState = leaveRoomPresenter.present()
|
val leaveRoomState = leaveRoomPresenter.present()
|
||||||
val filtersState = filtersPresenter.present()
|
val filtersState = filtersPresenter.present()
|
||||||
val searchState = searchPresenter.present()
|
val searchState = searchPresenter.present()
|
||||||
|
val spaceFiltersState = spaceFiltersPresenter.present()
|
||||||
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
|
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
@ -150,6 +158,13 @@ class RoomListPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(filtersState.filterSelectionStates, spaceFiltersState.selectedFilter()) {
|
||||||
|
val selectedFilters = filtersState.selectedFilters().map { filter -> filter.into() }
|
||||||
|
val selectedSpaceFilter = spaceFiltersState.selectedFilter().into()
|
||||||
|
val allFilters = RoomListFilter.All(selectedFilters + listOfNotNull(selectedSpaceFilter))
|
||||||
|
roomListDataSource.updateFilter(allFilters)
|
||||||
|
}
|
||||||
|
|
||||||
val contentState = roomListContentState(
|
val contentState = roomListContentState(
|
||||||
securityBannerDismissed,
|
securityBannerDismissed,
|
||||||
showNewNotificationSoundBanner,
|
showNewNotificationSoundBanner,
|
||||||
|
|
@ -163,6 +178,7 @@ class RoomListPresenter(
|
||||||
leaveRoomState = leaveRoomState,
|
leaveRoomState = leaveRoomState,
|
||||||
filtersState = filtersState,
|
filtersState = filtersState,
|
||||||
searchState = searchState,
|
searchState = searchState,
|
||||||
|
spaceFiltersState = spaceFiltersState,
|
||||||
contentState = contentState,
|
contentState = contentState,
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
hideInvitesAvatars = hideInvitesAvatar,
|
hideInvitesAvatars = hideInvitesAvatar,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
||||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchState
|
import io.element.android.features.home.impl.search.RoomListSearchState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||||
|
|
@ -26,6 +27,7 @@ data class RoomListState(
|
||||||
val leaveRoomState: LeaveRoomState,
|
val leaveRoomState: LeaveRoomState,
|
||||||
val filtersState: RoomListFiltersState,
|
val filtersState: RoomListFiltersState,
|
||||||
val searchState: RoomListSearchState,
|
val searchState: RoomListSearchState,
|
||||||
|
val spaceFiltersState: SpaceFiltersState,
|
||||||
val contentState: RoomListContentState,
|
val contentState: RoomListContentState,
|
||||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||||
val hideInvitesAvatars: Boolean,
|
val hideInvitesAvatars: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import io.element.android.features.home.impl.model.aRoomListRoomSummary
|
||||||
import io.element.android.features.home.impl.model.anInviteSender
|
import io.element.android.features.home.impl.model.anInviteSender
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchState
|
import io.element.android.features.home.impl.search.RoomListSearchState
|
||||||
import io.element.android.features.home.impl.search.aRoomListSearchState
|
import io.element.android.features.home.impl.search.aRoomListSearchState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.anUnselectedSpaceFiltersState
|
||||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||||
|
|
@ -52,6 +54,7 @@ internal fun aRoomListState(
|
||||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||||
searchState: RoomListSearchState = aRoomListSearchState(),
|
searchState: RoomListSearchState = aRoomListSearchState(),
|
||||||
filtersState: RoomListFiltersState = aRoomListFiltersState(),
|
filtersState: RoomListFiltersState = aRoomListFiltersState(),
|
||||||
|
spaceFiltersState: SpaceFiltersState = anUnselectedSpaceFiltersState(),
|
||||||
contentState: RoomListContentState = aRoomsContentState(),
|
contentState: RoomListContentState = aRoomsContentState(),
|
||||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||||
hideInvitesAvatars: Boolean = false,
|
hideInvitesAvatars: Boolean = false,
|
||||||
|
|
@ -63,6 +66,7 @@ internal fun aRoomListState(
|
||||||
leaveRoomState = leaveRoomState,
|
leaveRoomState = leaveRoomState,
|
||||||
filtersState = filtersState,
|
filtersState = filtersState,
|
||||||
searchState = searchState,
|
searchState = searchState,
|
||||||
|
spaceFiltersState = spaceFiltersState,
|
||||||
contentState = contentState,
|
contentState = contentState,
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
hideInvitesAvatars = hideInvitesAvatars,
|
hideInvitesAvatars = hideInvitesAvatars,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations 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.spacefilters
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
|
|
||||||
|
sealed interface SpaceFiltersEvent {
|
||||||
|
// Only valid in Unselected state
|
||||||
|
sealed interface Unselected : SpaceFiltersEvent {
|
||||||
|
data object ShowFilters : Unselected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only valid in Selecting state
|
||||||
|
sealed interface Selecting : SpaceFiltersEvent {
|
||||||
|
data object Cancel : Selecting
|
||||||
|
data class SelectFilter(val spaceFilter: SpaceServiceFilter) : Selecting
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only valid in Selected state
|
||||||
|
sealed interface Selected : SpaceFiltersEvent {
|
||||||
|
data object ClearSelection : Selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations 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.spacefilters
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
class SpaceFiltersPresenter(
|
||||||
|
private val featureFlagService: FeatureFlagService,
|
||||||
|
private val matrixClient: MatrixClient,
|
||||||
|
) : Presenter<SpaceFiltersState> {
|
||||||
|
@Composable
|
||||||
|
override fun present(): SpaceFiltersState {
|
||||||
|
val isFeatureEnabled by featureFlagService
|
||||||
|
.isFeatureEnabledFlow(FeatureFlags.RoomListSpaceFilters)
|
||||||
|
.collectAsState(initial = false)
|
||||||
|
|
||||||
|
val availableFilters by remember {
|
||||||
|
matrixClient.spaceService.spaceFiltersFlow.map { it.toImmutableList() }
|
||||||
|
}.collectAsState(initial = persistentListOf())
|
||||||
|
|
||||||
|
if (!isFeatureEnabled || availableFilters.isEmpty()) {
|
||||||
|
return SpaceFiltersState.Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectionMode by remember { mutableStateOf<SelectionMode>(SelectionMode.Unselected) }
|
||||||
|
|
||||||
|
fun handleUnselectedEvent(event: SpaceFiltersEvent.Unselected) {
|
||||||
|
when (event) {
|
||||||
|
SpaceFiltersEvent.Unselected.ShowFilters -> {
|
||||||
|
selectionMode = SelectionMode.Selecting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleSelectingEvent(event: SpaceFiltersEvent.Selecting) {
|
||||||
|
when (event) {
|
||||||
|
SpaceFiltersEvent.Selecting.Cancel -> {
|
||||||
|
selectionMode = SelectionMode.Unselected
|
||||||
|
}
|
||||||
|
is SpaceFiltersEvent.Selecting.SelectFilter -> {
|
||||||
|
selectionMode = SelectionMode.Selected(event.spaceFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleSelectedEvent(event: SpaceFiltersEvent.Selected) {
|
||||||
|
when (event) {
|
||||||
|
SpaceFiltersEvent.Selected.ClearSelection -> {
|
||||||
|
selectionMode = SelectionMode.Unselected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (val mode = selectionMode) {
|
||||||
|
SelectionMode.Unselected -> SpaceFiltersState.Unselected(
|
||||||
|
eventSink = ::handleUnselectedEvent,
|
||||||
|
)
|
||||||
|
SelectionMode.Selecting -> {
|
||||||
|
val searchQuery = rememberTextFieldState()
|
||||||
|
SpaceFiltersState.Selecting(
|
||||||
|
availableFilters = availableFilters,
|
||||||
|
searchQuery = searchQuery,
|
||||||
|
eventSink = ::handleSelectingEvent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SelectionMode.Selected -> {
|
||||||
|
var selectedFilter by remember { mutableStateOf(mode.filter) }
|
||||||
|
// Makes sure the selectedFilter stays in sync with the available filters
|
||||||
|
LaunchedEffect(availableFilters) {
|
||||||
|
val upToDateFilter = availableFilters
|
||||||
|
.firstOrNull { it.spaceRoom.roomId == mode.filter.spaceRoom.roomId }
|
||||||
|
if (upToDateFilter == null) {
|
||||||
|
selectionMode = SelectionMode.Unselected
|
||||||
|
} else {
|
||||||
|
selectedFilter = upToDateFilter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SpaceFiltersState.Selected(
|
||||||
|
selectedFilter = selectedFilter,
|
||||||
|
eventSink = ::handleSelectedEvent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed interface SelectionMode {
|
||||||
|
data object Unselected : SelectionMode
|
||||||
|
data object Selecting : SelectionMode
|
||||||
|
data class Selected(val filter: SpaceServiceFilter) : SelectionMode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations 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.spacefilters
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed interface SpaceFiltersState {
|
||||||
|
data object Disabled : SpaceFiltersState
|
||||||
|
|
||||||
|
data class Unselected(
|
||||||
|
val eventSink: (SpaceFiltersEvent.Unselected) -> Unit,
|
||||||
|
) : SpaceFiltersState
|
||||||
|
|
||||||
|
data class Selecting(
|
||||||
|
val availableFilters: ImmutableList<SpaceServiceFilter>,
|
||||||
|
val searchQuery: TextFieldState,
|
||||||
|
val eventSink: (SpaceFiltersEvent.Selecting) -> Unit,
|
||||||
|
) : SpaceFiltersState {
|
||||||
|
val visibleFilters: ImmutableList<SpaceServiceFilter>
|
||||||
|
get() {
|
||||||
|
val query = searchQuery.text.toString()
|
||||||
|
if (query.isBlank()) return availableFilters
|
||||||
|
return availableFilters.filter { filter ->
|
||||||
|
filter.spaceRoom.displayName.contains(query, ignoreCase = true) ||
|
||||||
|
(filter.spaceRoom.canonicalAlias?.value ?: "").contains(query, ignoreCase = true)
|
||||||
|
}.toImmutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Selected(
|
||||||
|
val selectedFilter: SpaceServiceFilter,
|
||||||
|
val eventSink: (SpaceFiltersEvent.Selected) -> Unit,
|
||||||
|
) : SpaceFiltersState
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SpaceFiltersState.selectedFilter(): SpaceServiceFilter? {
|
||||||
|
return when (this) {
|
||||||
|
is SpaceFiltersState.Selected -> this.selectedFilter
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SpaceServiceFilter?.into(): RoomListFilter? {
|
||||||
|
return this?.let { RoomListFilter.Identifiers(descendants) }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations 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.spacefilters
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
|
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
class SpaceFiltersStateProvider : PreviewParameterProvider<SpaceFiltersState> {
|
||||||
|
override val values: Sequence<SpaceFiltersState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aSelectingSpaceFiltersState(),
|
||||||
|
aSelectingSpaceFiltersState(searchQuery = "Pr")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aDisabledSpaceFiltersState() = SpaceFiltersState.Disabled
|
||||||
|
|
||||||
|
fun anUnselectedSpaceFiltersState(
|
||||||
|
eventSink: (SpaceFiltersEvent.Unselected) -> Unit = {},
|
||||||
|
) = SpaceFiltersState.Unselected(
|
||||||
|
eventSink = eventSink,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun aSelectingSpaceFiltersState(
|
||||||
|
availableFilters: List<SpaceServiceFilter> = listOf(
|
||||||
|
aSpaceServiceFilter(
|
||||||
|
displayName = "Work",
|
||||||
|
canonicalAlias = RoomAlias("#work:example.com"),
|
||||||
|
),
|
||||||
|
aSpaceServiceFilter(
|
||||||
|
displayName = "Personal",
|
||||||
|
roomId = RoomId("!personal:example.com"),
|
||||||
|
),
|
||||||
|
aSpaceServiceFilter(
|
||||||
|
displayName = "Projects",
|
||||||
|
roomId = RoomId("!projects:example.com"),
|
||||||
|
canonicalAlias = RoomAlias("#projects:example.com"),
|
||||||
|
level = 1,
|
||||||
|
),
|
||||||
|
aSpaceServiceFilter(
|
||||||
|
displayName = "Gaming",
|
||||||
|
roomId = RoomId("!gaming:example.com"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
searchQuery: String = "",
|
||||||
|
eventSink: (SpaceFiltersEvent.Selecting) -> Unit = {},
|
||||||
|
) = SpaceFiltersState.Selecting(
|
||||||
|
availableFilters = availableFilters.toImmutableList(),
|
||||||
|
searchQuery = TextFieldState(searchQuery),
|
||||||
|
eventSink = eventSink,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun aSelectedSpaceFiltersState(
|
||||||
|
selectedFilter: SpaceServiceFilter = aSpaceServiceFilter(displayName = "Work"),
|
||||||
|
eventSink: (SpaceFiltersEvent.Selected) -> Unit = {},
|
||||||
|
) = SpaceFiltersState.Selected(
|
||||||
|
selectedFilter = selectedFilter,
|
||||||
|
eventSink = eventSink,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun aSpaceServiceFilter(
|
||||||
|
displayName: String = "Space",
|
||||||
|
roomId: RoomId = RoomId("!space:example.com"),
|
||||||
|
canonicalAlias: RoomAlias? = null,
|
||||||
|
level: Int = 0,
|
||||||
|
descendants: List<RoomId> = emptyList(),
|
||||||
|
) = SpaceServiceFilter(
|
||||||
|
spaceRoom = aSpaceRoom(displayName = displayName, roomId = roomId, canonicalAlias = canonicalAlias),
|
||||||
|
level = level,
|
||||||
|
descendants = descendants,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations 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.spacefilters
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.SheetValue
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.features.home.impl.R
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.SearchField
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SpaceFiltersView(
|
||||||
|
state: SpaceFiltersState,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val isSelecting by rememberUpdatedState(state is SpaceFiltersState.Selecting)
|
||||||
|
val sheetState = rememberModalBottomSheetState(
|
||||||
|
skipPartiallyExpanded = true,
|
||||||
|
confirmValueChange = { sheetValueTarget ->
|
||||||
|
// This ensures the hide animation is not cancelled
|
||||||
|
when (sheetValueTarget) {
|
||||||
|
SheetValue.Expanded -> isSelecting
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
LaunchedEffect(isSelecting) {
|
||||||
|
if (!isSelecting) {
|
||||||
|
sheetState.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sheetState.isVisible || isSelecting) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
modifier = modifier
|
||||||
|
.systemBarsPadding()
|
||||||
|
.navigationBarsPadding(),
|
||||||
|
sheetState = sheetState,
|
||||||
|
onDismissRequest = {
|
||||||
|
if (state is SpaceFiltersState.Selecting) {
|
||||||
|
state.eventSink(SpaceFiltersEvent.Selecting.Cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(0.9f)
|
||||||
|
) {
|
||||||
|
if (state is SpaceFiltersState.Selecting) {
|
||||||
|
SpaceFiltersBottomSheetContent(
|
||||||
|
filters = state.visibleFilters,
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
onFilterSelected = { filter ->
|
||||||
|
state.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(filter))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SpaceFiltersBottomSheetContent(
|
||||||
|
filters: List<SpaceServiceFilter>,
|
||||||
|
searchQuery: TextFieldState,
|
||||||
|
onFilterSelected: (SpaceServiceFilter) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.padding(vertical = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.screen_roomlist_your_spaces),
|
||||||
|
style = ElementTheme.typography.fontHeadingSmMedium,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
SearchField(
|
||||||
|
state = searchQuery,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
placeholder = stringResource(CommonStrings.action_search),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
LazyColumn {
|
||||||
|
items(filters) { filter ->
|
||||||
|
SpaceFilterItem(
|
||||||
|
filter = filter,
|
||||||
|
onClick = { onFilterSelected(filter) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SpaceFilterItem(
|
||||||
|
filter: SpaceServiceFilter,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val spaceRoom = filter.spaceRoom
|
||||||
|
val supportingText = spaceRoom.canonicalAlias?.value
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
// Level-based indentation
|
||||||
|
Spacer(modifier = Modifier.width((16 * filter.level).dp))
|
||||||
|
Avatar(
|
||||||
|
avatarData = spaceRoom.getAvatarData(AvatarSize.RoomSelectRoomListItem),
|
||||||
|
avatarType = AvatarType.Space(),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = spaceRoom.displayName,
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
if (supportingText != null) {
|
||||||
|
Text(
|
||||||
|
text = supportingText,
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun SpaceFiltersViewPreview(@PreviewParameter(SpaceFiltersStateProvider::class) state: SpaceFiltersState) = ElementPreview {
|
||||||
|
SpaceFiltersView(state = state)
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ class HomeSpacesPresenter(
|
||||||
val canCreateSpaces by featureFlagsService.isFeatureEnabledFlow(FeatureFlags.CreateSpaces).collectAsState(false)
|
val canCreateSpaces by featureFlagsService.isFeatureEnabledFlow(FeatureFlags.CreateSpaces).collectAsState(false)
|
||||||
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
||||||
val spaceRooms by remember {
|
val spaceRooms by remember {
|
||||||
client.spaceService.spaceRoomsFlow.map { it.toImmutableList() }
|
client.spaceService.topLevelSpacesFlow.map { it.toImmutableList() }
|
||||||
}.collectAsState(persistentListOf())
|
}.collectAsState(persistentListOf())
|
||||||
|
|
||||||
val seenSpaceInvites by remember {
|
val seenSpaceInvites by remember {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ Nemáte žádné nepřečtené zprávy!"</string>
|
||||||
<string name="screen_roomlist_mark_as_read">"Označit jako přečtené"</string>
|
<string name="screen_roomlist_mark_as_read">"Označit jako přečtené"</string>
|
||||||
<string name="screen_roomlist_mark_as_unread">"Označit jako nepřečtené"</string>
|
<string name="screen_roomlist_mark_as_unread">"Označit jako nepřečtené"</string>
|
||||||
<string name="screen_roomlist_tombstoned_room_description">"Tato místnost byla aktualizována"</string>
|
<string name="screen_roomlist_tombstoned_room_description">"Tato místnost byla aktualizována"</string>
|
||||||
|
<string name="screen_roomlist_your_spaces">"Vaše prostory"</string>
|
||||||
<string name="session_verification_banner_message">"Zdá se, že používáte nové zařízení. Ověřte přihlášení, abyste měli přístup k zašifrovaným zprávám."</string>
|
<string name="session_verification_banner_message">"Zdá se, že používáte nové zařízení. Ověřte přihlášení, abyste měli přístup k zašifrovaným zprávám."</string>
|
||||||
<string name="session_verification_banner_title">"Ověřte, že jste to vy"</string>
|
<string name="session_verification_banner_title">"Ověřte, že jste to vy"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@ class RoomListFiltersEmptyStateResourcesTest {
|
||||||
@Test
|
@Test
|
||||||
fun `fromSelectedFilters should return null when selectedFilters is empty`() {
|
fun `fromSelectedFilters should return null when selectedFilters is empty`() {
|
||||||
val selectedFilters = emptyList<RoomListFilter>()
|
val selectedFilters = emptyList<RoomListFilter>()
|
||||||
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters)
|
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
|
||||||
assertThat(result).isNull()
|
assertThat(result).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only unread filter`() {
|
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only unread filter`() {
|
||||||
val selectedFilters = listOf(RoomListFilter.Unread)
|
val selectedFilters = listOf(RoomListFilter.Unread)
|
||||||
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters)
|
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
|
||||||
assertThat(result).isNotNull()
|
assertThat(result).isNotNull()
|
||||||
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_unreads_empty_state_title)
|
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_unreads_empty_state_title)
|
||||||
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
||||||
|
|
@ -32,7 +32,7 @@ class RoomListFiltersEmptyStateResourcesTest {
|
||||||
@Test
|
@Test
|
||||||
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only people filter`() {
|
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only people filter`() {
|
||||||
val selectedFilters = listOf(RoomListFilter.People)
|
val selectedFilters = listOf(RoomListFilter.People)
|
||||||
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters)
|
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
|
||||||
assertThat(result).isNotNull()
|
assertThat(result).isNotNull()
|
||||||
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_people_empty_state_title)
|
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_people_empty_state_title)
|
||||||
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
||||||
|
|
@ -41,7 +41,7 @@ class RoomListFiltersEmptyStateResourcesTest {
|
||||||
@Test
|
@Test
|
||||||
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only rooms filter`() {
|
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only rooms filter`() {
|
||||||
val selectedFilters = listOf(RoomListFilter.Rooms)
|
val selectedFilters = listOf(RoomListFilter.Rooms)
|
||||||
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters)
|
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
|
||||||
assertThat(result).isNotNull()
|
assertThat(result).isNotNull()
|
||||||
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_rooms_empty_state_title)
|
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_rooms_empty_state_title)
|
||||||
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
||||||
|
|
@ -50,7 +50,7 @@ class RoomListFiltersEmptyStateResourcesTest {
|
||||||
@Test
|
@Test
|
||||||
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only favourites filter`() {
|
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only favourites filter`() {
|
||||||
val selectedFilters = listOf(RoomListFilter.Favourites)
|
val selectedFilters = listOf(RoomListFilter.Favourites)
|
||||||
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters)
|
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
|
||||||
assertThat(result).isNotNull()
|
assertThat(result).isNotNull()
|
||||||
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_favourites_empty_state_title)
|
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_favourites_empty_state_title)
|
||||||
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_favourites_empty_state_subtitle)
|
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_favourites_empty_state_subtitle)
|
||||||
|
|
@ -59,7 +59,7 @@ class RoomListFiltersEmptyStateResourcesTest {
|
||||||
@Test
|
@Test
|
||||||
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only invites filter`() {
|
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has only invites filter`() {
|
||||||
val selectedFilters = listOf(RoomListFilter.Invites)
|
val selectedFilters = listOf(RoomListFilter.Invites)
|
||||||
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters)
|
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
|
||||||
assertThat(result).isNotNull()
|
assertThat(result).isNotNull()
|
||||||
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_invites_empty_state_title)
|
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_invites_empty_state_title)
|
||||||
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
||||||
|
|
@ -68,7 +68,15 @@ class RoomListFiltersEmptyStateResourcesTest {
|
||||||
@Test
|
@Test
|
||||||
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has multiple filters`() {
|
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when selectedFilters has multiple filters`() {
|
||||||
val selectedFilters = listOf(RoomListFilter.Unread, RoomListFilter.People, RoomListFilter.Rooms, RoomListFilter.Favourites)
|
val selectedFilters = listOf(RoomListFilter.Unread, RoomListFilter.People, RoomListFilter.Rooms, RoomListFilter.Favourites)
|
||||||
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters)
|
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(selectedFilters, isSpaceFilterSelected = false)
|
||||||
|
assertThat(result).isNotNull()
|
||||||
|
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_title)
|
||||||
|
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `fromSelectedFilters should return exact RoomListFiltersEmptyStateResources when isSpaceFilterSelected is true`() {
|
||||||
|
val result = RoomListFiltersEmptyStateResources.fromSelectedFilters(emptyList(), isSpaceFilterSelected = true)
|
||||||
assertThat(result).isNotNull()
|
assertThat(result).isNotNull()
|
||||||
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_title)
|
assertThat(result?.title).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_title)
|
||||||
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
assertThat(result?.subtitle).isEqualTo(R.string.screen_roomlist_filter_mixed_empty_state_subtitle)
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,10 @@
|
||||||
package io.element.android.features.home.impl.filters
|
package io.element.android.features.home.impl.filters
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.home.impl.FakeDateTimeObserver
|
|
||||||
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
|
||||||
import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFactory
|
|
||||||
import io.element.android.features.home.impl.filters.selection.DefaultFilterSelectionStrategy
|
import io.element.android.features.home.impl.filters.selection.DefaultFilterSelectionStrategy
|
||||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
|
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
|
||||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
|
|
||||||
import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter
|
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
|
||||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
|
||||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
|
||||||
import io.element.android.tests.testutils.awaitLastSequentialItem
|
import io.element.android.tests.testutils.awaitLastSequentialItem
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.advanceUntilIdle
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
|
|
@ -54,8 +41,7 @@ class RoomListFiltersPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun `present - toggle rooms filter`() = runTest {
|
fun `present - toggle rooms filter`() = runTest {
|
||||||
val roomListService = FakeRoomListService()
|
val presenter = createRoomListFiltersPresenter()
|
||||||
val presenter = createRoomListFiltersPresenter(roomListService)
|
|
||||||
presenter.test {
|
presenter.test {
|
||||||
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
||||||
awaitLastSequentialItem().let { state ->
|
awaitLastSequentialItem().let { state ->
|
||||||
|
|
@ -89,8 +75,7 @@ class RoomListFiltersPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun `present - clear filters event`() = runTest {
|
fun `present - clear filters event`() = runTest {
|
||||||
val roomListService = FakeRoomListService()
|
val presenter = createRoomListFiltersPresenter()
|
||||||
val presenter = createRoomListFiltersPresenter(roomListService)
|
|
||||||
presenter.test {
|
presenter.test {
|
||||||
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
||||||
awaitLastSequentialItem().let { state ->
|
awaitLastSequentialItem().let { state ->
|
||||||
|
|
@ -110,25 +95,8 @@ private fun filterSelectionState(filter: RoomListFilter, selected: Boolean) = Fi
|
||||||
isSelected = selected,
|
isSelected = selected,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun TestScope.createRoomListFiltersPresenter(
|
private fun TestScope.createRoomListFiltersPresenter(): RoomListFiltersPresenter {
|
||||||
roomListService: RoomListService = FakeRoomListService(),
|
|
||||||
notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
|
|
||||||
dateFormatter: DateFormatter = FakeDateFormatter(),
|
|
||||||
roomLatestEventFormatter: RoomLatestEventFormatter = FakeRoomLatestEventFormatter(),
|
|
||||||
): RoomListFiltersPresenter {
|
|
||||||
return RoomListFiltersPresenter(
|
return RoomListFiltersPresenter(
|
||||||
roomListDataSource = RoomListDataSource(
|
|
||||||
roomListService = roomListService,
|
|
||||||
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
|
||||||
dateFormatter = dateFormatter,
|
|
||||||
roomLatestEventFormatter = roomLatestEventFormatter,
|
|
||||||
),
|
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
|
||||||
notificationSettingsService = notificationSettingsService,
|
|
||||||
sessionCoroutineScope = backgroundScope,
|
|
||||||
dateTimeObserver = FakeDateTimeObserver(),
|
|
||||||
analyticsService = FakeAnalyticsService(),
|
|
||||||
),
|
|
||||||
filterSelectionStrategy = DefaultFilterSelectionStrategy(),
|
filterSelectionStrategy = DefaultFilterSelectionStrategy(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import io.element.android.features.home.impl.model.createRoomListRoomSummary
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchEvent
|
import io.element.android.features.home.impl.search.RoomListSearchEvent
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchState
|
import io.element.android.features.home.impl.search.RoomListSearchState
|
||||||
import io.element.android.features.home.impl.search.aRoomListSearchState
|
import io.element.android.features.home.impl.search.aRoomListSearchState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
|
import io.element.android.features.home.impl.spacefilters.aDisabledSpaceFiltersState
|
||||||
import io.element.android.features.invite.api.SeenInvitesStore
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||||
|
|
@ -660,6 +662,7 @@ class RoomListPresenterTest {
|
||||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||||
filtersPresenter: Presenter<RoomListFiltersState> = Presenter { aRoomListFiltersState() },
|
filtersPresenter: Presenter<RoomListFiltersState> = Presenter { aRoomListFiltersState() },
|
||||||
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
||||||
|
spaceFiltersPresenter: Presenter<SpaceFiltersState> = Presenter { aDisabledSpaceFiltersState() },
|
||||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
|
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
|
||||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||||
|
|
@ -683,6 +686,7 @@ class RoomListPresenterTest {
|
||||||
searchPresenter = searchPresenter,
|
searchPresenter = searchPresenter,
|
||||||
sessionPreferencesStore = sessionPreferencesStore,
|
sessionPreferencesStore = sessionPreferencesStore,
|
||||||
filtersPresenter = filtersPresenter,
|
filtersPresenter = filtersPresenter,
|
||||||
|
spaceFiltersPresenter = spaceFiltersPresenter,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||||
fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() },
|
fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,313 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations 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.spacefilters
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
|
||||||
|
import io.element.android.tests.testutils.awaitLastSequentialItem
|
||||||
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class SpaceFiltersPresenterTest {
|
||||||
|
@Test
|
||||||
|
fun `present - when feature flag is disabled returns Disabled state`() = runTest {
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state).isEqualTo(SpaceFiltersState.Disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - when available filters is empty returns Disabled state`() = runTest {
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitLastSequentialItem()
|
||||||
|
assertThat(state).isEqualTo(SpaceFiltersState.Disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - when feature flag is enabled and filters exist returns Unselected state`() = runTest {
|
||||||
|
val spaceFilter = aSpaceServiceFilter(displayName = "Test Space")
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Emit filters
|
||||||
|
spaceService.emitSpaceFilters(listOf(spaceFilter))
|
||||||
|
|
||||||
|
val state = awaitLastSequentialItem()
|
||||||
|
assertThat(state).isInstanceOf(SpaceFiltersState.Unselected::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - ShowFilters event transitions from Unselected to Selecting`() = runTest {
|
||||||
|
val spaceFilter = aSpaceServiceFilter(displayName = "Test Space")
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Emit filters first
|
||||||
|
spaceService.emitSpaceFilters(listOf(spaceFilter))
|
||||||
|
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
val selectingState = awaitLastSequentialItem()
|
||||||
|
assertThat(selectingState).isInstanceOf(SpaceFiltersState.Selecting::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - Cancel event in Selecting state transitions back to Unselected`() = runTest {
|
||||||
|
val spaceFilter = aSpaceServiceFilter(displayName = "Test Space")
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Emit filters first
|
||||||
|
spaceService.emitSpaceFilters(listOf(spaceFilter))
|
||||||
|
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Now in Selecting
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.Cancel)
|
||||||
|
|
||||||
|
// Back to Unselected
|
||||||
|
val finalState = awaitLastSequentialItem()
|
||||||
|
assertThat(finalState).isInstanceOf(SpaceFiltersState.Unselected::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - SelectFilter event in Selecting state transitions to Selected`() = runTest {
|
||||||
|
val spaceFilter = aSpaceServiceFilter(displayName = "Test Space")
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Emit filters first
|
||||||
|
spaceService.emitSpaceFilters(listOf(spaceFilter))
|
||||||
|
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Now in Selecting
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(spaceFilter))
|
||||||
|
|
||||||
|
// Now in Selected
|
||||||
|
val selectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
assertThat(selectedState.selectedFilter).isEqualTo(spaceFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - ClearSelection event in Selected state transitions back to Unselected`() = runTest {
|
||||||
|
val spaceFilter = aSpaceServiceFilter(displayName = "Test Space")
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Emit filters first
|
||||||
|
spaceService.emitSpaceFilters(listOf(spaceFilter))
|
||||||
|
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Now in Selecting
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(spaceFilter))
|
||||||
|
|
||||||
|
// Now in Selected
|
||||||
|
val selectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
selectedState.eventSink(SpaceFiltersEvent.Selected.ClearSelection)
|
||||||
|
|
||||||
|
// Back to Unselected
|
||||||
|
val finalState = awaitLastSequentialItem()
|
||||||
|
assertThat(finalState).isInstanceOf(SpaceFiltersState.Unselected::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - available filters are passed from SpaceService`() = runTest {
|
||||||
|
val spaceFilter1 = aSpaceServiceFilter(displayName = "Work", roomId = RoomId("!work:example.com"))
|
||||||
|
val spaceFilter2 = aSpaceServiceFilter(displayName = "Personal", roomId = RoomId("!personal:example.com"))
|
||||||
|
val spaceFilters = listOf(spaceFilter1, spaceFilter2)
|
||||||
|
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Emit space filters
|
||||||
|
spaceService.emitSpaceFilters(spaceFilters)
|
||||||
|
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Now in Selecting with available filters
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
assertThat(selectingState.availableFilters).containsExactly(spaceFilter1, spaceFilter2).inOrder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - selected filter is cleared when space is removed from available filters`() = runTest {
|
||||||
|
val spaceFilter = aSpaceServiceFilter(displayName = "Work", roomId = RoomId("!work:example.com"))
|
||||||
|
val otherSpaceFilter = aSpaceServiceFilter(displayName = "Personal", roomId = RoomId("!personal:example.com"))
|
||||||
|
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Emit filters first
|
||||||
|
spaceService.emitSpaceFilters(listOf(spaceFilter, otherSpaceFilter))
|
||||||
|
|
||||||
|
// Go to Selecting
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Select the filter
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(spaceFilter))
|
||||||
|
|
||||||
|
// Verify in Selected state
|
||||||
|
val selectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
assertThat(selectedState.selectedFilter).isEqualTo(spaceFilter)
|
||||||
|
|
||||||
|
// Remove the selected space from available filters (but keep other spaces)
|
||||||
|
spaceService.emitSpaceFilters(listOf(otherSpaceFilter))
|
||||||
|
|
||||||
|
// Should auto-transition to Unselected
|
||||||
|
val finalState = awaitLastSequentialItem()
|
||||||
|
assertThat(finalState).isInstanceOf(SpaceFiltersState.Unselected::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - selected filter stays in sync when available filters update`() = runTest {
|
||||||
|
val originalFilter = aSpaceServiceFilter(
|
||||||
|
displayName = "Work",
|
||||||
|
roomId = RoomId("!work:example.com"),
|
||||||
|
descendants = listOf(RoomId("!room1:example.com"))
|
||||||
|
)
|
||||||
|
val updatedFilter = aSpaceServiceFilter(
|
||||||
|
displayName = "Work",
|
||||||
|
roomId = RoomId("!work:example.com"),
|
||||||
|
descendants = listOf(RoomId("!room1:example.com"), RoomId("!room2:example.com"))
|
||||||
|
)
|
||||||
|
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Emit initial space filters
|
||||||
|
spaceService.emitSpaceFilters(listOf(originalFilter))
|
||||||
|
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Now in Selecting
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(originalFilter))
|
||||||
|
|
||||||
|
// Now in Selected
|
||||||
|
val selectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
assertThat(selectedState.selectedFilter.descendants).hasSize(1)
|
||||||
|
|
||||||
|
// Emit updated space filters
|
||||||
|
spaceService.emitSpaceFilters(listOf(updatedFilter))
|
||||||
|
|
||||||
|
// Selected filter should be updated
|
||||||
|
val updatedSelectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
assertThat(updatedSelectedState.selectedFilter.descendants).hasSize(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSpaceFiltersPresenter(
|
||||||
|
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
|
||||||
|
matrixClient: FakeMatrixClient = FakeMatrixClient(),
|
||||||
|
): SpaceFiltersPresenter {
|
||||||
|
return SpaceFiltersPresenter(
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations 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.spacefilters
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class SpaceFiltersViewTest {
|
||||||
|
@get:Rule
|
||||||
|
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on a filter with alias shows display name and alias`() {
|
||||||
|
val filter = aSpaceServiceFilter(
|
||||||
|
displayName = "Test Space",
|
||||||
|
canonicalAlias = A_ROOM_ALIAS,
|
||||||
|
)
|
||||||
|
val eventsRecorder = EventsRecorder<SpaceFiltersEvent.Selecting>()
|
||||||
|
rule.setSpaceFiltersView(
|
||||||
|
state = aSelectingSpaceFiltersState(
|
||||||
|
availableFilters = listOf(filter),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Both display name and alias should be visible
|
||||||
|
rule.onNodeWithText(filter.spaceRoom.displayName).assertExists()
|
||||||
|
rule.onNodeWithText(A_ROOM_ALIAS.value).assertExists()
|
||||||
|
|
||||||
|
rule.onNodeWithText(filter.spaceRoom.displayName).performClick()
|
||||||
|
|
||||||
|
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multiple filters are displayed and clickable`() {
|
||||||
|
val filter1 = aSpaceServiceFilter(displayName = "Space One")
|
||||||
|
val filter2 = aSpaceServiceFilter(displayName = "Space Two")
|
||||||
|
val eventsRecorder = EventsRecorder<SpaceFiltersEvent.Selecting>()
|
||||||
|
rule.setSpaceFiltersView(
|
||||||
|
state = aSelectingSpaceFiltersState(
|
||||||
|
availableFilters = listOf(filter1, filter2),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Both filters should be visible
|
||||||
|
rule.onNodeWithText(filter1.spaceRoom.displayName).assertExists()
|
||||||
|
rule.onNodeWithText(filter2.spaceRoom.displayName).assertExists()
|
||||||
|
|
||||||
|
// Click on second filter
|
||||||
|
rule.onNodeWithText(filter2.spaceRoom.displayName).performClick()
|
||||||
|
|
||||||
|
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceFiltersView(
|
||||||
|
state: SpaceFiltersState,
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
SpaceFiltersView(state = state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,16 +1,33 @@
|
||||||
<?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_link_new_device_desktop_scanning_title">"Skann QR-koden"</string>
|
<string name="screen_link_new_device_desktop_scanning_title">"Skann QR-koden"</string>
|
||||||
|
<string name="screen_link_new_device_desktop_step1">"Åpne %1$s på en bærbar eller stasjonær datamaskin"</string>
|
||||||
<string name="screen_link_new_device_desktop_step3">"Skann QR-koden med denne enheten"</string>
|
<string name="screen_link_new_device_desktop_step3">"Skann QR-koden med denne enheten"</string>
|
||||||
<string name="screen_link_new_device_desktop_submit">"Klar til å skanne"</string>
|
<string name="screen_link_new_device_desktop_submit">"Klar til å skanne"</string>
|
||||||
|
<string name="screen_link_new_device_desktop_title">"Åpne %1$s på en datamaskin for å få QR-koden"</string>
|
||||||
|
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"Tallene stemmer ikke overens"</string>
|
||||||
|
<string name="screen_link_new_device_enter_number_notice">"Skriv inn 2-sifret kode"</string>
|
||||||
|
<string name="screen_link_new_device_enter_number_subtitle">"Dette vil bekrefte at forbindelsen til den andre enheten din er sikker."</string>
|
||||||
|
<string name="screen_link_new_device_enter_number_title">"Skriv inn nummeret som vises på den andre enheten din"</string>
|
||||||
<string name="screen_link_new_device_error_app_not_supported_subtitle">"Kontotilbyderen din støtter ikke %1$s."</string>
|
<string name="screen_link_new_device_error_app_not_supported_subtitle">"Kontotilbyderen din støtter ikke %1$s."</string>
|
||||||
<string name="screen_link_new_device_error_app_not_supported_title">"%1$s støttes ikke"</string>
|
<string name="screen_link_new_device_error_app_not_supported_title">"%1$s støttes ikke"</string>
|
||||||
|
<string name="screen_link_new_device_error_not_supported_subtitle">"Din kontoleverandør støtter ikke pålogging på en ny enhet med QR-kode."</string>
|
||||||
<string name="screen_link_new_device_error_not_supported_title">"QR-kode støttes ikke"</string>
|
<string name="screen_link_new_device_error_not_supported_title">"QR-kode støttes ikke"</string>
|
||||||
<string name="screen_link_new_device_error_request_cancelled_subtitle">"Påloggingen ble kansellert på den andre enheten."</string>
|
<string name="screen_link_new_device_error_request_cancelled_subtitle">"Påloggingen ble kansellert på den andre enheten."</string>
|
||||||
<string name="screen_link_new_device_error_request_cancelled_title">"Påloggingsforespørsel kansellert"</string>
|
<string name="screen_link_new_device_error_request_cancelled_title">"Påloggingsforespørsel kansellert"</string>
|
||||||
<string name="screen_link_new_device_error_request_timeout_subtitle">"Påloggingen er utløpt. Vennligst prøv igjen."</string>
|
<string name="screen_link_new_device_error_request_timeout_subtitle">"Påloggingen er utløpt. Vennligst prøv igjen."</string>
|
||||||
<string name="screen_link_new_device_error_request_timeout_title">"Påloggingen ble ikke fullført i tide"</string>
|
<string name="screen_link_new_device_error_request_timeout_title">"Påloggingen ble ikke fullført i tide"</string>
|
||||||
|
<string name="screen_link_new_device_mobile_step1">"Åpne %1$s på den andre enheten"</string>
|
||||||
<string name="screen_link_new_device_mobile_step2">"Velg %1$s"</string>
|
<string name="screen_link_new_device_mobile_step2">"Velg %1$s"</string>
|
||||||
|
<string name="screen_link_new_device_mobile_step2_action">"Logg inn med QR-kode"</string>
|
||||||
|
<string name="screen_link_new_device_mobile_step3">"Skann QR-koden som vises her med den andre enheten"</string>
|
||||||
|
<string name="screen_link_new_device_mobile_title">"Åpne %1$s på den andre enheten"</string>
|
||||||
|
<string name="screen_link_new_device_root_desktop_computer">"Datamaskin"</string>
|
||||||
|
<string name="screen_link_new_device_root_loading_qr_code">"Laster QR-kode…"</string>
|
||||||
|
<string name="screen_link_new_device_root_mobile_device">"Mobil enhet"</string>
|
||||||
|
<string name="screen_link_new_device_root_title">"Hvilken type enhet ønsker du å koble til?"</string>
|
||||||
|
<string name="screen_link_new_device_wrong_number_subtitle">"Prøv igjen og påse at du har tastet inn den tosifrede koden riktig. Hvis tallene fortsatt ikke stemmer, må du kontakte kontoleverandøren din."</string>
|
||||||
|
<string name="screen_link_new_device_wrong_number_title">"Tallene stemmer ikke overens"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"En sikker tilkobling kunne ikke opprettes til den nye enheten. Dine eksisterende enheter er fortsatt trygge, og du trenger ikke å bekymre deg for dem."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"En sikker tilkobling kunne ikke opprettes til den nye enheten. Dine eksisterende enheter er fortsatt trygge, og du trenger ikke å bekymre deg for dem."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Hva nå?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Hva nå?"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Prøv å logge på igjen med en QR-kode i tilfelle dette var et nettverksproblem"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Prøv å logge på igjen med en QR-kode i tilfelle dette var et nettverksproblem"</string>
|
||||||
|
|
@ -21,6 +38,8 @@
|
||||||
<string name="screen_qr_code_login_error_cancelled_title">"Påloggingsforespørsel kansellert"</string>
|
<string name="screen_qr_code_login_error_cancelled_title">"Påloggingsforespørsel kansellert"</string>
|
||||||
<string name="screen_qr_code_login_error_declined_subtitle">"Påloggingen ble avvist på den andre enheten."</string>
|
<string name="screen_qr_code_login_error_declined_subtitle">"Påloggingen ble avvist på den andre enheten."</string>
|
||||||
<string name="screen_qr_code_login_error_declined_title">"Pålogging avslått"</string>
|
<string name="screen_qr_code_login_error_declined_title">"Pålogging avslått"</string>
|
||||||
|
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Du trenger ikke å gjøre noe annet."</string>
|
||||||
|
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Din andre enhet er allerede logget inn"</string>
|
||||||
<string name="screen_qr_code_login_error_expired_subtitle">"Påloggingen er utløpt. Vennligst prøv igjen."</string>
|
<string name="screen_qr_code_login_error_expired_subtitle">"Påloggingen er utløpt. Vennligst prøv igjen."</string>
|
||||||
<string name="screen_qr_code_login_error_expired_title">"Påloggingen ble ikke fullført i tide"</string>
|
<string name="screen_qr_code_login_error_expired_title">"Påloggingen ble ikke fullført i tide"</string>
|
||||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Den andre enheten din støtter ikke pålogging på %s med en QR-kode.
|
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Den andre enheten din støtter ikke pålogging på %s med en QR-kode.
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,8 @@ class QrCodeLoginFlowNode(
|
||||||
is QrLoginException.Cancelled -> {
|
is QrLoginException.Cancelled -> {
|
||||||
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Cancelled))
|
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Cancelled))
|
||||||
}
|
}
|
||||||
is QrLoginException.Expired -> {
|
is QrLoginException.Expired,
|
||||||
|
is QrLoginException.NotFound -> {
|
||||||
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Expired))
|
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.Expired))
|
||||||
}
|
}
|
||||||
is QrLoginException.Declined -> {
|
is QrLoginException.Declined -> {
|
||||||
|
|
@ -138,7 +139,9 @@ class QrCodeLoginFlowNode(
|
||||||
Timber.e(error, "OIDC metadata is invalid")
|
Timber.e(error, "OIDC metadata is invalid")
|
||||||
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError))
|
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError))
|
||||||
}
|
}
|
||||||
else -> {
|
QrLoginException.CheckCodeAlreadySent,
|
||||||
|
QrLoginException.CheckCodeCannotBeSent,
|
||||||
|
QrLoginException.Unknown -> {
|
||||||
Timber.e(error, "Unknown error found")
|
Timber.e(error, "Unknown error found")
|
||||||
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError))
|
backstack.replace(NavTarget.Error(QrCodeErrorScreenType.UnknownError))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@
|
||||||
<string name="screen_qr_code_login_error_cancelled_title">"Påloggingsforespørsel kansellert"</string>
|
<string name="screen_qr_code_login_error_cancelled_title">"Påloggingsforespørsel kansellert"</string>
|
||||||
<string name="screen_qr_code_login_error_declined_subtitle">"Påloggingen ble avvist på den andre enheten."</string>
|
<string name="screen_qr_code_login_error_declined_subtitle">"Påloggingen ble avvist på den andre enheten."</string>
|
||||||
<string name="screen_qr_code_login_error_declined_title">"Pålogging avslått"</string>
|
<string name="screen_qr_code_login_error_declined_title">"Pålogging avslått"</string>
|
||||||
|
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Du trenger ikke å gjøre noe annet."</string>
|
||||||
|
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Din andre enhet er allerede logget inn"</string>
|
||||||
<string name="screen_qr_code_login_error_expired_subtitle">"Påloggingen er utløpt. Vennligst prøv igjen."</string>
|
<string name="screen_qr_code_login_error_expired_subtitle">"Påloggingen er utløpt. Vennligst prøv igjen."</string>
|
||||||
<string name="screen_qr_code_login_error_expired_title">"Påloggingen ble ikke fullført i tide"</string>
|
<string name="screen_qr_code_login_error_expired_title">"Påloggingen ble ikke fullført i tide"</string>
|
||||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Den andre enheten din støtter ikke pålogging på %s med en QR-kode.
|
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Den andre enheten din støtter ikke pålogging på %s med en QR-kode.
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,9 @@ class QrCodeLoginFlowNodeTest {
|
||||||
qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Expired)
|
qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Expired)
|
||||||
assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.Expired))
|
assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.Expired))
|
||||||
|
|
||||||
|
qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.NotFound)
|
||||||
|
assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.Expired))
|
||||||
|
|
||||||
qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Declined)
|
qrCodeLoginManager.currentLoginStep.value = QrCodeLoginStep.Failed(QrLoginException.Declined)
|
||||||
assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.Declined))
|
assertThat(flowNode.currentNavTarget()).isEqualTo(QrCodeLoginFlowNode.NavTarget.Error(QrCodeErrorScreenType.Declined))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
|
@ -69,6 +70,7 @@ class DefaultVoiceMessageComposerPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private val permissionsPresenter = permissionsPresenterFactory.create(Manifest.permission.RECORD_AUDIO)
|
private val permissionsPresenter = permissionsPresenterFactory.create(Manifest.permission.RECORD_AUDIO)
|
||||||
|
private var pendingEvent: VoiceMessageRecorderEvent.Start? = null
|
||||||
private val mediaSender = mediaSenderFactory.create(timelineMode)
|
private val mediaSender = mediaSenderFactory.create(timelineMode)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -77,8 +79,7 @@ class DefaultVoiceMessageComposerPresenter(
|
||||||
val recorderState by voiceRecorder.state.collectAsState(initial = VoiceRecorderState.Idle)
|
val recorderState by voiceRecorder.state.collectAsState(initial = VoiceRecorderState.Idle)
|
||||||
val playerState by player.state.collectAsState(initial = VoiceMessageComposerPlayer.State.Initial)
|
val playerState by player.state.collectAsState(initial = VoiceMessageComposerPlayer.State.Initial)
|
||||||
val keepScreenOn by remember { derivedStateOf { recorderState is VoiceRecorderState.Recording } }
|
val keepScreenOn by remember { derivedStateOf { recorderState is VoiceRecorderState.Recording } }
|
||||||
|
val permissionState by rememberUpdatedState(permissionsPresenter.present())
|
||||||
val permissionState = permissionsPresenter.present()
|
|
||||||
var isSending by remember { mutableStateOf(false) }
|
var isSending by remember { mutableStateOf(false) }
|
||||||
var showSendFailureDialog by remember { mutableStateOf(false) }
|
var showSendFailureDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
@ -88,6 +89,15 @@ class DefaultVoiceMessageComposerPresenter(
|
||||||
player.setMedia(recording.file.path)
|
player.setMedia(recording.file.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(permissionState.permissionGranted) {
|
||||||
|
if (permissionState.permissionGranted) {
|
||||||
|
pendingEvent?.let {
|
||||||
|
localCoroutineScope.startRecording()
|
||||||
|
pendingEvent = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleLifecycleEvent(event: Lifecycle.Event) {
|
fun handleLifecycleEvent(event: Lifecycle.Event) {
|
||||||
when (event) {
|
when (event) {
|
||||||
Lifecycle.Event.ON_PAUSE -> {
|
Lifecycle.Event.ON_PAUSE -> {
|
||||||
|
|
@ -102,6 +112,7 @@ class DefaultVoiceMessageComposerPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleVoiceMessageRecorderEvent(event: VoiceMessageRecorderEvent) {
|
fun handleVoiceMessageRecorderEvent(event: VoiceMessageRecorderEvent) {
|
||||||
|
pendingEvent = null
|
||||||
when (event) {
|
when (event) {
|
||||||
VoiceMessageRecorderEvent.Start -> {
|
VoiceMessageRecorderEvent.Start -> {
|
||||||
Timber.v("Voice message record button pressed")
|
Timber.v("Voice message record button pressed")
|
||||||
|
|
@ -111,6 +122,7 @@ class DefaultVoiceMessageComposerPresenter(
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Timber.i("Voice message permission needed")
|
Timber.i("Voice message permission needed")
|
||||||
|
pendingEvent = VoiceMessageRecorderEvent.Start
|
||||||
permissionState.eventSink(PermissionsEvent.RequestPermissions)
|
permissionState.eventSink(PermissionsEvent.RequestPermissions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -522,7 +522,9 @@ class DefaultVoiceMessageComposerPresenterTest {
|
||||||
permissionsPresenter.setPermissionGranted()
|
permissionsPresenter.setPermissionGranted()
|
||||||
|
|
||||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
||||||
val finalState = awaitItem()
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
val finalState = expectMostRecentItem()
|
||||||
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
|
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
|
||||||
voiceRecorder.assertCalls(stopped = 1, started = 1)
|
voiceRecorder.assertCalls(stopped = 1, started = 1)
|
||||||
|
|
||||||
|
|
@ -547,14 +549,16 @@ class DefaultVoiceMessageComposerPresenterTest {
|
||||||
assertThat(it.showPermissionRationaleDialog).isTrue()
|
assertThat(it.showPermissionRationaleDialog).isTrue()
|
||||||
it.eventSink(VoiceMessageComposerEvent.AcceptPermissionRationale)
|
it.eventSink(VoiceMessageComposerEvent.AcceptPermissionRationale)
|
||||||
}
|
}
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
// Dialog is hidden, user accepts permissions
|
// Dialog is hidden, user accepts permissions
|
||||||
assertThat(awaitItem().showPermissionRationaleDialog).isFalse()
|
assertThat(awaitItem().showPermissionRationaleDialog).isFalse()
|
||||||
|
|
||||||
|
// Permission is granted, recording starts automatically
|
||||||
permissionsPresenter.setPermissionGranted()
|
permissionsPresenter.setPermissionGranted()
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
val finalState = expectMostRecentItem()
|
||||||
val finalState = awaitItem()
|
|
||||||
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
|
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
|
||||||
voiceRecorder.assertCalls(started = 1)
|
voiceRecorder.assertCalls(started = 1)
|
||||||
|
|
||||||
|
|
@ -579,12 +583,14 @@ class DefaultVoiceMessageComposerPresenterTest {
|
||||||
assertThat(it.showPermissionRationaleDialog).isTrue()
|
assertThat(it.showPermissionRationaleDialog).isTrue()
|
||||||
it.eventSink(VoiceMessageComposerEvent.DismissPermissionsRationale)
|
it.eventSink(VoiceMessageComposerEvent.DismissPermissionsRationale)
|
||||||
}
|
}
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
// Dialog is hidden, user tries to record again
|
// Dialog is hidden, user tries to record again
|
||||||
awaitItem().also {
|
awaitItem().also {
|
||||||
assertThat(it.showPermissionRationaleDialog).isFalse()
|
assertThat(it.showPermissionRationaleDialog).isFalse()
|
||||||
it.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
it.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
||||||
}
|
}
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
// Dialog is shown once again
|
// Dialog is shown once again
|
||||||
val finalState = awaitItem().also {
|
val finalState = awaitItem().also {
|
||||||
|
|
@ -593,6 +599,7 @@ class DefaultVoiceMessageComposerPresenterTest {
|
||||||
}
|
}
|
||||||
voiceRecorder.assertCalls(started = 0)
|
voiceRecorder.assertCalls(started = 0)
|
||||||
|
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
testPauseAndDestroy(finalState)
|
testPauseAndDestroy(finalState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,12 @@
|
||||||
<string name="screen_room_change_role_unsaved_changes_description">"Du har endringer som ikke er lagret."</string>
|
<string name="screen_room_change_role_unsaved_changes_description">"Du har endringer som ikke er lagret."</string>
|
||||||
<string name="screen_room_change_role_unsaved_changes_title">"Lagre endringer?"</string>
|
<string name="screen_room_change_role_unsaved_changes_title">"Lagre endringer?"</string>
|
||||||
<string name="screen_room_member_list_banned_empty">"Det er ingen utestengte brukere."</string>
|
<string name="screen_room_member_list_banned_empty">"Det er ingen utestengte brukere."</string>
|
||||||
|
<plurals name="screen_room_member_list_banned_header_title">
|
||||||
|
<item quantity="one">"%1$d utestengt"</item>
|
||||||
|
<item quantity="other">"%1$d utestengt"</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="screen_room_member_list_empty_search_subtitle">"Sjekk stavemåten eller prøv et nytt søk"</string>
|
||||||
|
<string name="screen_room_member_list_empty_search_title">"Ingen resultater for \"%1$s\""</string>
|
||||||
<plurals name="screen_room_member_list_header_title">
|
<plurals name="screen_room_member_list_header_title">
|
||||||
<item quantity="one">"%1$d person"</item>
|
<item quantity="one">"%1$d person"</item>
|
||||||
<item quantity="other">"%1$d personer"</item>
|
<item quantity="other">"%1$d personer"</item>
|
||||||
|
|
@ -49,6 +55,11 @@
|
||||||
<string name="screen_room_member_list_manage_member_unban_title">"Fjern utestengelsen fra rommet"</string>
|
<string name="screen_room_member_list_manage_member_unban_title">"Fjern utestengelsen fra rommet"</string>
|
||||||
<string name="screen_room_member_list_mode_banned">"Utestengt"</string>
|
<string name="screen_room_member_list_mode_banned">"Utestengt"</string>
|
||||||
<string name="screen_room_member_list_mode_members">"Medlemmer"</string>
|
<string name="screen_room_member_list_mode_members">"Medlemmer"</string>
|
||||||
|
<plurals name="screen_room_member_list_pending_header_title">
|
||||||
|
<item quantity="one">"%1$d invitert"</item>
|
||||||
|
<item quantity="other">"%1$d invitert"</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="screen_room_member_list_pending_status">"Venter"</string>
|
||||||
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
||||||
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
||||||
<string name="screen_room_member_list_role_owner">"Eier"</string>
|
<string name="screen_room_member_list_role_owner">"Eier"</string>
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,8 @@ class RoomDetailsPresenter(
|
||||||
|
|
||||||
val canReportRoom by produceState(false) { value = client.canReportRoom() }
|
val canReportRoom by produceState(false) { value = client.canReportRoom() }
|
||||||
|
|
||||||
|
val enableKeyShareOnInvite by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false)
|
||||||
|
|
||||||
return RoomDetailsState(
|
return RoomDetailsState(
|
||||||
roomId = room.roomId,
|
roomId = room.roomId,
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
|
|
@ -197,6 +199,8 @@ class RoomDetailsPresenter(
|
||||||
isTombstoned = roomInfo.successorRoom != null,
|
isTombstoned = roomInfo.successorRoom != null,
|
||||||
showDebugInfo = isDeveloperModeEnabled,
|
showDebugInfo = isDeveloperModeEnabled,
|
||||||
roomVersion = roomInfo.roomVersion,
|
roomVersion = roomInfo.roomVersion,
|
||||||
|
enableKeyShareOnInvite = enableKeyShareOnInvite,
|
||||||
|
roomHistoryVisibility = roomInfo.historyVisibility,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
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.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -50,6 +51,8 @@ data class RoomDetailsState(
|
||||||
val isTombstoned: Boolean,
|
val isTombstoned: Boolean,
|
||||||
val showDebugInfo: Boolean,
|
val showDebugInfo: Boolean,
|
||||||
val roomVersion: String?,
|
val roomVersion: String?,
|
||||||
|
val enableKeyShareOnInvite: Boolean,
|
||||||
|
val roomHistoryVisibility: RoomHistoryVisibility,
|
||||||
val eventSink: (RoomDetailsEvent) -> Unit
|
val eventSink: (RoomDetailsEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val roomBadges = buildList {
|
val roomBadges = buildList {
|
||||||
|
|
@ -61,6 +64,14 @@ data class RoomDetailsState(
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
add(RoomBadge.PUBLIC)
|
add(RoomBadge.PUBLIC)
|
||||||
}
|
}
|
||||||
|
if (enableKeyShareOnInvite && isEncrypted) {
|
||||||
|
when (roomHistoryVisibility) {
|
||||||
|
RoomHistoryVisibility.Invited, RoomHistoryVisibility.Joined -> add(RoomBadge.SHARED_HISTORY_HIDDEN)
|
||||||
|
RoomHistoryVisibility.Shared -> add(RoomBadge.SHARED_HISTORY_SHARED)
|
||||||
|
RoomHistoryVisibility.WorldReadable -> add(RoomBadge.SHARED_HISTORY_WORLD_READABLE)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}.toImmutableList()
|
}.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,4 +95,7 @@ enum class RoomBadge {
|
||||||
ENCRYPTED,
|
ENCRYPTED,
|
||||||
NOT_ENCRYPTED,
|
NOT_ENCRYPTED,
|
||||||
PUBLIC,
|
PUBLIC,
|
||||||
|
SHARED_HISTORY_HIDDEN,
|
||||||
|
SHARED_HISTORY_SHARED,
|
||||||
|
SHARED_HISTORY_WORLD_READABLE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -57,6 +58,9 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
|
||||||
aRoomDetailsState(isTombstoned = true),
|
aRoomDetailsState(isTombstoned = true),
|
||||||
aDmRoomDetailsState(dmRoomMemberVerificationState = UserProfileVerificationState.VERIFIED),
|
aDmRoomDetailsState(dmRoomMemberVerificationState = UserProfileVerificationState.VERIFIED),
|
||||||
aDmRoomDetailsState(dmRoomMemberVerificationState = UserProfileVerificationState.VERIFICATION_VIOLATION),
|
aDmRoomDetailsState(dmRoomMemberVerificationState = UserProfileVerificationState.VERIFICATION_VIOLATION),
|
||||||
|
aSharedHistoryRoomDetailsState(roomHistoryVisibility = RoomHistoryVisibility.Joined),
|
||||||
|
aSharedHistoryRoomDetailsState(roomHistoryVisibility = RoomHistoryVisibility.Shared),
|
||||||
|
aSharedHistoryRoomDetailsState(roomHistoryVisibility = RoomHistoryVisibility.WorldReadable),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +121,8 @@ fun aRoomDetailsState(
|
||||||
canReportRoom: Boolean = true,
|
canReportRoom: Boolean = true,
|
||||||
isTombstoned: Boolean = false,
|
isTombstoned: Boolean = false,
|
||||||
showDebugInfo: Boolean = false,
|
showDebugInfo: Boolean = false,
|
||||||
|
enableKeyShareOnInvite: Boolean = false,
|
||||||
|
roomHistoryVisibility: RoomHistoryVisibility = RoomHistoryVisibility.Shared,
|
||||||
eventSink: (RoomDetailsEvent) -> Unit = {},
|
eventSink: (RoomDetailsEvent) -> Unit = {},
|
||||||
) = RoomDetailsState(
|
) = RoomDetailsState(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
|
@ -147,6 +153,8 @@ fun aRoomDetailsState(
|
||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
showDebugInfo = showDebugInfo,
|
showDebugInfo = showDebugInfo,
|
||||||
roomVersion = "12",
|
roomVersion = "12",
|
||||||
|
enableKeyShareOnInvite = enableKeyShareOnInvite,
|
||||||
|
roomHistoryVisibility = roomHistoryVisibility,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -182,3 +190,11 @@ fun aDmRoomDetailsState(
|
||||||
verificationState = dmRoomMemberVerificationState,
|
verificationState = dmRoomMemberVerificationState,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun aSharedHistoryRoomDetailsState(
|
||||||
|
roomHistoryVisibility: RoomHistoryVisibility
|
||||||
|
) = aRoomDetailsState(
|
||||||
|
isEncrypted = true,
|
||||||
|
enableKeyShareOnInvite = true,
|
||||||
|
roomHistoryVisibility = roomHistoryVisibility,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -518,6 +518,27 @@ private fun RoomBadge.toMatrixBadgeData(): MatrixBadgeAtom.MatrixBadgeData {
|
||||||
type = MatrixBadgeAtom.Type.Info,
|
type = MatrixBadgeAtom.Type.Info,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
RoomBadge.SHARED_HISTORY_HIDDEN -> {
|
||||||
|
MatrixBadgeAtom.MatrixBadgeData(
|
||||||
|
text = stringResource(R.string.crypto_history_sharing_room_info_hidden_badge_content),
|
||||||
|
icon = CompoundIcons.VisibilityOff(),
|
||||||
|
type = MatrixBadgeAtom.Type.Info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RoomBadge.SHARED_HISTORY_SHARED -> {
|
||||||
|
MatrixBadgeAtom.MatrixBadgeData(
|
||||||
|
text = stringResource(R.string.crypto_history_sharing_room_info_shared_badge_content),
|
||||||
|
icon = CompoundIcons.History(),
|
||||||
|
type = MatrixBadgeAtom.Type.Info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RoomBadge.SHARED_HISTORY_WORLD_READABLE -> {
|
||||||
|
MatrixBadgeAtom.MatrixBadgeData(
|
||||||
|
text = stringResource(R.string.crypto_history_sharing_room_info_world_readable_badge_content),
|
||||||
|
icon = CompoundIcons.UserProfileSolid(),
|
||||||
|
type = MatrixBadgeAtom.Type.Info
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ Nedoporučujeme povolovat šifrování pro místnosti, které může kdokoli naj
|
||||||
<string name="screen_security_and_privacy_encryption_section_header">"Šifrování"</string>
|
<string name="screen_security_and_privacy_encryption_section_header">"Šifrování"</string>
|
||||||
<string name="screen_security_and_privacy_encryption_toggle_title">"Povolit koncové šifrování"</string>
|
<string name="screen_security_and_privacy_encryption_toggle_title">"Povolit koncové šifrování"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Vstoupit může kdokoli."</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Vstoupit může kdokoli."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Kdokoliv"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Kdokoli"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_footer">"Vyberte, kteří členové prostorů mohou vstoupit do této místnosti bez pozvánky. %1$s"</string>
|
<string name="screen_security_and_privacy_room_access_footer">"Vyberte, kteří členové prostorů mohou vstoupit do této místnosti bez pozvánky. %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Spravovat prostory"</string>
|
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Spravovat prostory"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Vstoupit mohou pouze pozvaní lidé."</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Vstoupit mohou pouze pozvaní lidé."</string>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<?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="crypto_history_sharing_room_info_hidden_badge_content">"Uued liikmed ei näe ajalugu"</string>
|
||||||
|
<string name="crypto_history_sharing_room_info_shared_badge_content">"Uued liikmed näevad ajalugu"</string>
|
||||||
|
<string name="crypto_history_sharing_room_info_world_readable_badge_content">"Kõik võivad ajalugu näha"</string>
|
||||||
<string name="screen_edit_room_address_room_address_section_footer">"Selleks, et jututuba oleks nähtav jututubade avalikus kataloogis, vajab ta aadressi."</string>
|
<string name="screen_edit_room_address_room_address_section_footer">"Selleks, et jututuba oleks nähtav jututubade avalikus kataloogis, vajab ta aadressi."</string>
|
||||||
<string name="screen_edit_room_address_title">"Muuda aadressi"</string>
|
<string name="screen_edit_room_address_title">"Muuda aadressi"</string>
|
||||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Teavituste seadistamisel tekkis viga"</string>
|
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Teavituste seadistamisel tekkis viga"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<?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="crypto_history_sharing_room_info_hidden_badge_content">"Les nouveaux membres ne voient pas l’historique."</string>
|
||||||
|
<string name="crypto_history_sharing_room_info_shared_badge_content">"Les nouveaux membres voient l’historique"</string>
|
||||||
|
<string name="crypto_history_sharing_room_info_world_readable_badge_content">"Tout le monde voit l’historique"</string>
|
||||||
<string name="screen_edit_room_address_room_address_section_footer">"Vous aurez besoin d’une adresse pour le rendre visible dans l’annuaire public."</string>
|
<string name="screen_edit_room_address_room_address_section_footer">"Vous aurez besoin d’une adresse pour le rendre visible dans l’annuaire public."</string>
|
||||||
<string name="screen_edit_room_address_title">"Modifier l’adresse"</string>
|
<string name="screen_edit_room_address_title">"Modifier l’adresse"</string>
|
||||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Une erreur s’est produite lors de la mise à jour du paramètre de notification."</string>
|
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Une erreur s’est produite lors de la mise à jour du paramètre de notification."</string>
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@
|
||||||
<string name="screen_room_details_profile_row_title">"Profil"</string>
|
<string name="screen_room_details_profile_row_title">"Profil"</string>
|
||||||
<string name="screen_room_details_requests_to_join_title">"Forespørsler om å bli med"</string>
|
<string name="screen_room_details_requests_to_join_title">"Forespørsler om å bli med"</string>
|
||||||
<string name="screen_room_details_roles_and_permissions">"Roller og tillatelser"</string>
|
<string name="screen_room_details_roles_and_permissions">"Roller og tillatelser"</string>
|
||||||
|
<string name="screen_room_details_room_name_label">"Navn"</string>
|
||||||
<string name="screen_room_details_security_and_privacy_title">"Sikkerhet og personvern"</string>
|
<string name="screen_room_details_security_and_privacy_title">"Sikkerhet og personvern"</string>
|
||||||
<string name="screen_room_details_security_title">"Sikkerhet"</string>
|
<string name="screen_room_details_security_title">"Sikkerhet"</string>
|
||||||
<string name="screen_room_details_share_room_title">"Del rom"</string>
|
<string name="screen_room_details_share_room_title">"Del rom"</string>
|
||||||
|
|
@ -70,6 +71,12 @@
|
||||||
<string name="screen_room_details_topic_title">"Emne"</string>
|
<string name="screen_room_details_topic_title">"Emne"</string>
|
||||||
<string name="screen_room_details_updating_room">"Oppdaterer rommet …"</string>
|
<string name="screen_room_details_updating_room">"Oppdaterer rommet …"</string>
|
||||||
<string name="screen_room_member_list_banned_empty">"Det er ingen utestengte brukere."</string>
|
<string name="screen_room_member_list_banned_empty">"Det er ingen utestengte brukere."</string>
|
||||||
|
<plurals name="screen_room_member_list_banned_header_title">
|
||||||
|
<item quantity="one">"%1$d utestengt"</item>
|
||||||
|
<item quantity="other">"%1$d utestengt"</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="screen_room_member_list_empty_search_subtitle">"Sjekk stavemåten eller prøv et nytt søk"</string>
|
||||||
|
<string name="screen_room_member_list_empty_search_title">"Ingen resultater for \"%1$s\""</string>
|
||||||
<plurals name="screen_room_member_list_header_title">
|
<plurals name="screen_room_member_list_header_title">
|
||||||
<item quantity="one">"%1$d person"</item>
|
<item quantity="one">"%1$d person"</item>
|
||||||
<item quantity="other">"%1$d personer"</item>
|
<item quantity="other">"%1$d personer"</item>
|
||||||
|
|
@ -81,6 +88,11 @@
|
||||||
<string name="screen_room_member_list_manage_member_unban_title">"Fjern utestengelsen fra rommet"</string>
|
<string name="screen_room_member_list_manage_member_unban_title">"Fjern utestengelsen fra rommet"</string>
|
||||||
<string name="screen_room_member_list_mode_banned">"Utestengt"</string>
|
<string name="screen_room_member_list_mode_banned">"Utestengt"</string>
|
||||||
<string name="screen_room_member_list_mode_members">"Medlemmer"</string>
|
<string name="screen_room_member_list_mode_members">"Medlemmer"</string>
|
||||||
|
<plurals name="screen_room_member_list_pending_header_title">
|
||||||
|
<item quantity="one">"%1$d invitert"</item>
|
||||||
|
<item quantity="other">"%1$d invitert"</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="screen_room_member_list_pending_status">"Venter"</string>
|
||||||
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
||||||
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
||||||
<string name="screen_room_member_list_role_owner">"Eier"</string>
|
<string name="screen_room_member_list_role_owner">"Eier"</string>
|
||||||
|
|
@ -117,8 +129,10 @@
|
||||||
<string name="screen_room_roles_and_permissions_room_details">"Romdetaljer"</string>
|
<string name="screen_room_roles_and_permissions_room_details">"Romdetaljer"</string>
|
||||||
<string name="screen_room_roles_and_permissions_title">"Roller og tillatelser"</string>
|
<string name="screen_room_roles_and_permissions_title">"Roller og tillatelser"</string>
|
||||||
<string name="screen_security_and_privacy_add_room_address_action">"Legg til adresse"</string>
|
<string name="screen_security_and_privacy_add_room_address_action">"Legg til adresse"</string>
|
||||||
|
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Alle i autoriserte områder kan bli med, men alle andre må be om tilgang."</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_description">"Alle må be om tilgang."</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_description">"Alle må be om tilgang."</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_title">"Be om å få bli med"</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_title">"Be om å få bli med"</string>
|
||||||
|
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Alle i %1$s kan bli med, men alle andre må be om tilgang."</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Ja, aktiver kryptering"</string>
|
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Ja, aktiver kryptering"</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldingshistorikken vil bare være synlig for rommedlemmer siden de ble invitert eller siden de ble med i rommet.
|
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldingshistorikken vil bare være synlig for rommedlemmer siden de ble invitert eller siden de ble med i rommet.
|
||||||
Ingen andre enn rommedlemmene vil kunne lese meldingene. Dette kan føre til at bots og broer ikke fungerer som de skal.
|
Ingen andre enn rommedlemmene vil kunne lese meldingene. Dette kan føre til at bots og broer ikke fungerer som de skal.
|
||||||
|
|
@ -129,22 +143,28 @@ Vi anbefaler ikke å aktivere kryptering for rom som hvem som helst kan finne og
|
||||||
<string name="screen_security_and_privacy_encryption_toggle_title">"Aktiver ende-til-ende-kryptering"</string>
|
<string name="screen_security_and_privacy_encryption_toggle_title">"Aktiver ende-til-ende-kryptering"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Alle kan bli med."</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Alle kan bli med."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Hvem som helst"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Hvem som helst"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Administrer områder"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Bare inviterte personer kan bli med."</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Bare inviterte personer kan bli med."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Kun for inviterte"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Kun for inviterte"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_section_header">"Tilgang"</string>
|
<string name="screen_security_and_privacy_room_access_section_header">"Tilgang"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Alle i autoriserte områder kan bli med."</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Alle i %1$s kan bli med."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Medlemmer av område"</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Medlemmer av område"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Områder støttes ikke for øyeblikket"</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Områder støttes ikke for øyeblikket"</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
<string name="screen_security_and_privacy_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_header">"Adresse"</string>
|
<string name="screen_security_and_privacy_room_address_section_header">"Adresse"</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Tillat at dette rommet blir funnet ved å søke %1$s offentlig romkatalog"</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Tillat at dette rommet blir funnet ved å søke %1$s offentlig romkatalog"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Tillat å bli funnet ved søk i den offentlige katalogen."</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Synlig i offentlig katalog"</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Synlig i offentlig katalog"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Alle (historikken er offentlig)"</string>
|
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Alle (historikken er offentlig)"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_history_section_footer">"Endringene vil ikke påvirke tidligere meldinger, kun nye. %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_section_header">"Hvem kan lese historikk"</string>
|
<string name="screen_security_and_privacy_room_history_section_header">"Hvem kan lese historikk"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Medlemmer siden de ble invitert"</string>
|
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Medlemmer siden de ble invitert"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Medlemmer (full historikk)"</string>
|
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Medlemmer (full historikk)"</string>
|
||||||
<string name="screen_security_and_privacy_room_publishing_section_footer">"Romadresser er måter å finne og få tilgang til rom på. Dette sikrer også at du enkelt kan dele rommet ditt med andre.
|
<string name="screen_security_and_privacy_room_publishing_section_footer">"Romadresser er måter å finne og få tilgang til rom på. Dette sikrer også at du enkelt kan dele rommet ditt med andre.
|
||||||
Du kan velge å publisere rommet ditt i hjemeserverens offentlige romkatalog."</string>
|
Du kan velge å publisere rommet ditt i hjemeserverens offentlige romkatalog."</string>
|
||||||
<string name="screen_security_and_privacy_room_publishing_section_header">"Publisering av rom"</string>
|
<string name="screen_security_and_privacy_room_publishing_section_header">"Publisering av rom"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_visibility_section_footer">"Adresser er en måte å finne og få tilgang til rom og områder. Dette sikrer også at du enkelt kan dele dem med andre."</string>
|
||||||
<string name="screen_security_and_privacy_room_visibility_section_header">"Synlighet"</string>
|
<string name="screen_security_and_privacy_room_visibility_section_header">"Synlighet"</string>
|
||||||
<string name="screen_security_and_privacy_title">"Sikkerhet og personvern"</string>
|
<string name="screen_security_and_privacy_title">"Sikkerhet og personvern"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
<string name="screen_room_roles_and_permissions_room_details">"Rumsdetaljer"</string>
|
<string name="screen_room_roles_and_permissions_room_details">"Rumsdetaljer"</string>
|
||||||
<string name="screen_room_roles_and_permissions_title">"Roller och behörigheter"</string>
|
<string name="screen_room_roles_and_permissions_title">"Roller och behörigheter"</string>
|
||||||
<string name="screen_security_and_privacy_add_room_address_action">"Lägg till rumsadress"</string>
|
<string name="screen_security_and_privacy_add_room_address_action">"Lägg till rumsadress"</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_description">"Vem som helst kan be om att gå med i rummet men en administratör eller moderator måste acceptera begäran."</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_description">"Alla måste begära åtkomst."</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_title">"Be om att gå med"</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_title">"Be om att gå med"</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Ja, aktivera kryptering"</string>
|
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Ja, aktivera kryptering"</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_description">"När det är aktiverat kan kryptering för ett rum inte inaktiveras, meddelandehistoriken visas bara för rumsmedlemmar sedan de blev inbjudna eller sedan de gick med i rummet.
|
<string name="screen_security_and_privacy_enable_encryption_alert_description">"När det är aktiverat kan kryptering för ett rum inte inaktiveras, meddelandehistoriken visas bara för rumsmedlemmar sedan de blev inbjudna eller sedan de gick med i rummet.
|
||||||
|
|
@ -126,9 +126,9 @@ Vi rekommenderar inte att aktivera kryptering för rum som vem som helst kan hit
|
||||||
<string name="screen_security_and_privacy_encryption_toggle_title">"Aktivera totalsträckskryptering"</string>
|
<string name="screen_security_and_privacy_encryption_toggle_title">"Aktivera totalsträckskryptering"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Vem som helst kan hitta och gå med"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Vem som helst kan hitta och gå med"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Vem som helst"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Vem som helst"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Användare kan bara gå med om de är inbjudna"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Endast inbjudna personer kan gå med."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Endast inbjudan"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Endast inbjudan"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_section_header">"Tillgång till rum"</string>
|
<string name="screen_security_and_privacy_room_access_section_header">"Åtkomst"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Utrymmesmedlemmar"</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Utrymmesmedlemmar"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Utrymmen stöds för närvarande inte"</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Utrymmen stöds för närvarande inte"</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_footer">"Du behöver en rumsadress för att göra den synlig i katalogen."</string>
|
<string name="screen_security_and_privacy_room_address_section_footer">"Du behöver en rumsadress för att göra den synlig i katalogen."</string>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<?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="crypto_history_sharing_room_info_hidden_badge_content">"New members don\'t see history"</string>
|
||||||
|
<string name="crypto_history_sharing_room_info_shared_badge_content">"New members see history"</string>
|
||||||
|
<string name="crypto_history_sharing_room_info_world_readable_badge_content">"Anyone can see history"</string>
|
||||||
<string name="screen_edit_room_address_room_address_section_footer">"You’ll need an address in order to make it visible in the public directory."</string>
|
<string name="screen_edit_room_address_room_address_section_footer">"You’ll need an address in order to make it visible in the public directory."</string>
|
||||||
<string name="screen_edit_room_address_title">"Edit address"</string>
|
<string name="screen_edit_room_address_title">"Edit address"</string>
|
||||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"An error occurred while updating the notification setting."</string>
|
<string name="screen_notification_settings_edit_failed_updating_default_mode">"An error occurred while updating the notification setting."</string>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
package io.element.android.features.roomdetails.impl
|
package io.element.android.features.roomdetails.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -56,4 +57,52 @@ class RoomDetailsStateTest {
|
||||||
persistentListOf(RoomBadge.ENCRYPTED)
|
persistentListOf(RoomBadge.ENCRYPTED)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `room public not encrypted should not have history sharing badges`() {
|
||||||
|
val sut = aRoomDetailsState(
|
||||||
|
isEncrypted = false,
|
||||||
|
enableKeyShareOnInvite = true,
|
||||||
|
roomHistoryVisibility = RoomHistoryVisibility.Shared
|
||||||
|
)
|
||||||
|
assertThat(sut.roomBadges).isEqualTo(
|
||||||
|
persistentListOf(RoomBadge.NOT_ENCRYPTED, RoomBadge.PUBLIC)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `room public encrypted should have history sharing hidden badge`() {
|
||||||
|
val sut = aRoomDetailsState(
|
||||||
|
isEncrypted = true,
|
||||||
|
enableKeyShareOnInvite = true,
|
||||||
|
roomHistoryVisibility = RoomHistoryVisibility.Joined
|
||||||
|
)
|
||||||
|
assertThat(sut.roomBadges).isEqualTo(
|
||||||
|
persistentListOf(RoomBadge.ENCRYPTED, RoomBadge.PUBLIC, RoomBadge.SHARED_HISTORY_HIDDEN)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `room public encrypted should have history sharing shared badge`() {
|
||||||
|
val sut = aRoomDetailsState(
|
||||||
|
isEncrypted = true,
|
||||||
|
enableKeyShareOnInvite = true,
|
||||||
|
roomHistoryVisibility = RoomHistoryVisibility.Shared
|
||||||
|
)
|
||||||
|
assertThat(sut.roomBadges).isEqualTo(
|
||||||
|
persistentListOf(RoomBadge.ENCRYPTED, RoomBadge.PUBLIC, RoomBadge.SHARED_HISTORY_SHARED)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `room public encrypted should have history sharing world_readable badge`() {
|
||||||
|
val sut = aRoomDetailsState(
|
||||||
|
isEncrypted = true,
|
||||||
|
enableKeyShareOnInvite = true,
|
||||||
|
roomHistoryVisibility = RoomHistoryVisibility.WorldReadable
|
||||||
|
)
|
||||||
|
assertThat(sut.roomBadges).isEqualTo(
|
||||||
|
persistentListOf(RoomBadge.ENCRYPTED, RoomBadge.PUBLIC, RoomBadge.SHARED_HISTORY_WORLD_READABLE)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ Nedoporučujeme povolovat šifrování pro místnosti, které může kdokoli naj
|
||||||
<string name="screen_security_and_privacy_encryption_section_header">"Šifrování"</string>
|
<string name="screen_security_and_privacy_encryption_section_header">"Šifrování"</string>
|
||||||
<string name="screen_security_and_privacy_encryption_toggle_title">"Povolit koncové šifrování"</string>
|
<string name="screen_security_and_privacy_encryption_toggle_title">"Povolit koncové šifrování"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Vstoupit může kdokoli."</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Vstoupit může kdokoli."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Kdokoliv"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Kdokoli"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_footer">"Vyberte, kteří členové prostorů mohou vstoupit do této místnosti bez pozvánky. %1$s"</string>
|
<string name="screen_security_and_privacy_room_access_footer">"Vyberte, kteří členové prostorů mohou vstoupit do této místnosti bez pozvánky. %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Spravovat prostory"</string>
|
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Spravovat prostory"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Vstoupit mohou pouze pozvaní lidé."</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Vstoupit mohou pouze pozvaní lidé."</string>
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,16 @@
|
||||||
<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_edit_room_address_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
<string name="screen_edit_room_address_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
||||||
<string name="screen_edit_room_address_title">"Rediger adresse"</string>
|
<string name="screen_edit_room_address_title">"Rediger adresse"</string>
|
||||||
|
<string name="screen_manage_authorized_spaces_header">"Områder hvor medlemmer kan bli med i rommet uten invitasjon."</string>
|
||||||
|
<string name="screen_manage_authorized_spaces_title">"Administrer områder"</string>
|
||||||
|
<string name="screen_manage_authorized_spaces_unknown_space">"(Ukjent område)"</string>
|
||||||
|
<string name="screen_manage_authorized_spaces_unknown_spaces_section_title">"Andre områder du ikke er medlem i"</string>
|
||||||
|
<string name="screen_manage_authorized_spaces_your_spaces_section_title">"Dine områder"</string>
|
||||||
<string name="screen_security_and_privacy_add_room_address_action">"Legg til adresse"</string>
|
<string name="screen_security_and_privacy_add_room_address_action">"Legg til adresse"</string>
|
||||||
|
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Alle i autoriserte områder kan bli med, men alle andre må be om tilgang."</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_description">"Alle må be om tilgang."</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_description">"Alle må be om tilgang."</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_title">"Be om å få bli med"</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_title">"Be om å få bli med"</string>
|
||||||
|
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Alle i %1$s kan bli med, men alle andre må be om tilgang."</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Ja, aktiver kryptering"</string>
|
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Ja, aktiver kryptering"</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldingshistorikken vil bare være synlig for rommedlemmer siden de ble invitert eller siden de ble med i rommet.
|
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldingshistorikken vil bare være synlig for rommedlemmer siden de ble invitert eller siden de ble med i rommet.
|
||||||
Ingen andre enn rommedlemmene vil kunne lese meldingene. Dette kan føre til at bots og broer ikke fungerer som de skal.
|
Ingen andre enn rommedlemmene vil kunne lese meldingene. Dette kan føre til at bots og broer ikke fungerer som de skal.
|
||||||
|
|
@ -15,22 +22,28 @@ Vi anbefaler ikke å aktivere kryptering for rom som hvem som helst kan finne og
|
||||||
<string name="screen_security_and_privacy_encryption_toggle_title">"Aktiver ende-til-ende-kryptering"</string>
|
<string name="screen_security_and_privacy_encryption_toggle_title">"Aktiver ende-til-ende-kryptering"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Alle kan bli med."</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Alle kan bli med."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Hvem som helst"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Hvem som helst"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Administrer områder"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Bare inviterte personer kan bli med."</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Bare inviterte personer kan bli med."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Kun for inviterte"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Kun for inviterte"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_section_header">"Tilgang"</string>
|
<string name="screen_security_and_privacy_room_access_section_header">"Tilgang"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Alle i autoriserte områder kan bli med."</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Alle i %1$s kan bli med."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Medlemmer av område"</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Medlemmer av område"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Områder støttes ikke for øyeblikket"</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Områder støttes ikke for øyeblikket"</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
<string name="screen_security_and_privacy_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_header">"Adresse"</string>
|
<string name="screen_security_and_privacy_room_address_section_header">"Adresse"</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Tillat at dette rommet blir funnet ved å søke %1$s offentlig romkatalog"</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Tillat at dette rommet blir funnet ved å søke %1$s offentlig romkatalog"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Tillat å bli funnet ved søk i den offentlige katalogen."</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Synlig i offentlig katalog"</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Synlig i offentlig katalog"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Alle (historikken er offentlig)"</string>
|
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Alle (historikken er offentlig)"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_history_section_footer">"Endringene vil ikke påvirke tidligere meldinger, kun nye. %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_section_header">"Hvem kan lese historikk"</string>
|
<string name="screen_security_and_privacy_room_history_section_header">"Hvem kan lese historikk"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Medlemmer siden de ble invitert"</string>
|
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Medlemmer siden de ble invitert"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Medlemmer (full historikk)"</string>
|
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Medlemmer (full historikk)"</string>
|
||||||
<string name="screen_security_and_privacy_room_publishing_section_footer">"Romadresser er måter å finne og få tilgang til rom på. Dette sikrer også at du enkelt kan dele rommet ditt med andre.
|
<string name="screen_security_and_privacy_room_publishing_section_footer">"Romadresser er måter å finne og få tilgang til rom på. Dette sikrer også at du enkelt kan dele rommet ditt med andre.
|
||||||
Du kan velge å publisere rommet ditt i hjemeserverens offentlige romkatalog."</string>
|
Du kan velge å publisere rommet ditt i hjemeserverens offentlige romkatalog."</string>
|
||||||
<string name="screen_security_and_privacy_room_publishing_section_header">"Publisering av rom"</string>
|
<string name="screen_security_and_privacy_room_publishing_section_header">"Publisering av rom"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_visibility_section_footer">"Adresser er en måte å finne og få tilgang til rom og områder. Dette sikrer også at du enkelt kan dele dem med andre."</string>
|
||||||
<string name="screen_security_and_privacy_room_visibility_section_header">"Synlighet"</string>
|
<string name="screen_security_and_privacy_room_visibility_section_header">"Synlighet"</string>
|
||||||
<string name="screen_security_and_privacy_title">"Sikkerhet og personvern"</string>
|
<string name="screen_security_and_privacy_title">"Sikkerhet og personvern"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<string name="screen_edit_room_address_room_address_section_footer">"Du behöver en rumsadress för att göra den synlig i katalogen."</string>
|
<string name="screen_edit_room_address_room_address_section_footer">"Du behöver en rumsadress för att göra den synlig i katalogen."</string>
|
||||||
<string name="screen_edit_room_address_title">"Rumsadress"</string>
|
<string name="screen_edit_room_address_title">"Rumsadress"</string>
|
||||||
<string name="screen_security_and_privacy_add_room_address_action">"Lägg till rumsadress"</string>
|
<string name="screen_security_and_privacy_add_room_address_action">"Lägg till rumsadress"</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_description">"Vem som helst kan be om att gå med i rummet men en administratör eller moderator måste acceptera begäran."</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_description">"Alla måste begära åtkomst."</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_title">"Be om att gå med"</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_title">"Be om att gå med"</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Ja, aktivera kryptering"</string>
|
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Ja, aktivera kryptering"</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_description">"När det är aktiverat kan kryptering för ett rum inte inaktiveras, meddelandehistoriken visas bara för rumsmedlemmar sedan de blev inbjudna eller sedan de gick med i rummet.
|
<string name="screen_security_and_privacy_enable_encryption_alert_description">"När det är aktiverat kan kryptering för ett rum inte inaktiveras, meddelandehistoriken visas bara för rumsmedlemmar sedan de blev inbjudna eller sedan de gick med i rummet.
|
||||||
|
|
@ -15,9 +15,9 @@ Vi rekommenderar inte att aktivera kryptering för rum som vem som helst kan hit
|
||||||
<string name="screen_security_and_privacy_encryption_toggle_title">"Aktivera totalsträckskryptering"</string>
|
<string name="screen_security_and_privacy_encryption_toggle_title">"Aktivera totalsträckskryptering"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Vem som helst kan hitta och gå med"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Vem som helst kan hitta och gå med"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Vem som helst"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Vem som helst"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Användare kan bara gå med om de är inbjudna"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Endast inbjudna personer kan gå med."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Endast inbjudan"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Endast inbjudan"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_section_header">"Tillgång till rum"</string>
|
<string name="screen_security_and_privacy_room_access_section_header">"Åtkomst"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Utrymmesmedlemmar"</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Utrymmesmedlemmar"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Utrymmen stöds för närvarande inte"</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Utrymmen stöds för närvarande inte"</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_footer">"Du behöver en rumsadress för att göra den synlig i katalogen."</string>
|
<string name="screen_security_and_privacy_room_address_section_footer">"Du behöver en rumsadress för att göra den synlig i katalogen."</string>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +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_leave_space_choose_owners_action">"Vyberte vlastníky"</string>
|
||||||
<string name="screen_leave_space_last_admin_info">"%1$s (Správce)"</string>
|
<string name="screen_leave_space_last_admin_info">"%1$s (Správce)"</string>
|
||||||
<plurals name="screen_leave_space_submit">
|
<plurals name="screen_leave_space_submit">
|
||||||
<item quantity="one">"Opustit %1$d místnost a prostor"</item>
|
<item quantity="one">"Opustit %1$d místnost a prostor"</item>
|
||||||
|
|
@ -8,10 +9,21 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="screen_leave_space_subtitle">"Tím budete také odstraněni ze všech místností v tomto prostoru."</string>
|
<string name="screen_leave_space_subtitle">"Tím budete také odstraněni ze všech místností v tomto prostoru."</string>
|
||||||
<string name="screen_leave_space_subtitle_last_admin">"Než budete moci odejít, musíte pro tento prostor přiřadit jiného správce."</string>
|
<string name="screen_leave_space_subtitle_last_admin">"Než budete moci odejít, musíte pro tento prostor přiřadit jiného správce."</string>
|
||||||
|
<string name="screen_leave_space_subtitle_last_owner">"Jste jediným vlastníkem %1$s. Před odchodem musíte převést vlastnictví na někoho jiného."</string>
|
||||||
<string name="screen_leave_space_subtitle_only_last_admin">"Z následujících místností nebudete odstraněni, protože jste jediným administrátorem:"</string>
|
<string name="screen_leave_space_subtitle_only_last_admin">"Z následujících místností nebudete odstraněni, protože jste jediným administrátorem:"</string>
|
||||||
<string name="screen_leave_space_title">"Opustit %1$s?"</string>
|
<string name="screen_leave_space_title">"Opustit %1$s?"</string>
|
||||||
<string name="screen_leave_space_title_last_admin">"Jste jediným administrátorem pro %1$s"</string>
|
<string name="screen_leave_space_title_last_admin">"Jste jediným administrátorem pro %1$s"</string>
|
||||||
|
<string name="screen_leave_space_title_last_owner">"Převést vlastnictví"</string>
|
||||||
|
<string name="screen_space_add_room_action">"Místnost"</string>
|
||||||
|
<string name="screen_space_add_rooms_room_access_description">"Přidání místnosti neovlivní přístup k místnosti. Chcete-li přístup změnit, přejděte do Nastavení místnosti > Zabezpečení a soukromí."</string>
|
||||||
|
<string name="screen_space_empty_state_title">"Přidejte svou první místnost"</string>
|
||||||
<string name="screen_space_menu_action_members">"Zobrazit členy"</string>
|
<string name="screen_space_menu_action_members">"Zobrazit členy"</string>
|
||||||
|
<string name="screen_space_remove_rooms_confirmation_content">"Odebrání místnosti neovlivní přístup k místnosti. Chcete-li přístup změnit, přejděte do sekce Informace o místnosti > Soukromí a zabezpečení."</string>
|
||||||
|
<plurals name="screen_space_remove_rooms_confirmation_title">
|
||||||
|
<item quantity="one">"Odstranit %1$d místnost od %2$s"</item>
|
||||||
|
<item quantity="few">"Odstranit %1$d místnosti od %2$s"</item>
|
||||||
|
<item quantity="other">"Odstranit %1$d místností od %2$s"</item>
|
||||||
|
</plurals>
|
||||||
<string name="screen_space_settings_leave_space">"Opustit prostor"</string>
|
<string name="screen_space_settings_leave_space">"Opustit prostor"</string>
|
||||||
<string name="screen_space_settings_roles_and_permissions">"Role a oprávnění"</string>
|
<string name="screen_space_settings_roles_and_permissions">"Role a oprávnění"</string>
|
||||||
<string name="screen_space_settings_security_and_privacy">"Zabezpečení a soukromí"</string>
|
<string name="screen_space_settings_security_and_privacy">"Zabezpečení a soukromí"</string>
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,16 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="screen_leave_space_subtitle">"Sellega eemaldad end ka kõikidest antud kogukonna jututubadest."</string>
|
<string name="screen_leave_space_subtitle">"Sellega eemaldad end ka kõikidest antud kogukonna jututubadest."</string>
|
||||||
<string name="screen_leave_space_subtitle_last_admin">"Enne lahkumist pead sa selle kogukonna jaoks lisama vähemalt ühe täiendava peakasutaja."</string>
|
<string name="screen_leave_space_subtitle_last_admin">"Enne lahkumist pead sa selle kogukonna jaoks lisama vähemalt ühe täiendava peakasutaja."</string>
|
||||||
|
<string name="screen_leave_space_subtitle_last_owner">"Sa oled „%1$s“ kogukonna viimane omanik. Enne lahkumist pead omandi kellelegi teisele üle andma."</string>
|
||||||
<string name="screen_leave_space_subtitle_only_last_admin">"Sind ei saa järgnevatest jututubadest eemaldada, kuna oled seal/neis ainus peakasutaja:"</string>
|
<string name="screen_leave_space_subtitle_only_last_admin">"Sind ei saa järgnevatest jututubadest eemaldada, kuna oled seal/neis ainus peakasutaja:"</string>
|
||||||
<string name="screen_leave_space_title">"Kas lahkud %1$s kogukonnast?"</string>
|
<string name="screen_leave_space_title">"Kas lahkud %1$s kogukonnast?"</string>
|
||||||
<string name="screen_leave_space_title_last_admin">"Sa oled siin ainus peakasutaja: %1$s"</string>
|
<string name="screen_leave_space_title_last_admin">"Sa oled siin ainus peakasutaja: %1$s"</string>
|
||||||
<string name="screen_leave_space_title_last_owner">"Anna omand üle"</string>
|
<string name="screen_leave_space_title_last_owner">"Anna omand üle"</string>
|
||||||
<string name="screen_space_add_room_action">"Jututuba"</string>
|
<string name="screen_space_add_room_action">"Jututuba"</string>
|
||||||
|
<string name="screen_space_add_rooms_room_access_description">"Jututoa lisamine ei mõjuta ligipääsu jututuppa. Selle muutmiseks ava „Jututoa seadistused“ → „Turvalisus ja privaatsus“."</string>
|
||||||
<string name="screen_space_empty_state_title">"Lisa oma esimene jututuba"</string>
|
<string name="screen_space_empty_state_title">"Lisa oma esimene jututuba"</string>
|
||||||
<string name="screen_space_menu_action_members">"Vaata liikmeid"</string>
|
<string name="screen_space_menu_action_members">"Vaata liikmeid"</string>
|
||||||
|
<string name="screen_space_remove_rooms_confirmation_content">"Jututoa eemaldamine ei mõjuta ligipääsu jututuppa. Selle muutmiseks ava „Jututoa seadistused“ → „Turvalisus ja privaatsus“."</string>
|
||||||
<plurals name="screen_space_remove_rooms_confirmation_title">
|
<plurals name="screen_space_remove_rooms_confirmation_title">
|
||||||
<item quantity="one">"Eemalda %1$d jututuba „%2$s“ kogukonnast"</item>
|
<item quantity="one">"Eemalda %1$d jututuba „%2$s“ kogukonnast"</item>
|
||||||
<item quantity="other">"Eemalda %1$d jututuba „%2$s“ kogukonnast"</item>
|
<item quantity="other">"Eemalda %1$d jututuba „%2$s“ kogukonnast"</item>
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
# Project
|
# Project
|
||||||
android_gradle_plugin = "8.13.2"
|
android_gradle_plugin = "8.13.2"
|
||||||
# When updateing this, please also update the version in the file ./idea/kotlinc.xml
|
# When updateing this, please also update the version in the file ./idea/kotlinc.xml
|
||||||
kotlin = "2.3.0"
|
kotlin = "2.3.10"
|
||||||
kotlinpoet = "2.2.0"
|
kotlinpoet = "2.2.0"
|
||||||
ksp = "2.3.5"
|
ksp = "2.3.5"
|
||||||
firebaseAppDistribution = "5.2.0"
|
firebaseAppDistribution = "5.2.1"
|
||||||
|
|
||||||
# AndroidX
|
# AndroidX
|
||||||
core = "1.17.0"
|
core = "1.17.0"
|
||||||
|
|
@ -17,7 +17,7 @@ constraintlayout = "2.2.1"
|
||||||
constraintlayout_compose = "1.1.1"
|
constraintlayout_compose = "1.1.1"
|
||||||
lifecycle = "2.10.0"
|
lifecycle = "2.10.0"
|
||||||
activity = "1.12.3"
|
activity = "1.12.3"
|
||||||
media3 = "1.9.1"
|
media3 = "1.9.2"
|
||||||
camera = "1.5.3"
|
camera = "1.5.3"
|
||||||
work = "2.11.1"
|
work = "2.11.1"
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin
|
||||||
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
|
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
|
||||||
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
||||||
# https://firebase.google.com/docs/android/setup#available-libraries
|
# https://firebase.google.com/docs/android/setup#available-libraries
|
||||||
google_firebase_bom = "com.google.firebase:firebase-bom:34.8.0"
|
google_firebase_bom = "com.google.firebase:firebase-bom:34.9.0"
|
||||||
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
||||||
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
||||||
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||||
|
|
@ -177,7 +177,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
|
||||||
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
|
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
|
||||||
# All new features should not be implemented in the pull request that upgrades the version, developers should
|
# All new features should not be implemented in the pull request that upgrades the version, developers should
|
||||||
# only fix API breaks and may add some TODOs.
|
# only fix API breaks and may add some TODOs.
|
||||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.1.27"
|
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.2.6"
|
||||||
|
|
||||||
# Others
|
# Others
|
||||||
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
||||||
|
|
@ -218,8 +218,8 @@ haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref =
|
||||||
color_picker = "io.mhssn:colorpicker:1.0.0"
|
color_picker = "io.mhssn:colorpicker:1.0.0"
|
||||||
|
|
||||||
# Analytics
|
# Analytics
|
||||||
posthog = "com.posthog:posthog-android:3.30.0"
|
posthog = "com.posthog:posthog-android:3.31.0"
|
||||||
sentry = "io.sentry:sentry-android:8.31.0"
|
sentry = "io.sentry:sentry-android:8.32.0"
|
||||||
# main branch can be tested replacing the version with main-SNAPSHOT
|
# main branch can be tested replacing the version with main-SNAPSHOT
|
||||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.29.2"
|
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.29.2"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,18 @@ internal fun MatrixBadgeAtomNegativePreview() = ElementPreview {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun MatrixBadgeAtomNeutralWrappingPreview() = ElementPreview {
|
||||||
|
MatrixBadgeAtom.View(
|
||||||
|
MatrixBadgeAtom.MatrixBadgeData(
|
||||||
|
text = "How much wood could a wood chuck chuck if a wood chuck could chuck wood",
|
||||||
|
icon = CompoundIcons.LockOff(),
|
||||||
|
type = MatrixBadgeAtom.Type.Info,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun MatrixBadgeAtomInfoPreview() = ElementPreview {
|
internal fun MatrixBadgeAtomInfoPreview() = ElementPreview {
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,10 @@
|
||||||
package io.element.android.libraries.designsystem.atomic.molecules
|
package io.element.android.libraries.designsystem.atomic.molecules
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.MatrixBadgeAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.MatrixBadgeAtom
|
||||||
|
|
@ -22,10 +23,11 @@ fun MatrixBadgeRowMolecule(
|
||||||
data: ImmutableList<MatrixBadgeAtom.MatrixBadgeData>,
|
data: ImmutableList<MatrixBadgeAtom.MatrixBadgeData>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
FlowRow(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
for (badge in data) {
|
for (badge in data) {
|
||||||
MatrixBadgeAtom.View(badge)
|
MatrixBadgeAtom.View(badge)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
|
@ -63,6 +64,8 @@ fun Badge(
|
||||||
text = text,
|
text = text,
|
||||||
style = ElementTheme.typography.fontBodySmRegular,
|
style = ElementTheme.typography.fontBodySmRegular,
|
||||||
color = textColor,
|
color = textColor,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
softWrap = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,13 @@ enum class FeatureFlags(
|
||||||
defaultValue = { false },
|
defaultValue = { false },
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
|
RoomListSpaceFilters(
|
||||||
|
key = "feature.roomListSpaceFilters",
|
||||||
|
title = "Room list space filters",
|
||||||
|
description = "Allow filtering the room list by space.",
|
||||||
|
defaultValue = { false },
|
||||||
|
isFinished = false,
|
||||||
|
),
|
||||||
PrintLogsToLogcat(
|
PrintLogsToLogcat(
|
||||||
key = "feature.print_logs_to_logcat",
|
key = "feature.print_logs_to_logcat",
|
||||||
title = "Print logs to logcat",
|
title = "Print logs to logcat",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ sealed class QrLoginException : Exception() {
|
||||||
data object ConnectionInsecure : QrLoginException()
|
data object ConnectionInsecure : QrLoginException()
|
||||||
data object Declined : QrLoginException()
|
data object Declined : QrLoginException()
|
||||||
data object Expired : QrLoginException()
|
data object Expired : QrLoginException()
|
||||||
|
data object NotFound : QrLoginException()
|
||||||
data object LinkingNotSupported : QrLoginException()
|
data object LinkingNotSupported : QrLoginException()
|
||||||
data object OidcMetadataInvalid : QrLoginException()
|
data object OidcMetadataInvalid : QrLoginException()
|
||||||
data object SlidingSyncNotAvailable : QrLoginException()
|
data object SlidingSyncNotAvailable : QrLoginException()
|
||||||
|
|
@ -20,5 +21,4 @@ sealed class QrLoginException : Exception() {
|
||||||
data object CheckCodeAlreadySent : QrLoginException()
|
data object CheckCodeAlreadySent : QrLoginException()
|
||||||
data object CheckCodeCannotBeSent : QrLoginException()
|
data object CheckCodeCannotBeSent : QrLoginException()
|
||||||
data object Unknown : QrLoginException()
|
data object Unknown : QrLoginException()
|
||||||
data object NotFound : QrLoginException()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.room.history
|
package io.element.android.libraries.matrix.api.room.history
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed interface RoomHistoryVisibility {
|
sealed interface RoomHistoryVisibility {
|
||||||
/**
|
/**
|
||||||
* Previous events are accessible to newly joined members from the point
|
* Previous events are accessible to newly joined members from the point
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,10 @@ sealed interface LatestEventValue {
|
||||||
val senderProfile: ProfileDetails,
|
val senderProfile: ProfileDetails,
|
||||||
val isSending: Boolean,
|
val isSending: Boolean,
|
||||||
) : LatestEventValue
|
) : LatestEventValue
|
||||||
|
|
||||||
|
data class RoomInvite(
|
||||||
|
val timestamp: Long,
|
||||||
|
val inviterId: UserId?,
|
||||||
|
val invitedProfile: ProfileDetails,
|
||||||
|
) : LatestEventValue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.roomlist
|
package io.element.android.libraries.matrix.api.roomlist
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
sealed interface RoomListFilter {
|
sealed interface RoomListFilter {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,6 +43,10 @@ sealed interface RoomListFilter {
|
||||||
val filters: List<RoomListFilter>
|
val filters: List<RoomListFilter>
|
||||||
) : RoomListFilter
|
) : RoomListFilter
|
||||||
|
|
||||||
|
data class Identifiers(
|
||||||
|
val values: List<RoomId>,
|
||||||
|
) : RoomListFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filter that matches rooms that are unread.
|
* A filter that matches rooms that are unread.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ data class RoomSummary(
|
||||||
is LatestEventValue.None -> null
|
is LatestEventValue.None -> null
|
||||||
is LatestEventValue.Local -> latestEvent.timestamp
|
is LatestEventValue.Local -> latestEvent.timestamp
|
||||||
is LatestEventValue.Remote -> latestEvent.timestamp
|
is LatestEventValue.Remote -> latestEvent.timestamp
|
||||||
|
is LatestEventValue.RoomInvite -> latestEvent.timestamp
|
||||||
}
|
}
|
||||||
val isOneToOne get() = info.activeMembersCount == 2L
|
val isOneToOne get() = info.activeMembersCount == 2L
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
|
||||||
interface SpaceService {
|
interface SpaceService {
|
||||||
val spaceRoomsFlow: SharedFlow<List<SpaceRoom>>
|
val topLevelSpacesFlow: SharedFlow<List<SpaceRoom>>
|
||||||
suspend fun joinedSpaces(): Result<List<SpaceRoom>>
|
val spaceFiltersFlow: SharedFlow<List<SpaceServiceFilter>>
|
||||||
|
|
||||||
suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>>
|
suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>>
|
||||||
|
|
||||||
suspend fun getSpaceRoom(spaceId: RoomId): SpaceRoom?
|
suspend fun getSpaceRoom(spaceId: RoomId): SpaceRoom?
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.api.spaces
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a space filter for filtering rooms by space membership.
|
||||||
|
*
|
||||||
|
* @property spaceRoom The space room associated with this filter.
|
||||||
|
* @property level The nesting level of the space (0 = top level, 1 = first level child, etc.).
|
||||||
|
* @property descendants The list of room IDs that are descendants of this space.
|
||||||
|
*/
|
||||||
|
data class SpaceServiceFilter(
|
||||||
|
val spaceRoom: SpaceRoom,
|
||||||
|
val level: Int,
|
||||||
|
val descendants: List<RoomId>,
|
||||||
|
)
|
||||||
|
|
@ -20,6 +20,7 @@ import org.matrix.rustcomponents.sdk.ClientDelegate
|
||||||
import org.matrix.rustcomponents.sdk.ClientSessionDelegate
|
import org.matrix.rustcomponents.sdk.ClientSessionDelegate
|
||||||
import org.matrix.rustcomponents.sdk.Session
|
import org.matrix.rustcomponents.sdk.Session
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uniffi.matrix_sdk_common.BackgroundTaskFailureReason
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
|
@ -120,6 +121,11 @@ class RustClientSessionDelegate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackgroundTaskErrorReport(taskName: String, error: BackgroundTaskFailureReason) {
|
||||||
|
// TODO actually implement the missing logic to report to sentry and crash the app
|
||||||
|
Timber.tag(loggerTag.value).e("onBackgroundTaskErrorReport(taskName=$taskName, error=$error)")
|
||||||
|
}
|
||||||
|
|
||||||
override fun retrieveSessionFromKeychain(userId: String): Session {
|
override fun retrieveSessionFromKeychain(userId: String): Session {
|
||||||
// This should never be called, as it's only used for multi-process setups
|
// This should never be called, as it's only used for multi-process setups
|
||||||
error("retrieveSessionFromKeychain should never be called for Android")
|
error("retrieveSessionFromKeychain should never be called for Android")
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
|
||||||
import io.element.android.libraries.matrix.impl.room.RoomInfoMapper
|
import io.element.android.libraries.matrix.impl.room.RoomInfoMapper
|
||||||
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
|
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
|
||||||
import io.element.android.libraries.matrix.impl.room.RustRoomFactory
|
import io.element.android.libraries.matrix.impl.room.RustRoomFactory
|
||||||
import io.element.android.libraries.matrix.impl.room.TimelineEventTypeFilterFactory
|
import io.element.android.libraries.matrix.impl.room.TimelineEventFilterFactory
|
||||||
import io.element.android.libraries.matrix.impl.room.history.map
|
import io.element.android.libraries.matrix.impl.room.history.map
|
||||||
import io.element.android.libraries.matrix.impl.room.join.map
|
import io.element.android.libraries.matrix.impl.room.join.map
|
||||||
import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewInfoMapper
|
import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewInfoMapper
|
||||||
|
|
@ -141,7 +141,7 @@ class RustMatrixClient(
|
||||||
dispatchers: CoroutineDispatchers,
|
dispatchers: CoroutineDispatchers,
|
||||||
baseCacheDirectory: File,
|
baseCacheDirectory: File,
|
||||||
clock: SystemClock,
|
clock: SystemClock,
|
||||||
timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
timelineEventFilterFactory: TimelineEventFilterFactory,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val workManagerScheduler: WorkManagerScheduler,
|
private val workManagerScheduler: WorkManagerScheduler,
|
||||||
|
|
@ -225,7 +225,7 @@ class RustMatrixClient(
|
||||||
systemClock = clock,
|
systemClock = clock,
|
||||||
roomContentForwarder = RoomContentForwarder(innerRoomListService),
|
roomContentForwarder = RoomContentForwarder(innerRoomListService),
|
||||||
roomSyncSubscriber = roomSyncSubscriber,
|
roomSyncSubscriber = roomSyncSubscriber,
|
||||||
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
|
timelineEventFilterFactory = timelineEventFilterFactory,
|
||||||
roomMembershipObserver = roomMembershipObserver,
|
roomMembershipObserver = roomMembershipObserver,
|
||||||
roomInfoMapper = roomInfoMapper,
|
roomInfoMapper = roomInfoMapper,
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.impl.certificates.UserCertificatesPro
|
||||||
import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
||||||
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
|
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
|
||||||
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
|
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
|
||||||
import io.element.android.libraries.matrix.impl.room.TimelineEventTypeFilterFactory
|
import io.element.android.libraries.matrix.impl.room.TimelineEventFilterFactory
|
||||||
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider
|
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider
|
||||||
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
|
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
|
||||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||||
|
|
@ -61,7 +61,7 @@ class RustMatrixClientFactory(
|
||||||
private val clock: SystemClock,
|
private val clock: SystemClock,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
private val timelineEventFilterFactory: TimelineEventFilterFactory,
|
||||||
private val clientBuilderProvider: ClientBuilderProvider,
|
private val clientBuilderProvider: ClientBuilderProvider,
|
||||||
private val sqliteStoreBuilderProvider: SqliteStoreBuilderProvider,
|
private val sqliteStoreBuilderProvider: SqliteStoreBuilderProvider,
|
||||||
private val workManagerScheduler: WorkManagerScheduler,
|
private val workManagerScheduler: WorkManagerScheduler,
|
||||||
|
|
@ -115,7 +115,7 @@ class RustMatrixClientFactory(
|
||||||
dispatchers = coroutineDispatchers,
|
dispatchers = coroutineDispatchers,
|
||||||
baseCacheDirectory = cacheDirectory,
|
baseCacheDirectory = cacheDirectory,
|
||||||
clock = clock,
|
clock = clock,
|
||||||
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
|
timelineEventFilterFactory = timelineEventFilterFactory,
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
workManagerScheduler = workManagerScheduler,
|
workManagerScheduler = workManagerScheduler,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ object QrErrorMapper {
|
||||||
is RustHumanQrLoginException.ConnectionInsecure -> QrLoginException.ConnectionInsecure
|
is RustHumanQrLoginException.ConnectionInsecure -> QrLoginException.ConnectionInsecure
|
||||||
is RustHumanQrLoginException.Declined -> QrLoginException.Declined
|
is RustHumanQrLoginException.Declined -> QrLoginException.Declined
|
||||||
is RustHumanQrLoginException.Expired -> QrLoginException.Expired
|
is RustHumanQrLoginException.Expired -> QrLoginException.Expired
|
||||||
|
is RustHumanQrLoginException.NotFound -> QrLoginException.NotFound
|
||||||
is RustHumanQrLoginException.OtherDeviceNotSignedIn -> QrLoginException.OtherDeviceNotSignedIn
|
is RustHumanQrLoginException.OtherDeviceNotSignedIn -> QrLoginException.OtherDeviceNotSignedIn
|
||||||
is RustHumanQrLoginException.LinkingNotSupported -> QrLoginException.LinkingNotSupported
|
is RustHumanQrLoginException.LinkingNotSupported -> QrLoginException.LinkingNotSupported
|
||||||
is RustHumanQrLoginException.Unknown -> QrLoginException.Unknown
|
is RustHumanQrLoginException.Unknown -> QrLoginException.Unknown
|
||||||
|
|
@ -45,6 +46,5 @@ object QrErrorMapper {
|
||||||
is RustHumanQrLoginException.SlidingSyncNotAvailable -> QrLoginException.SlidingSyncNotAvailable
|
is RustHumanQrLoginException.SlidingSyncNotAvailable -> QrLoginException.SlidingSyncNotAvailable
|
||||||
is RustHumanQrLoginException.CheckCodeAlreadySent -> QrLoginException.CheckCodeAlreadySent
|
is RustHumanQrLoginException.CheckCodeAlreadySent -> QrLoginException.CheckCodeAlreadySent
|
||||||
is RustHumanQrLoginException.CheckCodeCannotBeSent -> QrLoginException.CheckCodeCannotBeSent
|
is RustHumanQrLoginException.CheckCodeCannotBeSent -> QrLoginException.CheckCodeCannotBeSent
|
||||||
is RustHumanQrLoginException.NotFound -> QrLoginException.NotFound
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ import org.matrix.rustcomponents.sdk.getElementCallRequiredPermissions
|
||||||
import org.matrix.rustcomponents.sdk.use
|
import org.matrix.rustcomponents.sdk.use
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uniffi.matrix_sdk.RoomPowerLevelChanges
|
import uniffi.matrix_sdk.RoomPowerLevelChanges
|
||||||
|
import uniffi.matrix_sdk_ui.TimelineEventFocusThreadMode
|
||||||
import uniffi.matrix_sdk_ui.TimelineReadReceiptTracking
|
import uniffi.matrix_sdk_ui.TimelineReadReceiptTracking
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange
|
import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange
|
||||||
|
|
@ -177,21 +178,18 @@ class JoinedRustRoom(
|
||||||
): Result<Timeline> = withContext(roomDispatcher) {
|
): Result<Timeline> = withContext(roomDispatcher) {
|
||||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||||
val focus = when (createTimelineParams) {
|
val focus = when (createTimelineParams) {
|
||||||
is CreateTimelineParams.PinnedOnly -> TimelineFocus.PinnedEvents(
|
is CreateTimelineParams.PinnedOnly -> TimelineFocus.PinnedEvents
|
||||||
maxEventsToLoad = 100u,
|
|
||||||
maxConcurrentRequests = 10u,
|
|
||||||
)
|
|
||||||
is CreateTimelineParams.MediaOnly -> TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents)
|
is CreateTimelineParams.MediaOnly -> TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents)
|
||||||
is CreateTimelineParams.Focused -> TimelineFocus.Event(
|
is CreateTimelineParams.Focused -> TimelineFocus.Event(
|
||||||
eventId = createTimelineParams.focusedEventId.value,
|
eventId = createTimelineParams.focusedEventId.value,
|
||||||
numContextEvents = 50u,
|
numContextEvents = 50u,
|
||||||
hideThreadedEvents = hideThreadedEvents,
|
threadMode = TimelineEventFocusThreadMode.Automatic(hideThreadedEvents),
|
||||||
)
|
)
|
||||||
is CreateTimelineParams.MediaOnlyFocused -> TimelineFocus.Event(
|
is CreateTimelineParams.MediaOnlyFocused -> TimelineFocus.Event(
|
||||||
eventId = createTimelineParams.focusedEventId.value,
|
eventId = createTimelineParams.focusedEventId.value,
|
||||||
numContextEvents = 50u,
|
numContextEvents = 50u,
|
||||||
// Never hide threaded events in media focused timeline
|
// Never hide threaded events in media focused timeline
|
||||||
hideThreadedEvents = false,
|
threadMode = TimelineEventFocusThreadMode.Automatic(false),
|
||||||
)
|
)
|
||||||
is CreateTimelineParams.Threaded -> TimelineFocus.Thread(
|
is CreateTimelineParams.Threaded -> TimelineFocus.Thread(
|
||||||
rootEventId = createTimelineParams.threadRootEventId.value,
|
rootEventId = createTimelineParams.threadRootEventId.value,
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class RustRoomFactory(
|
||||||
private val roomListService: RoomListService,
|
private val roomListService: RoomListService,
|
||||||
private val innerRoomListService: InnerRoomListService,
|
private val innerRoomListService: InnerRoomListService,
|
||||||
private val roomSyncSubscriber: RoomSyncSubscriber,
|
private val roomSyncSubscriber: RoomSyncSubscriber,
|
||||||
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
private val timelineEventFilterFactory: TimelineEventFilterFactory,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val roomMembershipObserver: RoomMembershipObserver,
|
private val roomMembershipObserver: RoomMembershipObserver,
|
||||||
private val roomInfoMapper: RoomInfoMapper,
|
private val roomInfoMapper: RoomInfoMapper,
|
||||||
|
|
@ -70,7 +70,7 @@ class RustRoomFactory(
|
||||||
private val eventFilters = TimelineConfig.excludedEvents
|
private val eventFilters = TimelineConfig.excludedEvents
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
?.let { listStateEventType ->
|
?.let { listStateEventType ->
|
||||||
timelineEventTypeFilterFactory.create(listStateEventType)
|
timelineEventFilterFactory.create(listStateEventType)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun destroy() {
|
suspend fun destroy() {
|
||||||
|
|
@ -133,7 +133,7 @@ class RustRoomFactory(
|
||||||
sdkRoom.timelineWithConfiguration(
|
sdkRoom.timelineWithConfiguration(
|
||||||
TimelineConfiguration(
|
TimelineConfiguration(
|
||||||
focus = TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents),
|
focus = TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents),
|
||||||
filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All,
|
filter = eventFilters?.let(TimelineFilter::EventFilter) ?: TimelineFilter.All,
|
||||||
internalIdPrefix = "live",
|
internalIdPrefix = "live",
|
||||||
dateDividerMode = DateDividerMode.DAILY,
|
dateDividerMode = DateDividerMode.DAILY,
|
||||||
trackReadReceipts = TimelineReadReceiptTracking.ALL_EVENTS,
|
trackReadReceipts = TimelineReadReceiptTracking.ALL_EVENTS,
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,16 @@ import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
import org.matrix.rustcomponents.sdk.FilterTimelineEventType
|
import org.matrix.rustcomponents.sdk.FilterTimelineEventType
|
||||||
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
|
import org.matrix.rustcomponents.sdk.TimelineEventFilter
|
||||||
|
|
||||||
interface TimelineEventTypeFilterFactory {
|
interface TimelineEventFilterFactory {
|
||||||
fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter
|
fun create(listStateEventType: List<StateEventType>): TimelineEventFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class RustTimelineEventTypeFilterFactory : TimelineEventTypeFilterFactory {
|
class RustTimelineEventFilterFactory : TimelineEventFilterFactory {
|
||||||
override fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter {
|
override fun create(listStateEventType: List<StateEventType>): TimelineEventFilter {
|
||||||
return TimelineEventTypeFilter.exclude(
|
return TimelineEventFilter.excludeEventTypes(
|
||||||
listStateEventType.map { stateEventType ->
|
listStateEventType.map { stateEventType ->
|
||||||
FilterTimelineEventType.State(stateEventType.map())
|
FilterTimelineEventType.State(stateEventType.map())
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Any
|
||||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Category
|
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Category
|
||||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.DeduplicateVersions
|
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.DeduplicateVersions
|
||||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Favourite
|
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Favourite
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Identifiers
|
||||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Invite
|
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Invite
|
||||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.NonLeft
|
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.NonLeft
|
||||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.NonSpace
|
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.NonSpace
|
||||||
|
|
@ -60,6 +61,7 @@ internal object RoomListFilterMapper {
|
||||||
return when (filter) {
|
return when (filter) {
|
||||||
is RoomListFilter.All -> All(filters = filter.filters.map { mapFilter(it) })
|
is RoomListFilter.All -> All(filters = filter.filters.map { mapFilter(it) })
|
||||||
is RoomListFilter.Any -> Any(filters = filter.filters.map { mapFilter(it) })
|
is RoomListFilter.Any -> Any(filters = filter.filters.map { mapFilter(it) })
|
||||||
|
is RoomListFilter.Identifiers -> Identifiers(identifiers = filter.values.map { it.value })
|
||||||
RoomListFilter.None -> None
|
RoomListFilter.None -> None
|
||||||
RoomListFilter.Category.Group -> Category(RoomListFilterCategory.GROUP)
|
RoomListFilter.Category.Group -> Category(RoomListFilterCategory.GROUP)
|
||||||
RoomListFilter.Category.People -> Category(RoomListFilterCategory.PEOPLE)
|
RoomListFilter.Category.People -> Category(RoomListFilterCategory.PEOPLE)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,11 @@ class RoomSummaryFactory(
|
||||||
senderProfile = event.profile.map(),
|
senderProfile = event.profile.map(),
|
||||||
isOwn = event.isOwn,
|
isOwn = event.isOwn,
|
||||||
)
|
)
|
||||||
|
is RustLatestEventValue.RemoteInvite -> LatestEventValue.RoomInvite(
|
||||||
|
timestamp = event.timestamp.toLong(),
|
||||||
|
inviterId = event.inviter?.let(::UserId),
|
||||||
|
invitedProfile = event.inviterProfile.map(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return RoomSummary(
|
return RoomSummary(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.api.spaces.LeaveSpaceHandle
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
|
@ -31,9 +32,11 @@ import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.rustcomponents.sdk.SpaceFilterUpdate
|
||||||
import org.matrix.rustcomponents.sdk.SpaceListUpdate
|
import org.matrix.rustcomponents.sdk.SpaceListUpdate
|
||||||
import org.matrix.rustcomponents.sdk.SpaceServiceInterface
|
import org.matrix.rustcomponents.sdk.SpaceServiceInterface
|
||||||
import org.matrix.rustcomponents.sdk.SpaceServiceJoinedSpacesListener
|
import org.matrix.rustcomponents.sdk.SpaceServiceJoinedSpacesListener
|
||||||
|
import org.matrix.rustcomponents.sdk.SpaceServiceSpaceFiltersListener
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import org.matrix.rustcomponents.sdk.SpaceService as ClientSpaceService
|
import org.matrix.rustcomponents.sdk.SpaceService as ClientSpaceService
|
||||||
|
|
||||||
|
|
@ -45,20 +48,20 @@ class RustSpaceService(
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : SpaceService {
|
) : SpaceService {
|
||||||
private val spaceRoomMapper = SpaceRoomMapper()
|
private val spaceRoomMapper = SpaceRoomMapper()
|
||||||
override val spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
|
private val spaceFilterMapper = SpaceServiceFilterMapper(spaceRoomMapper)
|
||||||
|
|
||||||
|
override val topLevelSpacesFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
|
||||||
private val spaceListUpdateProcessor = SpaceListUpdateProcessor(
|
private val spaceListUpdateProcessor = SpaceListUpdateProcessor(
|
||||||
spaceRoomsFlow = spaceRoomsFlow,
|
spaceRoomsFlow = topLevelSpacesFlow,
|
||||||
mapper = spaceRoomMapper,
|
mapper = spaceRoomMapper,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
override val spaceFiltersFlow = MutableSharedFlow<List<SpaceServiceFilter>>(replay = 1, extraBufferCapacity = 1)
|
||||||
runCatchingExceptions {
|
private val spaceFilterUpdateProcessor = SpaceServiceFilterUpdateProcessor(
|
||||||
innerSpaceService
|
spaceFiltersFlow = spaceFiltersFlow,
|
||||||
.topLevelJoinedSpaces()
|
mapper = spaceFilterMapper,
|
||||||
.map(spaceRoomMapper::map)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
override suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
|
|
@ -123,6 +126,13 @@ class RustSpaceService(
|
||||||
spaceListUpdateProcessor.postUpdates(updates)
|
spaceListUpdateProcessor.postUpdates(updates)
|
||||||
}
|
}
|
||||||
.launchIn(sessionCoroutineScope)
|
.launchIn(sessionCoroutineScope)
|
||||||
|
|
||||||
|
innerSpaceService
|
||||||
|
.spaceFilterListUpdate()
|
||||||
|
.onEach { updates ->
|
||||||
|
spaceFilterUpdateProcessor.postUpdates(updates)
|
||||||
|
}
|
||||||
|
.launchIn(sessionCoroutineScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,3 +152,20 @@ internal fun SpaceServiceInterface.spaceListUpdate(): Flow<List<SpaceListUpdate>
|
||||||
}.catch {
|
}.catch {
|
||||||
Timber.d(it, "spaceDiffFlow() failed")
|
Timber.d(it, "spaceDiffFlow() failed")
|
||||||
}.buffer(Channel.UNLIMITED)
|
}.buffer(Channel.UNLIMITED)
|
||||||
|
|
||||||
|
internal fun SpaceServiceInterface.spaceFilterListUpdate(): Flow<List<SpaceFilterUpdate>> =
|
||||||
|
callbackFlow {
|
||||||
|
val listener = object : SpaceServiceSpaceFiltersListener {
|
||||||
|
override fun onUpdate(filterUpdates: List<SpaceFilterUpdate>) {
|
||||||
|
trySendBlocking(filterUpdates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timber.d("Open spaceFilterDiffFlow for SpaceServiceInterface ${this@spaceFilterListUpdate}")
|
||||||
|
val taskHandle = subscribeToSpaceFilters(listener)
|
||||||
|
awaitClose {
|
||||||
|
Timber.d("Close spaceFilterDiffFlow for SpaceServiceInterface ${this@spaceFilterListUpdate}")
|
||||||
|
taskHandle.cancelAndDestroy()
|
||||||
|
}
|
||||||
|
}.catch {
|
||||||
|
Timber.d(it, "spaceFilterListUpdate() failed")
|
||||||
|
}.buffer(Channel.UNLIMITED)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.spaces
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
|
import org.matrix.rustcomponents.sdk.SpaceFilter as RustSpaceFilter
|
||||||
|
|
||||||
|
class SpaceServiceFilterMapper(
|
||||||
|
private val spaceRoomMapper: SpaceRoomMapper,
|
||||||
|
) {
|
||||||
|
fun map(spaceFilter: RustSpaceFilter): SpaceServiceFilter {
|
||||||
|
return SpaceServiceFilter(
|
||||||
|
spaceRoom = spaceRoomMapper.map(spaceFilter.spaceRoom),
|
||||||
|
level = spaceFilter.level.toInt(),
|
||||||
|
descendants = spaceFilter.descendants.map { RoomId(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.spaces
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import org.matrix.rustcomponents.sdk.SpaceFilterUpdate
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
internal class SpaceServiceFilterUpdateProcessor(
|
||||||
|
private val spaceFiltersFlow: MutableSharedFlow<List<SpaceServiceFilter>>,
|
||||||
|
private val mapper: SpaceServiceFilterMapper,
|
||||||
|
) {
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
|
suspend fun postUpdates(updates: List<SpaceFilterUpdate>) {
|
||||||
|
Timber.v("Update space filters from postUpdates (with ${updates.size} items) on ${Thread.currentThread()}")
|
||||||
|
updateSpaceFilters {
|
||||||
|
updates.forEach { update -> applyUpdate(update) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateSpaceFilters(block: MutableList<SpaceServiceFilter>.() -> Unit) =
|
||||||
|
mutex.withLock {
|
||||||
|
val spaceFilters = if (spaceFiltersFlow.replayCache.isNotEmpty()) {
|
||||||
|
spaceFiltersFlow.first().toMutableList()
|
||||||
|
} else {
|
||||||
|
mutableListOf()
|
||||||
|
}
|
||||||
|
block(spaceFilters)
|
||||||
|
spaceFiltersFlow.emit(spaceFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<SpaceServiceFilter>.applyUpdate(update: SpaceFilterUpdate) {
|
||||||
|
when (update) {
|
||||||
|
is SpaceFilterUpdate.Append -> {
|
||||||
|
val newFilters = update.values.map(mapper::map)
|
||||||
|
addAll(newFilters)
|
||||||
|
}
|
||||||
|
SpaceFilterUpdate.Clear -> clear()
|
||||||
|
is SpaceFilterUpdate.Insert -> {
|
||||||
|
val newFilter = mapper.map(update.value)
|
||||||
|
add(update.index.toInt(), newFilter)
|
||||||
|
}
|
||||||
|
SpaceFilterUpdate.PopBack -> {
|
||||||
|
removeAt(lastIndex)
|
||||||
|
}
|
||||||
|
SpaceFilterUpdate.PopFront -> {
|
||||||
|
removeAt(0)
|
||||||
|
}
|
||||||
|
is SpaceFilterUpdate.PushBack -> {
|
||||||
|
val newFilter = mapper.map(update.value)
|
||||||
|
add(newFilter)
|
||||||
|
}
|
||||||
|
is SpaceFilterUpdate.PushFront -> {
|
||||||
|
val newFilter = mapper.map(update.value)
|
||||||
|
add(0, newFilter)
|
||||||
|
}
|
||||||
|
is SpaceFilterUpdate.Remove -> {
|
||||||
|
removeAt(update.index.toInt())
|
||||||
|
}
|
||||||
|
is SpaceFilterUpdate.Reset -> {
|
||||||
|
clear()
|
||||||
|
val newFilters = update.values.map(mapper::map)
|
||||||
|
addAll(newFilters)
|
||||||
|
}
|
||||||
|
is SpaceFilterUpdate.Set -> {
|
||||||
|
val newFilter = mapper.map(update.value)
|
||||||
|
this[update.index.toInt()] = newFilter
|
||||||
|
}
|
||||||
|
is SpaceFilterUpdate.Truncate -> {
|
||||||
|
subList(update.length.toInt(), size).clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
|
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
|
||||||
import io.element.android.libraries.matrix.impl.auth.FakeUserCertificatesProvider
|
import io.element.android.libraries.matrix.impl.auth.FakeUserCertificatesProvider
|
||||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory
|
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
|
||||||
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
|
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
|
||||||
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
|
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
|
|
@ -63,7 +63,7 @@ fun TestScope.createRustMatrixClientFactory(
|
||||||
clock = FakeSystemClock(),
|
clock = FakeSystemClock(),
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = FakeAnalyticsService(),
|
||||||
featureFlagService = FakeFeatureFlagService(),
|
featureFlagService = FakeFeatureFlagService(),
|
||||||
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
|
timelineEventFilterFactory = FakeTimelineEventFilterFactory(),
|
||||||
clientBuilderProvider = clientBuilderProvider,
|
clientBuilderProvider = clientBuilderProvider,
|
||||||
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||||
workManagerScheduler = workManagerScheduler,
|
workManagerScheduler = workManagerScheduler,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import io.element.android.libraries.core.data.bytes
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient
|
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient
|
||||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService
|
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService
|
||||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory
|
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
import io.element.android.libraries.matrix.test.A_DEVICE_ID
|
import io.element.android.libraries.matrix.test.A_DEVICE_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
|
@ -150,7 +150,7 @@ class RustMatrixClientTest {
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
baseCacheDirectory = File(""),
|
baseCacheDirectory = File(""),
|
||||||
clock = FakeSystemClock(),
|
clock = FakeSystemClock(),
|
||||||
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
|
timelineEventFilterFactory = FakeTimelineEventFilterFactory(),
|
||||||
featureFlagService = FakeFeatureFlagService(),
|
featureFlagService = FakeFeatureFlagService(),
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = FakeAnalyticsService(),
|
||||||
workManagerScheduler = FakeWorkManagerScheduler(submitLambda = {}),
|
workManagerScheduler = FakeWorkManagerScheduler(submitLambda = {}),
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ internal fun aRustNotificationItem(
|
||||||
hasMention: Boolean? = false,
|
hasMention: Boolean? = false,
|
||||||
threadId: ThreadId? = null,
|
threadId: ThreadId? = null,
|
||||||
actions: List<Action>? = null,
|
actions: List<Action>? = null,
|
||||||
|
rawEvent: String = "",
|
||||||
) = NotificationItem(
|
) = NotificationItem(
|
||||||
event = event,
|
event = event,
|
||||||
senderInfo = senderInfo,
|
senderInfo = senderInfo,
|
||||||
|
|
@ -38,6 +39,7 @@ internal fun aRustNotificationItem(
|
||||||
hasMention = hasMention,
|
hasMention = hasMention,
|
||||||
threadId = threadId?.value,
|
threadId = threadId?.value,
|
||||||
actions = actions,
|
actions = actions,
|
||||||
|
rawEvent = rawEvent,
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun aRustBatchNotificationResultOk(
|
internal fun aRustBatchNotificationResultOk(
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@
|
||||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||||
|
|
||||||
import org.matrix.rustcomponents.sdk.NoHandle
|
import org.matrix.rustcomponents.sdk.NoHandle
|
||||||
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
|
import org.matrix.rustcomponents.sdk.TimelineEventFilter
|
||||||
|
|
||||||
class FakeFfiTimelineEventTypeFilter : TimelineEventTypeFilter(NoHandle)
|
class FakeFfiTimelineEventFilter : TimelineEventFilter(NoHandle)
|
||||||
|
|
@ -9,11 +9,11 @@
|
||||||
package io.element.android.libraries.matrix.impl.room
|
package io.element.android.libraries.matrix.impl.room
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineEventTypeFilter
|
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineEventFilter
|
||||||
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
|
import org.matrix.rustcomponents.sdk.TimelineEventFilter
|
||||||
|
|
||||||
class FakeTimelineEventTypeFilterFactory : TimelineEventTypeFilterFactory {
|
class FakeTimelineEventFilterFactory : TimelineEventFilterFactory {
|
||||||
override fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter {
|
override fun create(listStateEventType: List<StateEventType>): TimelineEventFilter {
|
||||||
return FakeFfiTimelineEventTypeFilter()
|
return FakeFfiTimelineEventFilter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.spaces.LeaveSpaceHandle
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||||
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.simulateLongTask
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
@ -20,7 +21,6 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
|
||||||
class FakeSpaceService(
|
class FakeSpaceService(
|
||||||
private val joinedSpacesResult: () -> Result<List<SpaceRoom>> = { lambdaError() },
|
|
||||||
private val spaceRoomListResult: (RoomId) -> SpaceRoomList = { lambdaError() },
|
private val spaceRoomListResult: (RoomId) -> SpaceRoomList = { lambdaError() },
|
||||||
private val leaveSpaceHandleResult: (RoomId) -> LeaveSpaceHandle = { lambdaError() },
|
private val leaveSpaceHandleResult: (RoomId) -> LeaveSpaceHandle = { lambdaError() },
|
||||||
private val removeChildFromSpaceResult: (RoomId, RoomId) -> Result<Unit> = { _, _ -> lambdaError() },
|
private val removeChildFromSpaceResult: (RoomId, RoomId) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||||
|
|
@ -29,16 +29,20 @@ class FakeSpaceService(
|
||||||
private val editableSpacesResult: () -> Result<List<SpaceRoom>> = { lambdaError() },
|
private val editableSpacesResult: () -> Result<List<SpaceRoom>> = { lambdaError() },
|
||||||
private val addChildToSpaceResult: (RoomId, RoomId) -> Result<Unit> = { _, _ -> lambdaError() },
|
private val addChildToSpaceResult: (RoomId, RoomId) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||||
) : SpaceService {
|
) : SpaceService {
|
||||||
private val _spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>()
|
private val _topLevelSpacesFlow = MutableSharedFlow<List<SpaceRoom>>()
|
||||||
override val spaceRoomsFlow: SharedFlow<List<SpaceRoom>>
|
override val topLevelSpacesFlow: SharedFlow<List<SpaceRoom>>
|
||||||
get() = _spaceRoomsFlow.asSharedFlow()
|
get() = _topLevelSpacesFlow.asSharedFlow()
|
||||||
|
|
||||||
suspend fun emitSpaceRoomList(value: List<SpaceRoom>) {
|
suspend fun emitTopLevelSpaces(value: List<SpaceRoom>) {
|
||||||
_spaceRoomsFlow.emit(value)
|
_topLevelSpacesFlow.emit(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = simulateLongTask {
|
private val _spaceFiltersFlow = MutableSharedFlow<List<SpaceServiceFilter>>()
|
||||||
return joinedSpacesResult()
|
override val spaceFiltersFlow: SharedFlow<List<SpaceServiceFilter>>
|
||||||
|
get() = _spaceFiltersFlow.asSharedFlow()
|
||||||
|
|
||||||
|
suspend fun emitSpaceFilters(value: List<SpaceServiceFilter>) {
|
||||||
|
_spaceFiltersFlow.emit(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>> {
|
override suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>> {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
<item quantity="one">"%d varsel"</item>
|
<item quantity="one">"%d varsel"</item>
|
||||||
<item quantity="other">"%d varsler"</item>
|
<item quantity="other">"%d varsler"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="notification_error_unified_push_unregistered_android">"UnifiedPush-varslingsdistributøren kunne ikke registreres, så du vil ikke motta varsler lenger. Sjekk varslingsinnstillingene til appen og statusen til push-distributøren."</string>
|
||||||
<string name="notification_fallback_content">"Du har nye meldinger."</string>
|
<string name="notification_fallback_content">"Du har nye meldinger."</string>
|
||||||
<string name="notification_incoming_call">"📹 Innkommende anrop"</string>
|
<string name="notification_incoming_call">"📹 Innkommende anrop"</string>
|
||||||
<string name="notification_inline_reply_failed">"** Kunne ikke sende - vennligst åpne rommet"</string>
|
<string name="notification_inline_reply_failed">"** Kunne ikke sende - vennligst åpne rommet"</string>
|
||||||
|
|
@ -37,6 +38,8 @@
|
||||||
<string name="notification_room_invite_body_with_sender">"%1$s inviterte deg til å bli med i rommet"</string>
|
<string name="notification_room_invite_body_with_sender">"%1$s inviterte deg til å bli med i rommet"</string>
|
||||||
<string name="notification_sender_me">"Meg"</string>
|
<string name="notification_sender_me">"Meg"</string>
|
||||||
<string name="notification_sender_mention_reply">"%1$s nevnt eller besvart"</string>
|
<string name="notification_sender_mention_reply">"%1$s nevnt eller besvart"</string>
|
||||||
|
<string name="notification_space_invite_body">"Inviterte deg til å bli med i området"</string>
|
||||||
|
<string name="notification_space_invite_body_with_sender">"%1$s inviterte deg til å bli med i området"</string>
|
||||||
<string name="notification_test_push_notification_content">"Du ser på varselet! Klikk på meg!"</string>
|
<string name="notification_test_push_notification_content">"Du ser på varselet! Klikk på meg!"</string>
|
||||||
<string name="notification_thread_in_room">"Tråd i %1$s"</string>
|
<string name="notification_thread_in_room">"Tråd i %1$s"</string>
|
||||||
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
|
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
<string name="a11y_your_avatar">"Váš avatar"</string>
|
<string name="a11y_your_avatar">"Váš avatar"</string>
|
||||||
<string name="action_accept">"Přijmout"</string>
|
<string name="action_accept">"Přijmout"</string>
|
||||||
<string name="action_add_caption">"Přidat titulek"</string>
|
<string name="action_add_caption">"Přidat titulek"</string>
|
||||||
|
<string name="action_add_existing_rooms">"Přidat stávající místnosti"</string>
|
||||||
<string name="action_add_to_timeline">"Přidat na časovou osu"</string>
|
<string name="action_add_to_timeline">"Přidat na časovou osu"</string>
|
||||||
<string name="action_back">"Zpět"</string>
|
<string name="action_back">"Zpět"</string>
|
||||||
<string name="action_call">"Hovor"</string>
|
<string name="action_call">"Hovor"</string>
|
||||||
|
|
@ -77,6 +78,7 @@
|
||||||
<string name="action_copy_text">"Kopírovat text"</string>
|
<string name="action_copy_text">"Kopírovat text"</string>
|
||||||
<string name="action_create">"Vytvořit"</string>
|
<string name="action_create">"Vytvořit"</string>
|
||||||
<string name="action_create_room">"Vytvořit místnost"</string>
|
<string name="action_create_room">"Vytvořit místnost"</string>
|
||||||
|
<string name="action_create_space">"Vytvořte prostor"</string>
|
||||||
<string name="action_deactivate">"Deaktivovat"</string>
|
<string name="action_deactivate">"Deaktivovat"</string>
|
||||||
<string name="action_deactivate_account">"Deaktivovat účet"</string>
|
<string name="action_deactivate_account">"Deaktivovat účet"</string>
|
||||||
<string name="action_decline">"Odmítnout"</string>
|
<string name="action_decline">"Odmítnout"</string>
|
||||||
|
|
@ -93,6 +95,7 @@
|
||||||
<string name="action_enable">"Povolit"</string>
|
<string name="action_enable">"Povolit"</string>
|
||||||
<string name="action_end_poll">"Ukončit hlasování"</string>
|
<string name="action_end_poll">"Ukončit hlasování"</string>
|
||||||
<string name="action_enter_pin">"Zadejte PIN"</string>
|
<string name="action_enter_pin">"Zadejte PIN"</string>
|
||||||
|
<string name="action_explore_public_spaces">"Prozkoumejte veřejné prostory"</string>
|
||||||
<string name="action_finish">"Dokončit"</string>
|
<string name="action_finish">"Dokončit"</string>
|
||||||
<string name="action_forgot_password">"Zapomněli jste heslo?"</string>
|
<string name="action_forgot_password">"Zapomněli jste heslo?"</string>
|
||||||
<string name="action_forward">"Přeposlat"</string>
|
<string name="action_forward">"Přeposlat"</string>
|
||||||
|
|
@ -194,6 +197,7 @@
|
||||||
<string name="common_copied_to_clipboard">"Zkopírováno do schránky"</string>
|
<string name="common_copied_to_clipboard">"Zkopírováno do schránky"</string>
|
||||||
<string name="common_copyright">"Autorská práva"</string>
|
<string name="common_copyright">"Autorská práva"</string>
|
||||||
<string name="common_creating_room">"Vytváření místnosti…"</string>
|
<string name="common_creating_room">"Vytváření místnosti…"</string>
|
||||||
|
<string name="common_creating_space">"Vytváření prostoru…"</string>
|
||||||
<string name="common_current_user_canceled_knock">"Žádost zrušena"</string>
|
<string name="common_current_user_canceled_knock">"Žádost zrušena"</string>
|
||||||
<string name="common_current_user_left_room">"Místnost opuštěna"</string>
|
<string name="common_current_user_left_room">"Místnost opuštěna"</string>
|
||||||
<string name="common_current_user_left_space">"Opustit prostor"</string>
|
<string name="common_current_user_left_space">"Opustit prostor"</string>
|
||||||
|
|
@ -295,6 +299,7 @@ Důvod: %1$s."</string>
|
||||||
<string name="common_reason">"Důvod"</string>
|
<string name="common_reason">"Důvod"</string>
|
||||||
<string name="common_recovery_key">"Klíč pro obnovení"</string>
|
<string name="common_recovery_key">"Klíč pro obnovení"</string>
|
||||||
<string name="common_refreshing">"Obnovování…"</string>
|
<string name="common_refreshing">"Obnovování…"</string>
|
||||||
|
<string name="common_removing">"Odstraňování…"</string>
|
||||||
<plurals name="common_replies">
|
<plurals name="common_replies">
|
||||||
<item quantity="other">"%1$d odpovědí"</item>
|
<item quantity="other">"%1$d odpovědí"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
@ -303,6 +308,7 @@ Důvod: %1$s."</string>
|
||||||
<string name="common_report_a_problem">"Nahlásit problém"</string>
|
<string name="common_report_a_problem">"Nahlásit problém"</string>
|
||||||
<string name="common_report_submitted">"Zpráva odeslána"</string>
|
<string name="common_report_submitted">"Zpráva odeslána"</string>
|
||||||
<string name="common_rich_text_editor">"Editor formátovaného textu"</string>
|
<string name="common_rich_text_editor">"Editor formátovaného textu"</string>
|
||||||
|
<string name="common_role">"Role"</string>
|
||||||
<string name="common_room">"Místnost"</string>
|
<string name="common_room">"Místnost"</string>
|
||||||
<string name="common_room_name">"Název místnosti"</string>
|
<string name="common_room_name">"Název místnosti"</string>
|
||||||
<string name="common_room_name_placeholder">"např. název vašeho projektu"</string>
|
<string name="common_room_name_placeholder">"např. název vašeho projektu"</string>
|
||||||
|
|
@ -319,6 +325,11 @@ Důvod: %1$s."</string>
|
||||||
<string name="common_security">"Zabezpečení"</string>
|
<string name="common_security">"Zabezpečení"</string>
|
||||||
<string name="common_seen_by">"Viděno"</string>
|
<string name="common_seen_by">"Viděno"</string>
|
||||||
<string name="common_select_account">"Vybrat účet"</string>
|
<string name="common_select_account">"Vybrat účet"</string>
|
||||||
|
<plurals name="common_selected_count">
|
||||||
|
<item quantity="one">"%1$d vybraný"</item>
|
||||||
|
<item quantity="few">"%1$d vybrané"</item>
|
||||||
|
<item quantity="other">"%1$d vybraných"</item>
|
||||||
|
</plurals>
|
||||||
<string name="common_send_to">"Odeslat do"</string>
|
<string name="common_send_to">"Odeslat do"</string>
|
||||||
<string name="common_sending">"Odesílání…"</string>
|
<string name="common_sending">"Odesílání…"</string>
|
||||||
<string name="common_sending_failed">"Odeslání se nezdařilo"</string>
|
<string name="common_sending_failed">"Odeslání se nezdařilo"</string>
|
||||||
|
|
@ -329,6 +340,7 @@ Důvod: %1$s."</string>
|
||||||
<string name="common_server_url">"URL serveru"</string>
|
<string name="common_server_url">"URL serveru"</string>
|
||||||
<string name="common_settings">"Nastavení"</string>
|
<string name="common_settings">"Nastavení"</string>
|
||||||
<string name="common_share_space">"Sdílet prostor"</string>
|
<string name="common_share_space">"Sdílet prostor"</string>
|
||||||
|
<string name="common_shared_history">"Noví členové vidí historii"</string>
|
||||||
<string name="common_shared_location">"Sdílená poloha"</string>
|
<string name="common_shared_location">"Sdílená poloha"</string>
|
||||||
<string name="common_shared_space">"Sdílený prostor"</string>
|
<string name="common_shared_space">"Sdílený prostor"</string>
|
||||||
<string name="common_signing_out">"Odhlašování"</string>
|
<string name="common_signing_out">"Odhlašování"</string>
|
||||||
|
|
@ -344,6 +356,7 @@ Důvod: %1$s."</string>
|
||||||
<string name="common_starting_chat">"Zahajování chatu…"</string>
|
<string name="common_starting_chat">"Zahajování chatu…"</string>
|
||||||
<string name="common_sticker">"Nálepka"</string>
|
<string name="common_sticker">"Nálepka"</string>
|
||||||
<string name="common_success">"Úspěch"</string>
|
<string name="common_success">"Úspěch"</string>
|
||||||
|
<string name="common_suggested">"Doporučeno"</string>
|
||||||
<string name="common_suggestions">"Návrhy"</string>
|
<string name="common_suggestions">"Návrhy"</string>
|
||||||
<string name="common_syncing">"Synchronizace"</string>
|
<string name="common_syncing">"Synchronizace"</string>
|
||||||
<string name="common_system">"Systém"</string>
|
<string name="common_system">"Systém"</string>
|
||||||
|
|
@ -380,7 +393,10 @@ Důvod: %1$s."</string>
|
||||||
<string name="common_voice_message">"Hlasová zpráva"</string>
|
<string name="common_voice_message">"Hlasová zpráva"</string>
|
||||||
<string name="common_waiting">"Čekání…"</string>
|
<string name="common_waiting">"Čekání…"</string>
|
||||||
<string name="common_waiting_for_decryption_key">"Čekání na dešifrovací klíč"</string>
|
<string name="common_waiting_for_decryption_key">"Čekání na dešifrovací klíč"</string>
|
||||||
|
<string name="common_world_readable_history">"Kdokoli může vidět historii"</string>
|
||||||
<string name="common_you">"Vy"</string>
|
<string name="common_you">"Vy"</string>
|
||||||
|
<string name="crypto_event_key_forwarded_known_profile_dialog_content">"%1$s (%2$s) sdílel(a) tuto zprávu v době, kdy jste nebyli v místnosti."</string>
|
||||||
|
<string name="crypto_event_key_forwarded_unknown_profile_dialog_content">"%1$s sdílel(a) tuto zprávu v době, kdy jste nebyli v místnosti."</string>
|
||||||
<string name="crypto_history_visible">"Tato místnost byla nastavena tak, aby noví členové mohli číst historii. %1$s"</string>
|
<string name="crypto_history_visible">"Tato místnost byla nastavena tak, aby noví členové mohli číst historii. %1$s"</string>
|
||||||
<string name="crypto_identity_change_pin_violation">"Identita uživatele %1$s se změnila. %2$s"</string>
|
<string name="crypto_identity_change_pin_violation">"Identita uživatele %1$s se změnila. %2$s"</string>
|
||||||
<string name="crypto_identity_change_pin_violation_new">"Identita uživatele %1$s %2$s se změnila. %3$s"</string>
|
<string name="crypto_identity_change_pin_violation_new">"Identita uživatele %1$s %2$s se změnila. %3$s"</string>
|
||||||
|
|
@ -474,6 +490,7 @@ Opravdu chcete pokračovat?"</string>
|
||||||
<string name="screen_share_this_location_action">"Sdílet tuto polohu"</string>
|
<string name="screen_share_this_location_action">"Sdílet tuto polohu"</string>
|
||||||
<string name="screen_space_list_description">"Prostory, které jste vytvořili nebo se k nim připojili."</string>
|
<string name="screen_space_list_description">"Prostory, které jste vytvořili nebo se k nim připojili."</string>
|
||||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||||
|
<string name="screen_space_list_empty_state_title">"Vytvořte prostory pro uspořádání místností"</string>
|
||||||
<string name="screen_space_list_parent_space">"%1$s prostor"</string>
|
<string name="screen_space_list_parent_space">"%1$s prostor"</string>
|
||||||
<string name="screen_space_list_title">"Prostory"</string>
|
<string name="screen_space_list_title">"Prostory"</string>
|
||||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Zpráva nebyla odeslána, protože ověřená identita uživatele %1$s se změnila."</string>
|
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Zpráva nebyla odeslána, protože ověřená identita uživatele %1$s se změnila."</string>
|
||||||
|
|
|
||||||
|
|
@ -389,6 +389,7 @@ Põhjus: %1$s."</string>
|
||||||
<string name="common_world_readable_history">"Kõik võivad ajalugu näha"</string>
|
<string name="common_world_readable_history">"Kõik võivad ajalugu näha"</string>
|
||||||
<string name="common_you">"Sina"</string>
|
<string name="common_you">"Sina"</string>
|
||||||
<string name="crypto_event_key_forwarded_known_profile_dialog_content">"Kuna sind polnud saatmise ajal jututoas, siis %1$s (%2$s) jagas seda sõnumit sinuga."</string>
|
<string name="crypto_event_key_forwarded_known_profile_dialog_content">"Kuna sind polnud saatmise ajal jututoas, siis %1$s (%2$s) jagas seda sõnumit sinuga."</string>
|
||||||
|
<string name="crypto_event_key_forwarded_unknown_profile_dialog_content">"%1$s jagas seda sõnumit, kuna sind ei olnud selle algse saatmise ajal jututoas."</string>
|
||||||
<string name="crypto_history_visible">"See jututuba on seadistatud sedaviisi, et ka uued liikmed saavad lugeda varasemat ajalugu. %1$s"</string>
|
<string name="crypto_history_visible">"See jututuba on seadistatud sedaviisi, et ka uued liikmed saavad lugeda varasemat ajalugu. %1$s"</string>
|
||||||
<string name="crypto_identity_change_pin_violation">"Kasutaja %1$s võrguidentiteet on lähtestatud. %2$s"</string>
|
<string name="crypto_identity_change_pin_violation">"Kasutaja %1$s võrguidentiteet on lähtestatud. %2$s"</string>
|
||||||
<string name="crypto_identity_change_pin_violation_new">"Kasutaja %1$s %2$s võrguidentiteet on lähtestatud. %3$s"</string>
|
<string name="crypto_identity_change_pin_violation_new">"Kasutaja %1$s %2$s võrguidentiteet on lähtestatud. %3$s"</string>
|
||||||
|
|
|
||||||
|
|
@ -285,8 +285,10 @@ Raison : %1$s."</string>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="common_preparing">"Préparation…"</string>
|
<string name="common_preparing">"Préparation…"</string>
|
||||||
<string name="common_privacy_policy">"Politique de confidentialité"</string>
|
<string name="common_privacy_policy">"Politique de confidentialité"</string>
|
||||||
|
<string name="common_private">"Privé"</string>
|
||||||
<string name="common_private_room">"Salon privé"</string>
|
<string name="common_private_room">"Salon privé"</string>
|
||||||
<string name="common_private_space">"Espace privé"</string>
|
<string name="common_private_space">"Espace privé"</string>
|
||||||
|
<string name="common_public">"Public"</string>
|
||||||
<string name="common_public_room">"Salon public"</string>
|
<string name="common_public_room">"Salon public"</string>
|
||||||
<string name="common_public_space">"Espace public"</string>
|
<string name="common_public_space">"Espace public"</string>
|
||||||
<string name="common_reaction">"Réaction"</string>
|
<string name="common_reaction">"Réaction"</string>
|
||||||
|
|
@ -341,6 +343,7 @@ Raison : %1$s."</string>
|
||||||
<string name="common_something_went_wrong">"Une erreur s’est produite"</string>
|
<string name="common_something_went_wrong">"Une erreur s’est produite"</string>
|
||||||
<string name="common_something_went_wrong_message">"Nous avons rencontré un problème. Veuillez réessayer."</string>
|
<string name="common_something_went_wrong_message">"Nous avons rencontré un problème. Veuillez réessayer."</string>
|
||||||
<string name="common_space">"Espace"</string>
|
<string name="common_space">"Espace"</string>
|
||||||
|
<string name="common_space_members">"Membres de l’espace"</string>
|
||||||
<string name="common_space_topic_placeholder">"Quel est le sujet de cet espace ?"</string>
|
<string name="common_space_topic_placeholder">"Quel est le sujet de cet espace ?"</string>
|
||||||
<plurals name="common_spaces">
|
<plurals name="common_spaces">
|
||||||
<item quantity="one">"%1$d Espace"</item>
|
<item quantity="one">"%1$d Espace"</item>
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@
|
||||||
<string name="a11y_your_avatar">"Din avatar"</string>
|
<string name="a11y_your_avatar">"Din avatar"</string>
|
||||||
<string name="action_accept">"Godta"</string>
|
<string name="action_accept">"Godta"</string>
|
||||||
<string name="action_add_caption">"Legg til bildetekst"</string>
|
<string name="action_add_caption">"Legg til bildetekst"</string>
|
||||||
|
<string name="action_add_existing_rooms">"Legg til eksisterende rom"</string>
|
||||||
<string name="action_add_to_timeline">"Legg til i tidslinjen"</string>
|
<string name="action_add_to_timeline">"Legg til i tidslinjen"</string>
|
||||||
<string name="action_back">"Tilbake"</string>
|
<string name="action_back">"Tilbake"</string>
|
||||||
<string name="action_call">"Ring"</string>
|
<string name="action_call">"Ring"</string>
|
||||||
|
|
@ -75,6 +76,7 @@
|
||||||
<string name="action_copy_text">"Kopier tekst"</string>
|
<string name="action_copy_text">"Kopier tekst"</string>
|
||||||
<string name="action_create">"Opprett"</string>
|
<string name="action_create">"Opprett"</string>
|
||||||
<string name="action_create_room">"Opprett rom"</string>
|
<string name="action_create_room">"Opprett rom"</string>
|
||||||
|
<string name="action_create_space">"Opprett område"</string>
|
||||||
<string name="action_deactivate">"Deaktiver"</string>
|
<string name="action_deactivate">"Deaktiver"</string>
|
||||||
<string name="action_deactivate_account">"Deaktiver kontoen"</string>
|
<string name="action_deactivate_account">"Deaktiver kontoen"</string>
|
||||||
<string name="action_decline">"Avslå"</string>
|
<string name="action_decline">"Avslå"</string>
|
||||||
|
|
@ -162,6 +164,7 @@
|
||||||
<string name="action_static_map_load">"Trykk for å laste inn kart"</string>
|
<string name="action_static_map_load">"Trykk for å laste inn kart"</string>
|
||||||
<string name="action_take_photo">"Ta bilde"</string>
|
<string name="action_take_photo">"Ta bilde"</string>
|
||||||
<string name="action_tap_for_options">"Trykk for alternativer"</string>
|
<string name="action_tap_for_options">"Trykk for alternativer"</string>
|
||||||
|
<string name="action_translate">"Oversett"</string>
|
||||||
<string name="action_try_again">"Prøv igjen"</string>
|
<string name="action_try_again">"Prøv igjen"</string>
|
||||||
<string name="action_unpin">"Løsne"</string>
|
<string name="action_unpin">"Løsne"</string>
|
||||||
<string name="action_view">"Vis"</string>
|
<string name="action_view">"Vis"</string>
|
||||||
|
|
@ -191,6 +194,7 @@
|
||||||
<string name="common_copied_to_clipboard">"Kopiert til utklippstavlen"</string>
|
<string name="common_copied_to_clipboard">"Kopiert til utklippstavlen"</string>
|
||||||
<string name="common_copyright">"Opphavsrett"</string>
|
<string name="common_copyright">"Opphavsrett"</string>
|
||||||
<string name="common_creating_room">"Oppretter rom …"</string>
|
<string name="common_creating_room">"Oppretter rom …"</string>
|
||||||
|
<string name="common_creating_space">"Oppretter område…"</string>
|
||||||
<string name="common_current_user_canceled_knock">"Forespørsel kansellert"</string>
|
<string name="common_current_user_canceled_knock">"Forespørsel kansellert"</string>
|
||||||
<string name="common_current_user_left_room">"Forlot rommet"</string>
|
<string name="common_current_user_left_room">"Forlot rommet"</string>
|
||||||
<string name="common_current_user_left_space">"Forlot område"</string>
|
<string name="common_current_user_left_space">"Forlot område"</string>
|
||||||
|
|
@ -236,6 +240,7 @@
|
||||||
<string name="common_light">"Lys"</string>
|
<string name="common_light">"Lys"</string>
|
||||||
<string name="common_line_copied_to_clipboard">"Linje kopiert til utklippstavlen"</string>
|
<string name="common_line_copied_to_clipboard">"Linje kopiert til utklippstavlen"</string>
|
||||||
<string name="common_link_copied_to_clipboard">"Lenke kopiert til utklippstavlen"</string>
|
<string name="common_link_copied_to_clipboard">"Lenke kopiert til utklippstavlen"</string>
|
||||||
|
<string name="common_link_new_device">"Koble til ny enhet"</string>
|
||||||
<string name="common_loading">"Laster inn…"</string>
|
<string name="common_loading">"Laster inn…"</string>
|
||||||
<string name="common_loading_more">"Laster inn mer…"</string>
|
<string name="common_loading_more">"Laster inn mer…"</string>
|
||||||
<plurals name="common_many_members">
|
<plurals name="common_many_members">
|
||||||
|
|
@ -248,10 +253,12 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="common_message">"Melding"</string>
|
<string name="common_message">"Melding"</string>
|
||||||
<string name="common_message_actions">"Meldingshandlinger"</string>
|
<string name="common_message_actions">"Meldingshandlinger"</string>
|
||||||
|
<string name="common_message_failed_to_send">"Sending av beskjed feilet"</string>
|
||||||
<string name="common_message_layout">"Meldingsoppsett"</string>
|
<string name="common_message_layout">"Meldingsoppsett"</string>
|
||||||
<string name="common_message_removed">"Melding fjernet"</string>
|
<string name="common_message_removed">"Melding fjernet"</string>
|
||||||
<string name="common_modern">"Moderne"</string>
|
<string name="common_modern">"Moderne"</string>
|
||||||
<string name="common_mute">"Demp"</string>
|
<string name="common_mute">"Demp"</string>
|
||||||
|
<string name="common_name">"Navn"</string>
|
||||||
<string name="common_name_and_id">"%1$s (%2$s)"</string>
|
<string name="common_name_and_id">"%1$s (%2$s)"</string>
|
||||||
<string name="common_no_results">"Ingen resultater"</string>
|
<string name="common_no_results">"Ingen resultater"</string>
|
||||||
<string name="common_no_room_name">"Ingen romnavn"</string>
|
<string name="common_no_room_name">"Ingen romnavn"</string>
|
||||||
|
|
@ -324,6 +331,7 @@
|
||||||
<string name="common_something_went_wrong">"Noe gikk galt"</string>
|
<string name="common_something_went_wrong">"Noe gikk galt"</string>
|
||||||
<string name="common_something_went_wrong_message">"Vi har støtt på et problem. Vennligst prøv igjen."</string>
|
<string name="common_something_went_wrong_message">"Vi har støtt på et problem. Vennligst prøv igjen."</string>
|
||||||
<string name="common_space">"Område"</string>
|
<string name="common_space">"Område"</string>
|
||||||
|
<string name="common_space_topic_placeholder">"Hva handler dette området om?"</string>
|
||||||
<plurals name="common_spaces">
|
<plurals name="common_spaces">
|
||||||
<item quantity="one">"%1$d Område"</item>
|
<item quantity="one">"%1$d Område"</item>
|
||||||
<item quantity="other">"%1$d Områder"</item>
|
<item quantity="other">"%1$d Områder"</item>
|
||||||
|
|
@ -331,6 +339,7 @@
|
||||||
<string name="common_starting_chat">"Starter chat…"</string>
|
<string name="common_starting_chat">"Starter chat…"</string>
|
||||||
<string name="common_sticker">"Klistremerke"</string>
|
<string name="common_sticker">"Klistremerke"</string>
|
||||||
<string name="common_success">"Suksess"</string>
|
<string name="common_success">"Suksess"</string>
|
||||||
|
<string name="common_suggested">"Foreslått"</string>
|
||||||
<string name="common_suggestions">"Forslag"</string>
|
<string name="common_suggestions">"Forslag"</string>
|
||||||
<string name="common_syncing">"Synkroniserer"</string>
|
<string name="common_syncing">"Synkroniserer"</string>
|
||||||
<string name="common_system">"System"</string>
|
<string name="common_system">"System"</string>
|
||||||
|
|
@ -368,6 +377,7 @@
|
||||||
<string name="common_waiting">"Venter…"</string>
|
<string name="common_waiting">"Venter…"</string>
|
||||||
<string name="common_waiting_for_decryption_key">"Venter på denne meldingen"</string>
|
<string name="common_waiting_for_decryption_key">"Venter på denne meldingen"</string>
|
||||||
<string name="common_you">"Du"</string>
|
<string name="common_you">"Du"</string>
|
||||||
|
<string name="crypto_history_visible">"Dette rommet er konfigurert slik at nye medlemmer kan lese historikken.%1$s"</string>
|
||||||
<string name="crypto_identity_change_pin_violation">"%1$s\'s identitet ble tilbakestilt. %2$s"</string>
|
<string name="crypto_identity_change_pin_violation">"%1$s\'s identitet ble tilbakestilt. %2$s"</string>
|
||||||
<string name="crypto_identity_change_pin_violation_new">"%1$ss %2$s-identitet ble tilbakestilt. %3$s"</string>
|
<string name="crypto_identity_change_pin_violation_new">"%1$ss %2$s-identitet ble tilbakestilt. %3$s"</string>
|
||||||
<string name="crypto_identity_change_pin_violation_new_user_id">"(%1$s)"</string>
|
<string name="crypto_identity_change_pin_violation_new_user_id">"(%1$s)"</string>
|
||||||
|
|
@ -459,6 +469,7 @@ Er du sikker på at du vil fortsette?"</string>
|
||||||
<string name="screen_share_this_location_action">"Del denne lokasjonen"</string>
|
<string name="screen_share_this_location_action">"Del denne lokasjonen"</string>
|
||||||
<string name="screen_space_list_description">"Områder du har opprettet eller blitt med i."</string>
|
<string name="screen_space_list_description">"Områder du har opprettet eller blitt med i."</string>
|
||||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||||
|
<string name="screen_space_list_empty_state_title">"Opprett område for å organisere rom"</string>
|
||||||
<string name="screen_space_list_parent_space">"%1$s område"</string>
|
<string name="screen_space_list_parent_space">"%1$s område"</string>
|
||||||
<string name="screen_space_list_title">"Områder"</string>
|
<string name="screen_space_list_title">"Områder"</string>
|
||||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Meldingen ble ikke sendt fordi %1$ss verifiserte identitet er tilbakestilt."</string>
|
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Meldingen ble ikke sendt fordi %1$ss verifiserte identitet er tilbakestilt."</string>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:82d4ec8dfa27a109e3b6e09a989b57540ee24beacc325c32707b3fd2b6310821
|
oid sha256:adad85db73f043c52ad7218ec8d67375b784cbc268dd996e984b83a9821463bf
|
||||||
size 21869
|
size 22042
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:efe2ba437dadcc651834505f585101e8b69dd34fa7f5da3da000ee0a0021cac0
|
||||||
|
size 23051
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3e1492f5df9092f52dd9ecdc38d03aee7abda581bf7c12401561a6dea5a41f86
|
oid sha256:dfce9aca0a041ce2293cb075c5069a04fc0268494f0fdd4da6b6764060c5b812
|
||||||
size 22302
|
size 22470
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ec8262321fea4ad6cf9b648e262c3359557fa8dcfd127f70e62bc705d7ec5cd4
|
oid sha256:731442c83806cf4f45a9ebeddcc3124f595213673c3f8574da03f2948bb85802
|
||||||
size 21962
|
size 22131
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:55d1487d5eab42dba662fd813786894a674b406b43520f262036b1be675378f4
|
||||||
|
size 30753
|
||||||
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