batch invitation

// FREEBIE
master
Jake McGinty 2015-10-19 11:23:12 -07:00 committed by Moxie Marlinspike
parent 4371708fc4
commit 3e798a9863
47 changed files with 942 additions and 341 deletions

View File

@ -112,6 +112,16 @@
<activity android:name=".ImportExportActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".InviteActivity"
android:theme="@style/TextSecure.HighlightTheme"
android:windowSoftInputMode="stateHidden"
android:parentActivityName=".ConversationListActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
</activity>
<activity android:name=".PromptMmsActivity"
android:label="Configure MMS Settings"
android:windowSoftInputMode="stateUnchanged"

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="350"
android:fromYDelta="100%"
android:toYDelta="0%" />
</set>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="350"
android:fromYDelta="0%"
android:toYDelta="100%" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView android:id="@+id/action_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:clickable="true"
android:background="@drawable/circle_touch_highlight_background"
android:src="@drawable/ic_search_white_24dp" />
<LinearLayout android:id="@+id/toggle_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<EditText android:id="@+id/search_view"
android:layout_height="wrap_content"
android:layout_width="0px"
android:layout_weight="1"
android:layout_marginLeft="5dp"
android:hint="@string/contact_selection_activity__enter_name_or_number"
android:inputType="textPersonName"
style="@style/TextSecure.TitleTextStyle"
android:background="@android:color/transparent"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"/>
<org.thoughtcrime.securesms.components.AnimatingToggle
android:id="@+id/button_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center">
<ImageView android:id="@+id/search_dialpad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:clickable="true"
android:background="@drawable/circle_touch_highlight_background"
android:src="@drawable/ic_dialpad_white_24dp" />
<ImageView android:id="@+id/search_keyboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:clickable="true"
android:visibility="gone"
android:background="@drawable/circle_touch_highlight_background"
android:src="@drawable/ic_keyboard_white_24dp" />
<ImageView android:id="@+id/search_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:clickable="true"
android:visibility="gone"
android:background="@drawable/circle_touch_highlight_background"
android:src="@drawable/ic_clear_white_24dp" />
</org.thoughtcrime.securesms.components.AnimatingToggle>
</LinearLayout>
</LinearLayout>
</merge>

View File

@ -5,94 +5,17 @@
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.v7.widget.Toolbar
<org.thoughtcrime.securesms.components.ContactFilterToolbar
android:id="@+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/TextSecure.LightActionBar">
android:theme="@style/TextSecure.LightActionBar" />
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView android:id="@+id/action_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:clickable="true"
android:background="@drawable/circle_touch_highlight_background"
android:src="@drawable/ic_search_white_24dp" />
<LinearLayout android:id="@+id/toggle_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<EditText android:id="@+id/search_view"
android:layout_height="wrap_content"
android:layout_width="0px"
android:layout_weight="1"
android:layout_marginLeft="5dp"
android:hint="@string/contact_selection_activity__enter_name_or_number"
android:inputType="textPersonName"
style="@style/TextSecure.TitleTextStyle"
android:background="@android:color/transparent"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"/>
<org.thoughtcrime.securesms.components.AnimatingToggle
android:id="@+id/button_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center">
<ImageView android:id="@+id/search_dialpad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:clickable="true"
android:background="@drawable/circle_touch_highlight_background"
android:src="@drawable/ic_dialpad_white_24dp" />
<ImageView android:id="@+id/search_keyboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:clickable="true"
android:visibility="gone"
android:background="@drawable/circle_touch_highlight_background"
android:src="@drawable/ic_keyboard_white_24dp" />
<ImageView android:id="@+id/search_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:clickable="true"
android:visibility="gone"
android:background="@drawable/circle_touch_highlight_background"
android:src="@drawable/ic_clear_white_24dp" />
</org.thoughtcrime.securesms.components.AnimatingToggle>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.Toolbar>
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/contact_selection_list_fragment"
android:name="org.thoughtcrime.securesms.ContactSelectionListFragment">
</fragment>
<fragment android:id="@+id/contact_selection_list_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="org.thoughtcrime.securesms.ContactSelectionListFragment" />
</LinearLayout>

View File

@ -1,20 +1,13 @@
<?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="32sp"
android:paddingLeft="10dp"
android:paddingRight="25dp">
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:textSize="15sp"
android:textColor="?contact_selection_header_text"
android:textStyle="bold" />
</RelativeLayout>
<TextView android:layout_width="match_parent"
android:layout_height="32sp"
android:singleLine="true"
android:ellipsize="marquee"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:textSize="15sp"
android:paddingLeft="10dp"
android:paddingRight="25dp"
android:textStyle="bold"
xmlns:android="http://schemas.android.com/apk/res/android" />

View File

