Merge pull request #170 from loki-project/share

Fix sharing
This commit is contained in:
Niels Andriesse 2020-05-06 11:44:52 +10:00 committed by GitHub
commit 520645d40c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 446 additions and 968 deletions

View File

@ -19,6 +19,6 @@
<fragment android:id="@+id/contact_selection_list_fragment" <fragment android:id="@+id/contact_selection_list_fragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:name="org.thoughtcrime.securesms.ContactSelectionListFragment" /> android:name="org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListFragment" />
</LinearLayout> </LinearLayout>

View File

@ -1,92 +1,53 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/emptyStateContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="You don't have any contacts yet"
android:textColor="@color/text"
android:textSize="@dimen/medium_font_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/mainContentContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"> android:orientation="vertical">
<android.support.v4.widget.SwipeRefreshLayout <android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh" android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:scrollbars="vertical" /> android:scrollbars="vertical" />
<TextView android:id="@android:id/empty" <TextView
android:id="@+id/loadingTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center|center_vertical" android:gravity="center"
android:layout_marginTop="15dp" android:textColor="@color/text"
android:text="@string/contact_selection_group_activity__finding_contacts" android:text="@string/contact_selection_group_activity__finding_contacts"
android:textSize="20sp" /> android:textSize="@dimen/large_font_size" />
</android.support.v4.widget.SwipeRefreshLayout> </android.support.v4.widget.SwipeRefreshLayout>
<org.thoughtcrime.securesms.components.RecyclerViewFastScroller
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:visibility="gone"
android:layout_gravity="end"/>
<LinearLayout android:id="@+id/show_contacts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:visibility="gone"
tools:visibility="visible">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.pnikosis.materialishprogress.ProgressWheel
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
app:matProg_circleRadius="145dp"
app:matProg_barWidth="6dp"
app:matProg_rimColor="@color/signal_primary"
app:matProg_barColor="@color/signal_primary_dark"
app:matProg_progressIndeterminate="true"
tools:visibility="visible"
/>
<ImageView android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/no_contacts"/>
</FrameLayout>
<TextView android:id="@+id/show_contacts_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
android:textSize="15sp"
android:lineSpacingMultiplier="1.3"
android:gravity="center"
android:text="@string/contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them"/>
<Button android:id="@+id/show_contacts_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"
android:background="@color/signal_primary"
android:textColor="@color/white"
android:padding="10dp"
android:text="@string/contact_selection_list_fragment__show_contacts"/>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.contacts.ContactSelectionListItem
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:focusable="true"
android:background="@drawable/conversation_item_background"
android:paddingStart="16dp"
android:paddingEnd="8dp">
<org.thoughtcrime.securesms.loki.redesign.views.ProfilePictureView
android:id="@+id/profilePictureView"
android:layout_width="@dimen/medium_profile_picture_size"
android:layout_height="@dimen/medium_profile_picture_size" />
<LinearLayout android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:paddingEnd="16dp"
android:gravity="center_vertical"
android:orientation="vertical">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:singleLine="true"
android:ellipsize="marquee"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
tools:text="Frieeeeeeedrich Nieeeeeeeeeetzsche" />
<LinearLayout android:id="@+id/number_container"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<TextView android:id="@+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textDirection="ltr"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="14sp"
android:fontFamily="sans-serif-light"
tools:text="+1 (555) 555-5555" />
<TextView android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="10dip"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:fontFamily="sans-serif-light"
tools:text="Mobile" />
</LinearLayout>
</LinearLayout>
<CheckBox android:id="@+id/check_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:focusable="false"
android:clickable="false" />
</org.thoughtcrime.securesms.contacts.ContactSelectionListItem>

View File

@ -94,7 +94,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:name="org.thoughtcrime.securesms.ContactSelectionListFragment" android:name="org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListFragment"
tools:layout="@layout/contact_selection_list_fragment"/> tools:layout="@layout/contact_selection_list_fragment"/>
<LinearLayout android:layout_width="match_parent" <LinearLayout android:layout_width="match_parent"

View File

@ -49,7 +49,7 @@
android:layout_below="@id/toolbar" android:layout_below="@id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:name="org.thoughtcrime.securesms.ContactSelectionListFragment" /> android:name="org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListFragment" />
<org.thoughtcrime.securesms.components.SearchToolbar <org.thoughtcrime.securesms.components.SearchToolbar
android:id="@+id/search_toolbar" android:id="@+id/search_toolbar"

View File

@ -1663,13 +1663,14 @@
<string name="session_restore_banner_dismiss_button_title">Dismiss</string> <string name="session_restore_banner_dismiss_button_title">Dismiss</string>
<string name="session_restore_banner_restore_button_title">Restore</string> <string name="session_restore_banner_restore_button_title">Restore</string>
<!-- Loki -->
<!-- Session --> <!-- Session -->
<string name="activity_register_public_key_copied_message">Copied to clipboard</string> <string name="activity_register_public_key_copied_message">Copied to clipboard</string>
<string name="activity_home_leave_group_dialog_message">Are you sure you want to leave this group?</string> <string name="activity_home_leave_group_dialog_message">Are you sure you want to leave this group?</string>
<string name="activity_home_delete_conversation_dialog_message">Are you sure you want to delete this conversation?</string> <string name="activity_home_delete_conversation_dialog_message">Are you sure you want to delete this conversation?</string>
<string name="activity_home_conversation_deleted_message">Conversation deleted</string> <string name="activity_home_conversation_deleted_message">Conversation deleted</string>
<string name="fragment_contact_selection_contacts_title">Contacts</string>
<string name="fragment_contact_selection_closed_groups_title">Closed Groups</string>
<string name="fragment_contact_selection_open_groups_title">Open Groups</string>
<string name="activity_pn_mode_title">Push Notifications</string> <string name="activity_pn_mode_title">Push Notifications</string>
<string name="activity_pn_mode_explanation">There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose.</string> <string name="activity_pn_mode_explanation">There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose.</string>
<string name="activity_pn_mode_fcm_option_title">Firebase Cloud Messaging</string> <string name="activity_pn_mode_fcm_option_title">Firebase Cloud Messaging</string>

