Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,8 @@ sealed interface ChatStep : FlowStep, Parcelable {
* the flow is already open on the group, and every step here reads it off the conversation's
* view model.
*
* The design node lists four rows; only [EditGroupPicture] and [EditGroupName] are built.
* Membership card, description and social links have no field on `EditChatRequest` in
* flipcash2 0.11.0, so there is nothing for them to write.
* Name, description, picture and cover each open their own editor and save on their own;
* the balance requirements are shown read-only, since nothing on `EditChatRequest` writes them.
*/
@Parcelize
@Serializable
Expand All @@ -112,6 +111,16 @@ sealed interface ChatStep : FlowStep, Parcelable {
@Serializable
data object EditGroupPicture : ChatStep

/** The group's banner — the same pick, upload and `EditChat` as the picture, for the cover. */
@Parcelize
@Serializable
data object EditGroupCover : ChatStep

/** The group's description, up to 160 characters; emptying it clears it. */
@Parcelize
@Serializable
data object EditGroupDescription : ChatStep

/**
* The full emoji picker for a message's reaction, opened from the pill row's "+" or the
* quick strip's "+". A plain [Sheet] rather than [WrapContentSheet]: the picker's catalog
Expand Down
66 changes: 40 additions & 26 deletions apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1138,39 +1138,53 @@
<string name="error_title_groupCreateFailed">Something Went Wrong</string>
<string name="error_description_groupCreateFailed">We couldn\'t create this group. Please try again.</string>

<!-- Editing a group (node 10187:110373). The design titles this "Edit Community"; the app
calls the same thing a Group everywhere else (see title_newPublicGroup, hint_groupName),
so it is a Group here too rather than introducing a second word for one concept.

The node lists four rows — Icon, Membership Card, Description, Social Links. Only the
first ships, as Picture: that is the field it writes (EditChatRequest.picture) and the
word the rest of the app uses. The other three have no field on EditChatRequest in
flipcash2 0.11.0. Name is not in the node at all and takes the first row's styling. -->
<!-- Editing a group. Titled "Edit Group" rather than the design's "Edit Community": the app
calls the same thing a Group everywhere else (see title_newPublicGroup, hint_groupName). -->
<string name="title_editGroup">Edit Group</string>
<string name="title_editGroupPicture">Picture</string>
<string name="title_editGroupName">Name</string>
<!-- Each edit screen's own title. The row above says what it changes; this says what to do.
They are placed differently because the account flow places them differently for the same
two edits: PhotoSelectionScreen titles its app bar, NameEntryScreen leaves the bar bare and
heads the field itself. The name screen also borrows that screen's phrasing — it heads a
focused field, so it asks for the value the way title_profileNameSelection does. -->
<string name="title_setGroupPicture">Set Picture</string>
<string name="label_editGroupName">Group name</string>
<string name="placeholder_editGroupName">Add a name</string>
<string name="label_editGroupDescription">Description</string>
<string name="placeholder_editGroupDescription">Add a description</string>
<string name="footer_groupBalanceRequirements">People keep their balance. These amounts determine who can join and send messages.</string>
<!-- Each edit screen's own title. The name screen leaves the app bar bare and heads the field
itself, as NameEntryScreen does for the profile name. -->
<string name="title_setGroupPicture">Picture</string>
<string name="title_setGroupDescription">Description</string>
<string name="title_setGroupName">What\'s the group name?</string>

<!-- Both edits are confirmed before the call: a rename or a new picture is visible to the whole
group, so Save opens the prompt and the prompt's action is what sends it. -->
<string name="hint_groupNameEdit">Group Name</string>
<string name="hint_groupDescription">Add a description</string>
<string name="label_groupNameRemaining">%1$d characters</string>
<string name="error_groupDescriptionNotAllowed">This description isn\'t allowed. Try different wording</string>

<!-- Every edit is confirmed before the call: a change to the name, picture, cover or description
is visible to the whole group, so Save opens the prompt and the prompt's action is what
sends it. -->
<string name="prompt_title_changeGroupName">Change Group Name?</string>
<string name="prompt_description_changeGroupName">This will change the group name for everyone in it</string>
<string name="action_changeGroupName">Change Name</string>
<string name="action_changeGroupName">Change Group Name</string>
<string name="prompt_title_changeGroupPicture">Change Group Picture?</string>
<string name="prompt_description_changeGroupPicture">This will change the group picture for everyone in it</string>
<string name="action_changeGroupPicture">Change Picture</string>
<!-- EditChat answered DENIED: canEdit was false, or stopped being true since the menu was drawn. -->
<string name="error_title_groupEditDenied">Can\'t Edit This Group</string>
<string name="error_description_groupEditDenied">You don\'t have permission to edit this group</string>
<string name="action_changeGroupPicture">Change Group Picture</string>
<string name="prompt_title_changeGroupCover">Change Group Cover?</string>
<string name="prompt_description_changeGroupCover">This will change the group cover for everyone in it</string>
<string name="action_changeGroupCover">Change Group Cover</string>
<string name="prompt_title_changeGroupDescription">Change Group Description?</string>
<string name="prompt_description_changeGroupDescription">This will change the group description for everyone in it</string>
<string name="action_changeGroupDescription">Change Group Description</string>
<!-- EditChat answered DENIED: canEdit was false, or stopped being true since the screen was drawn. -->
<string name="error_title_groupEditDenied">You Can\'t Edit This Group</string>
<string name="error_description_groupEditDenied">Try again later</string>
<string name="error_title_groupEditNotFound">This Group No Longer Exists</string>
<string name="error_description_groupEditNotFound">It may have been deleted</string>
<string name="error_title_groupNameNotAllowed">This Name is Not Allowed</string>
<string name="error_description_groupNameNotAllowed">Try a different group name</string>
<string name="error_title_groupPhotoNotAllowed">This Photo Isn\'t Allowed</string>
<string name="error_description_groupPhotoNotAllowed">Try a different photo</string>
<!-- Anything EditChat can fail on that the screen cannot explain, including the transport. -->
<string name="error_title_groupEditFailed">Something Went Wrong</string>
<string name="error_description_groupEditFailed">We couldn\'t save your changes. Please try again.</string>
<string name="error_title_groupNameSaveFailed">Couldn\'t Save This Name</string>
<string name="error_title_groupPhotoSaveFailed">Couldn\'t Save This Photo</string>
<string name="error_title_groupDescriptionSaveFailed">Couldn\'t Save the Description</string>
<string name="error_description_groupEditFailed">Try again</string>

<!-- Handing out the group's invite link, straight after it is created (node 10127:118315). -->
<string name="title_inviteToJoinGroup">Invite to Join Group</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import com.flipcash.app.messenger.internal.screens.cash.ChatAmountEntryContent
import com.flipcash.app.messenger.internal.screens.profile.ChatProfileScreen
import com.flipcash.app.messenger.internal.screens.profile.ChatProfileViewModel
import com.flipcash.app.messenger.internal.screens.profile.GroupProfileScreen
import com.flipcash.app.messenger.internal.screens.profile.edit.EditGroupCoverScreen
import com.flipcash.app.messenger.internal.screens.profile.edit.EditGroupDescriptionScreen
import com.flipcash.app.messenger.internal.screens.profile.edit.EditGroupNameScreen
import com.flipcash.app.messenger.internal.screens.profile.edit.EditGroupPictureScreen
import com.flipcash.app.messenger.internal.screens.profile.edit.EditGroupScreen
Expand Down Expand Up @@ -115,14 +117,20 @@ private fun chatEntryProvider(
FlowGroupProfileScreen()
}
annotatedEntry<ChatStep.EditGroup> {
EditGroupScreen()
FlowEditGroupScreen()
}
annotatedEntry<ChatStep.EditGroupName> {
FlowEditGroupNameScreen()
}
annotatedEntry<ChatStep.EditGroupPicture> {
FlowEditGroupPictureScreen()
}
annotatedEntry<ChatStep.EditGroupCover> {
FlowEditGroupCoverScreen()
}
annotatedEntry<ChatStep.EditGroupDescription> {
FlowEditGroupDescriptionScreen()
}

annotatedEntry<ChatStep.ReactionPicker> { step ->
FlowReactionPickerScreen(step.messageId)
Expand Down Expand Up @@ -312,9 +320,24 @@ private fun FlowGroupProfileScreen() {
GroupProfileScreen(flowSharedViewModel<ChatViewModel>())
}

// Both edit steps read the chat they are editing off the flow's shared ChatViewModel, the same way
// The edit steps read the chat they are editing off the flow's shared ChatViewModel, the same way
// the profile above them does, and keep the edit itself in their own nav-entry-scoped view model.
// EditGroupScreen needs neither and is registered directly.
// The list itself holds no edit, but reads the group and its permissions the same way.
@Composable
private fun FlowEditGroupScreen() {
EditGroupScreen(flowSharedViewModel<ChatViewModel>())
}

@Composable
private fun FlowEditGroupCoverScreen() {
EditGroupCoverScreen(flowSharedViewModel<ChatViewModel>())
}

@Composable
private fun FlowEditGroupDescriptionScreen() {
EditGroupDescriptionScreen(flowSharedViewModel<ChatViewModel>())
}

@Composable
private fun FlowEditGroupNameScreen() {
EditGroupNameScreen(flowSharedViewModel<ChatViewModel>())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.flipcash.app.messenger.internal.screens.profile

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.flipcash.app.messenger.internal.RuleCurrency
import com.flipcash.features.messenger.R
import com.flipcash.services.models.chat.ChatRuleRequirement
import com.getcode.opencode.model.financial.Fiat
import com.getcode.solana.keys.Mint

/**
* A rule's amount as the profile writes it: "$10", "$2.50", and "$10 of NYC" for a token other
* than USDF whose name is known. A rule naming no mint, or the reserve, states the amount alone,
* as does any rule when [namesToken] is false because the card already names the token.
*/
@Composable
internal fun holdingLabel(
rule: ChatRuleRequirement.MinimumBalance,
tokens: Map<List<Byte>, RuleCurrency>,
namesToken: Boolean = true,
): String = holdingLabel(rule.amount, rule.mints.firstOrNull()?.takeIf { namesToken }?.let { Mint(it.bytes) }, tokens)

@Composable
internal fun holdingLabel(
amount: Fiat,
mint: Mint?,
tokens: Map<List<Byte>, RuleCurrency>,
): String {
val formatted = amount.formatted(Fiat.FormattingRule.Truncated)
val name = mint?.takeUnless { it == Mint.usdf }?.let { tokens[it.bytes] }?.name
?: return formatted
return stringResource(R.string.label_chat_preview_cash_suffix, formatted, name)
}

Original file line number Diff line number Diff line change
Expand Up @@ -394,30 +394,6 @@ internal fun GroupProfileScreen(viewModel: ChatViewModel) {
}
}

/**
* A rule's amount as the profile writes it: "$10", "$2.50", and "$10 of NYC" for a token other
* than USDF whose name is known. A rule naming no mint, or the reserve, states the amount alone,
* as does any rule when [namesToken] is false because the card already names the token.
*/
@Composable
private fun holdingLabel(
rule: ChatRuleRequirement.MinimumBalance,
tokens: Map<List<Byte>, com.flipcash.app.messenger.internal.RuleCurrency>,
namesToken: Boolean = true,
): String = holdingLabel(rule.amount, rule.mints.firstOrNull()?.takeIf { namesToken }?.let { Mint(it.bytes) }, tokens)

@Composable
private fun holdingLabel(
amount: Fiat,
mint: Mint?,
tokens: Map<List<Byte>, com.flipcash.app.messenger.internal.RuleCurrency>,
): String {
val formatted = amount.formatted(Fiat.FormattingRule.Truncated)
val name = mint?.takeUnless { it == Mint.usdf }?.let { tokens[it.bytes] }?.name
?: return formatted
return stringResource(R.string.label_chat_preview_cash_suffix, formatted, name)
}

/** The ⋯ button's menu: Encryption and Mute, then a divider and the red Report. */
@Composable
private fun GroupProfileMenu(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,12 @@ internal object ChatTitle {
fun lengthOf(input: CharSequence): Int =
normalize(input).let { it.codePointCount(0, it.length) }

/** How many more code points the field takes, counted on the text as typed; never negative. */
fun remaining(input: CharSequence): Int =
(MAX_LENGTH - input.toString().let { it.codePointCount(0, it.length) }).coerceAtLeast(0)

/** The counter only appears once the end is in sight. */
const val COUNTER_THRESHOLD = 10

fun isValid(input: CharSequence): Boolean = lengthOf(input) in MIN_LENGTH..MAX_LENGTH
}
Loading
Loading