Merge pull request #282 from loki-project/closed-group-editing-2

Closed Group Editing
This commit is contained in:
Niels Andriesse 2020-08-18 10:26:39 +10:00 committed by GitHub
commit e84a49bd38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 804 additions and 103 deletions

View File

@ -132,6 +132,9 @@
<activity
android:name="org.thoughtcrime.securesms.loki.activities.CreateClosedGroupActivity"
android:screenOrientation="portrait" />
<activity
android:name="org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity"
android:screenOrientation="portrait" />
<activity
android:name="org.thoughtcrime.securesms.loki.activities.JoinPublicChatActivity"
android:screenOrientation="portrait"
@ -139,6 +142,9 @@
<activity
android:name="org.thoughtcrime.securesms.loki.activities.SeedActivity"
android:screenOrientation="portrait" />
<activity
android:name="org.thoughtcrime.securesms.loki.activities.SelectContactsActivity"
android:screenOrientation="portrait" />
<activity
android:name="org.thoughtcrime.securesms.loki.activities.PrivacySettingsActivity"
android:screenOrientation="portrait" />

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -51,7 +51,7 @@
<Button
style="@style/MediumProminentOutlineButton"
android:id="@+id/createNewPrivateChatButton"
android:id="@+id/btnCreateNewPrivateChat"
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/medium_spacing"

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/default_session_background"
tools:context="org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity">
<LinearLayout
android:id="@+id/mainContentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/ctnGroupNameSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/large_spacing"
android:gravity="center">
<LinearLayout
android:id="@+id/cntGroupNameEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:visibility="invisible"
tools:visibility="visible">
<ImageView
android:id="@+id/btnCancelGroupNameEdit"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_close_white_24dp"/>
<EditText
style="@style/SessionEditText"
android:id="@+id/edtGroupName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/small_spacing"
android:layout_marginEnd="@dimen/small_spacing"
android:textAlignment="center"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:inputType="text"
android:singleLine="true"
android:imeOptions="actionDone"
android:hint="@string/activity_edit_closed_group_edit_text_hint" />
<ImageView
android:id="@+id/btnSaveGroupNameEdit"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_check_white_24dp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/cntGroupNameDisplay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="visible"
tools:visibility="invisible">
<TextView
android:id="@+id/lblGroupNameDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/text"
android:textSize="@dimen/very_large_font_size"
android:textStyle="bold"
android:textAlignment="center"
android:paddingStart="24dp"
android:paddingEnd="0dp"
android:drawableEnd="@drawable/ic_edit_white_24dp"
android:drawablePadding="@dimen/small_spacing"
tools:text="SomeGroupName"/>
</LinearLayout>
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/separator" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginEnd="@dimen/small_spacing"
android:layout_marginBottom="@dimen/small_spacing"
android:layout_weight="1"
android:text="@string/activity_edit_closed_group_edit_members"
android:textColor="@color/text"
android:textSize="@dimen/medium_font_size" />
<Button
android:id="@+id/addMembersClosedGroupButton"
style="@style/MediumProminentOutlineButton"
android:layout_width="wrap_content"
android:layout_height="@dimen/small_button_height"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginEnd="@dimen/medium_spacing"
android:layout_marginStart="@dimen/small_spacing"
android:layout_marginBottom="@dimen/small_spacing"
android:paddingStart="@dimen/medium_spacing"
android:paddingEnd="@dimen/medium_spacing"
android:text="@string/activity_edit_closed_group_add_members" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/separator" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:id="@+id/emptyStateContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_centerInParent="true">
</LinearLayout>
</RelativeLayout>

View File

@ -107,7 +107,7 @@
<Button
style="@style/MediumProminentOutlineButton"
android:id="@+id/createNewPrivateChatButton"
android:id="@+id/btnCreateNewPrivateChat"
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/medium_spacing"

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/default_session_background" >
<LinearLayout
android:id="@+id/mainContentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<LinearLayout
android:id="@+id/emptyStateContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_centerInParent="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="@string/activity_create_closed_group_empty_state_message" />
</LinearLayout>
</RelativeLayout>

View File