View File

@ -20,11 +20,11 @@ import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import org.thoughtcrime.securesms.components.ContactFilterToolbar; import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListFragment;
import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListLoader.DisplayMode;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import network.loki.messenger.R; import network.loki.messenger.R;
@ -57,9 +57,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
@Override @Override
protected void onCreate(Bundle icicle, boolean ready) { protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) { if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_ALL);
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_GROUPS;
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
} }
setContentView(R.layout.contact_selection_activity); setContentView(R.layout.contact_selection_activity);

View File

@ -1,303 +0,0 @@
/*
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import network.loki.messenger.R;
/**
* Fragment for selecting a one or more contacts from a list.
*
* @author Moxie Marlinspike
*
*/
public class ContactSelectionListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>
{
@SuppressWarnings("unused")
private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
public static final String DISPLAY_MODE = "display_mode";
public static final String MULTI_SELECT = "multi_select";
public static final String REFRESHABLE = "refreshable";
public static final String RECENTS = "recents";
private TextView emptyText;
private Set<String> selectedContacts;
private OnContactSelectedListener onContactSelectedListener;
private SwipeRefreshLayout swipeRefresh;
private View showContactsLayout;
private Button showContactsButton;
private TextView showContactsDescription;
private ProgressWheel showContactsProgress;
private String cursorFilter;
private RecyclerView recyclerView;
private RecyclerViewFastScroller fastScroller;
@Override
public void onActivityCreated(Bundle icicle) {
super.onActivityCreated(icicle);
initializeCursor();
}
@Override
public void onStart() {
super.onStart();
handleContactPermissionGranted();
// Permissions.with(this)
// .request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
// .ifNecessary()
// .onAllGranted(() -> {
// if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
// handleContactPermissionGranted();
// } else {
// this.getLoaderManager().initLoader(0, null, this);
// }
// })
// .onAnyDenied(() -> {
// getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
//
// if (getActivity().getIntent().getBooleanExtra(RECENTS, false)) {
// getLoaderManager().initLoader(0, null, ContactSelectionListFragment.this);
// } else {
// initializeNoContactsPermission();
// }
// })
// .execute();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
emptyText = ViewUtil.findById(view, android.R.id.empty);
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
swipeRefresh = ViewUtil.findById(view, R.id.swipe_refresh);
fastScroller = ViewUtil.findById(view, R.id.fast_scroller);
showContactsLayout = view.findViewById(R.id.show_contacts_container);
showContactsButton = view.findViewById(R.id.show_contacts_button);
showContactsDescription = view.findViewById(R.id.show_contacts_description);
showContactsProgress = view.findViewById(R.id.progress);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
swipeRefresh.setEnabled(getActivity().getIntent().getBooleanExtra(REFRESHABLE, true));
return view;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
public @NonNull List<String> getSelectedContacts() {
List<String> selected = new LinkedList<>();
if (selectedContacts != null) {
selected.addAll(selectedContacts);
}
return selected;
}
private boolean isMulti() {
return getActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
}
private void initializeCursor() {
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(),
GlideApp.with(this),
null,
new ListClickListener(),
isMulti());
selectedContacts = adapter.getSelectedContacts();
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new StickyHeaderDecoration(adapter, true, true));
}
private void initializeNoContactsPermission() {
swipeRefresh.setVisibility(View.GONE);
showContactsLayout.setVisibility(View.VISIBLE);
showContactsProgress.setVisibility(View.INVISIBLE);
showContactsDescription.setText(R.string.contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them);
showContactsButton.setVisibility(View.VISIBLE);
/*
showContactsButton.setOnClickListener(v -> {
Permissions.with(this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.ContactSelectionListFragment_signal_requires_the_contacts_permission_in_order_to_display_your_contacts))
.onSomeGranted(permissions -> {
if (permissions.contains(Manifest.permission.WRITE_CONTACTS)) {
handleContactPermissionGranted();
}
})
.execute();
});
*/
}
public void setQueryFilter(String filter) {
this.cursorFilter = filter;
this.getLoaderManager().restartLoader(0, null, this);
}
public void resetQueryFilter() {
setQueryFilter(null);
swipeRefresh.setRefreshing(false);
}
public void setRefreshing(boolean refreshing) {
swipeRefresh.setRefreshing(refreshing);
}
public void reset() {
selectedContacts.clear();
if (!isDetached() && !isRemoving() && getActivity() != null && !getActivity().isFinishing()) {
getLoaderManager().restartLoader(0, null, this);
}
}
@Override
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new ContactsCursorLoader(getActivity(),
getActivity().getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL),
cursorFilter, getActivity().getIntent().getBooleanExtra(RECENTS, false));
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
swipeRefresh.setVisibility(View.VISIBLE);
showContactsLayout.setVisibility(View.GONE);
((CursorRecyclerViewAdapter) recyclerView.getAdapter()).changeCursor(data);
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
boolean useFastScroller = (recyclerView.getAdapter().getItemCount() > 20);
recyclerView.setVerticalScrollBarEnabled(!useFastScroller);
if (useFastScroller) {
fastScroller.setVisibility(View.VISIBLE);
fastScroller.setRecyclerView(recyclerView);
}
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
((CursorRecyclerViewAdapter) recyclerView.getAdapter()).changeCursor(null);
fastScroller.setVisibility(View.GONE);
}
@SuppressLint("StaticFieldLeak")
private void handleContactPermissionGranted() {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected void onPreExecute() {
swipeRefresh.setVisibility(View.GONE);
showContactsLayout.setVisibility(View.VISIBLE);
showContactsButton.setVisibility(View.INVISIBLE);
showContactsDescription.setText(R.string.ConversationListFragment_loading);
showContactsProgress.setVisibility(View.VISIBLE);
showContactsProgress.spin();
}
@Override
protected Boolean doInBackground(Void... voids) {
return false;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
showContactsLayout.setVisibility(View.GONE);
swipeRefresh.setVisibility(View.VISIBLE);
reset();
} else {
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
initializeNoContactsPermission();
}
}
}.execute();
}
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
@Override
public void onItemClick(ContactSelectionListItem contact) {
if (!isMulti() || !selectedContacts.contains(contact.getNumber())) {
selectedContacts.add(contact.getNumber());
contact.setChecked(true);
if (onContactSelectedListener != null) onContactSelectedListener.onContactSelected(contact.getNumber());
} else {
selectedContacts.remove(contact.getNumber());
contact.setChecked(false);
if (onContactSelectedListener != null) onContactSelectedListener.onContactDeselected(contact.getNumber());
}
}
}
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
this.onContactSelectedListener = onContactSelectedListener;
}
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
}
public interface OnContactSelectedListener {
void onContactSelected(String number);
void onContactDeselected(String number);
}
}