@ -35,16 +35,14 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:fontFamily="sans-serif-light" />
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?contact_selection_label_text"
android:fontFamily="sans-serif-light"/>
<TextView android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:fontFamily="sans-serif-light"/>
</LinearLayout>

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:clickable="false">
<LinearLayout android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical"
android:padding="10dp"
android:background="@color/signal_primary">
<ImageView android:id="@+id/heart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/InviteActivity_heart_content_description"
android:src="@drawable/love_heart" />
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="20sp"
android:text="@string/InviteActivity_friends_dont_let_friends_text_unencrypted"
android:gravity="center_horizontal"
android:layout_marginBottom="30dp"
android:layout_marginTop="18dp"
android:fontFamily="sans-serif-light"
android:paddingLeft="10dp"/>
<EditText android:id="@+id/invite_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="2"
tools:text="Let's switch to Signal: http://sgnl.link/asdfdfsa"
android:background="@drawable/sent_bubble"
android:padding="10dp"
android:gravity="top"
android:inputType="textShortMessage|textMultiLine"
android:textColor="#ff333333"/>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button android:id="@+id/share_button"
android:drawablePadding="10dp"
android:drawableLeft="@drawable/ic_share_black_18dp"
android:text="@string/InviteActivity_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="ButtonStyle" />
<Button android:id="@+id/sms_button"
android:drawableLeft="@drawable/ic_message_black_18dp"
android:drawablePadding="10dp"
android:text="@string/InviteActivity_send_sms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="ButtonStyle" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout android:id="@+id/sms_send_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
android:visibility="gone">
<org.thoughtcrime.securesms.components.ContactFilterToolbar
android:id="@+id/contact_filter"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/TextSecure.LightActionBar" />
<fragment android:id="@+id/contact_selection_list_fragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="org.thoughtcrime.securesms.ContactSelectionListFragment"
tools:layout="@layout/contact_selection_list_fragment"/>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:padding="10dp"
android:orientation="horizontal">
<Button android:id="@+id/cancel_sms_button"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/InviteActivity_cancel" />
<Button android:id="@+id/send_sms_button"
style="@style/Widget.AppCompat.Button.Borderless"
android:textColor="@color/signal_primary"
android:enabled="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Send to 0 friends"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -1,29 +1,25 @@
<?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">
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/text_secure_normal__menu_new_group"
android:id="@+id/menu_new_group"
android:icon="@android:drawable/ic_menu_add" />
android:id="@+id/menu_new_group" />
<item android:title="@string/text_secure_normal__menu_clear_passphrase"
android:id="@+id/menu_clear_passphrase"
android:icon="@android:drawable/ic_menu_close_clear_cancel" />
android:id="@+id/menu_clear_passphrase" />
<item android:title="@string/text_secure_normal__mark_all_as_read"
android:id="@+id/menu_mark_all_read"
android:icon="@android:drawable/ic_menu_set_as" />
android:id="@+id/menu_mark_all_read" />
<item android:title="@string/text_secure_normal__invite_friends"
android:id="@+id/menu_invite" />
<item android:title="@string/arrays__import_export"
android:id="@+id/menu_import_export"
android:icon="@android:drawable/ic_menu_upload" />
android:id="@+id/menu_import_export" />
<item android:title="@string/arrays__my_identity_key"
android:id="@+id/menu_my_identity"
android:icon="@android:drawable/ic_menu_view" />
android:id="@+id/menu_my_identity" />
<item android:title="@string/text_secure_normal__menu_settings"
android:id="@+id/menu_settings"
android:icon="@android:drawable/ic_menu_preferences" />
android:id="@+id/menu_settings" />
</menu>

View File

@ -115,8 +115,8 @@
<string name="ConversationActivity_transport_insecure_sms">Insecure SMS</string>
<string name="ConversationActivity_transport_insecure_mms">Insecure MMS</string>
<string name="ConversationActivity_transport_signal">Signal</string>
<string name="ConversationActivity_get_with_it">Get with it: %s</string>
<string name="ConversationActivity_lets_use_this_to_chat">Let\'s use this to chat: %s</string>
<string name="ConversationActivity_lets_switch_to_signal">Let\'s switch to Signal %1$s</string>
<string name="ConversationActivity_lets_use_this_to_chat">Let\'s use this to chat: %1$s</string>
<string name="ConversationActivity_error_leaving_group">Error leaving group...</string>
<string name="ConversationActivity_mms_not_supported_title">MMS not supported</string>
<string name="ConversationActivity_mms_not_supported_message">This message cannot be sent since your carrier doesn\'t support MMS.</string>
@ -262,6 +262,20 @@
<string name="ImportFragment_no_encrypted_backup_found">No encrypted backup found!</string>
<string name="ImportFragment_restore_complete">Restore complete!</string>
<!-- InviteActivity -->
<string name="InviteActivity_share">Share</string>
<string name="InviteActivity_send_sms">Send SMS</string>
<string name="InviteActivity_cancel">Cancel</string>
<string name="InviteActivity_sending">Sending...</string>
<string name="InviteActivity_heart_content_description">Heart</string>
<string name="InviteActivity_invitations_sent">Invitations sent!</string>
<string name="InviteActivity_invite_to_signal">Invite to Signal</string>
<string name="InviteActivity_send_to_friends">SEND TO %1$s FRIEND(S)</string>
<string name="InviteActivity_send_sms_invites">Send %1$s SMS invite(s)?</string>
<string name="InviteActivity_lets_switch_to_signal">Let\'s switch to Signal: %1$s</string>
<string name="InviteActivity_no_app_to_share_to">It looks like you don\'t have any apps to share to.</string>
<string name="InviteActivity_friends_dont_let_friends_text_unencrypted">Friends don\'t let friends chat unencrypted.</string>
<!-- KeyScanningActivity -->
<string name="KeyScanningActivity_no_scanned_key_found_exclamation">No scanned key found!</string>
<string name="KeyScanningActivity_install_barcode_Scanner">Install Barcode Scanner?</string>
@ -803,6 +817,7 @@
<string name="AndroidManifest__media_overview_named">All images with %1$s</string>
<string name="AndroidManifest__message_details">Message Details</string>
<string name="AndroidManifest_manage_linked_devices">Manage linked devices</string>
<string name="AndroidManifest__invite_friends">Invite friends</string>
<!-- arrays.xml -->
<string name="arrays__import_export">Import / export</string>
@ -1031,6 +1046,7 @@
<string name="text_secure_normal__menu_settings">Settings</string>
<string name="text_secure_normal__menu_clear_passphrase">Lock</string>
<string name="text_secure_normal__mark_all_as_read">Mark all read</string>
<string name="text_secure_normal__invite_friends">Invite friends</string>
<!-- reminder_header -->
<string name="reminder_header_expired_build">Your build of Signal has expired!</string>
@ -1048,6 +1064,9 @@
<string name="reminder_header_invite_title">Invite to Signal</string>
<string name="reminder_header_invite_text">Take your conversation with %1$s to the next level.</string>
<string name="reminder_header_invite_button">INVITE</string>
<string name="reminder_header_share_title">Invite your friends!</string>
<string name="reminder_header_share_text">The more friends use Signal, the better it gets.</string>
<string name="reminder_header_share_button">SHARE</string>
<string name="reminder_header_close_button">CLOSE</string>
<!-- MediaPreviewActivity -->

