feat: creating the basic storage code to test creating group in configs and pushing

pull/1403/head
0x330a 2 years ago
parent 08c06a724c
commit 026f0056b9
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C

@ -880,6 +880,31 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
DatabaseComponent.get(context).groupDatabase().create(groupId, title, members, avatar, relay, admins, formationTimestamp) DatabaseComponent.get(context).groupDatabase().create(groupId, title, members, avatar, relay, admins, formationTimestamp)
} }
override fun createNewGroup(groupName: String, groupDescription: String, members: List<SessionId>): Long? {
val userGroups = configFactory.userGroups ?: return null
val ourSessionId = getUserPublicKey() ?: return null
val group = userGroups.createGroup()
userGroups.set(group)
val groupInfo = configFactory.groupInfoConfig(group.groupSessionId) ?: return null
val groupMembers = configFactory.groupMemberConfig(group.groupSessionId) ?: return null
val groupKeys = configFactory.groupKeysConfig(group.groupSessionId) ?: return null
with (groupInfo) {
setName(groupName)
setDescription(groupDescription)
}
groupMembers.set(
LibSessionGroupMember(ourSessionId, "admin", admin = true)
)
// Test the sending
val userGroupsUpdate =
TODO()
}
override fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map<String, Boolean>, formationTimestamp: Long, encryptionKeyPair: ECKeyPair) { override fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map<String, Boolean>, formationTimestamp: Long, encryptionKeyPair: ECKeyPair) {
val volatiles = configFactory.convoVolatile ?: return val volatiles = configFactory.convoVolatile ?: return
val userGroups = configFactory.userGroups ?: return val userGroups = configFactory.userGroups ?: return

@ -6,47 +6,47 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import androidx.annotation.StringRes
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.FragmentCreateGroupBinding import network.loki.messenger.databinding.FragmentCreateGroupBinding
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Contact
import org.session.libsession.utilities.Device import org.session.libsession.utilities.Device
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.SessionId import org.session.libsignal.utilities.SessionId
import org.thoughtcrime.securesms.contacts.SelectContactsAdapter
import org.thoughtcrime.securesms.conversation.start.NewConversationDelegate import org.thoughtcrime.securesms.conversation.start.NewConversationDelegate
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.ui.EditableAvatar
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.ui.NavigationBar import org.thoughtcrime.securesms.ui.NavigationBar
import org.thoughtcrime.securesms.ui.PreviewTheme import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider
import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -64,71 +64,13 @@ class CreateGroupFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val isLoading = viewModel.viewState.
return ComposeView(requireContext()).apply { return ComposeView(requireContext()).apply {
setContent { setContent {
CreateGroupScreen(createGroupState = CreateGroupState("", "", emptySet()))
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = SelectContactsAdapter(requireContext(), GlideApp.with(requireContext()))
binding.backButton.setOnClickListener { delegate.onDialogBackPressed() }
binding.closeButton.setOnClickListener { delegate.onDialogClosePressed() }
binding.contactSearch.callbacks = object : KeyboardPageSearchView.Callbacks {
override fun onQueryChanged(query: String) {
adapter.members = viewModel.filter(query).map { it.address.serialize() }
}
}
binding.createNewPrivateChatButton.setOnClickListener { delegate.onNewMessageSelected() }
binding.recyclerView.adapter = adapter
val divider = ContextCompat.getDrawable(requireActivity(), R.drawable.conversation_menu_divider)!!.let {
DividerItemDecoration(requireActivity(), RecyclerView.VERTICAL).apply {
setDrawable(it)
}
}
binding.recyclerView.addItemDecoration(divider)
var isLoading = false
binding.createClosedGroupButton.setOnClickListener {
if (isLoading) return@setOnClickListener
val name = binding.nameEditText.text.trim()
if (name.isEmpty()) {
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_group_name_missing_error, Toast.LENGTH_LONG).show()
}
if (name.length >= 30) {
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show()
}
val selectedMembers = adapter.selectedMembers
if (selectedMembers.isEmpty()) {
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
} }
if (selectedMembers.count() >= groupSizeLimit) { // Minus one because we're going to include self later
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
}
val userPublicKey = TextSecurePreferences.getLocalNumber(requireContext())!!
isLoading = true
binding.loaderContainer.fadeIn()
MessageSender.createClosedGroup(device, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
binding.loaderContainer.fadeOut()
isLoading = false
val threadID = DatabaseComponent.get(requireContext()).threadDatabase().getOrCreateThreadIdFor(Recipient.from(requireContext(), Address.fromSerialized(groupID), false))
openConversationActivity(
requireContext(),
threadID,
Recipient.from(requireContext(), Address.fromSerialized(groupID), false)
)
delegate.onDialogClosePressed()
}.failUi {
binding.loaderContainer.fadeOut()
isLoading = false
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
}
}
binding.mainContentGroup.isVisible = !viewModel.recipients.value.isNullOrEmpty()
binding.emptyStateGroup.isVisible = viewModel.recipients.value.isNullOrEmpty()
viewModel.recipients.observe(viewLifecycleOwner) { recipients ->
adapter.members = recipients.map { it.address.serialize() }
} }
} }
@ -144,17 +86,23 @@ class CreateGroupFragment : Fragment() {
CreateGroup( CreateGroup(
createGroupState, createGroupState,
onCreate = { onCreate = {
// launch something to create here
}, },
onClose = { onClose = {
delegate.onDialogClosePressed()
}, },
onBack = { onBack = {
delegate.onDialogBackPressed()
} }
) )
} }
data class ViewState(
val isLoading: Boolean,
@StringRes val error: Int?,
val createdThreadId: Long?
)
} }
data class CreateGroupState ( data class CreateGroupState (
@ -166,29 +114,93 @@ data class CreateGroupState (
@Composable @Composable
fun CreateGroup( fun CreateGroup(
createGroupState: CreateGroupState, createGroupState: CreateGroupState,
onCreate: (CreateGroupState) -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
onClose: () -> Unit, onClose: () -> Unit,
onCreate: suspend (CreateGroupState) -> Unit,
modifier: Modifier = Modifier) { modifier: Modifier = Modifier) {
var name by remember { mutableStateOf(createGroupState.groupName) }
var description by remember { mutableStateOf(createGroupState.groupDescription) }
val members by remember { mutableStateOf(createGroupState.members) }
val scrollState = rememberScrollState()
Column( Column(
modifier modifier
.fillMaxWidth()) { .fillMaxWidth()) {
NavigationBar( Column(modifier.scrollable(scrollState, orientation = Orientation.Vertical)) {
title = stringResource(id = R.string.activity_create_group_title), // Top bar
onBack = { NavigationBar(
onBack() title = stringResource(id = R.string.activity_create_group_title),
}, onBack = onBack,
onClose = { onClose = onClose
onClose() )
} // Editable avatar (future chunk)
) EditableAvatar(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = 16.dp)
)
// Title
OutlinedTextField(
value = name,
onValueChange = { name = it },
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally)
.padding(vertical = 8.dp, horizontal = 24.dp),
)
// Description
OutlinedTextField(
value = description,
onValueChange = { description = it },
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally)
.padding(vertical = 8.dp, horizontal = 24.dp),
)
// Group list
MemberList(contacts = members, modifier = Modifier.padding(vertical = 8.dp, horizontal = 24.dp))
}
// Create button
OutlinedButton(
onClick = { onCreate(CreateGroupState(name, description, members)) },
enabled = name.isNotBlank(),
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(16.dp),
shape = RoundedCornerShape(32.dp)
) {
Text(
text = stringResource(id = R.string.activity_create_group_create_button_title),
// TODO: colours of everything here probably needs to be redone
color = MaterialTheme.colors.onBackground,
modifier = Modifier.width(160.dp),
textAlign = TextAlign.Center
)
}
} }
} }
@Composable @Composable
fun MemberList(contacts: List<Contact>, modifier: Modifier = Modifier) { fun MemberList(contacts: Collection<SessionId>, modifier: Modifier = Modifier) {
Column(modifier = modifier
.fillMaxWidth()
.defaultMinSize(minHeight = 240.dp)) {
Text(text = stringResource(id = R.string.conversation_settings_group_members),
modifier = Modifier
.align(Alignment.Start)
.padding(vertical = 8.dp)
)
// TODO group list representation
Text(
text = stringResource(id = R.string.activity_create_closed_group_not_enough_group_members_error),
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(vertical = 8.dp)
)
}
} }
@Preview @Preview
@ -197,6 +209,11 @@ fun ClosedGroupPreview(
@PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int @PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int
) { ) {
PreviewTheme(themeResId) { PreviewTheme(themeResId) {
CreateGroup(CreateGroupState("Group Name", "Test Group Description", emptySet()), {}) CreateGroup(
CreateGroupState("Group Name", "Test Group Description", emptySet()),
onCreate = {},
onClose = {},
onBack = {},
)
} }
} }

