diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5ef671682..f06602738 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -388,6 +388,18 @@ android:theme="@style/TextSecure.LightTheme" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> + + + + + + + + diff --git a/res/drawable-hdpi/clear_profile_avatar.png b/res/drawable-hdpi/clear_profile_avatar.png new file mode 100644 index 000000000..85126cb69 Binary files /dev/null and b/res/drawable-hdpi/clear_profile_avatar.png differ diff --git a/res/drawable-mdpi/clear_profile_avatar.png b/res/drawable-mdpi/clear_profile_avatar.png new file mode 100644 index 000000000..a4548bcd9 Binary files /dev/null and b/res/drawable-mdpi/clear_profile_avatar.png differ diff --git a/res/drawable-xhdpi/clear_profile_avatar.png b/res/drawable-xhdpi/clear_profile_avatar.png new file mode 100644 index 000000000..384b15780 Binary files /dev/null and b/res/drawable-xhdpi/clear_profile_avatar.png differ diff --git a/res/drawable-xxhdpi/clear_profile_avatar.png b/res/drawable-xxhdpi/clear_profile_avatar.png new file mode 100644 index 000000000..93e07e60c Binary files /dev/null and b/res/drawable-xxhdpi/clear_profile_avatar.png differ diff --git a/res/drawable-xxxhdpi/clear_profile_avatar.png b/res/drawable-xxxhdpi/clear_profile_avatar.png new file mode 100644 index 000000000..ab64da56c Binary files /dev/null and b/res/drawable-xxxhdpi/clear_profile_avatar.png differ diff --git a/res/layout/profile_create_activity.xml b/res/layout/profile_create_activity.xml index 37fe9e372..e4ea5000a 100644 --- a/res/layout/profile_create_activity.xml +++ b/res/layout/profile_create_activity.xml @@ -16,7 +16,8 @@ + android:layout_height="64dp" + android:transitionName="avatar"/> + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index db8b4eee1..d98b3bb67 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -73,6 +73,10 @@ Incoming call + + Remove + Remove profile photo? + Your safety number with %1$s has changed. This could either mean that someone is trying to intercept your communication, or that %2$s simply reinstalled Signal. You may wish to verify your safety number with this contact. @@ -613,6 +617,14 @@ Signal update A new version of Signal is available, tap to update + + Send message? + Send + + + Send message? + Send + Your contact is running an old version of Signal. Please ask them to update before verifying your safety number. Your contact is running a newer version of Signal with an incompatible QR code format. Please update to compare. @@ -1087,6 +1099,7 @@ Linked devices Invite friends Archived conversations + Remove photo Import / export @@ -1409,10 +1422,7 @@ Transport icon - Send message? - Send - Send message? - Send + diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 373b8392e..49c56459c 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -1,6 +1,10 @@ + + + diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 0266765a6..fcdc34798 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -34,6 +34,7 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.app.AppCompatActivity; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment; @@ -60,6 +61,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA { private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName(); + private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile"; private static final String PREFERENCE_CATEGORY_SMS_MMS = "preference_category_sms_mms"; private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications"; private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection"; @@ -138,6 +140,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA addPreferencesFromResource(R.xml.preferences); MasterSecret masterSecret = getArguments().getParcelable("master_secret"); + this.findPreference(PREFERENCE_CATEGORY_PROFILE) + .setOnPreferenceClickListener(new ProfileClickListener()); this.findPreference(PREFERENCE_CATEGORY_SMS_MMS) .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_SMS_MMS)); this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS) @@ -273,5 +277,17 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA return true; } } + + private class ProfileClickListener implements Preference.OnPreferenceClickListener { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(preference.getContext(), CreateProfileActivity.class); + intent.putExtra(CreateProfileActivity.EXCLUDE_SYSTEM, true); + + ((BaseActionBarActivity)getActivity()).startActivitySceneTransition(intent, getActivity().findViewById(R.id.avatar), "avatar"); + return true; + } + } } + } diff --git a/src/org/thoughtcrime/securesms/ClearProfileAvatarActivity.java b/src/org/thoughtcrime/securesms/ClearProfileAvatarActivity.java new file mode 100644 index 000000000..4400b2997 --- /dev/null +++ b/src/org/thoughtcrime/securesms/ClearProfileAvatarActivity.java @@ -0,0 +1,26 @@ +package org.thoughtcrime.securesms; + + +import android.app.Activity; +import android.content.Intent; +import android.support.v7.app.AlertDialog; + +public class ClearProfileAvatarActivity extends Activity { + + @Override + public void onResume() { + super.onResume(); + + new AlertDialog.Builder(this) + .setTitle(R.string.ClearProfileActivity_remove_profile_photo) + .setNegativeButton(android.R.string.cancel, (dialog, which) -> finish()) + .setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> { + Intent result = new Intent(); + result.putExtra("delete", true); + setResult(Activity.RESULT_OK, result); + finish(); + }) + .show(); + } + +} diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java index ad40ed238..568b2da40 100644 --- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java +++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java @@ -53,6 +53,8 @@ import org.whispersystems.signalservice.api.util.StreamDetails; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.util.LinkedList; +import java.util.List; import java.util.concurrent.ExecutionException; import javax.inject.Inject; @@ -63,6 +65,9 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i private static final String TAG = CreateProfileActivity.class.getSimpleName(); + public static final String NEXT_INTENT = "next_intent"; + public static final String EXCLUDE_SYSTEM = "exclude_system"; + private static final int REQUEST_CODE_AVATAR = 1; @Inject SignalServiceAccountManager accountManager; @@ -74,8 +79,9 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i private EmojiToggle emojiToggle; private EmojiDrawer emojiDrawer; + private Intent nextIntent; private byte[] avatarBytes; - private File captureFile; + private File captureFile; @Override public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) { @@ -88,8 +94,8 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i initializeResources(); initializeEmojiInput(); - initializeProfileName(); - initializeProfileAvatar(); + initializeProfileName(getIntent().getBooleanExtra(EXCLUDE_SYSTEM, false)); + initializeProfileAvatar(getIntent().getBooleanExtra(EXCLUDE_SYSTEM, false)); ApplicationContext.getInstance(this).injectDependencies(this); } @@ -123,7 +129,13 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i inputFile = Uri.fromFile(captureFile); } - new Crop(inputFile).output(outputFile).asSquare().start(this); + if (data.getBooleanExtra("delete", false)) { + avatarBytes = null; + avatar.setImageDrawable(ContactPhotoFactory.getResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp) + .asDrawable(this, getResources().getColor(R.color.grey_400))); + } else { + new Crop(inputFile).output(outputFile).asSquare().start(this); + } } break; @@ -148,6 +160,7 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i this.emojiDrawer = ViewUtil.findById(this, R.id.emoji_drawer); this.container = ViewUtil.findById(this, R.id.container); this.finishButton = ViewUtil.findById(this, R.id.finish_button); + this.nextIntent = getIntent().getParcelableExtra(NEXT_INTENT); this.avatar.setImageDrawable(ContactPhotoFactory.getResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp) .asDrawable(this, getResources().getColor(R.color.grey_400))); @@ -160,7 +173,7 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i captureFile = null; } - Intent chooserIntent = createAvatarSelectionIntent(captureFile); + Intent chooserIntent = createAvatarSelectionIntent(captureFile, avatarBytes != null); startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR); }); @@ -169,13 +182,13 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i }); } - private void initializeProfileName() { + private void initializeProfileName(boolean excludeSystem) { if (!TextUtils.isEmpty(TextSecurePreferences.getProfileName(this))) { String profileName = TextSecurePreferences.getProfileName(this); name.setText(profileName); name.setSelection(profileName.length(), profileName.length()); - } else { + } else if (!excludeSystem) { SystemProfileUtil.getSystemProfileName(this).addListener(new ListenableFuture.Listener() { @Override public void onSuccess(String result) { @@ -193,7 +206,7 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i } } - private void initializeProfileAvatar() { + private void initializeProfileAvatar(boolean excludeSystem) { Address ourAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)); if (AvatarHelper.getAvatarFile(this, ourAddress).exists() && AvatarHelper.getAvatarFile(this, ourAddress).length() > 0) { @@ -217,7 +230,7 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i } } }.execute(); - } else { + } else if (!excludeSystem) { SystemProfileUtil.getSystemProfileAvatar(this, new ProfileMediaConstraints()).addListener(new ListenableFuture.Listener() { @Override public void onSuccess(byte[] result) { @@ -266,8 +279,9 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i this.name.setOnClickListener(v -> container.showSoftkey(name)); } - private Intent createAvatarSelectionIntent(@Nullable File captureFile) { - Intent galleryIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI); + private Intent createAvatarSelectionIntent(@Nullable File captureFile, boolean includeClear) { + List extraIntents = new LinkedList<>(); + Intent galleryIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI); galleryIntent.setType("image/*"); if (!IntentUtils.isResolvable(CreateProfileActivity.this, galleryIntent)) { @@ -276,23 +290,23 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i } Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + cameraIntent.putExtra(EXTRA_OUTPUT, Uri.fromFile(captureFile)); if (captureFile != null && cameraIntent.resolveActivity(getPackageManager()) != null) { - cameraIntent.putExtra(EXTRA_OUTPUT, Uri.fromFile(captureFile)); - } else { - cameraIntent = null; + extraIntents.add(cameraIntent); + } + + if (includeClear) { + extraIntents.add(new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO")); } Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.CreateProfileActivity_profile_photo)); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Intent[0])); - if (cameraIntent != null) { - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] {cameraIntent}); - } return chooserIntent; } - private void handleUpload() { final String name; final StreamDetails avatar; @@ -342,7 +356,8 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i if (result) { if (captureFile != null) captureFile.delete(); - startActivity(new Intent(CreateProfileActivity.this, ConversationListActivity.class)); + if (nextIntent != null) startActivity(nextIntent); + finish(); } else { Toast.makeText(CreateProfileActivity.this, R.string.CreateProfileActivity_problem_setting_profile, Toast.LENGTH_LONG).show(); diff --git a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java index 589d460cd..b4b7bd944 100644 --- a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java @@ -342,7 +342,9 @@ public class RegistrationProgressActivity extends BaseActionBarActivity { } shutdownService(); - startActivity(new Intent(this, CreateProfileActivity.class)); + Intent intent = new Intent(this, CreateProfileActivity.class); + intent.putExtra(CreateProfileActivity.NEXT_INTENT, new Intent(this, ConversationListActivity.class)); + startActivity(intent); finish(); } diff --git a/src/org/thoughtcrime/securesms/preferences/ProfilePreference.java b/src/org/thoughtcrime/securesms/preferences/ProfilePreference.java new file mode 100644 index 000000000..ac975b9dc --- /dev/null +++ b/src/org/thoughtcrime/securesms/preferences/ProfilePreference.java @@ -0,0 +1,76 @@ +package org.thoughtcrime.securesms.preferences; + + +import android.content.Context; +import android.os.AsyncTask; +import android.os.Build; +import android.preference.Preference; +import android.support.annotation.RequiresApi; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; +import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory; +import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.ViewUtil; + +public class ProfilePreference extends Preference { + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(); + } + + public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + public ProfilePreference(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public ProfilePreference(Context context) { + super(context); + initialize(); + } + + private void initialize() { + setLayoutResource(R.layout.profile_preference_view); + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + + final ImageView avatar = ViewUtil.findById(view, R.id.avatar); + final TextView profileName = ViewUtil.findById(view, R.id.profile_name); + final TextView profileNumber = ViewUtil.findById(view, R.id.number); + final Address localAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(getContext())); + + new AsyncTask() { + @Override + protected ContactPhoto doInBackground(Void... params) { + return ContactPhotoFactory.getSignalAvatarContactPhoto(getContext(), localAddress, null, getContext().getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size)); + } + + @Override + protected void onPostExecute(ContactPhoto contactPhoto) { + avatar.setImageDrawable(contactPhoto.asDrawable(getContext(), 0)); + } + }.execute(); + + if (!TextUtils.isEmpty(TextSecurePreferences.getProfileName(getContext()))) { + profileName.setText(TextSecurePreferences.getProfileName(getContext())); + } + + profileNumber.setText(localAddress.toPhoneString()); + } +}