View File

@ -44,6 +44,11 @@
<item name="android:textColorSecondary">#BFffffff</item>
</style>
<style name="TextSecure.FlatLightActionBar"
parent="@style/TextSecure.LightActionBar">
<item name="elevation">0dp</item>
</style>
<style name="TextSecure.DarkActionBar.TabBar"
parent="@style/Widget.AppCompat.ActionBar.TabBar">
<item name="background">@color/gray95</item>

View File

@ -30,6 +30,13 @@
<item name="contact_selection_header_text">#66eeeeee</item>
</style>
<style name="TextSecure.HighlightTheme" parent="@style/TextSecure.LightTheme">
<item name="actionBarStyle">@style/TextSecure.FlatLightActionBar</item>
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="android:windowBackground">@color/signal_primary</item>
<item name="colorButtonNormal">@color/white</item>
</style>
<style name="TextSecure.LightIntroTheme" parent="@style/Theme.AppCompat.Light">
<!--<item name="colorPrimary">@android:color/transparent</item>-->
<item name="actionBarStyle">@style/TextSecure.IntroActionBar</item>

View File

@ -1,8 +1,10 @@
package org.thoughtcrime.securesms;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
import android.content.Intent;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
@ -65,4 +67,12 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
.toBundle();
ActivityCompat.startActivity(this, intent, bundle);
}
@TargetApi(VERSION_CODES.LOLLIPOP)
protected void setStatusBarColor(int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(color);
}
}
}

View File