@ -77,7 +77,7 @@
android:layout_marginTop="@dimen/medium_spacing" />
<RelativeLayout
android:id="@+id/displayNameContainer"
android:id="@+id/ctnGroupNameSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/large_spacing"
@ -97,7 +97,7 @@
android:hint="@string/activity_settings_display_name_edit_text_hint" />
<TextView
android:id="@+id/displayNameTextView"
android:id="@+id/btnGroupNameDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_hideable="true"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
android:background="@color/dialog_background">
<TextView
android:id="@+id/removeFromGroup"
style="@style/ActionItem"
android:drawableStart="@drawable/ic_phonelink_erase_white_24dp"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="@string/fragment_edit_group_bottom_sheet_remove"/>
</LinearLayout>

View File

@ -35,7 +35,7 @@
android:gravity="center_vertical">
<TextView
android:id="@+id/displayNameTextView"
android:id="@+id/btnGroupNameDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"

View File

@ -30,7 +30,7 @@
</RelativeLayout>
<TextView
android:id="@+id/displayNameTextView"
android:id="@+id/btnGroupNameDisplay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_spacing"

View File

@ -37,7 +37,7 @@
android:layout_weight="1" />
<ImageView
android:id="@+id/tickImageView"
android:id="@+id/actionIndicatorImageView"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginLeft="@dimen/medium_spacing"

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:title="@string/GroupCreateActivity_menu_apply_button"
android:id="@+id/menu_create_group"
android:icon="?menu_accept_icon"
app:showAsAction="always|withText"/>
</menu>

11
res/menu/menu_apply.xml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:title="@string/menu_apply_button"
android:id="@+id/applyButton"
android:icon="?menu_accept_icon"
app:showAsAction="always" />
</menu>

View File

@ -3,8 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:title="Done"
android:id="@+id/createClosedGroupButton"
android:title="@string/menu_done_button"
android:id="@+id/doneButton"
android:icon="?menu_accept_icon"
app:showAsAction="always" />
</menu>

View File

@ -1830,4 +1830,25 @@
<string name="fragment_contact_selection_closed_groups_title">Closed Groups</string>
<string name="fragment_contact_selection_open_groups_title">Open Groups</string>
<string name="menu_apply_button">Apply</string>
<string name="menu_done_button">Done</string>
<!-- Next round of translation -->
<string name="activity_edit_closed_group_title">Edit Group</string>
<string name="activity_edit_closed_group_edit_text_hint">Enter a new group name</string>
<string name="activity_edit_closed_group_edit_members">Members</string>
<string name="activity_edit_closed_group_add_members">Add members</string>
<string name="activity_edit_closed_group_group_name_missing_error">Group name can\'t be empty</string>
<string name="activity_edit_closed_group_group_name_too_long_error">Please enter a shorter group name</string>
<string name="activity_edit_closed_group_not_enough_group_members_error">Groups must have at least 2 group members</string>
<string name="activity_edit_closed_group_too_many_group_members_error">A closed group cannot have more than 10 members</string>
<string name="activity_edit_closed_group_invalid_session_id_error">One of the members of your group has an invalid Session ID</string>
<string name="activity_edit_closed_group_confirm_removal">Are you sure you want to remove this user?</string>
<string name="activity_edit_closed_group_member_removed">User removed from group</string>
<string name="fragment_edit_group_bottom_sheet_remove">Remove user from group</string>
<string name="activity_select_contacts_title">Select Contacts</string>
</resources>

View File

@ -209,7 +209,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
MenuInflater inflater = this.getMenuInflater();
menu.clear();
inflater.inflate(R.menu.group_create, menu);
inflater.inflate(R.menu.menu_apply, menu);
super.onPrepareOptionsMenu(menu);
return true;
}
@ -221,7 +221,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
case android.R.id.home:
finish();
return true;
case R.id.menu_create_group:
case R.id.applyButton:
if (groupToUpdate.isPresent()) handleGroupUpdate();
else handleGroupCreate();
return true;
@ -389,7 +389,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
protected void onPreExecute() {
activity.findViewById(R.id.group_details_layout).setVisibility(View.GONE);
activity.findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE);
activity.findViewById(R.id.menu_create_group).setVisibility(View.GONE);
activity.findViewById(R.id.applyButton).setVisibility(View.GONE);
final int titleResId = activity.groupToUpdate.isPresent()
? R.string.GroupCreateActivity_updating_group
: R.string.GroupCreateActivity_creating_group;
@ -401,7 +401,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
if (activity.isFinishing()) return;
activity.findViewById(R.id.group_details_layout).setVisibility(View.VISIBLE);
activity.findViewById(R.id.creating_group_layout).setVisibility(View.GONE);
activity.findViewById(R.id.menu_create_group).setVisibility(View.VISIBLE);
activity.findViewById(R.id.applyButton).setVisibility(View.VISIBLE);
}
}