View File

@ -43,7 +43,6 @@ import com.bumptech.glide.request.transition.Transition;
import org.thoughtcrime.securesms.avatar.AvatarSelection; import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.components.PushRecipientsPanel; import org.thoughtcrime.securesms.components.PushRecipientsPanel;
import org.thoughtcrime.securesms.components.PushRecipientsPanel.RecipientsPanelChangedListener; import org.thoughtcrime.securesms.components.PushRecipientsPanel.RecipientsPanelChangedListener;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.RecipientsEditor; import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
@ -57,6 +56,8 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult; import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListFragment;
import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListLoader.DisplayMode;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
@ -321,11 +322,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class); Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
if (groupToUpdate.isPresent()) { intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_FRIENDS);
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_PUSH);
} else {
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_PUSH | DisplayMode.FLAG_SMS);
}
startActivityForResult(intent, PICK_CONTACT); startActivityForResult(intent, PICK_CONTACT);
} }
} }

View File

@ -26,9 +26,10 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.components.ContactFilterToolbar; import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener; import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListFragment;
import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListLoader.DisplayMode;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
@ -40,7 +41,7 @@ import java.util.concurrent.ExecutionException;
import network.loki.messenger.R; import network.loki.messenger.R;
public class InviteActivity extends PassphraseRequiredActionBarActivity implements ContactSelectionListFragment.OnContactSelectedListener { public class InviteActivity extends PassphraseRequiredActionBarActivity {
private ContactSelectionListFragment contactsFragment; private ContactSelectionListFragment contactsFragment;
private EditText inviteText; private EditText inviteText;
@ -52,7 +53,7 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
@Override @Override
protected void onCreate(Bundle savedInstanceState, boolean ready) { protected void onCreate(Bundle savedInstanceState, boolean ready) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS); getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_FRIENDS);
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true); getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false); getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
@ -84,7 +85,6 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
heart.getViewTreeObserver().addOnPreDrawListener(new HeartPreDrawListener()); heart.getViewTreeObserver().addOnPreDrawListener(new HeartPreDrawListener());
} }
contactsFragment.setOnContactSelectedListener(this);
shareButton.setOnClickListener(new ShareClickListener()); shareButton.setOnClickListener(new ShareClickListener());
smsButton.setOnClickListener(new SmsClickListener()); smsButton.setOnClickListener(new SmsClickListener());
smsCancelButton.setOnClickListener(new SmsCancelClickListener()); smsCancelButton.setOnClickListener(new SmsCancelClickListener());
@ -99,12 +99,10 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
return animation; return animation;
} }
@Override
public void onContactSelected(String number) { public void onContactSelected(String number) {
updateSmsButtonText(); updateSmsButtonText();
} }
@Override
public void onContactDeselected(String number) { public void onContactDeselected(String number) {
updateSmsButtonText(); updateSmsButtonText();
} }
@ -132,7 +130,6 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
} }
private void cancelSmsSelection() { private void cancelSmsSelection() {
contactsFragment.reset();
updateSmsButtonText(); updateSmsButtonText();
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE); ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE);
} }
@ -241,7 +238,6 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE).addListener(new Listener<Boolean>() { ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE).addListener(new Listener<Boolean>() {
@Override @Override
public void onSuccess(Boolean result) { public void onSuccess(Boolean result) {
contactsFragment.reset();
} }
@Override @Override

View File

@ -19,6 +19,8 @@ package org.thoughtcrime.securesms;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListFragment;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -29,19 +29,19 @@ import android.os.Process;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.conversation.ConversationActivity; import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListFragment;
import org.thoughtcrime.securesms.loki.redesign.fragments.contactselection.ContactSelectionListLoader.DisplayMode;
import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.providers.BlobProvider;
@ -52,7 +52,6 @@ import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -68,7 +67,7 @@ import network.loki.messenger.R;
* @author Jake McGinty * @author Jake McGinty
*/ */
public class ShareActivity extends PassphraseRequiredActionBarActivity public class ShareActivity extends PassphraseRequiredActionBarActivity
implements ContactSelectionListFragment.OnContactSelectedListener, SwipeRefreshLayout.OnRefreshListener implements ContactSelectionListFragment.OnContactSelectedListener
{ {
private static final String TAG = ShareActivity.class.getSimpleName(); private static final String TAG = ShareActivity.class.getSimpleName();
@ -96,14 +95,10 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected void onCreate(Bundle icicle, boolean ready) { protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) { if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_ALL);
TextSecurePreferences.isSmsEnabled(this)
? DisplayMode.FLAG_ALL
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_GROUPS);
} }
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false); getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
getIntent().putExtra(ContactSelectionListFragment.RECENTS, true);
setContentView(R.layout.share_activity); setContentView(R.layout.share_activity);
@ -170,7 +165,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
searchAction = findViewById(R.id.search_action); searchAction = findViewById(R.id.search_action);
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment); contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setOnContactSelectedListener(this); contactsFragment.setOnContactSelectedListener(this);
contactsFragment.setOnRefreshListener(this);
} }
private void initializeSearch() { private void initializeSearch() {
@ -281,12 +275,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void onContactDeselected(String number) { public void onContactDeselected(String number) {
}
@Override
public void onRefresh() {
} }
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")

View File

@ -1,271 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller.FastScrollAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolder;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapter;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* List adapter to display all contacts and their related information
*
* @author Jake McGinty
*/
public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewHolder>
implements FastScrollAdapter,
StickyHeaderAdapter<HeaderViewHolder>
{
private final static String TAG = ContactSelectionListAdapter.class.getSimpleName();
private static final int VIEW_TYPE_CONTACT = 0;
private static final int VIEW_TYPE_DIVIDER = 1;
private final static int STYLE_ATTRIBUTES[] = new int[]{R.attr.contact_selection_push_user,
R.attr.contact_selection_lay_user};
private final boolean multiSelect;
private final LayoutInflater li;
private final TypedArray drawables;
private final ItemClickListener clickListener;
private final GlideRequests glideRequests;
private final Set<String> selectedContacts = new HashSet<>();
public abstract static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
public abstract void bind(@NonNull GlideRequests glideRequests, int type, String name, String number, String label, int color, boolean multiSelect);
public abstract void unbind(@NonNull GlideRequests glideRequests);
public abstract void setChecked(boolean checked);
}
public static class ContactViewHolder extends ViewHolder {
ContactViewHolder(@NonNull final View itemView,
@Nullable final ItemClickListener clickListener)
{
super(itemView);
itemView.setOnClickListener(v -> {
if (clickListener != null) clickListener.onItemClick(getView());
});
}
public ContactSelectionListItem getView() {
return (ContactSelectionListItem) itemView;
}
public void bind(@NonNull GlideRequests glideRequests, int type, String name, String number, String label, int color, boolean multiSelect) {
getView().set(glideRequests, type, name, number, label, color, multiSelect);
}
@Override
public void unbind(@NonNull GlideRequests glideRequests) {
getView().unbind(glideRequests);
}
@Override
public void setChecked(boolean checked) {
getView().setChecked(checked);
}
}
public static class DividerViewHolder extends ViewHolder {
private final TextView label;
DividerViewHolder(View itemView) {
super(itemView);
this.label = itemView.findViewById(R.id.label);
}
@Override
public void bind(@NonNull GlideRequests glideRequests, int type, String name, String number, String label, int color, boolean multiSelect) {
this.label.setText(name);
}
@Override
public void unbind(@NonNull GlideRequests glideRequests) {}
@Override
public void setChecked(boolean checked) {}
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
HeaderViewHolder(View itemView) {
super(itemView);
}
}
public ContactSelectionListAdapter(@NonNull Context context,
@NonNull GlideRequests glideRequests,
@Nullable Cursor cursor,
@Nullable ItemClickListener clickListener,
boolean multiSelect)
{
super(context, cursor);
this.li = LayoutInflater.from(context);
this.glideRequests = glideRequests;
this.drawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
this.multiSelect = multiSelect;
this.clickListener = clickListener;
}
@Override
public long getHeaderId(int i) {
if (!isActiveCursor()) return -1;
int contactType = getContactType(i);
if (contactType == ContactsDatabase.DIVIDER_TYPE) return -1;
return Util.hashCode(getHeaderString(i), getContactType(i));
}
@Override
public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_CONTACT) {
return new ContactViewHolder(li.inflate(R.layout.contact_selection_list_item, parent, false), clickListener);
} else {
return new DividerViewHolder(li.inflate(R.layout.contact_selection_list_divider, parent, false));
}
}
@Override
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN));
String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN));
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(getContext().getResources(),
numberType, label).toString();
int color = (contactType == ContactsDatabase.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
drawables.getColor(1, 0xff000000);
viewHolder.unbind(glideRequests);
viewHolder.bind(glideRequests, contactType, name, number, labelText, color, multiSelect);
viewHolder.setChecked(selectedContacts.contains(number));
}
@Override
public int getItemViewType(@NonNull Cursor cursor) {
if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN)) == ContactsDatabase.DIVIDER_TYPE) {
return VIEW_TYPE_DIVIDER;
} else {
return VIEW_TYPE_CONTACT;
}
}
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false));
}
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position));
}
@Override
public void onItemViewRecycled(ViewHolder holder) {
holder.unbind(glideRequests);
}
@Override
public CharSequence getBubbleText(int position) {
return getHeaderString(position);
}
public Set<String> getSelectedContacts() {
return selectedContacts;
}
private CharSequence getSpannedHeaderString(int position) {
final String headerString = getHeaderString(position);
if (isPush(position)) {
SpannableString spannable = new SpannableString(headerString);
spannable.setSpan(new ForegroundColorSpan(getContext().getResources().getColor(R.color.signal_primary)), 0, headerString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
} else {
return headerString;
}
}
private @NonNull String getHeaderString(int position) {
int contactType = getContactType(position);
if (contactType == ContactsDatabase.RECENT_TYPE || contactType == ContactsDatabase.DIVIDER_TYPE) {
return " ";
}
Cursor cursor = getCursorAtPositionOrThrow(position);
String letter = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
if (!TextUtils.isEmpty(letter)) {
String firstChar = letter.trim().substring(0, 1).toUpperCase();
if (Character.isLetterOrDigit(firstChar.codePointAt(0))) {
return firstChar;
}
}
return "#";
}
private int getContactType(int position) {
final Cursor cursor = getCursorAtPositionOrThrow(position);
return cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
}
private boolean isPush(int position) {
return getContactType(position) == ContactsDatabase.PUSH_TYPE;
}
public interface ItemClickListener {
void onItemClick(ContactSelectionListItem item);
}
}