@ -17,28 +17,21 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log;
import android.view.TouchDelegate;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.io.IOException;
import java.lang.ref.WeakReference;
@ -53,23 +46,15 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
implements SwipeRefreshLayout.OnRefreshListener,
ContactSelectionListFragment.OnContactSelectedListener
{
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
public final static String PUSH_ONLY_EXTRA = "push_only";
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
protected ContactSelectionListFragment contactsFragment;
private MasterSecret masterSecret;
private Toolbar toolbar;
private EditText searchText;
private AnimatingToggle toggle;
protected ImageView action;
private ImageView keyboardToggle;
private ImageView dialpadToggle;
private ImageView clearToggle;
private LinearLayout toggleContainer;
private MasterSecret masterSecret;
private ContactFilterToolbar toolbar;
@Override
protected void onPreCreate() {
@ -78,9 +63,16 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
}
@Override
protected void onCreate(Bundle icicle, MasterSecret masterSecret) {
setContentView(R.layout.contact_selection_activity);
protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
this.masterSecret = masterSecret;
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE,
TextSecurePreferences.isSmsEnabled(this)
? ContactSelectionListFragment.DISPLAY_MODE_ALL
: ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY);
}
setContentView(R.layout.contact_selection_activity);
initializeToolbar();
initializeResources();
@ -94,8 +86,12 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
dynamicLanguage.onResume(this);
}
protected ContactFilterToolbar getToolbar() {
return toolbar;
}
private void initializeToolbar() {
this.toolbar = (Toolbar) findViewById(R.id.toolbar);
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
@ -103,69 +99,15 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
}
private void initializeResources() {
this.action = (ImageView) findViewById(R.id.action_icon);
this.searchText = (EditText) findViewById(R.id.search_view);
this.toggle = (AnimatingToggle) findViewById(R.id.button_toggle);
this.keyboardToggle = (ImageView) findViewById(R.id.search_keyboard);
this.dialpadToggle = (ImageView) findViewById(R.id.search_dialpad);
this.clearToggle = (ImageView) findViewById(R.id.search_clear);
this.toggleContainer = (LinearLayout) findViewById(R.id.toggle_container);
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setOnContactSelectedListener(this);
contactsFragment.setOnRefreshListener(this);
this.keyboardToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
ServiceUtil.getInputMethodManager(ContactSelectionActivity.this).showSoftInput(searchText, 0);
displayTogglingView(dialpadToggle);
}
});
this.dialpadToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchText.setInputType(InputType.TYPE_CLASS_PHONE);
ServiceUtil.getInputMethodManager(ContactSelectionActivity.this).showSoftInput(searchText, 0);
displayTogglingView(keyboardToggle);
}
});
this.clearToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchText.setText("");
if (SearchUtil.isTextInput(searchText)) displayTogglingView(dialpadToggle);
else displayTogglingView(keyboardToggle);
}
});
expandTapArea(toolbar, action, 500);
expandTapArea(toggleContainer, dialpadToggle, 500);
}
private void initializeSearch() {
this.searchText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (!SearchUtil.isEmpty(searchText)) displayTogglingView(clearToggle);
else if (SearchUtil.isTextInput(searchText)) displayTogglingView(dialpadToggle);
else if (SearchUtil.isPhoneInput(searchText)) displayTogglingView(keyboardToggle);
contactsFragment.setQueryFilter(searchText.getText().toString());
toolbar.setOnFilterChangedListener(new OnFilterChangedListener() {
@Override public void onFilterChanged(String filter) {
contactsFragment.setQueryFilter(filter);
}
});
}
@ -178,42 +120,8 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
@Override
public void onContactSelected(String number) {}
private void displayTogglingView(View view) {
toggle.display(view);
expandTapArea(toggleContainer, view, 500);
}
private void expandTapArea(final View container, final View child, final int padding) {
container.post(new Runnable() {
@Override
public void run() {
Rect rect = new Rect();
child.getHitRect(rect);
rect.top -= padding;
rect.left -= padding;
rect.right += padding;
rect.bottom += padding;
container.setTouchDelegate(new TouchDelegate(rect, child));
}
});
}
private static class SearchUtil {
public static boolean isTextInput(EditText editText) {
return (editText.getInputType() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT;
}
public static boolean isPhoneInput(EditText editText) {
return (editText.getInputType() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_PHONE;
}
public static boolean isEmpty(EditText editText) {
return editText.getText().length() <= 0;
}
}
@Override
public void onContactDeselected(String number) {}
private static class RefreshDirectoryTask extends AsyncTask<Context, Void, Void> {
@ -243,7 +151,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
ContactSelectionActivity activity = this.activity.get();
if (activity != null && !activity.isFinishing()) {
activity.searchText.setText("");
activity.toolbar.clear();
activity.contactsFragment.resetQueryFilter();
}
}

View File

@ -20,6 +20,7 @@ package org.thoughtcrime.securesms;
import android.database.Cursor;
import android.os.Build;
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;
@ -34,7 +35,6 @@ import android.widget.TextView;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.LinkedList;
import java.util.List;
@ -53,6 +53,14 @@ public class ContactSelectionListFragment extends Fragment
{
private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
public final static String DISPLAY_MODE = "display_mode";
public final static String MULTI_SELECT = "multi_select";
public final static String REFRESHABLE = "refreshable";
public final static int DISPLAY_MODE_ALL = ContactsCursorLoader.MODE_ALL;
public final static int DISPLAY_MODE_PUSH_ONLY = ContactsCursorLoader.MODE_PUSH_ONLY;
public final static int DISPLAY_MODE_OTHER_ONLY = ContactsCursorLoader.MODE_OTHER_ONLY;
private TextView emptyText;
private Map<Long, String> selectedContacts;
@ -61,8 +69,6 @@ public class ContactSelectionListFragment extends Fragment
private SwipeRefreshLayout swipeRefresh;
private String cursorFilter;
private boolean multi = false;
@Override
public void onActivityCreated(Bundle icicle) {
super.onCreate(icicle);
@ -91,26 +97,27 @@ public class ContactSelectionListFragment extends Fragment
listView.setDrawingListUnderStickyHeader(false);
listView.setOnItemClickListener(new ListClickListener());
swipeRefresh.setEnabled(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN);
swipeRefresh.setEnabled(getActivity().getIntent().getBooleanExtra(REFRESHABLE, true) &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN);
return view;
}
public List<String> getSelectedContacts() {
if (selectedContacts == null) return null;
public @NonNull List<String> getSelectedContacts() {
List<String> selected = new LinkedList<>();
selected.addAll(selectedContacts.values());
if (selectedContacts != null) {
selected.addAll(selectedContacts.values());
}
return selected;
}
public void setMultiSelect(boolean multi) {
this.multi = multi;
private boolean isMulti() {
return getActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
}
private void initializeCursor() {
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(), null, multi);
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(), null, isMulti());
selectedContacts = adapter.getSelectedContacts();
listView.setAdapter(adapter);
this.getLoaderManager().initLoader(0, null, this);
@ -126,12 +133,16 @@ public class ContactSelectionListFragment extends Fragment
swipeRefresh.setRefreshing(false);
}
public void reset() {
selectedContacts.clear();
getLoaderManager().restartLoader(0, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
boolean pushOnly = getActivity().getIntent().getBooleanExtra(ContactSelectionActivity.PUSH_ONLY_EXTRA, false);
boolean supportsSms = TextSecurePreferences.isSmsEnabled(getActivity());
return new ContactsCursorLoader(getActivity(), !pushOnly && supportsSms, cursorFilter);
return new ContactsCursorLoader(getActivity(),
getActivity().getIntent().getIntExtra(DISPLAY_MODE, DISPLAY_MODE_ALL),
cursorFilter);
}
@Override
@ -150,13 +161,14 @@ public class ContactSelectionListFragment extends Fragment
public void onItemClick(AdapterView<?> l, View v, int position, long id) {
ContactSelectionListItem contact = (ContactSelectionListItem)v;
if (!multi || !selectedContacts.containsKey(contact.getContactId())) {
if (!isMulti() || !selectedContacts.containsKey(contact.getContactId())) {
selectedContacts.put(contact.getContactId(), contact.getNumber());
contact.setChecked(true);
if (onContactSelectedListener != null) onContactSelectedListener.onContactSelected(contact.getNumber());
} else {
selectedContacts.remove(contact.getContactId());
contact.setChecked(false);
if (onContactSelectedListener != null) onContactSelectedListener.onContactDeselected(contact.getNumber());
}
}
}
@ -170,6 +182,7 @@ public class ContactSelectionListFragment extends Fragment
}
public interface OnContactSelectedListener {
public void onContactSelected(String number);
void onContactSelected(String number);
void onContactDeselected(String number);
}
}

View File

@ -488,8 +488,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleInviteLink() {
try {
boolean a = SecureRandom.getInstance("SHA1PRNG").nextBoolean();
if (a) composeText.appendInvite(getString(R.string.ConversationActivity_get_with_it, "http://sgnl.link/1IvurmD"));
else composeText.appendInvite(getString(R.string.ConversationActivity_lets_use_this_to_chat, "http://sgnl.link/1CYCQQN"));
if (a) composeText.appendInvite(getString(R.string.ConversationActivity_lets_switch_to_signal, "http://sgnl.link/1LoIMUl"));
else composeText.appendInvite(getString(R.string.ConversationActivity_lets_use_this_to_chat, "http://sgnl.link/1MF56H1"));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
@ -1108,10 +1108,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void setActionBarColor(MaterialColor color) {
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color.toActionBarColor(this)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(color.toStatusBarColor(this));
}
setStatusBarColor(color.toStatusBarColor(this));
}
private void setBlockedUserState(Recipients recipients) {

View File

@ -146,12 +146,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_new_group: createGroup(); return true;
case R.id.menu_settings: handleDisplaySettings(); return true;
case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
case R.id.menu_mark_all_read: handleMarkAllRead(); return true;
case R.id.menu_import_export: handleImportExport(); return true;
case R.id.menu_my_identity: handleMyIdentity(); return true;
case R.id.menu_new_group: createGroup(); return true;
case R.id.menu_settings: handleDisplaySettings(); return true;
case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
case R.id.menu_mark_all_read: handleMarkAllRead(); return true;
case R.id.menu_import_export: handleImportExport(); return true;
case R.id.menu_my_identity: handleMyIdentity(); return true;
case R.id.menu_invite: handleInvite(); return true;
}
return false;
@ -207,6 +208,10 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
}.execute();
}
private void handleInvite() {
startActivity(new Intent(this, InviteActivity.class));
}
private void initializeContactUpdatesReceiver() {
observer = new ContentObserver(null) {
@Override

View File

@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.ReminderView.OnDismissListener;
import org.thoughtcrime.securesms.components.reminder.ShareReminder;
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -149,6 +150,8 @@ public class ConversationListFragment extends Fragment
return Optional.of((new SystemSmsImportReminder(context, masterSecret)));
} else if (PushRegistrationReminder.isEligible(context)) {
return Optional.of((new PushRegistrationReminder(context, masterSecret)));
} else if (ShareReminder.isEligible(context)) {
return Optional.of(new ShareReminder(context));
} else {
return Optional.absent();
}

View File

@ -136,13 +136,6 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity {
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
}
@TargetApi(VERSION_CODES.LOLLIPOP)
private void setStatusBarColor(int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(color);
}
}
private void onContinue(Optional<ExperienceUpgrade> seenUpgrade) {
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
int latestVersion = seenUpgrade.isPresent() ? seenUpgrade.get().getVersion()

View File

@ -50,6 +50,7 @@ import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.components.PushRecipientsPanel;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -422,7 +423,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
@Override
public void onClick(View v) {
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
if (existingContacts != null) intent.putExtra(PushContactSelectionActivity.PUSH_ONLY_EXTRA, true);
if (existingContacts != null) intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE,
ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY);
startActivityForResult(intent, PICK_CONTACT);
}
}