@ -5,37 +5,54 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.Storage
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class CreateGroupViewModel @Inject constructor( class CreateGroupViewModel @Inject constructor(
private val threadDb: ThreadDatabase, private val textSecurePreferences: TextSecurePreferences,
private val textSecurePreferences: TextSecurePreferences private val storage: Storage,
) : ViewModel() { ) : ViewModel() {
private val _recipients = MutableLiveData<List<Recipient>>() private val _recipients = MutableLiveData<List<Recipient>>()
val recipients: LiveData<List<Recipient>> = _recipients val recipients: LiveData<List<Recipient>> = _recipients
private val _viewState = MutableLiveData(CreateGroupFragment.ViewState(false, null, null))
val viewState: LiveData<CreateGroupFragment.ViewState> = _viewState
init { init {
viewModelScope.launch { viewModelScope.launch {
threadDb.approvedConversationList.use { openCursor -> // threadDb.approvedConversationList.use { openCursor ->
val reader = threadDb.readerFor(openCursor) // val reader = threadDb.readerFor(openCursor)
val recipients = mutableListOf<Recipient>() // val recipients = mutableListOf<Recipient>()
while (true) { // while (true) {
recipients += reader.next?.recipient ?: break // recipients += reader.next?.recipient ?: break
} // }
withContext(Dispatchers.Main) { // withContext(Dispatchers.Main) {
_recipients.value = recipients // _recipients.value = recipients
.filter { !it.isGroupRecipient && it.hasApprovedMe() && it.address.serialize() != textSecurePreferences.getLocalNumber() } // .filter { !it.isGroupRecipient && it.hasApprovedMe() && it.address.serialize() != textSecurePreferences.getLocalNumber() }
} // }
} // }
}
}
fun tryCreateGroup(createGroupState: CreateGroupState) {
_viewState.postValue(CreateGroupFragment.ViewState(true, null, null))
// do some validations
if (createGroupState.groupName.isEmpty()) {
return _viewState.postValue(
CreateGroupFragment.ViewState(false, R.string.error, null)
)
} }
// TODO: add future validation for empty group ? we'll add ourselves anyway ig
// make a group
storage.createGroup()
} }
fun filter(query: String): List<Recipient> { fun filter(query: String): List<Recipient> {

@ -197,8 +197,9 @@ fun RowScope.Avatar(recipient: Recipient) {
@Composable @Composable
fun EditableAvatar( fun EditableAvatar(
// TODO: add attachment-based state for current view rendering? // TODO: add attachment-based state for current view rendering?
modifier: Modifier = Modifier
) { ) {
Box(modifier = Modifier Box(modifier = modifier
.size(110.dp) .size(110.dp)
.padding(15.dp) .padding(15.dp)
) { ) {

@ -184,4 +184,21 @@ Java_network_loki_messenger_libsession_1util_GroupInfoConfig_id(JNIEnv *env, job
std::lock_guard guard{util::util_mutex_}; std::lock_guard guard{util::util_mutex_};
auto group_info = ptrToInfo(env, thiz); auto group_info = ptrToInfo(env, thiz);
return util::serialize_session_id(env, group_info->id); return util::serialize_session_id(env, group_info->id);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_network_loki_messenger_libsession_1util_GroupInfoConfig_getDescription(JNIEnv *env,
jobject thiz) {
std::lock_guard guard{util::util_mutex_};
auto group_info = ptrToInfo(env, thiz);
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_GroupInfoConfig_setDescription(JNIEnv *env,
jobject thiz,
jstring new_description) {
std::lock_guard guard{util::util_mutex_};
auto group_info = ptrToInfo(env, thiz);
} }

@ -237,7 +237,7 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer), Closeable {
external fun getDeleteAttachmentsBefore(): Long? external fun getDeleteAttachmentsBefore(): Long?
external fun getDeleteBefore(): Long? external fun getDeleteBefore(): Long?
external fun getExpiryTimer(): Long? // TODO: maybe refactor this to new type when disappearing messages merged external fun getExpiryTimer(): Long? // TODO: maybe refactor this to new type when disappearing messages merged
external fun getName(): String? external fun getName(): String
external fun getProfilePic(): UserPic external fun getProfilePic(): UserPic
external fun isDestroyed(): Boolean external fun isDestroyed(): Boolean
external fun setCreated(createdAt: Long) external fun setCreated(createdAt: Long)
@ -245,6 +245,8 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer), Closeable {
external fun setDeleteBefore(deleteBefore: Long) external fun setDeleteBefore(deleteBefore: Long)
external fun setExpiryTimer(expireSeconds: Long) external fun setExpiryTimer(expireSeconds: Long)
external fun setName(newName: String) external fun setName(newName: String)
external fun getDescription(): String
external fun setDescription(newDescription: String)
external fun setProfilePic(newProfilePic: UserPic) external fun setProfilePic(newProfilePic: UserPic)
external fun storageNamespace(): Long external fun storageNamespace(): Long
override fun close() { override fun close() {

@ -32,6 +32,7 @@ import org.session.libsession.utilities.recipients.Recipient.RecipientSettings
import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceAttachmentPointer
import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.messages.SignalServiceGroup
import org.session.libsignal.utilities.SessionId
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
interface StorageProtocol { interface StorageProtocol {
@ -154,6 +155,7 @@ interface StorageProtocol {
fun setExpirationTimer(address: String, duration: Int) fun setExpirationTimer(address: String, duration: Int)
// Closed Groups // Closed Groups
fun createNewGroup(groupName: String, groupDescription: String, members: List<SessionId>): Long?
fun getMembers(groupPublicKey: String): List<network.loki.messenger.libsession_util.util.GroupMember> fun getMembers(groupPublicKey: String): List<network.loki.messenger.libsession_util.util.GroupMember>
// Groups // Groups

Loading…
Cancel
Save