diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9668b0d83..d0a6e63bc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -158,6 +158,7 @@
android:screenOrientation="portrait" />
) {
return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
index 702762b84..3ca341910 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
@@ -7,6 +7,7 @@ import com.ramcosta.composedestinations.navigation.dependency
import dagger.hilt.android.AndroidEntryPoint
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.groups.compose.EditGroupViewModel
+import org.thoughtcrime.securesms.groups.destinations.EditClosedGroupScreenDestination
import org.thoughtcrime.securesms.ui.AppTheme
import javax.inject.Inject
@@ -19,8 +20,13 @@ class EditClosedGroupActivity: PassphraseRequiredActionBarActivity() {
@Inject lateinit var factory: EditGroupViewModel.Factory
+ private fun onFinish() {
+ finish()
+ }
+
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
setContent {
+
AppTheme {
DestinationsNavHost(
navGraph = NavGraphs.editGroup,
@@ -28,6 +34,9 @@ class EditClosedGroupActivity: PassphraseRequiredActionBarActivity() {
dependency(NavGraphs.editGroup) {
factory.create(intent.getStringExtra(groupIDKey)!!)
}
+ dependency(EditClosedGroupScreenDestination) {
+ ::onFinish
+ }
}
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt
index c46d5b2e5..92a441c44 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt
@@ -77,11 +77,11 @@ fun LazyListScope.multiSelectMemberList(
verticalAlignment = CenterVertically
) {
ContactPhoto(
- contact = contact,
+ contact.sessionID,
modifier = Modifier
.size(48.dp)
)
- MemberName(name = contact.getSearchName())
+ MemberName(name = contact.getSearchName(), modifier = Modifier.padding(16.dp))
RadioButton(selected = isSelected, onClick = update)
}
}
@@ -89,13 +89,13 @@ fun LazyListScope.multiSelectMemberList(
@Composable
fun RowScope.MemberName(
- name: String
+ name: String,
+ modifier: Modifier = Modifier
) = Text(
text = name,
fontWeight = FontWeight.Bold,
- modifier = Modifier
+ modifier = modifier
.weight(1f)
- .padding(16.dp)
.align(CenterVertically)
)
@@ -120,12 +120,12 @@ fun LazyListScope.deleteMemberList(
items(contacts) { contact ->
Row(modifier.fillMaxWidth()) {
ContactPhoto(
- contact,
+ contact.sessionID,
modifier = Modifier
.size(48.dp)
.align(CenterVertically)
)
- MemberName(name = contact.getSearchName())
+ MemberName(name = contact.getSearchName(), modifier = Modifier.padding(16.dp))
Image(
painterResource(id = R.drawable.ic_baseline_close_24),
null,
@@ -143,7 +143,7 @@ fun LazyListScope.deleteMemberList(
@Composable
-fun RowScope.ContactPhoto(contact: Contact, modifier: Modifier = Modifier) {
+fun RowScope.ContactPhoto(sessionId: String, modifier: Modifier = Modifier) {
return if (LocalPreviewMode.current) {
Image(
painterResource(id = R.drawable.ic_profile_default),
@@ -158,8 +158,8 @@ fun RowScope.ContactPhoto(contact: Contact, modifier: Modifier = Modifier) {
} else {
val context = LocalContext.current
// Ideally we migrate to something that doesn't require recipient, or get contact photo another way
- val recipient = remember(contact) {
- Recipient.from(context, Address.fromSerialized(contact.sessionID), false)
+ val recipient = remember(sessionId) {
+ Recipient.from(context, Address.fromSerialized(sessionId), false)
}
Avatar(recipient)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroup.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroup.kt
index 433bb5588..406ba8dfd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroup.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroup.kt
@@ -34,6 +34,7 @@ import network.loki.messenger.R
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
+import org.thoughtcrime.securesms.ui.CloseIcon
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.EditableAvatar
import org.thoughtcrime.securesms.ui.NavigationBar
@@ -69,7 +70,7 @@ fun CreateGroup(
NavigationBar(
title = stringResource(id = R.string.activity_create_group_title),
onBack = onBack,
- onClose = onClose
+ actionElement = { CloseIcon(onClose) }
)
// Editable avatar (future chunk)
EditableAvatar(
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroup.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroup.kt
index 2cb5942d9..9f00a182a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroup.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroup.kt
@@ -1,15 +1,29 @@
package org.thoughtcrime.securesms.groups.compose
-import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.Scaffold
import androidx.compose.material.Text
+import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
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 androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.cash.molecule.RecompositionMode.Immediate
@@ -19,35 +33,108 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import network.loki.messenger.R
+import network.loki.messenger.libsession_util.util.GroupMember
import org.session.libsession.database.StorageProtocol
+import org.thoughtcrime.securesms.ui.Divider
+import org.thoughtcrime.securesms.ui.NavigationBar
@EditGroupNavGraph(start = true)
@Composable
@Destination
fun EditClosedGroupScreen(
navigator: DestinationsNavigator,
- viewModel: EditGroupViewModel
+ viewModel: EditGroupViewModel,
+ onFinish: () -> Unit
) {
val group by viewModel.viewState.collectAsState()
val viewState = group.viewState
val eventSink = group.eventSink
- when (viewState) {
- is EditGroupViewState.Display -> {
- Text(
- text = viewState.text,
- modifier = Modifier.fillMaxSize()
- .clickable {
- eventSink(Unit)
- }
- )
- }
- else -> {
+ EditGroupView(
+ onBack = {
+ onFinish()
+ },
+ viewState = viewState as EditGroupViewState.Group
+ )
+}
+
+@Composable
+fun EditGroupView(
+ onBack: ()->Unit,
+ viewState: EditGroupViewState.Group,
+) {
+ val scaffoldState = rememberScaffoldState()
+
+ Scaffold(
+ scaffoldState = scaffoldState,
+ topBar = {
+ NavigationBar(
+ title = stringResource(id = R.string.activity_edit_closed_group_title),
+ onBack = onBack
+ ) {
+ Text(
+ text = stringResource(id = R.string.menu_done_button),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(2.dp)
+ .align(Alignment.Center),
+ textAlign = TextAlign.Center,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ ) { paddingValues ->
+ Column(modifier = Modifier.padding(paddingValues)) {
+ // Group name title
+ Text(
+ text = viewState.groupName,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ fontSize = 26.sp,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center
+ )
+ // members header
+ Divider()
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(60.dp)
+ ) {
+ Text(
+ text = stringResource(id = R.string.activity_edit_closed_group_edit_members),
+ modifier = Modifier
+ .weight(1f)
+ .padding(horizontal = 24.dp)
+ .align(CenterVertically),
+ )
+ // if admin add member outline button TODO
+ }
+ Divider()
+ LazyColumn(modifier = Modifier) {
+
+ items(viewState.memberStateList) { member ->
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp, horizontal = 16.dp)) {
+ ContactPhoto(member.memberSessionId)
+ Column(modifier = Modifier.weight(1f)) {
+ Row(modifier = Modifier.fillMaxSize()) {
+ MemberName(name = member.memberName ?: member.memberSessionId, modifier = Modifier.align(CenterVertically))
+ }
+ }
+ }
+ }
+
+ }
}
}
-
}
@@ -58,19 +145,41 @@ class EditGroupViewModel @AssistedInject constructor(
val viewState = viewModelScope.launchMolecule(Immediate) {
- val closedGroup = remember {
- storage.getLibSessionClosedGroup(groupSessionId)
+ val currentUserId = rememberSaveable {
+ storage.getUserPublicKey()!!
}
- var displayText by remember {
- mutableStateOf(closedGroup!!.groupSessionId.hexString())
+ val closedGroupInfo = remember {
+ storage.getLibSessionClosedGroup(groupSessionId)!!
}
- EditGroupState(EditGroupViewState.Display(displayText)) { event ->
- when (event) {
- Unit -> displayText = "different"
+ val closedGroup = remember(closedGroupInfo) {
+ storage.getClosedGroupDisplayInfo(groupSessionId)!!
+ }
+
+ val closedGroupMembers = remember(closedGroupInfo) {
+ storage.getMembers(groupSessionId).map { member ->
+ MemberViewModel(
+ memberName = member.name,
+ memberSessionId = member.sessionId,
+ currentUser = member.sessionId == currentUserId,
+ memberState = memberStateOf(member)
+ )
}
}
+
+ val name = closedGroup.name
+ val description = closedGroup.description
+
+ EditGroupState(
+ EditGroupViewState.Group(
+ groupName = name,
+ groupDescription = description,
+ memberStateList = closedGroupMembers,
+ )
+ ) { event ->
+
+ }
}
@AssistedFactory
@@ -85,7 +194,37 @@ data class EditGroupState(
val eventSink: (Unit)->Unit
)
+data class MemberViewModel(
+ val memberName: String?,
+ val memberSessionId: String,
+ val memberState: MemberState,
+ val currentUser: Boolean,
+)
+
+enum class MemberState {
+ InviteSent,
+ Inviting, // maybe just use these in view
+ InviteFailed,
+ PromotionSent,
+ Promoting, // maybe just use these in view
+ PromotionFailed,
+ Admin,
+ Member
+}
+
+fun memberStateOf(member: GroupMember): MemberState = when {
+ member.invitePending -> MemberState.InviteSent
+ member.inviteFailed -> MemberState.InviteFailed
+ member.promotionPending -> MemberState.PromotionSent
+ member.promotionFailed -> MemberState.PromotionFailed
+ member.admin -> MemberState.Admin
+ else -> MemberState.Member
+}
+
sealed class EditGroupViewState {
- data object NoOp: EditGroupViewState()
- data class Display(val text: String) : EditGroupViewState()
+ data class Group(
+ val groupName: String,
+ val groupDescription: String?,
+ val memberStateList: List,
+ ): EditGroupViewState()
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/SelectContacts.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/SelectContacts.kt
index 46247218c..9653b3b0c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/SelectContacts.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/SelectContacts.kt
@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp
import network.loki.messenger.R
import org.session.libsession.messaging.contacts.Contact
import org.thoughtcrime.securesms.home.search.getSearchName
+import org.thoughtcrime.securesms.ui.CloseIcon
import org.thoughtcrime.securesms.ui.NavigationBar
import org.thoughtcrime.securesms.ui.SearchBar
@@ -54,7 +55,7 @@ fun SelectContacts(
NavigationBar(
title = stringResource(id = R.string.activity_create_closed_group_select_contacts),
onBack = onBack,
- onClose = onClose
+ actionElement = { CloseIcon(onClose) }
)
LazyColumn(modifier = Modifier.weight(1f)) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
index 1de4d62a9..e1d5c1e83 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
@@ -5,11 +5,13 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -30,7 +32,9 @@ import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
+import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -264,12 +268,10 @@ fun SearchBar(
@Composable
fun NavigationBar(
- //
title: String,
titleAlignment: Alignment = Alignment.Center,
- // if
onBack: (() -> Unit)? = null,
- onClose: (()->Unit)? = null,
+ actionElement: (@Composable BoxScope.() -> Unit)? = null
) {
Row(
Modifier
@@ -278,6 +280,8 @@ fun NavigationBar(
// Optional back button, layout should still take up space
Box(modifier = Modifier
.fillMaxHeight()
+ .aspectRatio(1.0f, true)
+ .padding(16.dp)
) {
if (onBack != null) {
Icon(
@@ -286,8 +290,10 @@ fun NavigationBar(
id = R.string.new_conversation_dialog_back_button_content_description
),
Modifier
- .clickable { onBack() }
- .padding(16.dp)
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = rememberRipple(bounded = false, radius = 16.dp),
+ ) { onBack() }
.align(Alignment.Center)
)
}
@@ -305,32 +311,41 @@ fun NavigationBar(
fontWeight = FontWeight.Bold
)
}
- // Optional close button
- Box(modifier = Modifier
- .fillMaxHeight()
- .align(Alignment.CenterVertically)
- ) {
- if (onClose != null) {
- Icon(
- painter = painterResource(id = R.drawable.ic_baseline_close_24),
- contentDescription = stringResource(
- id = R.string.new_conversation_dialog_close_button_content_description
- ),
- Modifier
- .clickable { onClose() }
- .padding(16.dp)
- .align(Alignment.Center)
- )
+ // Optional action
+ if (actionElement != null) {
+ Box(modifier = Modifier
+ .fillMaxHeight()
+ .align(Alignment.CenterVertically)
+ .aspectRatio(1.0f, true),
+ contentAlignment = Alignment.Center
+ ) {
+ actionElement(this)
}
}
}
}
+@Composable
+fun BoxScope.CloseIcon(onClose: ()->Unit) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_baseline_close_24),
+ contentDescription = stringResource(
+ id = R.string.new_conversation_dialog_close_button_content_description
+ ),
+ Modifier
+ .clickable { onClose() }
+ .align(Alignment.Center)
+ .padding(16.dp)
+ )
+}
+
@Composable
@Preview
fun PreviewNavigationBar(@PreviewParameter(provider = ThemeResPreviewParameterProvider::class) themeResId: Int) {
PreviewTheme(themeResId = themeResId) {
- NavigationBar(title = "Create Group", onBack = {}, onClose = {})
+ NavigationBar(title = "Create Group", onBack = {}, actionElement = {
+ CloseIcon {}
+ })
}
}
diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt
index 59ab227b1..d0c5ee6a5 100644
--- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt
+++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt
@@ -4,6 +4,7 @@ import network.loki.messenger.libsession_util.util.BaseCommunityInfo
import network.loki.messenger.libsession_util.util.ConfigPush
import network.loki.messenger.libsession_util.util.Contact
import network.loki.messenger.libsession_util.util.Conversation
+import network.loki.messenger.libsession_util.util.GroupDisplayInfo
import network.loki.messenger.libsession_util.util.GroupInfo
import network.loki.messenger.libsession_util.util.GroupMember
import network.loki.messenger.libsession_util.util.UserPic
@@ -262,6 +263,17 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer), Closeable {
external fun setDescription(newDescription: String)
external fun setProfilePic(newProfilePic: UserPic)
external fun storageNamespace(): Long
+
+ fun displayInfo() = GroupDisplayInfo(
+ id = id(),
+ name = getName(),
+ description = null,
+ created = getCreated(),
+ destroyed = isDestroyed(),
+ expiryTimer = getExpiryTimer(),
+ profilePic = getProfilePic()
+ )
+
override fun close() {
free()
}
diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/GroupDisplayInfo.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/GroupDisplayInfo.kt
new file mode 100644
index 000000000..033614f14
--- /dev/null
+++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/GroupDisplayInfo.kt
@@ -0,0 +1,13 @@
+package network.loki.messenger.libsession_util.util
+
+import org.session.libsignal.utilities.SessionId
+
+data class GroupDisplayInfo(
+ val id: SessionId,
+ val created: Long?,
+ val expiryTimer: Long?,
+ val name: String,
+ val description: String?,
+ val destroyed: Boolean,
+ val profilePic: UserPic
+)
\ No newline at end of file
diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt
index 46c516732..b904f2309 100644
--- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt
+++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt
@@ -3,7 +3,7 @@ package org.session.libsession.database
import android.content.Context
import android.net.Uri
import network.loki.messenger.libsession_util.Config
-import network.loki.messenger.libsession_util.util.Conversation
+import network.loki.messenger.libsession_util.util.GroupDisplayInfo
import network.loki.messenger.libsession_util.util.GroupInfo
import org.session.libsession.messaging.BlindedIdMapping
import org.session.libsession.messaging.calls.CallMessageType
@@ -34,7 +34,6 @@ import org.session.libsession.utilities.recipients.Recipient.RecipientSettings
import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.messages.SignalServiceAttachmentPointer
import org.session.libsignal.messages.SignalServiceGroup
-import org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.ClosedGroup
import org.session.libsignal.utilities.SessionId
import org.session.libsignal.utilities.guava.Optional
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
@@ -165,6 +164,7 @@ interface StorageProtocol {
fun acceptClosedGroupInvite(groupId: SessionId, name: String, authData: ByteArray, invitingAdmin: SessionId)
fun setGroupInviteCompleteIfNeeded(approved: Boolean, invitee: String, closedGroup: SessionId)
fun getLibSessionClosedGroup(groupSessionId: String): GroupInfo.ClosedGroupInfo?
+ fun getClosedGroupDisplayInfo(groupSessionId: String): GroupDisplayInfo?
// Groups
fun getAllGroups(includeInactive: Boolean): List