View File

@ -0,0 +1,244 @@
package org.thoughtcrime.securesms;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.annotation.AnimRes;
import android.support.annotation.NonNull;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.ViewUtil;
public class InviteActivity extends PassphraseRequiredActionBarActivity implements ContactSelectionListFragment.OnContactSelectedListener {
private MasterSecret masterSecret;
private ContactSelectionListFragment contactsFragment;
private EditText inviteText;
private View shareButton;
private View smsButton;
private ViewGroup smsSendFrame;
private Button smsSendButton;
private Button smsCancelButton;
private Animation slideInAnimation;
private Animation slideOutAnimation;
private ContactFilterToolbar contactFilter;
private ImageView heart;
@Override
protected void onCreate(Bundle savedInstanceState, @NonNull MasterSecret masterSecret) {
this.masterSecret = masterSecret;
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, ContactSelectionListFragment.DISPLAY_MODE_OTHER_ONLY);
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
super.onCreate(savedInstanceState, masterSecret);
setContentView(R.layout.invite_activity);
getSupportActionBar().setTitle(R.string.AndroidManifest__invite_friends);
initializeResources();
}
private void initializeResources() {
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
shareButton = ViewUtil.findById(this, R.id.share_button);
smsButton = ViewUtil.findById(this, R.id.sms_button);
inviteText = ViewUtil.findById(this, R.id.invite_text);
smsSendFrame = ViewUtil.findById(this, R.id.sms_send_frame);
smsSendButton = ViewUtil.findById(this, R.id.send_sms_button);
smsCancelButton = ViewUtil.findById(this, R.id.cancel_sms_button);
contactFilter = ViewUtil.findById(this, R.id.contact_filter);
heart = ViewUtil.findById(this, R.id.heart);
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, "http://sgnl.link/1KpeYmF"));
updateSmsButtonText();
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
heart.getViewTreeObserver().addOnPreDrawListener(new HeartPreDrawListener());
}
contactsFragment.setOnContactSelectedListener(this);
shareButton.setOnClickListener(new ShareClickListener());
smsButton.setOnClickListener(new SmsClickListener());
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
smsSendButton.setOnClickListener(new SmsSendClickListener());
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
}
private Animation loadAnimation(@AnimRes int animResId) {
final Animation animation = AnimationUtils.loadAnimation(this, animResId);
animation.setInterpolator(new FastOutSlowInInterpolator());
return animation;
}
@Override
public void onContactSelected(String number) {
updateSmsButtonText();
}
@Override
public void onContactDeselected(String number) {
updateSmsButtonText();
}
private void sendSmsInvites() {
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
.execute(contactsFragment.getSelectedContacts()
.toArray(new String[contactsFragment.getSelectedContacts().size()]));
}
private void updateSmsButtonText() {
smsSendButton.setText(getString(R.string.InviteActivity_send_to_friends, contactsFragment.getSelectedContacts().size()));
smsSendButton.setEnabled(!contactsFragment.getSelectedContacts().isEmpty());
}
@Override public void onBackPressed() {
if (smsSendFrame.getVisibility() == View.VISIBLE) {
cancelSmsSelection();
} else {
super.onBackPressed();
}
}
private void cancelSmsSelection() {
contactsFragment.reset();
updateSmsButtonText();
ViewUtil.animateOut(smsSendFrame, slideOutAnimation);
}
private class ShareClickListener implements OnClickListener {
@Override
public void onClick(View v) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, inviteText.getText().toString());
sendIntent.setType("text/plain");
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(Intent.createChooser(sendIntent, getString(R.string.InviteActivity_invite_to_signal)));
} else {
Toast.makeText(InviteActivity.this, R.string.InviteActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
}
}
}
private class SmsClickListener implements OnClickListener {
@Override
public void onClick(View v) {
ViewUtil.animateIn(smsSendFrame, slideInAnimation);
}
}
private class SmsCancelClickListener implements OnClickListener {
@Override
public void onClick(View v) {
cancelSmsSelection();
}
}
private class SmsSendClickListener implements OnClickListener {
@Override
public void onClick(View v) {
new AlertDialog.Builder(InviteActivity.this)
.setTitle(getString(R.string.InviteActivity_send_sms_invites, contactsFragment.getSelectedContacts().size()))
.setMessage(inviteText.getText().toString())
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
sendSmsInvites();
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
}
}
private class ContactFilterChangedListener implements OnFilterChangedListener {
@Override
public void onFilterChanged(String filter) {
contactsFragment.setQueryFilter(filter);
}
}
private class HeartPreDrawListener implements OnPreDrawListener {
@Override
@TargetApi(VERSION_CODES.LOLLIPOP)
public boolean onPreDraw() {
heart.getViewTreeObserver().removeOnPreDrawListener(this);
final int w = heart.getWidth();
final int h = heart.getHeight();
Animator reveal = ViewAnimationUtils.createCircularReveal(heart,
w / 2, h,
0, (float)Math.sqrt(h*h + (w*w/4)));
reveal.setInterpolator(new FastOutSlowInInterpolator());
reveal.setDuration(800);
reveal.start();
return false;
}
}
private class SendSmsInvitesAsyncTask extends ProgressDialogAsyncTask<String,Void,Void> {
private final String message;
public SendSmsInvitesAsyncTask(Context context, String message) {
super(context, R.string.InviteActivity_sending, R.string.InviteActivity_sending);
this.message = message;
}
@Override
protected Void doInBackground(String... numbers) {
final Context context = getContext();
if (context == null) return null;
for (String number : numbers) {
final Recipients recipients = RecipientFactory.getRecipientsFromString(context, number, false);
if (recipients != null && recipients.getPrimaryRecipient() != null) {
MessageSender.send(context, masterSecret, new OutgoingTextMessage(recipients, message), -1L, true);
if (recipients.getPrimaryRecipient().getContactUri() != null) {
DatabaseFactory.getRecipientPreferenceDatabase(context).setSeenInviteReminder(recipients, true);
}
}
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
final Context context = getContext();
if (context == null) return;
ViewUtil.animateOut(smsSendFrame, slideOutAnimation);
Toast.makeText(context, R.string.InviteActivity_invitations_sent, Toast.LENGTH_LONG).show();
}
}
}