View File

@ -152,6 +152,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
@ -760,7 +761,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inflater.inflate(R.menu.conversation_block, menu);
}
} else if (isGroupConversation() && !isOpenGroupOrRSSFeed) {
inflater.inflate(R.menu.conversation_group_options, menu);
// inflater.inflate(R.menu.conversation_group_options, menu);
if (!isPushGroupConversation()) {
inflater.inflate(R.menu.conversation_mms_group_options, menu);
@ -1195,10 +1196,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleEditPushGroup() {
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setMessage("The ability to add members to a closed group is coming soon.");
alert.setPositiveButton("OK", (dialog, which) -> dialog.dismiss());
alert.create().show();
Intent intent = new Intent(this, EditClosedGroupActivity.class);
String groupID = this.recipient.getAddress().toGroupString();
intent.putExtra(EditClosedGroupActivity.Companion.getGroupIDKey(), groupID);
startActivity(intent);
}
private void handleDistributionBroadcastEnabled(MenuItem item) {

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.os.AsyncTask
@ -27,44 +28,45 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.util.guava.Optional
import java.lang.ref.WeakReference
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberClickListener, LoaderManager.LoaderCallbacks<List<String>> {
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
private var members = listOf<String>()
set(value) { field = value; createClosedGroupAdapter.members = value }
set(value) {
field = value
selectContactsAdapter.members = value
}
private val createClosedGroupAdapter by lazy {
val result = CreateClosedGroupAdapter(this)
result.glide = GlideApp.with(this)
result.memberClickListener = this
result
private val selectContactsAdapter by lazy {
SelectContactsAdapter(this, GlideApp.with(this))
}
private val selectedMembers: Set<String>
get() { return createClosedGroupAdapter.selectedMembers }
companion object {
public val createNewPrivateChatResultCode = 100
val closedGroupCreatedResultCode = 100
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_create_closed_group)
supportActionBar!!.title = resources.getString(R.string.activity_create_closed_group_title)
recyclerView.adapter = createClosedGroupAdapter
recyclerView.adapter = this.selectContactsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
btnCreateNewPrivateChat.setOnClickListener { createNewPrivateChat() }
LoaderManager.getInstance(this).initLoader(0, null, this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_create_closed_group, menu)
menuInflater.inflate(R.menu.menu_done, menu)
return members.isNotEmpty()
}
// endregion
// region Updating
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> {
return CreateClosedGroupLoader(this)
return SelectContactsLoader(this)
}
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) {
@ -85,23 +87,17 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
// region Interaction
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when(id) {
R.id.createClosedGroupButton -> createClosedGroup()
else -> { /* Do nothing */ }
when(item.itemId) {
R.id.doneButton -> createClosedGroup()
}
return super.onOptionsItemSelected(item)
}
private fun createNewPrivateChat() {
setResult(createNewPrivateChatResultCode)
setResult(Companion.closedGroupCreatedResultCode)
finish()
}
override fun onMemberClick(member: String) {
createClosedGroupAdapter.onMemberClick(member)
}
private fun createClosedGroup() {
if (ClosedGroupsProtocol.isSharedSenderKeysEnabled) {
createSSKBasedClosedGroup()
@ -118,17 +114,17 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
if (name.length >= 64) {
return Toast.makeText(this, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show()
}
val selectedMembers = this.selectedMembers
val selectedMembers = this.selectContactsAdapter.selectedMembers
if (selectedMembers.count() < 2) {
return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
}
if (selectedMembers.count() > 49) { // Minus one because we're going to include self later
if (selectedMembers.count() > ClosedGroupsProtocol.groupSizeLimit) { // Minus one because we're going to include self later
return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
}
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
val groupID = ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey ))
val threadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
openConversation(threadID, Recipient.from(this, Address.fromSerialized(groupID), false))
openConversationActivity(this, threadID, Recipient.from(this, Address.fromSerialized(groupID), false))
}
private fun createLegacyClosedGroup() {
@ -139,7 +135,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
if (name.length >= 64) {
return Toast.makeText(this, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show()
}
val selectedMembers = this.selectedMembers
val selectedMembers = this.selectContactsAdapter.selectedMembers
if (selectedMembers.count() < 2) {
return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
}
@ -151,26 +147,18 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
}.toSet()
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this) ?: TextSecurePreferences.getLocalNumber(this)
val admin = Recipient.from(this, Address.fromSerialized(masterHexEncodedPublicKey), false)
CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf( admin )).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
private fun openConversation(threadId: Long, recipient: Recipient) {
val intent = Intent(this, ConversationActivity::class.java)
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId)
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT)
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
startActivity(intent)
finish()
CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf( admin ))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
// endregion
// region Tasks
// region Group Creation Task (Legacy)
internal class CreateClosedGroupTask(
private val activity: WeakReference<CreateClosedGroupActivity>,
private val profilePicture: Bitmap?,
private val name: String?,
private val members: Set<Recipient>,
private val admins: Set<Recipient>
private val activity: WeakReference<CreateClosedGroupActivity>,
private val profilePicture: Bitmap?,
private val name: String?,
private val members: Set<Recipient>,
private val admins: Set<Recipient>
) : AsyncTask<Void, Void, Optional<GroupManager.GroupActionResult>>() {
override fun doInBackground(vararg params: Void?): Optional<GroupManager.GroupActionResult> {
@ -182,7 +170,8 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
val activity = activity.get() ?: return super.onPostExecute(result)
if (result.isPresent && result.get().threadId > -1) {
if (!activity.isFinishing) {
activity.openConversation(result.get().threadId, result.get().groupRecipient)
openConversationActivity(activity, result.get().threadId, result.get().groupRecipient)
activity.finish()
}
} else {
super.onPostExecute(result)
@ -190,5 +179,15 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
}
}
}
// endregion
}
}
// endregion
// region Convenience
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
val intent = Intent(context, ConversationActivity::class.java)
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId)
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT)
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
context.startActivity(intent)
}
// endregion