View File

@ -1,161 +0,0 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.loki.redesign.views.ProfilePictureView;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.signalservice.loki.api.LokiAPI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import network.loki.messenger.R;
public class ContactSelectionListItem extends LinearLayout implements RecipientModifiedListener {
@SuppressWarnings("unused")
private static final String TAG = ContactSelectionListItem.class.getSimpleName();
private ProfilePictureView profilePictureView;
private TextView numberView;
private TextView nameView;
private TextView labelView;
private CheckBox checkBox;
private String number;
private Recipient recipient;
private GlideRequests glideRequests;
private long threadID;
public ContactSelectionListItem(Context context) {
super(context);
}
public ContactSelectionListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
this.profilePictureView = findViewById(R.id.profilePictureView);
this.numberView = findViewById(R.id.number);
this.labelView = findViewById(R.id.label);
this.nameView = findViewById(R.id.name);
this.checkBox = findViewById(R.id.check_box);
ViewUtil.setTextViewGravityStart(this.nameView, getContext());
}
public void set(@NonNull GlideRequests glideRequests, int type, String name, String number, String label, int color, boolean multiSelect) {
this.glideRequests = glideRequests;
this.number = number;
if (type == ContactsDatabase.NEW_TYPE) {
this.recipient = null;
} else if (!TextUtils.isEmpty(number)) {
Address address = Address.fromExternal(getContext(), number);
this.recipient = Recipient.from(getContext(), address, true);
this.recipient.addListener(this);
if (this.recipient.getName() != null) {
name = this.recipient.getName();
}
}
threadID = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(recipient);
this.numberView.setTextColor(color);
updateProfilePicture(glideRequests, name, threadID);
if (!multiSelect && recipient != null && recipient.isLocalNumber()) {
name = getContext().getString(R.string.note_to_self);
}
setText(type, name, number, label);
if (multiSelect) this.checkBox.setVisibility(View.VISIBLE);
else this.checkBox.setVisibility(View.GONE);
}
public void setChecked(boolean selected) {
this.checkBox.setChecked(selected);
}
public void unbind(GlideRequests glideRequests) {
if (recipient != null) {
recipient.removeListener(this);
recipient = null;
}
}
private void setText(int type, String name, String number, String label) {
if (number == null || number.isEmpty() || GroupUtil.isEncodedGroup(number)) {
this.nameView.setEnabled(false);
this.numberView.setText("");
this.labelView.setVisibility(View.GONE);
} else if (type == ContactsDatabase.PUSH_TYPE) {
this.numberView.setText(number);
this.nameView.setEnabled(true);
this.labelView.setVisibility(View.GONE);
} else {
this.numberView.setText(number);
this.nameView.setEnabled(true);
this.labelView.setText(label);
this.labelView.setVisibility(View.VISIBLE);
}
this.nameView.setText(name);
}
public String getNumber() {
return number;
}
@Override
public void onModified(final Recipient recipient) {
if (this.recipient == recipient) {
Util.runOnMain(() -> {
threadID = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdFor(recipient);
updateProfilePicture(glideRequests, recipient.getName(), threadID);
nameView.setText(recipient.toShortString());
});
}
}
private void updateProfilePicture(GlideRequests glide, String name, long threadID) {
if (this.recipient.isGroupRecipient()) {
Set<String> usersAsSet = LokiAPI.Companion.getUserHexEncodedPublicKeyCache().get(threadID);
if (usersAsSet == null) {
usersAsSet = new HashSet<>();
}
ArrayList<String> users = new ArrayList<>(usersAsSet);
Collections.sort(users); // Sort to provide a level of stability
profilePictureView.setHexEncodedPublicKey(users.size() > 0 ? users.get(0) : "");
profilePictureView.setAdditionalHexEncodedPublicKey(users.size() > 1 ? users.get(1) : "");
profilePictureView.setRSSFeed(name.equals("Loki News") || name.equals("Session Updates"));
} else {
profilePictureView.setHexEncodedPublicKey(this.number);
profilePictureView.setAdditionalHexEncodedPublicKey(null);
profilePictureView.setRSSFeed(false);
}
profilePictureView.glide = glide;
profilePictureView.update();
}
}