View File

@ -18,8 +18,8 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.MenuItem;
import android.view.View;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -38,10 +38,10 @@ public class NewConversationActivity extends ContactSelectionActivity {
private static final String TAG = NewConversationActivity.class.getSimpleName();
@Override
public void onCreate(Bundle bundle, MasterSecret masterSecret) {
public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
super.onCreate(bundle, masterSecret);
action.setVisibility(View.GONE);
getToolbar().setShowCustomNavigationButton(false);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}

View File

@ -19,17 +19,9 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.ArrayList;
import java.util.List;
@ -46,14 +38,14 @@ public class PushContactSelectionActivity extends ContactSelectionActivity {
@Override
protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
super.onCreate(icicle, masterSecret);
contactsFragment.setMultiSelect(true);
action.setImageDrawable(getResources().getDrawable(R.drawable.ic_check_white_24dp));
action.setOnClickListener(new View.OnClickListener() {
getToolbar().setNavigationIcon(R.drawable.ic_check_white_24dp);
getToolbar().setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent resultIntent = getIntent();
Intent resultIntent = getIntent();
List<String> selectedContacts = contactsFragment.getSelectedContacts();
if (selectedContacts != null) {

View File

@ -0,0 +1,171 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.TouchDelegate;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
public class ContactFilterToolbar extends Toolbar {
private OnFilterChangedListener listener;
private EditText searchText;
private AnimatingToggle toggle;
private ImageView action;
private ImageView keyboardToggle;
private ImageView dialpadToggle;
private ImageView clearToggle;
private LinearLayout toggleContainer;
public ContactFilterToolbar(Context context) {
this(context, null);
}
public ContactFilterToolbar(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.toolbarStyle);
}
public ContactFilterToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.contact_filter_toolbar, this);
this.action = ViewUtil.findById(this, R.id.action_icon);
this.searchText = ViewUtil.findById(this, R.id.search_view);
this.toggle = ViewUtil.findById(this, R.id.button_toggle);
this.keyboardToggle = ViewUtil.findById(this, R.id.search_keyboard);
this.dialpadToggle = ViewUtil.findById(this, R.id.search_dialpad);
this.clearToggle = ViewUtil.findById(this, R.id.search_clear);
this.toggleContainer = ViewUtil.findById(this, R.id.toggle_container);
this.keyboardToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
ServiceUtil.getInputMethodManager(getContext()).showSoftInput(searchText, 0);
displayTogglingView(dialpadToggle);
}
});
this.dialpadToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchText.setInputType(InputType.TYPE_CLASS_PHONE);
ServiceUtil.getInputMethodManager(getContext()).showSoftInput(searchText, 0);
displayTogglingView(keyboardToggle);
}
});
this.clearToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchText.setText("");
if (SearchUtil.isTextInput(searchText)) displayTogglingView(dialpadToggle);
else displayTogglingView(keyboardToggle);
}
});
this.searchText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (!SearchUtil.isEmpty(searchText)) displayTogglingView(clearToggle);
else if (SearchUtil.isTextInput(searchText)) displayTogglingView(dialpadToggle);
else if (SearchUtil.isPhoneInput(searchText)) displayTogglingView(keyboardToggle);
notifyListener();
}
});
expandTapArea(this, action, 500);
expandTapArea(toggleContainer, dialpadToggle, 500);
}
@Override
public void setNavigationIcon(int resId) {
action.setImageResource(resId);
}
@Override
public void setNavigationOnClickListener(OnClickListener listener) {
super.setNavigationOnClickListener(listener);
action.setOnClickListener(listener);
}
public void setShowCustomNavigationButton(boolean show) {
action.setVisibility(show ? VISIBLE : GONE);
}
public void clear() {
searchText.setText("");
notifyListener();
}
public void setOnFilterChangedListener(OnFilterChangedListener listener) {
this.listener = listener;
}
private void notifyListener() {
if (listener != null) listener.onFilterChanged(searchText.getText().toString());
}
private void displayTogglingView(View view) {
toggle.display(view);
expandTapArea(toggleContainer, view, 500);
}
private void expandTapArea(final View container, final View child, final int padding) {
container.post(new Runnable() {
@Override
public void run() {
Rect rect = new Rect();
child.getHitRect(rect);
rect.top -= padding;
rect.left -= padding;
rect.right += padding;
rect.bottom += padding;
container.setTouchDelegate(new TouchDelegate(rect, child));
}
});
}
private static class SearchUtil {
public static boolean isTextInput(EditText editText) {
return (editText.getInputType() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT;
}
public static boolean isPhoneInput(EditText editText) {
return (editText.getInputType() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_PHONE;
}
public static boolean isEmpty(EditText editText) {
return editText.getText().length() <= 0;
}
}
public interface OnFilterChangedListener {
void onFilterChanged(String filter);
}
}

View File

@ -54,18 +54,15 @@ public class ReminderView extends LinearLayout {
setOnClickListener(reminder.getOkListener());
if (reminder.isDismissable()) {
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hide();
if (reminder.getDismissListener() != null) reminder.getDismissListener().onClick(v);
if (dismissListener != null) dismissListener.onDismiss();
}
});
} else {
closeButton.setVisibility(View.GONE);
}
closeButton.setVisibility(reminder.isDismissable() ? View.VISIBLE : View.GONE);
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hide();
if (reminder.getDismissListener() != null) reminder.getDismissListener().onClick(v);
if (dismissListener != null) dismissListener.onDismiss();
}
});
container.setVisibility(View.VISIBLE);
}