View File

@ -0,0 +1,243 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_create_closed_group.emptyStateContainer
import kotlinx.android.synthetic.main.activity_create_closed_group.mainContentContainer
import kotlinx.android.synthetic.main.activity_edit_closed_group.*
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.dialogs.ClosedGroupEditingOptionsBottomSheet
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.utilities.toHexString
import java.io.IOException
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
private val originalMembers = HashSet<String>()
private val members = HashSet<String>()
private var hasNameChanged = false
private lateinit var groupID: String
private lateinit var originalName: String
private lateinit var name: String
private var isEditingName = false
set(value) {
if (field == value) return
field = value
handleIsEditingNameChanged()
}
private val memberListAdapter by lazy {
EditClosedGroupMembersAdapter(this, GlideApp.with(this), this::onMemberClick)
}
companion object {
@JvmStatic val groupIDKey = "groupIDKey"
private val loaderID = 0
val addUsersRequestCode = 124
val legacyGroupSizeLimit = 10
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_edit_closed_group)
supportActionBar!!.title = resources.getString(R.string.activity_edit_closed_group_title)
groupID = intent.getStringExtra(Companion.groupIDKey)
originalName = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().title
name = originalName
addMembersClosedGroupButton.setOnClickListener { onAddMembersClick() }
recyclerView.adapter = memberListAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
lblGroupNameDisplay.text = originalName
cntGroupNameDisplay.setOnClickListener { isEditingName = true }
btnCancelGroupNameEdit.setOnClickListener { isEditingName = false }
btnSaveGroupNameEdit.setOnClickListener { saveName() }
edtGroupName.setImeActionLabel(getString(R.string.save), EditorInfo.IME_ACTION_DONE)
edtGroupName.setOnEditorActionListener { _, actionId, _ ->
when (actionId) {
EditorInfo.IME_ACTION_DONE -> {
saveName()
return@setOnEditorActionListener true
}
else -> return@setOnEditorActionListener false
}
}
LoaderManager.getInstance(this).initLoader(Companion.loaderID, null, object : LoaderManager.LoaderCallbacks<List<String>> {
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> {
return EditClosedGroupLoader(this@EditClosedGroupActivity, groupID)
}
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) {
// We no longer need any subsequent loading events
// (they will occur on every activity resume).
LoaderManager.getInstance(this@EditClosedGroupActivity).destroyLoader(Companion.loaderID)
originalMembers.clear()
originalMembers.addAll(members.toHashSet())
updateMembers(originalMembers)
}
override fun onLoaderReset(loader: Loader<List<String>>) {
updateMembers(setOf())
}
})
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_apply, menu)
return members.isNotEmpty()
}
// endregion
// region Updating
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
Companion.addUsersRequestCode -> {
if (resultCode != RESULT_OK) return
if (data == null || data.extras == null || !data.hasExtra(SelectContactsActivity.selectedContactsKey)) return
val selectedContacts = data.extras!!.getStringArray(SelectContactsActivity.selectedContactsKey)!!.toSet()
val changedMembers = members + selectedContacts
updateMembers(changedMembers)
}
}
}
private fun handleIsEditingNameChanged() {
cntGroupNameEdit.visibility = if (isEditingName) View.VISIBLE else View.INVISIBLE
cntGroupNameDisplay.visibility = if (isEditingName) View.INVISIBLE else View.VISIBLE
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (isEditingName) {
edtGroupName.setText(name)
edtGroupName.selectAll()
edtGroupName.requestFocus()
inputMethodManager.showSoftInput(edtGroupName, 0)
} else {
inputMethodManager.hideSoftInputFromWindow(edtGroupName.windowToken, 0)
}
}
private fun updateMembers(members: Set<String>) {
this.members.clear()
this.members.addAll(members)
memberListAdapter.setMembers(members)
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
memberListAdapter.setLockedMembers(arrayListOf(userPublicKey))
mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
invalidateOptionsMenu()
}
// endregion
// region Interaction
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
R.id.applyButton -> commitChanges()
}
return super.onOptionsItemSelected(item)
}
private fun onMemberClick(member: String) {
val bottomSheet = ClosedGroupEditingOptionsBottomSheet()
bottomSheet.onRemoveTapped = {
val changedMembers = members - member
updateMembers(changedMembers)
bottomSheet.dismiss()
}
bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet")
}
private fun onAddMembersClick() {
val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java)
startActivityForResult(intent, Companion.addUsersRequestCode)
}
private fun saveName() {
val name = edtGroupName.text.toString().trim()
if (name.isEmpty()) {
return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_missing_error, Toast.LENGTH_SHORT).show()
}
if (name.length >= 64) {
return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_too_long_error, Toast.LENGTH_SHORT).show()
}
this.name = name
lblGroupNameDisplay.text = name
hasNameChanged = true
isEditingName = false
}
private fun commitChanges() {
val hasMemberListChanges = members != originalMembers
if (!hasNameChanged && !hasMemberListChanges) {
return finish()
}
val name = if (hasNameChanged) this.name else originalName
val members = this.members.map {
Recipient.from(this, Address.fromSerialized(it), false)
}.toSet()
val admins = members.toSet() //TODO For now, consider all the users to be admins.
var isSSKBasedClosedGroup: Boolean
var groupPublicKey: String?
try {
groupPublicKey = ClosedGroupsProtocol.doubleDecodeGroupID(groupID).toHexString()
isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(this).isSSKBasedClosedGroup(groupPublicKey)
} catch (e: IOException) {
groupPublicKey = null
isSSKBasedClosedGroup = false
}
if (members.size < 2) {
return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
}
val maxGroupMembers = if (isSSKBasedClosedGroup) ClosedGroupsProtocol.groupSizeLimit else Companion.legacyGroupSizeLimit
if (members.size > maxGroupMembers) {
// TODO: Update copy for SSK based closed groups
return Toast.makeText(this, R.string.activity_edit_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
}
if (isSSKBasedClosedGroup) {
ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() },
name, admins.map { it.address.serialize() })
} else {
GroupManager.updateGroup(this, groupID, members, null, name, admins)
}
finish()
}
}