View File

@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.loki.redesign.activities
import android.content.Context import android.content.Context
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.loki.redesign.views.UserView import org.thoughtcrime.securesms.loki.redesign.views.UserView
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adapter<CreateClosedGroupAdapter.ViewHolder>() { class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adapter<CreateClosedGroupAdapter.ViewHolder>() {
lateinit var glide: GlideRequests lateinit var glide: GlideRequests
@ -28,7 +30,7 @@ class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adap
val member = members[position] val member = members[position]
viewHolder.view.setOnClickListener { memberClickListener?.onMemberClick(member) } viewHolder.view.setOnClickListener { memberClickListener?.onMemberClick(member) }
val isSelected = selectedMembers.contains(member) val isSelected = selectedMembers.contains(member)
viewHolder.view.bind(member, isSelected, glide) viewHolder.view.bind(Recipient.from(context, Address.fromSerialized(member), false), isSelected, glide)
} }
fun onMemberClick(member: String) { fun onMemberClick(member: String) {

View File

@ -1,33 +1,18 @@
package org.thoughtcrime.securesms.loki.redesign.activities package org.thoughtcrime.securesms.loki.redesign.activities
import android.content.Context import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.redesign.utilities.ContactUtilities
import org.thoughtcrime.securesms.util.AsyncLoader import org.thoughtcrime.securesms.util.AsyncLoader
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
class CreateClosedGroupLoader(context: Context) : AsyncLoader<List<String>>(context) { class CreateClosedGroupLoader(context: Context) : AsyncLoader<List<String>>(context) {
override fun loadInBackground(): List<String> { override fun loadInBackground(): List<String> {
val threadDatabase = DatabaseFactory.getThreadDatabase(context) val contacts = ContactUtilities.getAllContacts(context)
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) // Only show the master devices of the users we are friends with
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) return contacts.filter { contact ->
val deviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(userHexEncodedPublicKey) !contact.recipient.isGroupRecipient && contact.isFriend && !contact.isOurDevice && !contact.isSlave
val userLinkedDeviceHexEncodedPublicKeys = deviceLinks.flatMap { }.map {
listOf( it.masterHexEncodedPublicKey.toLowerCase(), it.slaveHexEncodedPublicKey.toLowerCase() ) it.recipient.address.toPhoneString()
}.toMutableSet() }
userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase())
val cursor = threadDatabase.conversationList
val reader = threadDatabase.readerFor(cursor)
val result = mutableListOf<String>()
while (reader.next != null) {
val thread = reader.current
if (thread.recipient.isGroupRecipient) { continue }
if (lokiThreadDatabase.getFriendRequestStatus(thread.threadId) != LokiThreadFriendRequestStatus.FRIENDS) { continue }
val hexEncodedPublicKey = thread.recipient.address.toString().toLowerCase()
if (userLinkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey)) { continue }
result.add(hexEncodedPublicKey)
}
return result
} }
} }

