mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Merge pull request #4291 from vitorpamplona/ccr-740792fe-ptnqe9
feat(cordn): fold "Review invitations" into the Cordn card on the new-conversation screen
This commit is contained in:
+11
@@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_groups_none
|
||||
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_none_detail
|
||||
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_start
|
||||
import com.vitorpamplona.amethyst.commons.resources.cordn_groups_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_title
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.EmptyState
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
||||
@@ -110,6 +111,16 @@ fun CordnGroupListScreen(
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBarWithBackButton(stringRes(Res.string.cordn_groups_title), nav) {
|
||||
// Invitations are fetched only when someone opens that screen
|
||||
// (every call to a coordinator is metadata, spec/00.md §8), so
|
||||
// this is a plain way in, never a badge.
|
||||
IconButton(onClick = { nav.nav(Route.CordnInvitations) }) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.MoveToInbox,
|
||||
contentDescription = stringRes(Res.string.cordn_invitations_title),
|
||||
modifier = Modifier.size(22.dp),
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { nav.nav(Route.CordnCoordinators) }) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.Dns,
|
||||
|
||||
@@ -5591,7 +5591,6 @@
|
||||
<string name="new_conversation_cordn_pro_1">End-to-end šifrování pomocí MLS — koordinátor nikdy nepřečte žádnou zprávu.</string>
|
||||
<string name="new_conversation_cordn_pro_2">Jedno dohodnuté pořadí pro všechny, takže historii nemohou přeházet špatně nastavené hodiny.</string>
|
||||
<string name="new_conversation_cordn_con_1">Koordinátor zjistí, kdo je ve skupině a kdy spolu mluvíte, i když nemůže číst, co píšete.</string>
|
||||
<string name="cordn_invitations_entry">Máte pozvánku do skupiny cordn?</string>
|
||||
<string name="cordn_invitations_entry_action">Zobrazit pozvánky</string>
|
||||
<!-- cordn device migration (spec/applications/multi-device.md §9/§11), as a
|
||||
one-shot handoff rather than continuous sync. The exposure strings say
|
||||
|
||||
@@ -5361,7 +5361,6 @@
|
||||
<string name="new_conversation_cordn_pro_1">Ende-zu-Ende-verschlüsselt mit MLS — der Koordinator kann nie eine Nachricht lesen.</string>
|
||||
<string name="new_conversation_cordn_pro_2">Eine gemeinsame Reihenfolge für alle, sodass der Verlauf nicht durch eine falsch gehende Uhr durcheinandergeraten kann.</string>
|
||||
<string name="new_conversation_cordn_con_1">Der Koordinator erfährt, wer in der Gruppe ist und wann ihr schreibt, auch wenn er nicht lesen kann, was ihr sagt.</string>
|
||||
<string name="cordn_invitations_entry">In eine cordn-Gruppe eingeladen?</string>
|
||||
<string name="cordn_invitations_entry_action">Einladungen ansehen</string>
|
||||
<!-- cordn device migration (spec/applications/multi-device.md §9/§11), as a
|
||||
one-shot handoff rather than continuous sync. The exposure strings say
|
||||
|
||||
@@ -5455,7 +5455,6 @@
|
||||
<string name="new_conversation_cordn_pro_1">आरम्भ से अन्त तक रहस्यीकृत एमएलएस॰ के साथ। समायोजक सन्देश कभी पढ नहीं सकता।</string>
|
||||
<string name="new_conversation_cordn_pro_2">एक स्वीकृत अनुक्रमण सभी के लिए। जिससे इतिहास को अस्तव्यस्त नहीं किया जा सकता एक भ्रष्ट घडी से।</string>
|
||||
<string name="new_conversation_cordn_con_1">समायोजक जान लेता है समूह में कौन हैं तथा आप कब बात करते है। यद्यपि वह पढ नहीं सकता कि आप ने क्या कहा।</string>
|
||||
<string name="cordn_invitations_entry">क्या आप एक कोर्डन समूह में आमन्त्रित हैं।</string>
|
||||
<string name="cordn_invitations_entry_action">आमन्त्रण पुनरीक्षण</string>
|
||||
<!-- cordn device migration (spec/applications/multi-device.md §9/§11), as a
|
||||
one-shot handoff rather than continuous sync. The exposure strings say
|
||||
|
||||
@@ -5455,7 +5455,6 @@
|
||||
<string name="new_conversation_cordn_pro_1">Végponttól végpontig titkosított MLS-sel – a koordinátor soha nem olvashat el egyetlen üzenetet sem.</string>
|
||||
<string name="new_conversation_cordn_pro_2">Egy mindenki által elfogadott sorrend, így az előzményeket egy hibás óra sem keverheti össze.</string>
|
||||
<string name="new_conversation_cordn_con_1">A koordinátor tudja, ki van a csoportban és mikor beszélnek, még ha nem is tudja elolvasni, mit mondanak.</string>
|
||||
<string name="cordn_invitations_entry">Meghívták egy cordn csoportba?</string>
|
||||
<string name="cordn_invitations_entry_action">Meghívók áttekintése</string>
|
||||
<!-- cordn device migration (spec/applications/multi-device.md §9/§11), as a
|
||||
one-shot handoff rather than continuous sync. The exposure strings say
|
||||
|
||||
@@ -5660,7 +5660,6 @@
|
||||
<string name="new_conversation_cordn_pro_1">Szyfrowanie typu „end-to-end” z wykorzystaniem MLS — koordynator nigdy nie ma możliwości odczytania wiadomości.</string>
|
||||
<string name="new_conversation_cordn_pro_2">Jedno uzgodnione postanowienie dla wszystkich, aby historia nie mogła zostać zmieniona przez błędny zegar.</string>
|
||||
<string name="new_conversation_cordn_con_1">Koordynator wie, kto należy do grupy i kiedy ktoś zabiera głos, mimo że nie potrafi odczytać treści wypowiedzi.</string>
|
||||
<string name="cordn_invitations_entry">Zostałeś zaproszony do grupy „cordn”?</string>
|
||||
<string name="cordn_invitations_entry_action">Przejrzyj zaproszenia</string>
|
||||
<!-- cordn device migration (spec/applications/multi-device.md §9/§11), as a
|
||||
one-shot handoff rather than continuous sync. The exposure strings say
|
||||
|
||||
@@ -5359,7 +5359,6 @@
|
||||
<string name="new_conversation_cordn_pro_1">Criptografia de ponta a ponta com MLS — o coordenador nunca consegue ler uma mensagem.</string>
|
||||
<string name="new_conversation_cordn_pro_2">Uma única ordem acordada para todos, então o histórico não pode ser embaralhado por um relógio errado.</string>
|
||||
<string name="new_conversation_cordn_con_1">O coordenador fica sabendo quem está no grupo e quando vocês conversam, mesmo sem conseguir ler o que é dito.</string>
|
||||
<string name="cordn_invitations_entry">Convidado para um grupo cordn?</string>
|
||||
<string name="cordn_invitations_entry_action">Ver convites</string>
|
||||
<!-- cordn device migration (spec/applications/multi-device.md §9/§11), as a
|
||||
one-shot handoff rather than continuous sync. The exposure strings say
|
||||
|
||||
@@ -5359,7 +5359,6 @@
|
||||
<string name="new_conversation_cordn_pro_1">End-to-end-krypterad med MLS — koordinatorn kan aldrig läsa ett meddelande.</string>
|
||||
<string name="new_conversation_cordn_pro_2">En gemensam ordning för alla, så att historiken inte kan kastas om av en felaktig klocka.</string>
|
||||
<string name="new_conversation_cordn_con_1">Koordinatorn får veta vilka som är med i gruppen och när ni pratar, även om den inte kan läsa vad ni säger.</string>
|
||||
<string name="cordn_invitations_entry">Inbjuden till en cordn-grupp?</string>
|
||||
<string name="cordn_invitations_entry_action">Granska inbjudningar</string>
|
||||
<!-- cordn device migration (spec/applications/multi-device.md §9/§11), as a
|
||||
one-shot handoff rather than continuous sync. The exposure strings say
|
||||
|
||||
@@ -5543,15 +5543,14 @@
|
||||
<string name="cordn_preview_photo">Photo</string>
|
||||
<string name="cordn_preview_video">Video</string>
|
||||
<string name="cordn_preview_file">File</string>
|
||||
<string name="new_conversation_cordn_title">Cordn group</string>
|
||||
<string name="new_conversation_cordn_tagline">Encrypted group chat ordered by a coordinator you pick.</string>
|
||||
<string name="new_conversation_cordn_title">Cordn Group</string>
|
||||
<string name="new_conversation_cordn_tagline">Encrypted groups on a coordinator you pick</string>
|
||||
<string name="new_conversation_cordn_chip">Coordinated</string>
|
||||
<string name="new_conversation_cordn_best">Best for teams that want one reliable ordering of the conversation.</string>
|
||||
<string name="new_conversation_cordn_best">Teams that want one reliable ordering of the conversation.</string>
|
||||
<string name="new_conversation_cordn_cta">Create Cordn group</string>
|
||||
<string name="new_conversation_cordn_pro_1">End-to-end encrypted with MLS — the coordinator can never read a message.</string>
|
||||
<string name="new_conversation_cordn_pro_2">One agreed order for everyone, so history cannot be reshuffled by a bad clock.</string>
|
||||
<string name="new_conversation_cordn_con_1">The coordinator learns who is in the group and when you talk, even though it cannot read what you say.</string>
|
||||
<string name="cordn_invitations_entry">Invited to a cordn group?</string>
|
||||
<string name="new_conversation_cordn_pro_1">Coordinator can't read messages</string>
|
||||
<string name="new_conversation_cordn_pro_2">One agreed message order</string>
|
||||
<string name="new_conversation_cordn_con_1">Coordinator sees who talks, and when</string>
|
||||
<string name="cordn_invitations_entry_action">Review invitations</string>
|
||||
|
||||
<!-- cordn device migration (spec/applications/multi-device.md §9/§11), as a
|
||||
|
||||
+35
-43
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.commons.chats.ui
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -42,6 +42,7 @@ import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
@@ -64,7 +65,6 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_entry
|
||||
import com.vitorpamplona.amethyst.commons.resources.cordn_invitations_entry_action
|
||||
import com.vitorpamplona.amethyst.commons.resources.new_conversation_best_for
|
||||
import com.vitorpamplona.amethyst.commons.resources.new_conversation_concord_best
|
||||
@@ -164,7 +164,9 @@ private val ColorLocation = Color(0xFF0891B2)
|
||||
* One selectable conversation type. Collapsed, a row shows only the icon, name, a short tagline, and
|
||||
* a one-word [chip] naming its deciding axis (scale / device-bound / moderation / live). Tapping the
|
||||
* row expands it to reveal [bestFor] and the [pros]/[cons] before the [cta] button routes to that
|
||||
* type's existing creation (or browse) flow.
|
||||
* type's existing creation (or browse) flow. A type whose joining is its own flow (rather than
|
||||
* something that just shows up in the inbox) adds it as [secondary], an outlined button under
|
||||
* the main one, so "join" sits on the same card as "create" instead of floating elsewhere.
|
||||
*/
|
||||
@Immutable
|
||||
private class ConversationType(
|
||||
@@ -178,6 +180,13 @@ private class ConversationType(
|
||||
val pros: List<StringResource>,
|
||||
val cons: List<StringResource>,
|
||||
val route: Route,
|
||||
val secondary: SecondaryAction? = null,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
private class SecondaryAction(
|
||||
val label: StringResource,
|
||||
val route: Route,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
@@ -253,6 +262,13 @@ private val conversationSections =
|
||||
// to it, and this is the first place it can be said.
|
||||
cons = listOf(Res.string.new_conversation_cordn_con_1),
|
||||
route = Route.CordnCreateGroup,
|
||||
// Being invited is the other half of "start a
|
||||
// conversation", so it sits on the same card as
|
||||
// creating one. It is a button you press, never a
|
||||
// count: a count would take a call to every
|
||||
// coordinator, and every call to a coordinator is
|
||||
// metadata (spec/00.md §8).
|
||||
secondary = SecondaryAction(Res.string.cordn_invitations_entry_action, Route.CordnInvitations),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -351,50 +367,11 @@ fun NewConversationScreen(nav: INav) {
|
||||
expanded = expandedId == type.title.key,
|
||||
onToggle = { expandedId = if (expandedId == type.title.key) "" else type.title.key },
|
||||
onCreate = { nav.nav(type.route) },
|
||||
onSecondary = { type.secondary?.let { nav.nav(it.route) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Being invited is the other half of "start a conversation", so it
|
||||
// belongs on the screen people reach for when they want one --
|
||||
// not buried in settings. The count is deliberately absent: it
|
||||
// would take a call to every coordinator, and every call to a
|
||||
// coordinator is metadata (spec/00.md §8).
|
||||
item(key = "cordn-invitations") {
|
||||
CordnInvitationsEntry(onClick = { nav.nav(Route.CordnInvitations) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CordnInvitationsEntry(onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 6.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.Dns,
|
||||
contentDescription = null,
|
||||
tint = ColorCordn,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = stringRes(Res.string.cordn_invitations_entry),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(Res.string.cordn_invitations_entry_action),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.grayText,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,6 +382,7 @@ private fun ConversationRow(
|
||||
expanded: Boolean,
|
||||
onToggle: () -> Unit,
|
||||
onCreate: () -> Unit,
|
||||
onSecondary: () -> Unit,
|
||||
) {
|
||||
// Lighten the accent in dark mode so chip/label/checkmark text stays legible on the dark ground;
|
||||
// the solid icon tile keeps the saturated base color in both themes.
|
||||
@@ -493,6 +471,20 @@ private fun ConversationRow(
|
||||
) {
|
||||
Text(stringRes(type.cta), fontWeight = FontWeight.Bold)
|
||||
}
|
||||
|
||||
// Stacked rather than side by side: at phone width each half
|
||||
// would be ~150dp and the bold primary label would wrap.
|
||||
type.secondary?.let { secondary ->
|
||||
OutlinedButton(
|
||||
onClick = onSecondary,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
border = BorderStroke(1.dp, accent),
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = accent),
|
||||
) {
|
||||
Text(stringRes(secondary.label), fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user