View File

@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.util.AsyncLoader
class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader<List<String>>(context) {
override fun loadInBackground(): List<String> {
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true)
return members.map {
it.address.toPhoneString()
}
}
}

View File

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.ViewGroup
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.loki.views.UserView
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
class EditClosedGroupMembersAdapter(
private val context: Context,
private val glide: GlideRequests,
private val memberClickListener: ((String) -> Unit)? = null
) : RecyclerView.Adapter<EditClosedGroupMembersAdapter.ViewHolder>() {
private val members = ArrayList<String>()
private val lockedMembers = HashSet<String>()
fun setMembers(members: Collection<String>) {
this.members.clear()
this.members.addAll(members)
notifyDataSetChanged()
}
fun setLockedMembers(members: Collection<String>) {
this.lockedMembers.clear()
this.lockedMembers.addAll(members)
notifyDataSetChanged()
}
override fun getItemCount(): Int = members.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = UserView(context)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val member = members[position]
val lockedMember = lockedMembers.contains(member)
viewHolder.view.bind(Recipient.from(
context,
Address.fromSerialized(member), false),
glide,
if (lockedMember) UserView.ActionIndicator.None else UserView.ActionIndicator.Menu)
if (!lockedMember) {
viewHolder.view.setOnClickListener { this.memberClickListener?.invoke(member) }
}
}
class ViewHolder(val view: UserView) : RecyclerView.ViewHolder(view)
}