View File

@ -0,0 +1,85 @@
package org.thoughtcrime.securesms.loki.redesign.fragments.contactselection
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.contact_selection_list_divider.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.redesign.views.UserView
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
class ContactSelectionListAdapter(private val context: Context, private val multiSelect: Boolean) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
lateinit var glide: GlideRequests
val selectedContacts = mutableSetOf<Recipient>()
var items = listOf<ContactSelectionListItem>()
set(value) { field = value; notifyDataSetChanged() }
var contactClickListener: ContactClickListener? = null
private object ViewType {
const val Contact = 0
const val Divider = 1
}
class UserViewHolder(val view: UserView) : RecyclerView.ViewHolder(view)
class DividerViewHolder(val view: View) : RecyclerView.ViewHolder(view)
override fun getItemCount(): Int {
return items.size
}
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is ContactSelectionListItem.Header -> ViewType.Divider
else -> ViewType.Contact
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ViewType.Contact) {
UserViewHolder(UserView(context))
} else {
val view = LayoutInflater.from(context).inflate(R.layout.contact_selection_list_divider, parent, false)
DividerViewHolder(view)
}
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
val item = items[position]
if (viewHolder is UserViewHolder) {
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)
} else if (viewHolder is DividerViewHolder) {
item as ContactSelectionListItem.Header
viewHolder.view.label.text = item.name
}
}
fun onContactClick(recipient: Recipient) {
if (selectedContacts.contains(recipient)) {
selectedContacts.remove(recipient)
contactClickListener?.onContactDeselected(recipient)
} else if (multiSelect || selectedContacts.isEmpty()) {
selectedContacts.add(recipient)
contactClickListener?.onContactSelected(recipient)
}
val index = items.indexOfFirst {
when (it) {
is ContactSelectionListItem.Header -> false
is ContactSelectionListItem.Contact -> it.recipient == recipient
}
}
notifyItemChanged(index)
}
}
interface ContactClickListener {
fun onContactClick(contact: Recipient)
fun onContactSelected(contact: Recipient)
fun onContactDeselected(contact: Recipient)
}