View File

@ -0,0 +1,50 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.View.OnClickListener;
import org.thoughtcrime.securesms.InviteActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class ShareReminder extends Reminder {
public ShareReminder(final @NonNull Context context) {
super(context.getString(R.string.reminder_header_share_title),
context.getString(R.string.reminder_header_share_text));
setDismissListener(new OnClickListener() {
@Override public void onClick(View v) {
TextSecurePreferences.setPromptedShare(context, true);
}
});
setOkListener(new OnClickListener() {
@Override public void onClick(View v) {
TextSecurePreferences.setPromptedShare(context, true);
context.startActivity(new Intent(context, InviteActivity.class));
}
});
}
public static boolean isEligible(final @NonNull Context context) {
if (!TextSecurePreferences.isPushRegistered(context) ||
TextSecurePreferences.hasPromptedShare(context))
{
return false;
}
Cursor cursor = null;
try {
cursor = DatabaseFactory.getThreadDatabase(context).getConversationList();
return cursor.getCount() >= 1;
} finally {
if (cursor != null) cursor.close();
}
}
}

View File

@ -21,6 +21,7 @@ import android.content.res.TypedArray;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -59,10 +60,6 @@ public class ContactSelectionListAdapter extends CursorAdapter
this.multiSelect = multiSelect;
}
public static class HeaderViewHolder {
TextView text;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return li.inflate(R.layout.contact_selection_list_item, parent, false);
@ -90,27 +87,23 @@ public class ContactSelectionListAdapter extends CursorAdapter
@Override
public View getHeaderView(int i, View convertView, ViewGroup viewGroup) {
Cursor cursor = getCursor();
HeaderViewHolder holder;
Cursor cursor = getCursor();
final TextView text;
if (convertView == null) {
holder = new HeaderViewHolder();
convertView = li.inflate(R.layout.contact_selection_list_header, viewGroup, false);
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
text = (TextView)li.inflate(R.layout.contact_selection_list_header, viewGroup, false);
} else {
holder = (HeaderViewHolder) convertView.getTag();
text = (TextView)convertView;
}
cursor.moveToPosition(i);
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
if (contactType == ContactsDatabase.PUSH_TYPE) holder.text.setText(R.string.contact_selection_list__header_signal_users);
else holder.text.setText(R.string.contact_selection_list__header_other);
if (contactType == ContactsDatabase.PUSH_TYPE) text.setText(R.string.contact_selection_list__header_signal_users);
else text.setText(R.string.contact_selection_list__header_other);
return convertView;
return text;
}
@Override

View File

@ -18,12 +18,18 @@ package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.support.annotation.NonNull;
import android.support.v4.content.CursorLoader;
import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DirectoryHelper.UserCapabilities.Capability;
import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.ArrayList;
@ -37,14 +43,18 @@ public class ContactsCursorLoader extends CursorLoader {
private static final String TAG = ContactsCursorLoader.class.getSimpleName();
private final String filter;
private boolean includeSmsContacts;
public final static int MODE_ALL = 0;
public final static int MODE_PUSH_ONLY = 1;
public final static int MODE_OTHER_ONLY = 2;
public ContactsCursorLoader(Context context, boolean includeSmsContacts, String filter) {
private final String filter;
private final int mode;
public ContactsCursorLoader(Context context, int mode, String filter) {
super(context);
this.filter = filter;
this.includeSmsContacts = includeSmsContacts;
this.filter = filter;
this.mode = mode;
}
@Override
@ -52,10 +62,14 @@ public class ContactsCursorLoader extends CursorLoader {
ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(getContext());
ArrayList<Cursor> cursorList = new ArrayList<>(3);
cursorList.add(contactsDatabase.queryTextSecureContacts(filter));
if (mode != MODE_OTHER_ONLY) {
cursorList.add(contactsDatabase.queryTextSecureContacts(filter));
}
if (includeSmsContacts) {
if (mode == MODE_ALL) {
cursorList.add(contactsDatabase.querySystemContacts(filter));
} else if (mode == MODE_OTHER_ONLY) {
cursorList.add(filterNonPushContacts(contactsDatabase.querySystemContacts(filter)));
}
if (!TextUtils.isEmpty(filter) && NumberUtil.isValidSmsOrEmail(filter)) {
@ -64,4 +78,35 @@ public class ContactsCursorLoader extends CursorLoader {
return new MergeCursor(cursorList.toArray(new Cursor[0]));
}
private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) {
try {
final long startMillis = System.currentTimeMillis();
final MatrixCursor matrix = new MatrixCursor(new String[]{ContactsDatabase.ID_COLUMN,
ContactsDatabase.NAME_COLUMN,
ContactsDatabase.NUMBER_COLUMN,
ContactsDatabase.NUMBER_TYPE_COLUMN,
ContactsDatabase.LABEL_COLUMN,
ContactsDatabase.CONTACT_TYPE_COLUMN});
while (cursor.moveToNext()) {
final String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
final Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), number, true);
if (DirectoryHelper.getUserCapabilities(getContext(), recipients)
.getTextCapability() != Capability.SUPPORTED)
{
matrix.addRow(new Object[]{cursor.getLong(cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN)),
number,
cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN)),
ContactsDatabase.NORMAL_TYPE});
}
}
Log.w(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms");
return matrix;
} finally {
cursor.close();
}
}
}