View File

@ -121,7 +121,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
recyclerView.adapter = homeAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
// Set up empty state view
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
btnCreateNewPrivateChat.setOnClickListener { createNewPrivateChat() }
// This is a workaround for the fact that CursorRecyclerViewAdapter doesn't actually auto-update (even though it says it will)
LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks<Cursor> {
@ -223,7 +223,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == CreateClosedGroupActivity.createNewPrivateChatResultCode) {
if (resultCode == CreateClosedGroupActivity.closedGroupCreatedResultCode) {
createNewPrivateChat()
}
}

View File

@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.loki.activities
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
import kotlinx.android.synthetic.main.activity_create_closed_group.*
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.mms.GlideApp
class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
private var members = listOf<String>()
set(value) { field = value; selectContactsAdapter.members = value }
private val selectContactsAdapter by lazy {
SelectContactsAdapter(this, GlideApp.with(this))
}
companion object {
val selectedContactsKey = "selectedContactsKey"
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_select_contacts)
supportActionBar!!.title = resources.getString(R.string.activity_select_contacts_title)
recyclerView.adapter = selectContactsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
LoaderManager.getInstance(this).initLoader(0, null, this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_done, menu)
return members.isNotEmpty()
}
// endregion
// region Updating
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> {
return SelectContactsLoader(this)
}
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) {
update(members)
}
override fun onLoaderReset(loader: Loader<List<String>>) {
update(listOf())
}
private fun update(members: List<String>) {
this.members = members
mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
invalidateOptionsMenu()
}
// endregion
// region Interaction
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
R.id.doneButton -> closeAndReturnSelected()
}
return super.onOptionsItemSelected(item)
}
private fun closeAndReturnSelected() {
val selectedMembers = selectContactsAdapter.selectedMembers
val selectedContacts = selectedMembers.toTypedArray()
val intent = Intent()
intent.putExtra(selectedContactsKey, selectedContacts)
setResult(Activity.RESULT_OK, intent)
finish()
}
// endregion
}

View File

