mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 12:58:25 +00:00
Merge pull request #282 from loki-project/closed-group-editing-2
Closed Group Editing
This commit is contained in:
commit
e84a49bd38
@ -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" />
|
||||
|
5
res/drawable/ic_more_horiz_white.xml
Normal file
5
res/drawable/ic_more_horiz_white.xml
Normal 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>
|
@ -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"
|
||||
|
149
res/layout/activity_edit_closed_group.xml
Normal file
149
res/layout/activity_edit_closed_group.xml
Normal 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>
|
@ -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"
|
||||
|
38
res/layout/activity_select_contacts.xml
Normal file
38
res/layout/activity_select_contacts.xml
Normal 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>
|
@ -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"
|
||||
|
20
res/layout/fragment_closed_group_edit_bottom_sheet.xml
Normal file
20
res/layout/fragment_closed_group_edit_bottom_sheet.xml
Normal 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>
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
11
res/menu/menu_apply.xml
Normal 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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
@ -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) {
|
||||
|
@ -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() }
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user