View File

@ -23,11 +23,13 @@ import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Build;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.RawContacts;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -36,6 +38,10 @@ import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.TextSecureDirectory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DirectoryHelper.UserCapabilities.Capability;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
@ -55,7 +61,9 @@ import java.util.Set;
*/
public class ContactsDatabase {
private static final String TAG = ContactsDatabase.class.getSimpleName();
private static final String TAG = ContactsDatabase.class.getSimpleName();
private static final String MIME = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact";
private static final String SYNC = "__TS";
public static final String ID_COLUMN = "_id";
public static final String NAME_COLUMN = "name";
@ -154,12 +162,12 @@ public class ContactsDatabase {
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, e164number)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_OTHER)
.withValue(ContactsContract.Data.SYNC2, "__TS")
.withValue(ContactsContract.Data.SYNC2, SYNC)
.build());
operations.add(ContentProviderOperation.newInsert(dataUri)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact")
.withValue(ContactsContract.Data.MIMETYPE, MIME)
.withValue(ContactsContract.Data.DATA1, e164number)
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_message_s, e164number))
@ -219,7 +227,7 @@ public class ContactsDatabase {
Cursor cursor = context.getContentResolver().query(uri, projection,
ContactsContract.Data.SYNC2 + " IS NULL OR " +
ContactsContract.Data.SYNC2 + " != ?",
new String[] {"__TS"},
new String[] {SYNC},
sort);
return new ProjectionMappingCursor(cursor, projectionMap,
@ -245,13 +253,13 @@ public class ContactsDatabase {
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.MIMETYPE + " = ?",
new String[] {"vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact"},
new String[] {MIME},
sort);
} else {
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.MIMETYPE + " = ? AND (" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? OR " + ContactsContract.Data.DATA1 + " LIKE ?)",
new String[] {"vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact",
new String[] {MIME,
"%" + filter + "%", "%" + filter + "%"},
sort);
}

View File

@ -150,7 +150,9 @@ public class DirectoryHelper {
}
}
public static UserCapabilities getUserCapabilities(Context context, Recipients recipients) {
public static @NonNull UserCapabilities getUserCapabilities(@NonNull Context context,
@Nullable Recipients recipients)
{
try {
if (recipients == null) {
return UserCapabilities.UNSUPPORTED;

View File

@ -14,7 +14,7 @@ public abstract class ProgressDialogAsyncTask<Params, Progress, Result> extends
public ProgressDialogAsyncTask(Context context, String title, String message) {
super();
this.contextReference = new WeakReference<Context>(context);
this.contextReference = new WeakReference<>(context);
this.title = title;
this.message = message;
}

View File

@ -68,6 +68,7 @@ public class TextSecurePreferences {
private static final String GCM_PASSWORD_PREF = "pref_gcm_password";
private static final String PROMPTED_PUSH_REGISTRATION_PREF = "pref_prompted_push_registration";
private static final String PROMPTED_DEFAULT_SMS_PREF = "pref_prompted_default_sms";
private static final String PROMPTED_SHARE_PREF = "pref_prompted_share";
private static final String SIGNALING_KEY_PREF = "pref_signaling_key";
private static final String DIRECTORY_FRESH_TIME_PREF = "pref_directory_refresh_time";
private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
@ -423,6 +424,14 @@ public class TextSecurePreferences {
setBooleanPreference(context, PROMPTED_DEFAULT_SMS_PREF, value);
}
public static boolean hasPromptedShare(Context context) {
return getBooleanPreference(context, PROMPTED_SHARE_PREF, false);
}
public static void setPromptedShare(Context context, boolean value) {
setBooleanPreference(context, PROMPTED_SHARE_PREF, value);
}
public static boolean isInterceptAllMmsEnabled(Context context) {
return getBooleanPreference(context, ALL_MMS_PREF, true);
}