@ -8,12 +8,10 @@ import org.thoughtcrime.securesms.loki.views.UserView
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adapter<CreateClosedGroupAdapter.ViewHolder>() {
lateinit var glide: GlideRequests
class SelectContactsAdapter(private val context: Context, private val glide: GlideRequests) : RecyclerView.Adapter<SelectContactsAdapter.ViewHolder>() {
val selectedMembers = mutableSetOf<String>()
var members = listOf<String>()
set(value) { field = value; notifyDataSetChanged() }
var memberClickListener: MemberClickListener? = null
class ViewHolder(val view: UserView) : RecyclerView.ViewHolder(view)
@ -28,12 +26,17 @@ class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adap
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val member = members[position]
viewHolder.view.setOnClickListener { memberClickListener?.onMemberClick(member) }
viewHolder.view.setOnClickListener { onMemberClick(member) }
val isSelected = selectedMembers.contains(member)
viewHolder.view.bind(Recipient.from(context, Address.fromSerialized(member), false), isSelected, glide)
viewHolder.view.bind(Recipient.from(
context,
Address.fromSerialized(member), false),
glide,
UserView.ActionIndicator.Tick,
isSelected)
}
fun onMemberClick(member: String) {
private fun onMemberClick(member: String) {
if (selectedMembers.contains(member)) {
selectedMembers.remove(member)
} else {
@ -42,9 +45,4 @@ class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adap
val index = members.indexOf(member)
notifyItemChanged(index)
}
}
interface MemberClickListener {
fun onMemberClick(member: String)
}

View File

@ -4,7 +4,7 @@ import android.content.Context
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.util.AsyncLoader
class CreateClosedGroupLoader(context: Context) : AsyncLoader<List<String>>(context) {
class SelectContactsLoader(context: Context) : AsyncLoader<List<String>>(context) {
override fun loadInBackground(): List<String> {
val contacts = ContactUtilities.getAllContacts(context)

View File

@ -77,8 +77,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
profilePictureView.isLarge = true
profilePictureView.update()
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
displayNameContainer.setOnClickListener { showEditDisplayNameUI() }
displayNameTextView.text = DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey)
ctnGroupNameSection.setOnClickListener { showEditDisplayNameUI() }
btnGroupNameDisplay.text = DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey)
publicKeyTextView.text = hexEncodedPublicKey
copyButton.setOnClickListener { copyPublicKey() }
shareButton.setOnClickListener { sharePublicKey() }
@ -132,7 +132,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
cancelButton.visibility = if (isEditingDisplayName) View.VISIBLE else View.GONE
showQRCodeButton.visibility = if (isEditingDisplayName) View.GONE else View.VISIBLE
saveButton.visibility = if (isEditingDisplayName) View.VISIBLE else View.GONE
displayNameTextView.visibility = if (isEditingDisplayName) View.INVISIBLE else View.VISIBLE
btnGroupNameDisplay.visibility = if (isEditingDisplayName) View.INVISIBLE else View.VISIBLE
displayNameEditText.visibility = if (isEditingDisplayName) View.VISIBLE else View.INVISIBLE
val titleTextViewLayoutParams = titleTextView.layoutParams as LinearLayout.LayoutParams
titleTextViewLayoutParams.leftMargin = if (isEditingDisplayName) toPx(16, resources) else 0
@ -176,7 +176,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
}
all(promises).alwaysUi {
if (displayName != null) {
displayNameTextView.text = displayName
btnGroupNameDisplay.text = displayName
}
displayNameToBeUploaded = null
if (isUpdatingProfilePicture && profilePicture != null) {

View File

@ -0,0 +1,22 @@
package org.thoughtcrime.securesms.loki.dialogs
import android.os.Bundle
import android.support.design.widget.BottomSheetDialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_closed_group_edit_bottom_sheet.*
import network.loki.messenger.R
public class ClosedGroupEditingOptionsBottomSheet : BottomSheetDialogFragment() {
var onRemoveTapped: (() -> Unit)? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_closed_group_edit_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
removeFromGroup.setOnClickListener { onRemoveTapped?.invoke() }
}
}

View File

@ -52,8 +52,11 @@ class ContactSelectionListAdapter(private val context: Context, private val mult
item as ContactSelectionListItem.Contact
viewHolder.view.setOnClickListener { contactClickListener?.onContactClick(item.recipient) }
val isSelected = selectedContacts.contains(item.recipient)
viewHolder.view.bind(item.recipient, isSelected, glide)
viewHolder.view.setCheckBoxVisible(multiSelect)
viewHolder.view.bind(
item.recipient,
glide,
if (multiSelect) UserView.ActionIndicator.Tick else UserView.ActionIndicator.None,
isSelected)
} else if (viewHolder is DividerViewHolder) {
item as ContactSelectionListItem.Header
viewHolder.view.label.text = item.name

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.loki.protocol
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import com.google.protobuf.ByteString
import org.thoughtcrime.securesms.ApplicationContext
@ -32,6 +33,7 @@ import java.util.*
object ClosedGroupsProtocol {
val isSharedSenderKeysEnabled = false
val groupSizeLimit = 10
public fun createClosedGroup(context: Context, name: String, members: Collection<String>): String {
// Prepare
@ -183,6 +185,25 @@ object ClosedGroupsProtocol {
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID)
}
public fun update(context: Context, groupPublicKey: String, members: Collection<String>, name: String, admins: Collection<String>) {
val groupDB = DatabaseFactory.getGroupDatabase(context)
val groupID = doubleEncodeGroupID(groupPublicKey)
if (groupDB.getGroup(groupID).orNull() == null) {
Log.d("Loki", "Can't update nonexistent closed group.")
return
}
// Send the update to the group
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey),
name, setOf(), members.map { Hex.fromStringCondensed(it) }, admins.map { Hex.fromStringCondensed(it) })
val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind)
ApplicationContext.getInstance(context).jobManager.add(job)
// Update the group
groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) })
// Notify the user
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID)
}
@JvmStatic
public fun requestSenderKey(context: Context, groupPublicKey: String, senderPublicKey: String) {
// Establish session if needed

View File

@ -80,7 +80,7 @@ class ConversationView : LinearLayout {
profilePictureView.glide = glide
profilePictureView.update()
val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else if (!thread.recipient.name.isNullOrEmpty()) thread.recipient.name else thread.recipient.address.toString()
displayNameTextView.text = senderDisplayName
btnGroupNameDisplay.text = senderDisplayName
timestampTextView.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), thread.date)
muteIndicatorImageView.visibility = if (thread.recipient.isMuted) VISIBLE else GONE
val rawSnippet = thread.getDisplayBody(context)