View File

@ -0,0 +1,112 @@
package org.thoughtcrime.securesms.loki.redesign.fragments.contactselection
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.contact_selection_list_fragment.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient
class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<List<ContactSelectionListItem>>, ContactClickListener {
private var cursorFilter: String? = null
var onContactSelectedListener: OnContactSelectedListener? = null
val selectedContacts: List<String>
get() = listAdapter.selectedContacts.map { it.address.serialize() }
private val multiSelect: Boolean by lazy {
activity!!.intent.getBooleanExtra(MULTI_SELECT, false)
}
private val listAdapter by lazy {
val result = ContactSelectionListAdapter(activity!!, multiSelect)
result.glide = GlideApp.with(this)
result.contactClickListener = this
result
}
companion object {
@JvmField val DISPLAY_MODE = "display_mode"
@JvmField val MULTI_SELECT = "multi_select"
@JvmField val REFRESHABLE = "refreshable"
}
interface OnContactSelectedListener {
fun onContactSelected(number: String?)
fun onContactDeselected(number: String?)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
recyclerView.layoutManager = LinearLayoutManager(activity)
recyclerView.adapter = listAdapter
swipeRefreshLayout.isEnabled = activity!!.intent.getBooleanExtra(REFRESHABLE, true)
}
override fun onStart() {
super.onStart()
LoaderManager.getInstance(this).initLoader(0, null, this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.contact_selection_list_fragment, container, false)
}
fun setQueryFilter(filter: String?) {
cursorFilter = filter
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
fun resetQueryFilter() {
setQueryFilter(null)
swipeRefreshLayout.isRefreshing = false
}
fun setRefreshing(refreshing: Boolean) {
swipeRefreshLayout.isRefreshing = refreshing
}
fun setOnRefreshListener(onRefreshListener: OnRefreshListener?) {
swipeRefreshLayout.setOnRefreshListener(onRefreshListener)
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<ContactSelectionListItem>> {
return ContactSelectionListLoader(activity!!,
activity!!.intent.getIntExtra(DISPLAY_MODE, ContactsCursorLoader.DisplayMode.FLAG_ALL),
cursorFilter)
}
override fun onLoadFinished(loader: Loader<List<ContactSelectionListItem>>, items: List<ContactSelectionListItem>) {
update(items)
}
override fun onLoaderReset(loader: Loader<List<ContactSelectionListItem>>) {
update(listOf())
}
private fun update(items: List<ContactSelectionListItem>) {
listAdapter.items = items
mainContentContainer.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE
emptyStateContainer.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE
}
override fun onContactClick(contact: Recipient) {
listAdapter.onContactClick(contact)
}
override fun onContactSelected(contact: Recipient) {
onContactSelectedListener?.onContactSelected(contact.address.serialize())
}
override fun onContactDeselected(contact: Recipient) {
onContactSelectedListener?.onContactDeselected(contact.address.serialize())
}
}

View File

@ -0,0 +1,83 @@
package org.thoughtcrime.securesms.loki.redesign.fragments.contactselection
import android.content.Context
import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.redesign.utilities.Contact
import org.thoughtcrime.securesms.loki.redesign.utilities.ContactUtilities
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.AsyncLoader
sealed class ContactSelectionListItem {
class Header(val name: String) : ContactSelectionListItem()
class Contact(val recipient: Recipient) : ContactSelectionListItem()
}
class ContactSelectionListLoader(context: Context, val mode: Int, val filter: String?) : AsyncLoader<List<ContactSelectionListItem>>(context) {
object DisplayMode {
const val FLAG_FRIENDS = 1
const val FLAG_CLOSED_GROUPS = 1 shl 1
const val FLAG_OPEN_GROUPS = 1 shl 2
const val FLAG_ALL = FLAG_FRIENDS or FLAG_CLOSED_GROUPS or FLAG_OPEN_GROUPS
}
private fun isFlagSet(flag: Int): Boolean {
return mode and flag > 0
}
override fun loadInBackground(): List<ContactSelectionListItem> {
val contacts = ContactUtilities.getAllContacts(context).filter {
if (filter.isNullOrEmpty()) return@filter true
it.recipient.toShortString().contains(filter.trim(), true) || it.recipient.address.serialize().contains(filter.trim(), true)
}.sortedBy {
it.recipient.toShortString()
}
val list = mutableListOf<ContactSelectionListItem>()
if (isFlagSet(DisplayMode.FLAG_CLOSED_GROUPS)) {
list.addAll(getClosedGroups(contacts))
}
if (isFlagSet(DisplayMode.FLAG_OPEN_GROUPS)) {
list.addAll(getOpenGroups(contacts))
}
if (isFlagSet(DisplayMode.FLAG_FRIENDS)) {
list.addAll(getFriends(contacts))
}
return list
}
private fun getFriends(contacts: List<Contact>): List<ContactSelectionListItem> {
return getItems(contacts, context.getString(R.string.fragment_contact_selection_contacts_title)) {
!it.recipient.isGroupRecipient && it.isFriend && !it.isOurDevice && !it.isSlave
}
}
private fun getClosedGroups(contacts: List<Contact>): List<ContactSelectionListItem> {
return getItems(contacts, context.getString(R.string.fragment_contact_selection_closed_groups_title)) {
it.recipient.address.isSignalGroup
}
}
private fun getOpenGroups(contacts: List<Contact>): List<ContactSelectionListItem> {
return getItems(contacts, context.getString(R.string.fragment_contact_selection_open_groups_title)) {
it.recipient.address.isPublicChat
}
}
private fun getItems(contacts: List<Contact>, title: String, contactFilter: (Contact) -> Boolean): List<ContactSelectionListItem> {
val items = contacts.filter(contactFilter).map {
ContactSelectionListItem.Contact(it.recipient)
}
if (items.isEmpty()) return listOf()
val header = ContactSelectionListItem.Header(title)
return listOf(header) + items
}
}

View File

@ -0,0 +1,60 @@
package org.thoughtcrime.securesms.loki.redesign.utilities
import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
data class Contact(
val recipient: Recipient,
val isFriend: Boolean,
val isSlave: Boolean,
val isOurDevice: Boolean
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Contact
return recipient == other.recipient
}
override fun hashCode(): Int {
return recipient.hashCode()
}
}
object ContactUtilities {
@JvmStatic
fun getAllContacts(context: Context): Set<Contact> {
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
val ourDeviceLinks = lokiAPIDatabase.getDeviceLinks(userHexEncodedPublicKey)
val ourDevices = ourDeviceLinks.flatMap {
listOf( it.masterHexEncodedPublicKey.toLowerCase(), it.slaveHexEncodedPublicKey.toLowerCase() )
}.toMutableSet()
ourDevices.add(userHexEncodedPublicKey.toLowerCase())
val cursor = threadDatabase.conversationList
val result = mutableSetOf<Contact>()
threadDatabase.readerFor(cursor).use { reader ->
while (reader.next != null) {
val thread = reader.current
val recipient = thread.recipient
val address = recipient.address.serialize()
val isOurDevice = ourDevices.contains(address)
val isFriend = lokiThreadDatabase.getFriendRequestStatus(thread.threadId) == LokiThreadFriendRequestStatus.FRIENDS
var isSlave = false
if (!recipient.isGroupRecipient) {
val deviceLinks = lokiAPIDatabase.getDeviceLinks(address)
isSlave = deviceLinks.find { it.slaveHexEncodedPublicKey == address } != null
}
result.add(Contact(recipient, isFriend, isSlave, isOurDevice))
}
}
return result
}
}

View File

@ -3,16 +3,17 @@ package org.thoughtcrime.securesms.loki.redesign.views
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_conversation.view.profilePictureView import kotlinx.android.synthetic.main.view_conversation.view.profilePictureView
import kotlinx.android.synthetic.main.view_user.view.* import kotlinx.android.synthetic.main.view_user.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.loki.api.LokiAPI
class UserView : LinearLayout { class UserView : LinearLayout {
var user: String? = null
// region Lifecycle // region Lifecycle
constructor(context: Context) : super(context) { constructor(context: Context) : super(context) {
@ -39,13 +40,33 @@ class UserView : LinearLayout {
// endregion // endregion
// region Updating // region Updating
fun bind(user: String, isSelected: Boolean, glide: GlideRequests) { fun setCheckBoxVisible(visible: Boolean) {
profilePictureView.hexEncodedPublicKey = user tickImageView.visibility = if (visible) View.VISIBLE else View.GONE
}
fun bind(user: Recipient, isSelected: Boolean, glide: GlideRequests) {
val address = user.address.serialize()
if (user.isGroupRecipient) {
if ("Session Public Chat" == user.name || user.address.isRSSFeed) {
profilePictureView.hexEncodedPublicKey = ""
profilePictureView.additionalHexEncodedPublicKey = null
profilePictureView.isRSSFeed = true
} else {
val threadID = GroupManager.getThreadIdFromGroupId(address, context)
val users = LokiAPI.userHexEncodedPublicKeyCache[threadID]?.toList() ?: listOf()
val randomUsers = users.sorted() // Sort to provide a level of stability
profilePictureView.hexEncodedPublicKey = randomUsers.getOrNull(0) ?: ""
profilePictureView.additionalHexEncodedPublicKey = randomUsers.getOrNull(1) ?: ""
profilePictureView.isRSSFeed = false
}
} else {
profilePictureView.hexEncodedPublicKey = address
profilePictureView.additionalHexEncodedPublicKey = null profilePictureView.additionalHexEncodedPublicKey = null
profilePictureView.isRSSFeed = false profilePictureView.isRSSFeed = false
}
profilePictureView.glide = glide profilePictureView.glide = glide
profilePictureView.update() profilePictureView.update()
nameTextView.text = Recipient.from(context, Address.fromSerialized(user), false).name ?: "Unknown Contact" nameTextView.text = user.name ?: "Unknown Contact"
tickImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle) tickImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle)
} }
// endregion // endregion

View File

@ -455,7 +455,8 @@ public class Recipient implements RecipientModifiedListener {
} }
public synchronized String toShortString() { public synchronized String toShortString() {
return (getName() == null ? address.serialize() : getName()); String name = getName();
return (name == null ? address.serialize() : name);
} }
public synchronized @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) { public synchronized @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) {