View File

@ -30,7 +30,7 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr:
}
private fun update() {
displayNameTextView.text = mentionCandidate.displayName
btnGroupNameDisplay.text = mentionCandidate.displayName
profilePictureView.publicKey = mentionCandidate.publicKey
profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = false

View File

@ -15,6 +15,12 @@ import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
class UserView : LinearLayout {
enum class ActionIndicator {
None,
Menu,
Tick
}
// region Lifecycle
constructor(context: Context) : super(context) {
setUpViewHierarchy()
@ -40,11 +46,7 @@ class UserView : LinearLayout {
// endregion
// region Updating
fun setCheckBoxVisible(visible: Boolean) {
tickImageView.visibility = if (visible) View.VISIBLE else View.GONE
}
fun bind(user: Recipient, isSelected: Boolean, glide: GlideRequests) {
fun bind(user: Recipient, glide: GlideRequests, actionIndicator: ActionIndicator, isSelected: Boolean = false) {
val address = user.address.serialize()
if (user.isGroupRecipient) {
if ("Session Public Chat" == user.name || user.address.isRSSFeed) {
@ -64,10 +66,23 @@ class UserView : LinearLayout {
profilePictureView.additionalPublicKey = null
profilePictureView.isRSSFeed = false
}
actionIndicatorImageView.setImageResource(R.drawable.ic_edit_white_24dp)
profilePictureView.glide = glide
profilePictureView.update()
nameTextView.text = user.name ?: "Unknown Contact"
tickImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle)
when (actionIndicator) {
ActionIndicator.None -> {
actionIndicatorImageView.visibility = View.GONE
}
ActionIndicator.Menu -> {
actionIndicatorImageView.visibility = View.VISIBLE
actionIndicatorImageView.setImageResource(R.drawable.ic_more_horiz_white)
}
ActionIndicator.Tick -> {
actionIndicatorImageView.visibility = View.VISIBLE
actionIndicatorImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle)
}
}
}
// endregion
}