mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-18 16:57:46 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3176bbb67 | ||
|
|
4203dde151 | ||
|
|
880661710f | ||
|
|
d844fa0fb5 | ||
|
|
18ede2e900 | ||
|
|
06cc96bee7 | ||
|
|
43a12d2a81 | ||
|
|
0a29ffcf4c | ||
|
|
9c88532c21 | ||
|
|
f3450b8f10 | ||
|
|
a4d56e376f | ||
|
|
24b5bac589 | ||
|
|
762f17f1c1 | ||
|
|
105c8c9745 | ||
|
|
93d99287eb | ||
|
|
ce156c3450 | ||
|
|
7db16e6156 | ||
|
|
e80033c287 | ||
|
|
1553f9b75d | ||
|
|
c244a98962 | ||
|
|
a8ad1e718e | ||
|
|
b5712f4bd1 | ||
|
|
6bcb0de43d | ||
|
|
80651d2425 | ||
|
|
46492b8238 | ||
|
|
1be561543c | ||
|
|
e430a46e20 | ||
|
|
8d187c8ba1 | ||
|
|
eacf03768f | ||
|
|
b077c9b4f3 | ||
|
|
893749fcab | ||
|
|
848ead5e78 | ||
|
|
9c47acb004 | ||
|
|
8ca54bcc7b | ||
|
|
7e64d57ba8 | ||
|
|
a517fc4e15 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
custom: https://signal.org/donate/
|
||||
@@ -61,8 +61,8 @@ protobuf {
|
||||
}
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 771
|
||||
def canonicalVersionName = "5.2.2"
|
||||
def canonicalVersionCode = 774
|
||||
def canonicalVersionName = "5.3.1"
|
||||
|
||||
def postFixSize = 100
|
||||
def abiPostFix = ['universal' : 0,
|
||||
@@ -340,11 +340,11 @@ dependencies {
|
||||
implementation project(':video')
|
||||
|
||||
implementation 'org.signal:zkgroup-android:0.7.0'
|
||||
implementation 'org.whispersystems:signal-client-android:0.1.5'
|
||||
implementation 'org.whispersystems:signal-client-android:0.2.2'
|
||||
implementation 'com.google.protobuf:protobuf-javalite:3.10.0'
|
||||
implementation 'org.signal:argon2:13.1@aar'
|
||||
|
||||
implementation 'org.signal:ringrtc-android:2.8.9'
|
||||
implementation 'org.signal:ringrtc-android:2.8.10'
|
||||
|
||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||
|
||||
@@ -159,12 +159,15 @@
|
||||
<activity android:name=".preferences.MmsPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity android:name=".sharing.ShareActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:noHistory="true"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
<intent-filter>
|
||||
@@ -354,6 +357,16 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".wallpaper.ChatWallpaperActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".wallpaper.ChatWallpaperPreviewActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
@@ -460,6 +473,10 @@
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity android:name=".profiles.manage.ManageProfileActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
@@ -535,6 +552,15 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/TextSecure.FullScreenMedia" />
|
||||
|
||||
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Signal.WallpaperCropper" />
|
||||
|
||||
<service android:enabled="true" android:name=".service.WebRtcCallService"/>
|
||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||
|
||||
@@ -43,6 +43,7 @@ public final class AppInitialization {
|
||||
SignalStore.onFirstEverAppLaunch();
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||
}
|
||||
@@ -55,6 +56,7 @@ public final class AppInitialization {
|
||||
SignalStore.onboarding().clearAll();
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||
}
|
||||
@@ -76,6 +78,7 @@ public final class AppInitialization {
|
||||
SignalStore.onFirstEverAppLaunch();
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.UsernamePreference;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
@@ -345,7 +346,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
requireActivity().startActivity(EditProfileActivity.getIntentForUserProfileEdit(preference.getContext()));
|
||||
requireActivity().startActivity(ManageProfileActivity.getIntent(requireActivity()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -353,7 +354,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
private class UsernameClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
requireActivity().startActivity(EditProfileActivity.getIntentForUsernameEdit(preference.getContext()));
|
||||
requireActivity().startActivity(ManageProfileActivity.getIntentForUsernameEdit(preference.getContext()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ public interface BindableConversationItem extends Unbindable {
|
||||
@NonNull Set<ConversationMessage> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseMention);
|
||||
boolean pulseMention,
|
||||
boolean hasWallpaper);
|
||||
|
||||
ConversationMessage getConversationMessage();
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||
|
||||
private void initializeResources() {
|
||||
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||
contactsFragment.setOnContactSelectedListener(this);
|
||||
contactsFragment.setOnRefreshListener(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -110,22 +110,26 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
public static final String SELECTION_LIMITS = "selection_limits";
|
||||
public static final String CURRENT_SELECTION = "current_selection";
|
||||
public static final String HIDE_COUNT = "hide_count";
|
||||
public static final String CAN_SELECT_SELF = "can_select_self";
|
||||
public static final String DISPLAY_CHIPS = "display_chips";
|
||||
|
||||
private ConstraintLayout constraintLayout;
|
||||
private TextView emptyText;
|
||||
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;
|
||||
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
||||
private ChipGroup chipGroup;
|
||||
private HorizontalScrollView chipGroupScrollContainer;
|
||||
private WarningTextView groupLimit;
|
||||
private OnSelectionLimitReachedListener onSelectionLimitReachedListener;
|
||||
|
||||
private ConstraintLayout constraintLayout;
|
||||
private TextView emptyText;
|
||||
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;
|
||||
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
||||
private ChipGroup chipGroup;
|
||||
private HorizontalScrollView chipGroupScrollContainer;
|
||||
private WarningTextView groupLimit;
|
||||
|
||||
@Nullable private FixedViewsAdapter headerAdapter;
|
||||
@Nullable private FixedViewsAdapter footerAdapter;
|
||||
@@ -136,6 +140,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
private Set<RecipientId> currentSelection;
|
||||
private boolean isMulti;
|
||||
private boolean hideCount;
|
||||
private boolean canSelectSelf;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
@@ -148,6 +153,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
if (context instanceof ScrollCallback) {
|
||||
scrollCallback = (ScrollCallback) context;
|
||||
}
|
||||
|
||||
if (context instanceof OnContactSelectedListener) {
|
||||
onContactSelectedListener = (OnContactSelectedListener) context;
|
||||
}
|
||||
|
||||
if (context instanceof OnSelectionLimitReachedListener) {
|
||||
onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) context;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,6 +230,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
hideCount = intent.getBooleanExtra(HIDE_COUNT, false);
|
||||
selectionLimit = intent.getParcelableExtra(SELECTION_LIMITS);
|
||||
isMulti = selectionLimit != null;
|
||||
canSelectSelf = intent.getBooleanExtra(CAN_SELECT_SELF, !isMulti);
|
||||
|
||||
if (!isMulti) {
|
||||
selectionLimit = SelectionLimits.NO_LIMITS;
|
||||
@@ -295,7 +309,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
}
|
||||
|
||||
recyclerView.setAdapter(concatenateAdapter);
|
||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
|
||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true, 0));
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
@@ -464,14 +478,18 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
||||
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
||||
|
||||
if (isMulti && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||
if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isMulti || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) {
|
||||
if (selectionHardLimitReached()) {
|
||||
GroupLimitDialog.showHardLimitMessage(requireContext());
|
||||
if (onSelectionLimitReachedListener != null) {
|
||||
onSelectionLimitReachedListener.onHardLimitReached(selectionLimit.getHardLimit());
|
||||
} else {
|
||||
GroupLimitDialog.showHardLimitMessage(requireContext());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -489,11 +507,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
if (onContactSelectedListener != null) {
|
||||
if (onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null)) {
|
||||
markContactSelected(selected);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
}
|
||||
} else {
|
||||
markContactSelected(selected);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
}
|
||||
} else {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
@@ -507,16 +525,16 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
if (onContactSelectedListener != null) {
|
||||
if (onContactSelectedListener.onBeforeContactSelected(contact.getRecipientId(), contact.getNumber())) {
|
||||
markContactSelected(selectedContact);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
}
|
||||
} else {
|
||||
markContactSelected(selectedContact);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
markContactUnselected(selectedContact);
|
||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||
|
||||
if (onContactSelectedListener != null) {
|
||||
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
|
||||
@@ -611,7 +629,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
chipGroup.addView(chip);
|
||||
updateGroupLimit(getChipCount());
|
||||
if (selectionWarningLimitReachedExactly()) {
|
||||
GroupLimitDialog.showRecommendedLimitMessage(requireContext());
|
||||
if (onSelectionLimitReachedListener != null) {
|
||||
onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit());
|
||||
} else {
|
||||
GroupLimitDialog.showRecommendedLimitMessage(requireContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +655,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
}
|
||||
|
||||
private void setChipGroupVisibility(int visibility) {
|
||||
if (!requireActivity().getIntent().getBooleanExtra(DISPLAY_CHIPS, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
|
||||
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
@@ -641,10 +667,6 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
constraintSet.applyTo(constraintLayout);
|
||||
}
|
||||
|
||||
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
||||
this.onContactSelectedListener = onContactSelectedListener;
|
||||
}
|
||||
|
||||
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
|
||||
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
||||
}
|
||||
@@ -660,6 +682,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
||||
}
|
||||
|
||||
public interface OnSelectionLimitReachedListener {
|
||||
void onSuggestedLimitReached(int limit);
|
||||
void onHardLimitReached(int limit);
|
||||
}
|
||||
|
||||
public interface ListCallback {
|
||||
void onInvite();
|
||||
void onNewGroup(boolean forceV1);
|
||||
|
||||
@@ -107,7 +107,6 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
|
||||
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||
|
||||
contactsFragment.setOnContactSelectedListener(this);
|
||||
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
|
||||
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
||||
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
||||
|
||||
@@ -124,8 +124,6 @@ public class BlockedUsersActivity extends PassphraseRequiredActivity implements
|
||||
ContactSelectionListFragment fragment = new ContactSelectionListFragment();
|
||||
Intent intent = getIntent();
|
||||
|
||||
fragment.setOnContactSelectedListener(this);
|
||||
|
||||
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
||||
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, 1);
|
||||
intent.putExtra(ContactSelectionListFragment.HIDE_COUNT, true);
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -130,6 +131,20 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
presentDeliveryStatus(messageRecord);
|
||||
}
|
||||
|
||||
public void enableBubbleBackground(@DrawableRes int drawableRes, @Nullable Integer tint) {
|
||||
setBackgroundResource(drawableRes);
|
||||
|
||||
if (tint != null) {
|
||||
getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY);
|
||||
} else {
|
||||
getBackground().clearColorFilter();
|
||||
}
|
||||
}
|
||||
|
||||
public void disableBubbleBackground() {
|
||||
setBackground(null);
|
||||
}
|
||||
|
||||
private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale) {
|
||||
dateView.forceLayout();
|
||||
if (messageRecord.isFailed()) {
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ConversationTypingView extends LinearLayout {
|
||||
bubble.getBackground().setColorFilter(typist.getColor().toConversationColor(getContext()), PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
if (isGroupThread) {
|
||||
avatar.setAvatar(glideRequests, typist, false);
|
||||
avatar.setAvatar(glideRequests, typist, true);
|
||||
avatar.setVisibility(VISIBLE);
|
||||
} else {
|
||||
avatar.setVisibility(GONE);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.Guideline;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class InsetAwareConstraintLayout extends ConstraintLayout {
|
||||
|
||||
public InsetAwareConstraintLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public InsetAwareConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public InsetAwareConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public InsetAwareConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fitSystemWindows(Rect insets) {
|
||||
Guideline statusBarGuideline = findViewById(R.id.status_bar_guideline);
|
||||
Guideline navigationBarGuideline = findViewById(R.id.navigation_bar_guideline);
|
||||
|
||||
if (statusBarGuideline != null) {
|
||||
statusBarGuideline.setGuidelineBegin(insets.top);
|
||||
}
|
||||
|
||||
if (navigationBarGuideline != null) {
|
||||
navigationBarGuideline.setGuidelineEnd(insets.bottom);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -17,6 +16,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class LabeledEditText extends FrameLayout implements View.OnFocusChangeListener {
|
||||
|
||||
@@ -94,16 +94,6 @@ public class LabeledEditText extends FrameLayout implements View.OnFocusChangeLi
|
||||
}
|
||||
|
||||
public void focusAndMoveCursorToEndAndOpenKeyboard() {
|
||||
input.requestFocus();
|
||||
|
||||
int numberLength = getText().length();
|
||||
input.setSelection(numberLength, numberLength);
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT);
|
||||
|
||||
if (!imm.isAcceptingText()) {
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
}
|
||||
ViewUtil.focusAndMoveCursorToEndAndOpenKeyboard(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
|
||||
|
||||
/**
|
||||
* Selection aware {@link EmojiEditText}. This view allows the developer to provide an
|
||||
* {@link OnSelectionChangedListener} that will be notified when the selection is changed.
|
||||
*/
|
||||
public class SelectionAwareEmojiEditText extends EmojiEditText {
|
||||
|
||||
private OnSelectionChangedListener onSelectionChangedListener;
|
||||
|
||||
public SelectionAwareEmojiEditText(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SelectionAwareEmojiEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SelectionAwareEmojiEditText(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setOnSelectionChangedListener(@Nullable OnSelectionChangedListener onSelectionChangedListener) {
|
||||
this.onSelectionChangedListener = onSelectionChangedListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSelectionChanged(int selStart, int selEnd) {
|
||||
if (onSelectionChangedListener != null) {
|
||||
onSelectionChangedListener.onSelectionChanged(selStart, selEnd);
|
||||
}
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
}
|
||||
|
||||
public interface OnSelectionChangedListener {
|
||||
void onSelectionChanged(int selStart, int selEnd);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
|
||||
{
|
||||
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
|
||||
|
||||
private static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
|
||||
public static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
|
||||
|
||||
private final Context context;
|
||||
private final List<EmojiPageModel> models;
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -72,4 +79,15 @@ public final class EmojiUtil {
|
||||
String canonical = VARIATION_MAP.get(emoji);
|
||||
return canonical != null ? canonical : emoji;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided emoji string into a single drawable, if possible.
|
||||
*/
|
||||
public static @Nullable Drawable convertToDrawable(@NonNull Context context, @Nullable String emoji) {
|
||||
if (Util.isEmpty(emoji)) {
|
||||
return null;
|
||||
} else {
|
||||
return EmojiProvider.getInstance(context).getEmojiDrawable(emoji);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.ResizeAnimation;
|
||||
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
|
||||
@@ -330,7 +331,6 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
|
||||
public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
|
||||
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
largeLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
|
||||
smallLocalRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
|
||||
@@ -346,11 +346,12 @@ public class WebRtcCallView extends FrameLayout {
|
||||
if (state == WebRtcLocalRenderState.EXPANDED) {
|
||||
expandPip(localCallParticipant, focusedParticipant);
|
||||
return;
|
||||
} else if (state == WebRtcLocalRenderState.SMALL_RECTANGLE && pictureInPictureExpansionHelper.isExpandedOrExpanding()) {
|
||||
} else if ((state == WebRtcLocalRenderState.SMALL_RECTANGLE || state == WebRtcLocalRenderState.GONE) && pictureInPictureExpansionHelper.isExpandedOrExpanding()) {
|
||||
shrinkPip(localCallParticipant);
|
||||
return;
|
||||
} else {
|
||||
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
@@ -597,11 +598,14 @@ public class WebRtcCallView extends FrameLayout {
|
||||
@Override
|
||||
public void onPictureInPictureExpanded() {
|
||||
largeLocalRenderFrame.setVisibility(View.VISIBLE);
|
||||
largeLocalRenderNoVideo.setVisibility(View.GONE);
|
||||
largeLocalRenderNoVideoAvatar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureNotVisible() {
|
||||
smallLocalRender.setCallParticipant(focusedParticipant);
|
||||
smallLocalRender.setMirror(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -626,6 +630,11 @@ public class WebRtcCallView extends FrameLayout {
|
||||
@Override
|
||||
public void onPictureInPictureNotVisible() {
|
||||
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
|
||||
if (!localCallParticipant.isVideoEnabled()) {
|
||||
smallLocalRenderFrame.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
@@ -42,6 +43,7 @@ public class ContactRepository {
|
||||
static final String NUMBER_TYPE_COLUMN = "number_type";
|
||||
static final String LABEL_COLUMN = "label";
|
||||
static final String CONTACT_TYPE_COLUMN = "contact_type";
|
||||
static final String ABOUT_COLUMN = "about";
|
||||
|
||||
static final int NORMAL_TYPE = 0;
|
||||
static final int PUSH_TYPE = 1;
|
||||
@@ -52,18 +54,18 @@ public class ContactRepository {
|
||||
|
||||
/** Maps the recipient results to the legacy contact column names */
|
||||
private static final List<Pair<String, ValueMapper>> SEARCH_CURSOR_MAPPERS = new ArrayList<Pair<String, ValueMapper>>() {{
|
||||
add(new Pair<>(ID_COLUMN, cursor -> cursor.getLong(cursor.getColumnIndexOrThrow(RecipientDatabase.ID))));
|
||||
add(new Pair<>(ID_COLUMN, cursor -> CursorUtil.requireLong(cursor, RecipientDatabase.ID)));
|
||||
|
||||
add(new Pair<>(NAME_COLUMN, cursor -> {
|
||||
String system = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_DISPLAY_NAME));
|
||||
String profile = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SEARCH_PROFILE_NAME));
|
||||
String system = CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_DISPLAY_NAME);
|
||||
String profile = CursorUtil.requireString(cursor, RecipientDatabase.SEARCH_PROFILE_NAME);
|
||||
|
||||
return Util.getFirstNonEmpty(system, profile);
|
||||
}));
|
||||
|
||||
add(new Pair<>(NUMBER_COLUMN, cursor -> {
|
||||
String phone = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.PHONE));
|
||||
String email = cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.EMAIL));
|
||||
String phone = CursorUtil.requireString(cursor, RecipientDatabase.PHONE);
|
||||
String email = CursorUtil.requireString(cursor, RecipientDatabase.EMAIL);
|
||||
|
||||
if (phone != null) {
|
||||
phone = PhoneNumberFormatter.prettyPrint(phone);
|
||||
@@ -72,14 +74,31 @@ public class ContactRepository {
|
||||
return Util.getFirstNonEmpty(phone, email);
|
||||
}));
|
||||
|
||||
add(new Pair<>(NUMBER_TYPE_COLUMN, cursor -> cursor.getInt(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_PHONE_TYPE))));
|
||||
add(new Pair<>(NUMBER_TYPE_COLUMN, cursor -> CursorUtil.requireInt(cursor, RecipientDatabase.SYSTEM_PHONE_TYPE)));
|
||||
|
||||
add(new Pair<>(LABEL_COLUMN, cursor -> cursor.getString(cursor.getColumnIndexOrThrow(RecipientDatabase.SYSTEM_PHONE_LABEL))));
|
||||
add(new Pair<>(LABEL_COLUMN, cursor -> CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_PHONE_LABEL)));
|
||||
|
||||
add(new Pair<>(CONTACT_TYPE_COLUMN, cursor -> {
|
||||
int registered = cursor.getInt(cursor.getColumnIndexOrThrow(RecipientDatabase.REGISTERED));
|
||||
int registered = CursorUtil.requireInt(cursor, RecipientDatabase.REGISTERED);
|
||||
return registered == RecipientDatabase.RegisteredState.REGISTERED.getId() ? PUSH_TYPE : NORMAL_TYPE;
|
||||
}));
|
||||
|
||||
add(new Pair<>(ABOUT_COLUMN, cursor -> {
|
||||
String aboutEmoji = CursorUtil.requireString(cursor, RecipientDatabase.ABOUT_EMOJI);
|
||||
String about = CursorUtil.requireString(cursor, RecipientDatabase.ABOUT);
|
||||
|
||||
if (aboutEmoji != null) {
|
||||
if (about != null) {
|
||||
return aboutEmoji + " " + about;
|
||||
} else {
|
||||
return aboutEmoji;
|
||||
}
|
||||
} else if (about != null) {
|
||||
return about;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}));
|
||||
}};
|
||||
|
||||
public ContactRepository(@NonNull Context context) {
|
||||
@@ -106,7 +125,7 @@ public class ContactRepository {
|
||||
|
||||
if (shouldAdd) {
|
||||
MatrixCursor selfCursor = new MatrixCursor(RecipientDatabase.SEARCH_PROJECTION_NAMES);
|
||||
selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, null, self.getE164().or(""), self.getEmail().orNull(), null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), noteToSelfTitle });
|
||||
selfCursor.addRow(new Object[]{ self.getId().serialize(), noteToSelfTitle, self.getE164().or(""), self.getEmail().orNull(), null, -1, RecipientDatabase.RegisteredState.REGISTERED.getId(), self.getAbout(), self.getAboutEmoji(), noteToSelfTitle, noteToSelfTitle });
|
||||
|
||||
cursor = cursor == null ? selfCursor : new MergeCursor(new Cursor[]{ cursor, selfCursor });
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolde
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapter;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
@@ -97,7 +98,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkboxVisible);
|
||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible);
|
||||
public abstract void unbind(@NonNull GlideRequests glideRequests);
|
||||
public abstract void setChecked(boolean checked);
|
||||
public abstract void setEnabled(boolean enabled);
|
||||
@@ -117,8 +118,8 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
return (ContactSelectionListItem) itemView;
|
||||
}
|
||||
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkBoxVisible) {
|
||||
getView().set(glideRequests, recipientId, type, name, number, label, color, checkBoxVisible);
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkBoxVisible) {
|
||||
getView().set(glideRequests, recipientId, type, name, number, label, about, color, checkBoxVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,7 +148,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkboxVisible) {
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible) {
|
||||
this.label.setText(name);
|
||||
}
|
||||
|
||||
@@ -204,13 +205,14 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
String rawId = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN));
|
||||
String rawId = CursorUtil.requireString(cursor, ContactRepository.ID_COLUMN);
|
||||
RecipientId id = rawId != null ? RecipientId.from(rawId) : null;
|
||||
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.CONTACT_TYPE_COLUMN));
|
||||
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN ));
|
||||
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN));
|
||||
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN ));
|
||||
String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.LABEL_COLUMN ));
|
||||
int contactType = CursorUtil.requireInt(cursor, ContactRepository.CONTACT_TYPE_COLUMN);
|
||||
String name = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
|
||||
String number = CursorUtil.requireString(cursor, ContactRepository.NUMBER_COLUMN);
|
||||
int numberType = CursorUtil.requireInt(cursor, ContactRepository.NUMBER_TYPE_COLUMN);
|
||||
String about = CursorUtil.requireString(cursor, ContactRepository.ABOUT_COLUMN);
|
||||
String label = CursorUtil.requireString(cursor, ContactRepository.LABEL_COLUMN);
|
||||
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(getContext().getResources(),
|
||||
numberType, label).toString();
|
||||
|
||||
@@ -220,7 +222,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
boolean currentContact = currentContacts.contains(id);
|
||||
|
||||
viewHolder.unbind(glideRequests);
|
||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, color, multiSelect || currentContact);
|
||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, about, color, multiSelect || currentContact);
|
||||
viewHolder.setEnabled(true);
|
||||
|
||||
if (currentContact) {
|
||||
@@ -239,10 +241,10 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
String rawId = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN));
|
||||
String rawId = CursorUtil.requireString(cursor, ContactRepository.ID_COLUMN);
|
||||
RecipientId id = rawId != null ? RecipientId.from(rawId) : null;
|
||||
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN));
|
||||
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN));
|
||||
int numberType = CursorUtil.requireInt(cursor, ContactRepository.NUMBER_TYPE_COLUMN);
|
||||
String number = CursorUtil.requireString(cursor, ContactRepository.NUMBER_COLUMN);
|
||||
|
||||
viewHolder.setEnabled(true);
|
||||
|
||||
@@ -258,7 +260,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public int getItemViewType(@NonNull Cursor cursor) {
|
||||
if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.CONTACT_TYPE_COLUMN)) == ContactRepository.DIVIDER_TYPE) {
|
||||
if (CursorUtil.requireInt(cursor, ContactRepository.CONTACT_TYPE_COLUMN) == ContactRepository.DIVIDER_TYPE) {
|
||||
return VIEW_TYPE_DIVIDER;
|
||||
} else {
|
||||
return VIEW_TYPE_CONTACT;
|
||||
@@ -266,12 +268,12 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) {
|
||||
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
|
||||
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
|
||||
((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position));
|
||||
}
|
||||
|
||||
@@ -317,7 +319,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
|
||||
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||
String letter = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN));
|
||||
String letter = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
|
||||
|
||||
if (letter != null) {
|
||||
letter = letter.trim();
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@@ -65,6 +66,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
String name,
|
||||
String number,
|
||||
String label,
|
||||
String about,
|
||||
int color,
|
||||
boolean checkboxVisible)
|
||||
{
|
||||
@@ -87,7 +89,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.numberView.setTextColor(color);
|
||||
this.contactPhotoImage.setAvatar(glideRequests, recipientSnapshot, false);
|
||||
|
||||
setText(recipientSnapshot, type, name, number, label);
|
||||
setText(recipientSnapshot, type, name, number, label, about);
|
||||
|
||||
this.checkBox.setVisibility(checkboxVisible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
@@ -110,7 +112,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void setText(@Nullable Recipient recipient, int type, String name, String number, String label) {
|
||||
private void setText(@Nullable Recipient recipient, int type, String name, String number, String label, @Nullable String about) {
|
||||
if (number == null || number.isEmpty()) {
|
||||
this.nameView.setEnabled(false);
|
||||
this.numberView.setText("");
|
||||
@@ -120,7 +122,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.numberView.setText(getGroupMemberCount(recipient));
|
||||
this.labelView.setVisibility(View.GONE);
|
||||
} else if (type == ContactRepository.PUSH_TYPE) {
|
||||
this.numberView.setText(number);
|
||||
this.numberView.setText(!Util.isEmpty(about) ? about : number);
|
||||
this.nameView.setEnabled(true);
|
||||
this.labelView.setVisibility(View.GONE);
|
||||
} else if (type == ContactRepository.NEW_USERNAME_TYPE) {
|
||||
@@ -129,7 +131,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.labelView.setText(label);
|
||||
this.labelView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
this.numberView.setText(number);
|
||||
this.numberView.setText(!Util.isEmpty(about) ? about : number);
|
||||
this.nameView.setEnabled(true);
|
||||
this.labelView.setText(label != null && !label.equals("null") ? label : "");
|
||||
this.labelView.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -71,7 +71,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
ContactRepository.NUMBER_COLUMN,
|
||||
ContactRepository.NUMBER_TYPE_COLUMN,
|
||||
ContactRepository.LABEL_COLUMN,
|
||||
ContactRepository.CONTACT_TYPE_COLUMN};
|
||||
ContactRepository.CONTACT_TYPE_COLUMN,
|
||||
ContactRepository.ABOUT_COLUMN};
|
||||
|
||||
private static final int RECENT_CONVERSATION_MAX = 25;
|
||||
|
||||
@@ -212,7 +213,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
ContactRepository.DIVIDER_TYPE,
|
||||
"" });
|
||||
return recentsHeader;
|
||||
}
|
||||
|
||||
@@ -223,7 +225,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
ContactRepository.DIVIDER_TYPE,
|
||||
"" });
|
||||
return contactsHeader;
|
||||
}
|
||||
|
||||
@@ -234,7 +237,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
ContactRepository.DIVIDER_TYPE,
|
||||
"" });
|
||||
return groupHeader;
|
||||
}
|
||||
|
||||
@@ -245,7 +249,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
ContactRepository.DIVIDER_TYPE,
|
||||
"" });
|
||||
return contactsHeader;
|
||||
}
|
||||
|
||||
@@ -256,7 +261,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
"",
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.DIVIDER_TYPE });
|
||||
ContactRepository.DIVIDER_TYPE,
|
||||
"" });
|
||||
return contactsHeader;
|
||||
}
|
||||
|
||||
@@ -281,7 +287,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
stringId,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
|
||||
"",
|
||||
ContactRepository.RECENT_TYPE });
|
||||
ContactRepository.RECENT_TYPE,
|
||||
recipient.getCombinedAboutAndEmoji() });
|
||||
}
|
||||
}
|
||||
return recentConversations;
|
||||
@@ -316,7 +323,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
groupRecord.getId(),
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||
"",
|
||||
ContactRepository.NORMAL_TYPE });
|
||||
ContactRepository.NORMAL_TYPE,
|
||||
"" });
|
||||
}
|
||||
}
|
||||
return groupContacts;
|
||||
@@ -329,7 +337,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
filter,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||
"\u21e2",
|
||||
ContactRepository.NEW_PHONE_TYPE});
|
||||
ContactRepository.NEW_PHONE_TYPE,
|
||||
"" });
|
||||
return newNumberCursor;
|
||||
}
|
||||
|
||||
@@ -340,7 +349,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
filter,
|
||||
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
|
||||
"\u21e2",
|
||||
ContactRepository.NEW_USERNAME_TYPE});
|
||||
ContactRepository.NEW_USERNAME_TYPE,
|
||||
"" });
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@@ -368,7 +378,8 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.LABEL_COLUMN)),
|
||||
ContactRepository.NORMAL_TYPE});
|
||||
ContactRepository.NORMAL_TYPE,
|
||||
"" });
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms");
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
@@ -60,6 +61,7 @@ import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -200,7 +202,7 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestState;
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel;
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestsBottomView;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
|
||||
import org.thoughtcrime.securesms.mms.SlideFactory.MediaType;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||
import org.thoughtcrime.securesms.mms.GifSlide;
|
||||
@@ -216,6 +218,7 @@ import org.thoughtcrime.securesms.mms.QuoteId;
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.mms.SlideFactory;
|
||||
import org.thoughtcrime.securesms.mms.StickerSlide;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
@@ -256,6 +259,7 @@ import org.thoughtcrime.securesms.util.DynamicDarkToolbarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MessageUtil;
|
||||
@@ -265,6 +269,7 @@ import org.thoughtcrime.securesms.util.SmsUtil;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences.MediaKeyboardMode;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
@@ -273,6 +278,8 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
@@ -373,6 +380,8 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
private Stub<View> mentionsSuggestions;
|
||||
private MaterialButton joinGroupCallButton;
|
||||
private boolean callingTooltipShown;
|
||||
private ImageView wallpaper;
|
||||
private View wallpaperDim;
|
||||
|
||||
private LinkPreviewViewModel linkPreviewViewModel;
|
||||
private ConversationSearchViewModel searchViewModel;
|
||||
@@ -386,8 +395,8 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
private LiveRecipient recipient;
|
||||
private long threadId;
|
||||
private int distributionType;
|
||||
private int reactWithAnyEmojiStartPage;
|
||||
private boolean isSecureText;
|
||||
private int reactWithAnyEmojiStartPage = -1;
|
||||
private boolean isDefaultSms = true;
|
||||
private boolean isMmsEnabled = true;
|
||||
private boolean isSecurityInitialized = false;
|
||||
@@ -412,18 +421,22 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
return;
|
||||
}
|
||||
|
||||
new FullscreenHelper(this).showSystemUI();
|
||||
|
||||
ConversationIntents.Args args = ConversationIntents.Args.from(getIntent());
|
||||
|
||||
reportShortcutLaunch(args.getRecipientId());
|
||||
setContentView(R.layout.conversation_activity);
|
||||
|
||||
getWindow().getDecorView().setBackgroundResource(R.color.signal_background_primary);
|
||||
WindowUtil.setLightNavigationBar(getWindow());
|
||||
|
||||
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale());
|
||||
|
||||
initializeReceivers();
|
||||
initializeActionBar();
|
||||
initializeViews();
|
||||
updateWallpaper(args.getWallpaper());
|
||||
initializeResources(args);
|
||||
initializeLinkPreviewObserver();
|
||||
initializeSearchObserver();
|
||||
@@ -475,7 +488,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
return;
|
||||
}
|
||||
|
||||
reactWithAnyEmojiStartPage = 0;
|
||||
reactWithAnyEmojiStartPage = -1;
|
||||
if (!Util.isEmpty(composeText) || attachmentManager.isAttachmentPresent() || inputPanel.getQuote().isPresent()) {
|
||||
saveDraft();
|
||||
attachmentManager.clear(glideRequests, false);
|
||||
@@ -612,10 +625,10 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
|
||||
switch (reqCode) {
|
||||
case PICK_DOCUMENT:
|
||||
setMedia(data.getData(), MediaType.DOCUMENT);
|
||||
setMedia(data.getData(), SlideFactory.MediaType.DOCUMENT);
|
||||
break;
|
||||
case PICK_AUDIO:
|
||||
setMedia(data.getData(), MediaType.AUDIO);
|
||||
setMedia(data.getData(), SlideFactory.MediaType.AUDIO);
|
||||
break;
|
||||
case PICK_CONTACT:
|
||||
if (isSecureText && !isSmsForced()) {
|
||||
@@ -655,7 +668,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
break;
|
||||
case PICK_GIF:
|
||||
setMedia(data.getData(),
|
||||
MediaType.GIF,
|
||||
SlideFactory.MediaType.GIF,
|
||||
data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0),
|
||||
data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0),
|
||||
data.getBooleanExtra(GiphyActivity.EXTRA_BORDERLESS, false));
|
||||
@@ -732,7 +745,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
|
||||
reactWithAnyEmojiStartPage = savedInstanceState.getInt(STATE_REACT_WITH_ANY_PAGE, 0);
|
||||
reactWithAnyEmojiStartPage = savedInstanceState.getInt(STATE_REACT_WITH_ANY_PAGE, -1);
|
||||
}
|
||||
|
||||
private void setVisibleThread(long threadId) {
|
||||
@@ -766,7 +779,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
|
||||
getContentResolver().delete(attachmentManager.getCaptureUri(), null, null);
|
||||
|
||||
setMedia(mediaUri, MediaType.IMAGE);
|
||||
setMedia(mediaUri, SlideFactory.MediaType.IMAGE);
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, "Could not handle public image", ioe);
|
||||
}
|
||||
@@ -1470,7 +1483,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
final CharSequence draftText = args.getDraftText();
|
||||
final Uri draftMedia = getIntent().getData();
|
||||
final String draftContentType = getIntent().getType();
|
||||
final MediaType draftMediaType = MediaType.from(draftContentType);
|
||||
final MediaType draftMediaType = SlideFactory.MediaType.from(draftContentType);
|
||||
final List<Media> mediaList = args.getMedia();
|
||||
final StickerLocator stickerLocator = args.getStickerLocator();
|
||||
final boolean borderless = args.isBorderless();
|
||||
@@ -1652,13 +1665,13 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
attachmentManager.setLocation(SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints()).addListener(listener);
|
||||
break;
|
||||
case Draft.IMAGE:
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE).addListener(listener);
|
||||
setMedia(Uri.parse(draft.getValue()), SlideFactory.MediaType.IMAGE).addListener(listener);
|
||||
break;
|
||||
case Draft.AUDIO:
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO).addListener(listener);
|
||||
setMedia(Uri.parse(draft.getValue()), SlideFactory.MediaType.AUDIO).addListener(listener);
|
||||
break;
|
||||
case Draft.VIDEO:
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO).addListener(listener);
|
||||
setMedia(Uri.parse(draft.getValue()), SlideFactory.MediaType.VIDEO).addListener(listener);
|
||||
break;
|
||||
case Draft.QUOTE:
|
||||
SettableFuture<Boolean> quoteResult = new SettableFuture<>();
|
||||
@@ -1908,6 +1921,8 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
messageRequestBottomView = findViewById(R.id.conversation_activity_message_request_bottom_bar);
|
||||
reactionOverlay = findViewById(R.id.conversation_reaction_scrubber);
|
||||
mentionsSuggestions = ViewUtil.findStubById(this, R.id.conversation_mention_suggestions_stub);
|
||||
wallpaper = findViewById(R.id.conversation_wallpaper);
|
||||
wallpaperDim = findViewById(R.id.conversation_wallpaper_dim);
|
||||
|
||||
ImageButton quickCameraToggle = findViewById(R.id.quick_camera_toggle);
|
||||
ImageButton inlineAttachmentButton = findViewById(R.id.inline_attachment_button);
|
||||
@@ -1974,6 +1989,18 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
joinGroupCallButton.setOnClickListener(v -> handleVideo(getRecipient()));
|
||||
}
|
||||
|
||||
private void updateWallpaper(@Nullable ChatWallpaper chatWallpaper) {
|
||||
Log.d(TAG, "Setting wallpaper.");
|
||||
if (chatWallpaper != null) {
|
||||
chatWallpaper.loadInto(wallpaper);
|
||||
ChatWallpaperDimLevelUtil.applyDimLevelForNightMode(wallpaperDim, chatWallpaper);
|
||||
} else {
|
||||
wallpaper.setImageDrawable(null);
|
||||
wallpaperDim.setVisibility(View.GONE);
|
||||
}
|
||||
fragment.onWallpaperChanged(chatWallpaper);
|
||||
}
|
||||
|
||||
protected void initializeActionBar() {
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
@@ -2081,6 +2108,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
this.viewModel = ViewModelProviders.of(this, new ConversationViewModel.Factory()).get(ConversationViewModel.class);
|
||||
|
||||
this.viewModel.setArgs(args);
|
||||
this.viewModel.getWallpaper().observe(this, this::updateWallpaper);
|
||||
}
|
||||
|
||||
private void initializeGroupViewModel() {
|
||||
@@ -2234,6 +2262,10 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
reactWithAnyEmojiStartPage = page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReactWithAnyEmojiSelected(@NonNull String emoji) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchMoveUpPressed() {
|
||||
searchViewModel.onMoveUp();
|
||||
@@ -2335,10 +2367,10 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
return new SettableFuture<>(false);
|
||||
}
|
||||
|
||||
if (MediaType.VCARD.equals(mediaType) && isSecureText) {
|
||||
if (SlideFactory.MediaType.VCARD.equals(mediaType) && isSecureText) {
|
||||
openContactShareEditor(uri);
|
||||
return new SettableFuture<>(false);
|
||||
} else if (MediaType.IMAGE.equals(mediaType) || MediaType.GIF.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) {
|
||||
} else if (SlideFactory.MediaType.IMAGE.equals(mediaType) || SlideFactory.MediaType.GIF.equals(mediaType) || SlideFactory.MediaType.VIDEO.equals(mediaType)) {
|
||||
String mimeType = MediaUtil.getMimeType(this, uri);
|
||||
if (mimeType == null) {
|
||||
mimeType = mediaType.toFallbackMimeType();
|
||||
@@ -2910,6 +2942,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
vibrator.vibrate(20);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
|
||||
|
||||
audioRecorder.startRecording();
|
||||
}
|
||||
@@ -2926,6 +2959,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
vibrator.vibrate(20);
|
||||
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
|
||||
ListenableFuture<Pair<Uri, Long>> future = audioRecorder.stopRecording();
|
||||
future.addListener(new ListenableFuture.Listener<Pair<Uri, Long>>() {
|
||||
@@ -3033,9 +3067,9 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
() -> getKeyboardImageDetails(uri),
|
||||
details -> sendKeyboardImage(uri, contentType, details));
|
||||
} else if (MediaUtil.isVideoType(contentType)) {
|
||||
setMedia(uri, MediaType.VIDEO);
|
||||
setMedia(uri, SlideFactory.MediaType.VIDEO);
|
||||
} else if (MediaUtil.isAudioType(contentType)) {
|
||||
setMedia(uri, MediaType.AUDIO);
|
||||
setMedia(uri, SlideFactory.MediaType.AUDIO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3536,7 +3570,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
|
||||
private void sendKeyboardImage(@NonNull Uri uri, @NonNull String contentType, @Nullable KeyboardImageDetails details) {
|
||||
if (details == null || !details.hasTransparency) {
|
||||
setMedia(uri, Objects.requireNonNull(MediaType.from(contentType)));
|
||||
setMedia(uri, Objects.requireNonNull(SlideFactory.MediaType.from(contentType)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -72,6 +73,10 @@ public class ConversationAdapter
|
||||
|
||||
private static final String TAG = Log.tag(ConversationAdapter.class);
|
||||
|
||||
public static final int HEADER_TYPE_POPOVER_DATE = 1;
|
||||
public static final int HEADER_TYPE_INLINE_DATE = 2;
|
||||
public static final int HEADER_TYPE_LAST_SEEN = 3;
|
||||
|
||||
private static final int MESSAGE_TYPE_OUTGOING_MULTIMEDIA = 0;
|
||||
private static final int MESSAGE_TYPE_OUTGOING_TEXT = 1;
|
||||
private static final int MESSAGE_TYPE_INCOMING_MULTIMEDIA = 2;
|
||||
@@ -101,6 +106,7 @@ public class ConversationAdapter
|
||||
private View headerView;
|
||||
private View footerView;
|
||||
private PagingController pagingController;
|
||||
private boolean hasWallpaper;
|
||||
|
||||
ConversationAdapter(@NonNull LifecycleOwner lifecycleOwner,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@@ -131,6 +137,7 @@ public class ConversationAdapter
|
||||
this.releasedFastRecords = new HashSet<>();
|
||||
this.calendar = Calendar.getInstance();
|
||||
this.digest = getMessageDigestOrThrow();
|
||||
this.hasWallpaper = recipient.hasWallpaper();
|
||||
|
||||
setHasStableIds(true);
|
||||
}
|
||||
@@ -241,7 +248,8 @@ public class ConversationAdapter
|
||||
selected,
|
||||
recipient,
|
||||
searchQuery,
|
||||
conversationMessage == recordToPulse);
|
||||
conversationMessage == recordToPulse,
|
||||
hasWallpaper);
|
||||
|
||||
if (conversationMessage == recordToPulse) {
|
||||
recordToPulse = null;
|
||||
@@ -288,14 +296,28 @@ public class ConversationAdapter
|
||||
}
|
||||
|
||||
@Override
|
||||
public StickyHeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) {
|
||||
public StickyHeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
|
||||
return new StickyHeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_item_header, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position) {
|
||||
public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position, int type) {
|
||||
ConversationMessage conversationMessage = Objects.requireNonNull(getItem(position));
|
||||
viewHolder.setText(DateUtils.getRelativeDate(viewHolder.itemView.getContext(), locale, conversationMessage.getMessageRecord().getDateReceived()));
|
||||
|
||||
if (type == HEADER_TYPE_POPOVER_DATE) {
|
||||
if (hasWallpaper) {
|
||||
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
|
||||
} else {
|
||||
viewHolder.setBackgroundRes(R.drawable.sticky_date_header_background);
|
||||
}
|
||||
} else if (type == HEADER_TYPE_INLINE_DATE) {
|
||||
if (hasWallpaper) {
|
||||
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
|
||||
} else {
|
||||
viewHolder.clearBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable ConversationMessage getItem(int position) {
|
||||
@@ -325,6 +347,12 @@ public class ConversationAdapter
|
||||
|
||||
void onBindLastSeenViewHolder(StickyHeaderViewHolder viewHolder, int position) {
|
||||
viewHolder.setText(viewHolder.itemView.getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1)));
|
||||
|
||||
if (hasWallpaper) {
|
||||
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
|
||||
} else {
|
||||
viewHolder.clearBackground();
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasNoConversationMessages() {
|
||||
@@ -421,6 +449,17 @@ public class ConversationAdapter
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets the adapter know that the wallpaper state has changed.
|
||||
*/
|
||||
void onHasWallpaperChanged(boolean hasWallpaper) {
|
||||
if (this.hasWallpaper != hasWallpaper) {
|
||||
Log.d(TAG, "Resetting adapter due to wallpaper change.");
|
||||
this.hasWallpaper = hasWallpaper;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a record to a memory cache to allow it to be rendered immediately, as opposed to waiting
|
||||
* for a database change.
|
||||
@@ -563,6 +602,14 @@ public class ConversationAdapter
|
||||
public void setText(CharSequence text) {
|
||||
textView.setText(text);
|
||||
}
|
||||
|
||||
public void setBackgroundRes(@DrawableRes int resId) {
|
||||
textView.setBackgroundResource(resId);
|
||||
}
|
||||
|
||||
public void clearBackground() {
|
||||
textView.setBackground(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class HeaderFooterViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@@ -21,6 +21,7 @@ public class ConversationBannerView extends ConstraintLayout {
|
||||
|
||||
private AvatarImageView contactAvatar;
|
||||
private TextView contactTitle;
|
||||
private TextView contactAbout;
|
||||
private TextView contactSubtitle;
|
||||
private TextView contactDescription;
|
||||
|
||||
@@ -39,6 +40,7 @@ public class ConversationBannerView extends ConstraintLayout {
|
||||
|
||||
contactAvatar = findViewById(R.id.message_request_avatar);
|
||||
contactTitle = findViewById(R.id.message_request_title);
|
||||
contactAbout = findViewById(R.id.message_request_about);
|
||||
contactSubtitle = findViewById(R.id.message_request_subtitle);
|
||||
contactDescription = findViewById(R.id.message_request_description);
|
||||
|
||||
@@ -53,6 +55,11 @@ public class ConversationBannerView extends ConstraintLayout {
|
||||
contactTitle.setText(title);
|
||||
}
|
||||
|
||||
public void setAbout(@Nullable String about) {
|
||||
contactAbout.setText(about);
|
||||
contactAbout.setVisibility(TextUtils.isEmpty(about) ? GONE : VISIBLE);
|
||||
}
|
||||
|
||||
public void setSubtitle(@Nullable CharSequence subtitle) {
|
||||
contactSubtitle.setText(subtitle);
|
||||
contactSubtitle.setVisibility(TextUtils.isEmpty(subtitle) ? GONE : VISIBLE);
|
||||
@@ -62,6 +69,14 @@ public class ConversationBannerView extends ConstraintLayout {
|
||||
contactDescription.setText(description);
|
||||
}
|
||||
|
||||
public void showBackgroundBubble(boolean enabled) {
|
||||
if (enabled) {
|
||||
setBackgroundResource(R.drawable.wallpaper_bubble_background_12);
|
||||
} else {
|
||||
setBackground(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void hideSubtitle() {
|
||||
contactSubtitle.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.HtmlUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
@@ -141,6 +142,7 @@ import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -171,7 +173,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
private Locale locale;
|
||||
private RecyclerView list;
|
||||
private RecyclerView.ItemDecoration lastSeenDecoration;
|
||||
private RecyclerView.ItemDecoration stickyHeaderDecoration;
|
||||
private RecyclerView.ItemDecoration popoverDateDecoration;
|
||||
private ViewSwitcher topLoadMoreView;
|
||||
private ViewSwitcher bottomLoadMoreView;
|
||||
private ConversationTypingView typingView;
|
||||
@@ -390,6 +392,17 @@ public class ConversationFragment extends LoggingFragment {
|
||||
snapToTopDataObserver.requestScrollPosition(position);
|
||||
}
|
||||
|
||||
public void onWallpaperChanged(@Nullable ChatWallpaper wallpaper) {
|
||||
if (list != null) {
|
||||
ConversationAdapter adapter = getListAdapter();
|
||||
|
||||
if (adapter != null) {
|
||||
Log.d(TAG, "Notifying adapter that wallpaper state has changed.");
|
||||
getListAdapter().onHasWallpaperChanged(wallpaper != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getStartPosition() {
|
||||
return conversationViewModel.getArgs().getStartingPosition();
|
||||
}
|
||||
@@ -409,7 +422,6 @@ public class ConversationFragment extends LoggingFragment {
|
||||
}
|
||||
|
||||
private static void presentMessageRequestProfileView(@NonNull Context context, @NonNull MessageRequestViewModel.RecipientInfo recipientInfo, @Nullable ConversationBannerView conversationBanner) {
|
||||
|
||||
if (conversationBanner == null) {
|
||||
return;
|
||||
}
|
||||
@@ -422,9 +434,11 @@ public class ConversationFragment extends LoggingFragment {
|
||||
|
||||
if (recipient != null) {
|
||||
conversationBanner.setAvatar(GlideApp.with(context), recipient);
|
||||
conversationBanner.showBackgroundBubble(recipient.hasWallpaper());
|
||||
|
||||
String title = isSelf ? context.getString(R.string.note_to_self) : recipient.getDisplayNameOrUsername(context);
|
||||
conversationBanner.setTitle(title);
|
||||
conversationBanner.setAbout(recipient.getCombinedAboutAndEmoji());
|
||||
|
||||
if (recipient.isGroup()) {
|
||||
if (pendingMemberCount > 0) {
|
||||
@@ -487,7 +501,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
this.threadId = conversationViewModel.getArgs().getThreadId();
|
||||
this.markReadHelper = new MarkReadHelper(threadId, requireContext());
|
||||
|
||||
conversationViewModel.onConversationDataAvailable(threadId, startingPosition);
|
||||
conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, startingPosition);
|
||||
messageCountsViewModel.setThreadId(threadId);
|
||||
|
||||
messageCountsViewModel.getUnreadMessagesCount().observe(getViewLifecycleOwner(), scrollToBottomButton::setUnreadCount);
|
||||
@@ -510,7 +524,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
ConversationAdapter adapter = new ConversationAdapter(this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get());
|
||||
adapter.setPagingController(conversationViewModel.getPagingController());
|
||||
list.setAdapter(adapter);
|
||||
setStickyHeaderDecoration(adapter);
|
||||
setPopoverDateDecoration(adapter);
|
||||
ConversationAdapter.initializePool(list.getRecycledViewPool());
|
||||
|
||||
adapter.registerAdapterDataObserver(snapToTopDataObserver);
|
||||
@@ -614,7 +628,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
menu.findItem(R.id.menu_context_copy).setVisible(menuState.shouldShowCopyAction());
|
||||
}
|
||||
|
||||
private ConversationAdapter getListAdapter() {
|
||||
private @Nullable ConversationAdapter getListAdapter() {
|
||||
return (ConversationAdapter) list.getAdapter();
|
||||
}
|
||||
|
||||
@@ -637,7 +651,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
messageRequestViewModel.setConversationInfo(recipient.getId(), threadId);
|
||||
|
||||
snapToTopDataObserver.requestScrollPosition(0);
|
||||
conversationViewModel.onConversationDataAvailable(threadId, -1);
|
||||
conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, -1);
|
||||
messageCountsViewModel.setThreadId(threadId);
|
||||
initializeListAdapter();
|
||||
}
|
||||
@@ -651,13 +665,13 @@ public class ConversationFragment extends LoggingFragment {
|
||||
}
|
||||
}
|
||||
|
||||
public void setStickyHeaderDecoration(@NonNull ConversationAdapter adapter) {
|
||||
if (stickyHeaderDecoration != null) {
|
||||
list.removeItemDecoration(stickyHeaderDecoration);
|
||||
public void setPopoverDateDecoration(@NonNull ConversationAdapter adapter) {
|
||||
if (popoverDateDecoration != null) {
|
||||
list.removeItemDecoration(popoverDateDecoration);
|
||||
}
|
||||
|
||||
stickyHeaderDecoration = new StickyHeaderDecoration(adapter, false, false);
|
||||
list.addItemDecoration(stickyHeaderDecoration);
|
||||
popoverDateDecoration = new StickyHeaderDecoration(adapter, false, false, ConversationAdapter.HEADER_TYPE_INLINE_DATE);
|
||||
list.addItemDecoration(popoverDateDecoration);
|
||||
}
|
||||
|
||||
public void setLastSeen(long lastSeen) {
|
||||
@@ -1179,7 +1193,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
|
||||
private void bindScrollHeader(StickyHeaderViewHolder headerViewHolder, int positionId) {
|
||||
if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) {
|
||||
((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId);
|
||||
((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId, ConversationAdapter.HEADER_TYPE_POPOVER_DATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -147,6 +149,11 @@ public class ConversationIntents {
|
||||
public boolean isFirstTimeInSelfCreatedGroup() {
|
||||
return firstTimeInSelfCreatedGroup;
|
||||
}
|
||||
|
||||
public @Nullable ChatWallpaper getWallpaper() {
|
||||
// TODO [greyson][wallpaper] Is it worth it to do this beforehand?
|
||||
return Recipient.resolved(recipientId).getWallpaper();
|
||||
}
|
||||
}
|
||||
|
||||
public final static class Builder {
|
||||
|
||||
@@ -155,7 +155,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
private boolean groupThread;
|
||||
private LiveRecipient recipient;
|
||||
private GlideRequests glideRequests;
|
||||
private ValueAnimator pulseOutlinerAlphaAnimator;
|
||||
private ValueAnimator pulseOutlinerAlphaAnimator;
|
||||
|
||||
protected ConversationItemBodyBubble bodyBubble;
|
||||
protected View reply;
|
||||
@@ -165,7 +165,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
private ConversationItemFooter footer;
|
||||
private ConversationItemFooter stickerFooter;
|
||||
@Nullable private TextView groupSender;
|
||||
@Nullable private TextView groupSenderProfileName;
|
||||
@Nullable private View groupSenderHolder;
|
||||
private AvatarImageView contactPhoto;
|
||||
private AlertView alertView;
|
||||
@@ -223,7 +222,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
this.footer = findViewById(R.id.conversation_item_footer);
|
||||
this.stickerFooter = findViewById(R.id.conversation_item_sticker_footer);
|
||||
this.groupSender = findViewById(R.id.group_message_sender);
|
||||
this.groupSenderProfileName = findViewById(R.id.group_message_sender_profile);
|
||||
this.alertView = findViewById(R.id.indicators_parent);
|
||||
this.contactPhoto = findViewById(R.id.contact_photo);
|
||||
this.contactPhotoHolder = findViewById(R.id.contact_photo_container);
|
||||
@@ -256,7 +254,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
@NonNull Set<ConversationMessage> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulse)
|
||||
boolean pulse,
|
||||
boolean hasWallpaper)
|
||||
{
|
||||
if (this.recipient != null) this.recipient.removeForeverObserver(this);
|
||||
if (this.conversationRecipient != null) this.conversationRecipient.removeForeverObserver(this);
|
||||
@@ -279,17 +278,17 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread);
|
||||
setBodyText(messageRecord, searchQuery);
|
||||
setBubbleState(messageRecord);
|
||||
setBubbleState(messageRecord, hasWallpaper);
|
||||
setInteractionState(conversationMessage, pulse);
|
||||
setStatusIcons(messageRecord);
|
||||
setContactPhoto(recipient.get());
|
||||
setGroupMessageStatus(messageRecord, recipient.get());
|
||||
setGroupAuthorColor(messageRecord);
|
||||
setAuthor(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setGroupAuthorColor(messageRecord, hasWallpaper);
|
||||
setAuthor(messageRecord, previousMessageRecord, nextMessageRecord, groupThread, hasWallpaper);
|
||||
setQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setMessageSpacing(context, messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setReactions(messageRecord);
|
||||
setFooter(messageRecord, nextMessageRecord, locale, groupThread);
|
||||
setFooter(messageRecord, nextMessageRecord, locale, groupThread, hasWallpaper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -344,12 +343,14 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
}
|
||||
|
||||
ConversationItemFooter activeFooter = getActiveFooter(messageRecord);
|
||||
int availableWidth = getAvailableMessageBubbleWidth(footer);
|
||||
if (!hasNoBubble(messageRecord)) {
|
||||
ConversationItemFooter activeFooter = getActiveFooter(messageRecord);
|
||||
int availableWidth = getAvailableMessageBubbleWidth(footer);
|
||||
|
||||
if (activeFooter.getVisibility() != GONE && activeFooter.getMeasuredWidth() != availableWidth) {
|
||||
activeFooter.getLayoutParams().width = availableWidth;
|
||||
needsMeasure = true;
|
||||
if (activeFooter.getVisibility() != GONE && activeFooter.getMeasuredWidth() != availableWidth) {
|
||||
activeFooter.getLayoutParams().width = availableWidth;
|
||||
needsMeasure = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsMeasure) {
|
||||
@@ -366,7 +367,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
@Override
|
||||
public void onRecipientChanged(@NonNull Recipient modified) {
|
||||
setBubbleState(messageRecord);
|
||||
setBubbleState(messageRecord, modified.hasWallpaper());
|
||||
if (recipient.getId().equals(modified.getId())) {
|
||||
setContactPhoto(modified);
|
||||
setGroupMessageStatus(messageRecord, modified);
|
||||
@@ -409,16 +410,20 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
/// MessageRecord Attribute Parsers
|
||||
|
||||
private void setBubbleState(MessageRecord messageRecord) {
|
||||
private void setBubbleState(MessageRecord messageRecord, boolean hasWallpaper) {
|
||||
if (messageRecord.isOutgoing() && !messageRecord.isRemoteDelete()) {
|
||||
bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||
footer.setTextColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
|
||||
footer.setIconColor(ContextCompat.getColor(context, R.color.signal_icon_tint_secondary));
|
||||
footer.setOnlyShowSendingStatus(false, messageRecord);
|
||||
} else if (messageRecord.isRemoteDelete() || (isViewOnceMessage(messageRecord) && ViewOnceUtil.isViewed((MmsMessageRecord) messageRecord))) {
|
||||
bodyBubble.getBackground().setColorFilter(ContextCompat.getColor(context, R.color.signal_background_primary), PorterDuff.Mode.MULTIPLY);
|
||||
if (hasWallpaper) {
|
||||
bodyBubble.getBackground().setColorFilter(ContextCompat.getColor(context, R.color.wallpaper_bubble_color), PorterDuff.Mode.SRC_IN);
|
||||
} else {
|
||||
bodyBubble.getBackground().setColorFilter(ContextCompat.getColor(context, R.color.signal_background_primary), PorterDuff.Mode.MULTIPLY);
|
||||
footer.setIconColor(ContextCompat.getColor(context, R.color.signal_icon_tint_secondary));
|
||||
}
|
||||
footer.setTextColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
|
||||
footer.setIconColor(ContextCompat.getColor(context, R.color.signal_icon_tint_secondary));
|
||||
footer.setOnlyShowSendingStatus(messageRecord.isRemoteDelete(), messageRecord);
|
||||
} else {
|
||||
bodyBubble.getBackground().setColorFilter(messageRecord.getRecipient().getColor().toConversationColor(context), PorterDuff.Mode.MULTIPLY);
|
||||
@@ -433,7 +438,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
pulseOutliner.setStrokeWidth(ViewUtil.dpToPx(4));
|
||||
|
||||
outliners.clear();
|
||||
if (shouldDrawBodyBubbleOutline(messageRecord)) {
|
||||
if (shouldDrawBodyBubbleOutline(messageRecord, hasWallpaper)) {
|
||||
outliners.add(outliner);
|
||||
}
|
||||
outliners.add(pulseOutliner);
|
||||
@@ -515,9 +520,13 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
pulseOutliner.setAlpha(0);
|
||||
}
|
||||
|
||||
private boolean shouldDrawBodyBubbleOutline(MessageRecord messageRecord) {
|
||||
boolean isIncomingViewedOnce = !messageRecord.isOutgoing() && isViewOnceMessage(messageRecord) && ViewOnceUtil.isViewed((MmsMessageRecord) messageRecord);
|
||||
return isIncomingViewedOnce || messageRecord.isRemoteDelete();
|
||||
private boolean shouldDrawBodyBubbleOutline(MessageRecord messageRecord, boolean hasWallpaper) {
|
||||
if (hasWallpaper) {
|
||||
return false;
|
||||
} else {
|
||||
boolean isIncomingViewedOnce = !messageRecord.isOutgoing() && isViewOnceMessage(messageRecord) && ViewOnceUtil.isViewed((MmsMessageRecord) messageRecord);
|
||||
return isIncomingViewedOnce || messageRecord.isRemoteDelete();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCaptionlessMms(MessageRecord messageRecord) {
|
||||
@@ -543,6 +552,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide().isBorderless();
|
||||
}
|
||||
|
||||
private boolean hasNoBubble(MessageRecord messageRecord) {
|
||||
return hasSticker(messageRecord) || isBorderless(messageRecord);
|
||||
}
|
||||
|
||||
private boolean hasOnlyThumbnail(MessageRecord messageRecord) {
|
||||
return hasThumbnail(messageRecord) &&
|
||||
!hasAudio(messageRecord) &&
|
||||
@@ -1074,7 +1087,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
});
|
||||
}
|
||||
|
||||
private void setFooter(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> next, @NonNull Locale locale, boolean isGroupThread) {
|
||||
private void setFooter(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> next, @NonNull Locale locale, boolean isGroupThread, boolean hasWallpaper) {
|
||||
ViewUtil.updateLayoutParams(footer, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
|
||||
footer.setVisibility(GONE);
|
||||
@@ -1090,11 +1103,27 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
ConversationItemFooter activeFooter = getActiveFooter(current);
|
||||
activeFooter.setVisibility(VISIBLE);
|
||||
activeFooter.setMessageRecord(current, locale);
|
||||
|
||||
if (hasWallpaper && hasNoBubble((messageRecord))) {
|
||||
if (messageRecord.isOutgoing()) {
|
||||
activeFooter.enableBubbleBackground(R.drawable.wallpaper_bubble_background_tintable_11, defaultBubbleColor);
|
||||
} else {
|
||||
activeFooter.enableBubbleBackground(R.drawable.wallpaper_bubble_background_tintable_11, messageRecord.getRecipient().getColor().toConversationColor(context));
|
||||
activeFooter.setTextColor(ContextCompat.getColor(context, R.color.conversation_item_received_text_secondary_color));
|
||||
activeFooter.setIconColor(ContextCompat.getColor(context, R.color.conversation_item_received_text_secondary_color));
|
||||
}
|
||||
} else if (hasNoBubble(messageRecord)){
|
||||
activeFooter.disableBubbleBackground();
|
||||
activeFooter.setTextColor(ContextCompat.getColor(context, R.color.signal_text_secondary));
|
||||
activeFooter.setIconColor(ContextCompat.getColor(context, R.color.signal_icon_tint_secondary));
|
||||
} else {
|
||||
activeFooter.disableBubbleBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) {
|
||||
if (hasSticker(messageRecord) || isBorderless(messageRecord)) {
|
||||
if (hasNoBubble(messageRecord)) {
|
||||
return stickerFooter;
|
||||
} else if (hasSharedContact(messageRecord) && TextUtils.isEmpty(messageRecord.getDisplayBody(getContext()))) {
|
||||
return sharedContactStub.get().getFooter();
|
||||
@@ -1118,30 +1147,27 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) {
|
||||
if (groupThread && !messageRecord.isOutgoing() && groupSender != null && groupSenderProfileName != null) {
|
||||
if (groupThread && !messageRecord.isOutgoing() && groupSender != null) {
|
||||
groupSender.setText(recipient.getDisplayName(getContext()));
|
||||
groupSenderProfileName.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setGroupAuthorColor(@NonNull MessageRecord messageRecord) {
|
||||
if (groupSender != null && groupSenderProfileName != null) {
|
||||
private void setGroupAuthorColor(@NonNull MessageRecord messageRecord, boolean hasWallpaper) {
|
||||
if (groupSender != null) {
|
||||
int stickerAuthorColor = ContextCompat.getColor(context, R.color.signal_text_primary);
|
||||
if (shouldDrawBodyBubbleOutline(messageRecord)) {
|
||||
|
||||
if (shouldDrawBodyBubbleOutline(messageRecord, hasWallpaper)) {
|
||||
groupSender.setTextColor(stickerAuthorColor);
|
||||
groupSenderProfileName.setTextColor(stickerAuthorColor);
|
||||
} else if (hasSticker(messageRecord) || isBorderless(messageRecord)) {
|
||||
} else if (!hasWallpaper && hasNoBubble(messageRecord)) {
|
||||
groupSender.setTextColor(stickerAuthorColor);
|
||||
groupSenderProfileName.setTextColor(stickerAuthorColor);
|
||||
} else {
|
||||
groupSender.setTextColor(ContextCompat.getColor(context, R.color.conversation_item_received_text_primary_color));
|
||||
groupSenderProfileName.setTextColor(ContextCompat.getColor(context, R.color.conversation_item_received_text_primary_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void setAuthor(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||
private void setAuthor(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread, boolean hasWallpaper) {
|
||||
if (isGroupThread && !current.isOutgoing()) {
|
||||
contactPhotoHolder.setVisibility(VISIBLE);
|
||||
|
||||
@@ -1149,6 +1175,13 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
!DateUtils.isSameDay(previous.get().getTimestamp(), current.getTimestamp()))
|
||||
{
|
||||
groupSenderHolder.setVisibility(VISIBLE);
|
||||
|
||||
if (hasWallpaper && hasNoBubble(current)) {
|
||||
groupSenderHolder.setBackgroundResource(R.drawable.wallpaper_bubble_background_tintable_11);
|
||||
groupSenderHolder.getBackground().setColorFilter(messageRecord.getRecipient().getColor().toConversationColor(context), PorterDuff.Mode.MULTIPLY);
|
||||
} else {
|
||||
groupSenderHolder.setBackground(null);
|
||||
}
|
||||
} else {
|
||||
groupSenderHolder.setVisibility(GONE);
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||
}
|
||||
|
||||
public void setListVerticalTranslation(float translationY) {
|
||||
maskView.setTargetParentTranslationY(translationY);
|
||||
maskView.setTargetParentTranslationY(translationY - ViewUtil.getStatusBarHeight(maskView));
|
||||
}
|
||||
|
||||
public void show(@NonNull Activity activity,
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -41,7 +41,7 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public final class ConversationUpdateItem extends LinearLayout
|
||||
public final class ConversationUpdateItem extends FrameLayout
|
||||
implements BindableConversationItem
|
||||
{
|
||||
private static final String TAG = ConversationUpdateItem.class.getSimpleName();
|
||||
@@ -50,6 +50,7 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||
|
||||
private TextView body;
|
||||
private TextView actionButton;
|
||||
private View background;
|
||||
private ConversationMessage conversationMessage;
|
||||
private Recipient conversationRecipient;
|
||||
private Optional<MessageRecord> nextMessageRecord;
|
||||
@@ -76,6 +77,7 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||
super.onFinishInflate();
|
||||
this.body = findViewById(R.id.conversation_update_body);
|
||||
this.actionButton = findViewById(R.id.conversation_update_action);
|
||||
this.background = findViewById(R.id.conversation_update_background);
|
||||
|
||||
this.setOnClickListener(new InternalClickListener(null));
|
||||
}
|
||||
@@ -90,11 +92,12 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||
@NonNull Set<ConversationMessage> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseMention)
|
||||
boolean pulseMention,
|
||||
boolean hasWallpaper)
|
||||
{
|
||||
this.batchSelected = batchSelected;
|
||||
|
||||
bind(lifecycleOwner, conversationMessage, nextMessageRecord, conversationRecipient);
|
||||
bind(lifecycleOwner, conversationMessage, nextMessageRecord, conversationRecipient, hasWallpaper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,7 +113,8 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||
private void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||
@NonNull ConversationMessage conversationMessage,
|
||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||
@NonNull Recipient conversationRecipient)
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean hasWallpaper)
|
||||
{
|
||||
this.conversationMessage = conversationMessage;
|
||||
this.messageRecord = conversationMessage.getMessageRecord();
|
||||
@@ -125,6 +129,12 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||
groupObserver.observe(lifecycleOwner, null);
|
||||
}
|
||||
|
||||
if (hasWallpaper) {
|
||||
background.setBackgroundResource(R.drawable.wallpaper_bubble_background_12);
|
||||
} else {
|
||||
background.setBackground(null);
|
||||
}
|
||||
|
||||
UpdateDescription updateDescription = Objects.requireNonNull(messageRecord.getUpdateDisplayBody(getContext()));
|
||||
LiveData<Spannable> liveUpdateMessage = LiveUpdateMessage.fromMessageDescription(getContext(), updateDescription, ContextCompat.getColor(getContext(), R.color.conversation_item_update_text_color));
|
||||
LiveData<Spannable> spannableMessage = loading(liveUpdateMessage);
|
||||
|
||||
@@ -19,7 +19,11 @@ import org.thoughtcrime.securesms.database.DatabaseObserver;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mediasend.MediaRepository;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
@@ -41,6 +45,8 @@ class ConversationViewModel extends ViewModel {
|
||||
private final LiveData<Boolean> canShowAsBubble;
|
||||
private final ProxyPagingController pagingController;
|
||||
private final DatabaseObserver.Observer messageObserver;
|
||||
private final MutableLiveData<RecipientId> recipientId;
|
||||
private final LiveData<ChatWallpaper> wallpaper;
|
||||
|
||||
private ConversationIntents.Args args;
|
||||
private int jumpToPosition;
|
||||
@@ -53,6 +59,7 @@ class ConversationViewModel extends ViewModel {
|
||||
this.threadId = new MutableLiveData<>();
|
||||
this.showScrollButtons = new MutableLiveData<>(false);
|
||||
this.hasUnreadMentions = new MutableLiveData<>(false);
|
||||
this.recipientId = new MutableLiveData<>();
|
||||
this.pagingController = new ProxyPagingController();
|
||||
this.messageObserver = pagingController::onDataInvalidated;
|
||||
|
||||
@@ -97,6 +104,9 @@ class ConversationViewModel extends ViewModel {
|
||||
|
||||
conversationMetadata = Transformations.switchMap(messages, m -> metadata);
|
||||
canShowAsBubble = LiveDataUtil.mapAsync(threadId, conversationRepository::canShowAsBubble);
|
||||
wallpaper = Transformations.distinctUntilChanged(Transformations.map(Transformations.switchMap(recipientId,
|
||||
id -> Recipient.live(id).getLiveData()),
|
||||
Recipient::getWallpaper));
|
||||
}
|
||||
|
||||
void onAttachmentKeyboardOpen() {
|
||||
@@ -104,11 +114,12 @@ class ConversationViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
@MainThread
|
||||
void onConversationDataAvailable(long threadId, int startingPosition) {
|
||||
void onConversationDataAvailable(@NonNull RecipientId recipientId, long threadId, int startingPosition) {
|
||||
Log.d(TAG, "[onConversationDataAvailable] threadId: " + threadId + ", startingPosition: " + startingPosition);
|
||||
this.jumpToPosition = startingPosition;
|
||||
|
||||
this.threadId.setValue(threadId);
|
||||
this.recipientId.setValue(recipientId);
|
||||
}
|
||||
|
||||
void clearThreadId() {
|
||||
@@ -128,6 +139,10 @@ class ConversationViewModel extends ViewModel {
|
||||
return Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(showScrollButtons, hasUnreadMentions, (a, b) -> a && b));
|
||||
}
|
||||
|
||||
@NonNull LiveData<ChatWallpaper> getWallpaper() {
|
||||
return wallpaper;
|
||||
}
|
||||
|
||||
void setHasUnreadMentions(boolean hasUnreadMentions) {
|
||||
this.hasUnreadMentions.setValue(hasUnreadMentions);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class LastSeenHeader extends StickyHeaderDecoration {
|
||||
private final long lastSeenTimestamp;
|
||||
|
||||
LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) {
|
||||
super(adapter, false, false);
|
||||
super(adapter, false, false, ConversationAdapter.HEADER_TYPE_LAST_SEEN);
|
||||
this.adapter = adapter;
|
||||
this.lastSeenTimestamp = lastSeenTimestamp;
|
||||
}
|
||||
|
||||
@@ -481,7 +481,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
private void initializeListAdapters() {
|
||||
defaultAdapter = new ConversationListAdapter(GlideApp.with(this), this);
|
||||
searchAdapter = new ConversationListSearchAdapter(GlideApp.with(this), this, Locale.getDefault());
|
||||
searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false);
|
||||
searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false, 0);
|
||||
|
||||
setAdapter(defaultAdapter);
|
||||
|
||||
|
||||
@@ -94,13 +94,13 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<Conversation
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) {
|
||||
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
|
||||
return new HeaderViewHolder(LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.search_result_list_divider, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
|
||||
viewHolder.bind((int) getHeaderId(position));
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(recipientId, address.getDeviceId());
|
||||
|
||||
return sessionRecord != null &&
|
||||
sessionRecord.getSessionState().hasSenderChain() &&
|
||||
sessionRecord.getSessionState().getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
|
||||
sessionRecord.hasSenderChain() &&
|
||||
sessionRecord.getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileKeyCredentialColumnData;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.v2.ProfileKeySet;
|
||||
@@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -53,6 +55,9 @@ import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory;
|
||||
import org.thoughtcrime.securesms.wallpaper.WallpaperStorage;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
@@ -131,6 +136,10 @@ public class RecipientDatabase extends Database {
|
||||
private static final String STORAGE_PROTO = "storage_proto";
|
||||
private static final String LAST_GV1_MIGRATE_REMINDER = "last_gv1_migrate_reminder";
|
||||
private static final String LAST_SESSION_RESET = "last_session_reset";
|
||||
private static final String WALLPAPER = "wallpaper";
|
||||
private static final String WALLPAPER_URI = "wallpaper_file";
|
||||
public static final String ABOUT = "about";
|
||||
public static final String ABOUT_EMOJI = "about_emoji";
|
||||
|
||||
public static final String SEARCH_PROFILE_NAME = "search_signal_profile";
|
||||
private static final String SORT_NAME = "sort_name";
|
||||
@@ -155,12 +164,14 @@ public class RecipientDatabase extends Database {
|
||||
FORCE_SMS_SELECTION,
|
||||
CAPABILITIES,
|
||||
STORAGE_SERVICE_ID, DIRTY,
|
||||
MENTION_SETTING
|
||||
MENTION_SETTING, WALLPAPER, WALLPAPER_URI,
|
||||
MENTION_SETTING,
|
||||
ABOUT, ABOUT_EMOJI
|
||||
};
|
||||
|
||||
private static final String[] ID_PROJECTION = new String[]{ID};
|
||||
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
|
||||
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, SEARCH_PROFILE_NAME, SORT_NAME};
|
||||
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
|
||||
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, SEARCH_PROFILE_NAME, SORT_NAME};
|
||||
private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
|
||||
.map(columnName -> TABLE_NAME + "." + columnName)
|
||||
.toList().toArray(new String[0]);
|
||||
@@ -350,7 +361,11 @@ public class RecipientDatabase extends Database {
|
||||
STORAGE_PROTO + " TEXT DEFAULT NULL, " +
|
||||
CAPABILITIES + " INTEGER DEFAULT 0, " +
|
||||
LAST_GV1_MIGRATE_REMINDER + " INTEGER DEFAULT 0, " +
|
||||
LAST_SESSION_RESET + " BLOB DEFAULT NULL);";
|
||||
LAST_SESSION_RESET + " BLOB DEFAULT NULL, " +
|
||||
WALLPAPER + " BLOB DEFAULT NULL, " +
|
||||
WALLPAPER_URI + " TEXT DEFAULT NULL, " +
|
||||
ABOUT + " TEXT DEFAULT NULL, " +
|
||||
ABOUT_EMOJI + " TEXT DEFAULT NULL);";
|
||||
|
||||
private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
|
||||
" FROM " + TABLE_NAME +
|
||||
@@ -1264,6 +1279,9 @@ public class RecipientDatabase extends Database {
|
||||
long capabilities = CursorUtil.requireLong(cursor, CAPABILITIES);
|
||||
String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID);
|
||||
int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING);
|
||||
byte[] wallpaper = CursorUtil.requireBlob(cursor, WALLPAPER);
|
||||
String about = CursorUtil.requireString(cursor, ABOUT);
|
||||
String aboutEmoji = CursorUtil.requireString(cursor, ABOUT_EMOJI);
|
||||
|
||||
MaterialColor color;
|
||||
byte[] profileKey = null;
|
||||
@@ -1303,6 +1321,16 @@ public class RecipientDatabase extends Database {
|
||||
|
||||
byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null;
|
||||
|
||||
ChatWallpaper chatWallpaper = null;
|
||||
|
||||
if (wallpaper != null) {
|
||||
try {
|
||||
chatWallpaper = ChatWallpaperFactory.create(Wallpaper.parseFrom(wallpaper));
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
Log.w(TAG, "Failed to parse wallpaper.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new RecipientSettings(RecipientId.from(id),
|
||||
uuid,
|
||||
username,
|
||||
@@ -1338,6 +1366,9 @@ public class RecipientDatabase extends Database {
|
||||
InsightsBannerTier.fromId(insightsBannerTier),
|
||||
storageKey,
|
||||
MentionSetting.fromId(mentionSettingId),
|
||||
chatWallpaper,
|
||||
about,
|
||||
aboutEmoji,
|
||||
getSyncExtras(cursor));
|
||||
}
|
||||
|
||||
@@ -1529,7 +1560,12 @@ public class RecipientDatabase extends Database {
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] {LAST_SESSION_RESET}, ID_WHERE, SqlUtil.buildArgs(id), null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
try {
|
||||
return DeviceLastResetTime.parseFrom(CursorUtil.requireBlob(cursor, LAST_SESSION_RESET));
|
||||
byte[] serialized = CursorUtil.requireBlob(cursor, LAST_SESSION_RESET);
|
||||
if (serialized != null) {
|
||||
return DeviceLastResetTime.parseFrom(serialized);
|
||||
} else {
|
||||
return DeviceLastResetTime.newBuilder().build();
|
||||
}
|
||||
} catch (InvalidProtocolBufferException | SQLException e) {
|
||||
Log.w(TAG, e);
|
||||
return DeviceLastResetTime.newBuilder().build();
|
||||
@@ -1751,6 +1787,16 @@ public class RecipientDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public void setAbout(@NonNull RecipientId id, @Nullable String about, @Nullable String emoji) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(ABOUT, about);
|
||||
contentValues.put(ABOUT_EMOJI, emoji);
|
||||
|
||||
if (update(id, contentValues)) {
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public void setProfileSharing(@NonNull RecipientId id, @SuppressWarnings("SameParameterValue") boolean enabled) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(PROFILE_SHARING, enabled ? 1 : 0);
|
||||
@@ -1773,6 +1819,134 @@ public class RecipientDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public void resetAllWallpaper() {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String[] selection = SqlUtil.buildArgs(ID, WALLPAPER_URI);
|
||||
String where = WALLPAPER + " IS NOT NULL";
|
||||
List<Pair<RecipientId, String>> idWithWallpaper = new LinkedList<>();
|
||||
|
||||
database.beginTransaction();
|
||||
|
||||
try {
|
||||
try (Cursor cursor = database.query(TABLE_NAME, selection, where, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
idWithWallpaper.add(new Pair<>(RecipientId.from(CursorUtil.requireInt(cursor, ID)),
|
||||
CursorUtil.getString(cursor, WALLPAPER_URI).orNull()));
|
||||
}
|
||||
}
|
||||
|
||||
if (idWithWallpaper.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(WALLPAPER_URI, (String) null);
|
||||
values.put(WALLPAPER, (byte[]) null);
|
||||
|
||||
int rowsUpdated = database.update(TABLE_NAME, values, where, null);
|
||||
if (rowsUpdated == idWithWallpaper.size()) {
|
||||
for (Pair<RecipientId, String> pair : idWithWallpaper) {
|
||||
Recipient.live(pair.first()).refresh();
|
||||
if (pair.second() != null) {
|
||||
WallpaperStorage.onWallpaperDeselected(context, Uri.parse(pair.second()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new AssertionError("expected " + idWithWallpaper.size() + " but got " + rowsUpdated);
|
||||
}
|
||||
|
||||
} finally {
|
||||
database.setTransactionSuccessful();
|
||||
database.endTransaction();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setWallpaper(@NonNull RecipientId id, @Nullable ChatWallpaper chatWallpaper) {
|
||||
setWallpaper(id, chatWallpaper != null ? chatWallpaper.serialize() : null);
|
||||
}
|
||||
|
||||
private void setWallpaper(@NonNull RecipientId id, @Nullable Wallpaper wallpaper) {
|
||||
Uri existingWallpaperUri = getWallpaperUri(id);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(WALLPAPER, wallpaper != null ? wallpaper.toByteArray() : null);
|
||||
|
||||
if (wallpaper != null && wallpaper.hasFile()) {
|
||||
values.put(WALLPAPER_URI, wallpaper.getFile().getUri());
|
||||
} else {
|
||||
values.putNull(WALLPAPER_URI);
|
||||
}
|
||||
|
||||
if (update(id, values)) {
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
|
||||
if (existingWallpaperUri != null) {
|
||||
WallpaperStorage.onWallpaperDeselected(context, existingWallpaperUri);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDimWallpaperInDarkTheme(@NonNull RecipientId id, boolean enabled) {
|
||||
Wallpaper wallpaper = getWallpaper(id);
|
||||
|
||||
if (wallpaper == null) {
|
||||
throw new IllegalStateException("No wallpaper set for " + id);
|
||||
}
|
||||
|
||||
Wallpaper updated = wallpaper.toBuilder()
|
||||
.setDimLevelInDarkTheme(enabled ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME : 0)
|
||||
.build();
|
||||
|
||||
setWallpaper(id, updated);
|
||||
}
|
||||
|
||||
private @Nullable Wallpaper getWallpaper(@NonNull RecipientId id) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] {WALLPAPER}, ID_WHERE, SqlUtil.buildArgs(id), null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
byte[] raw = CursorUtil.requireBlob(cursor, WALLPAPER);
|
||||
|
||||
if (raw != null) {
|
||||
try {
|
||||
return Wallpaper.parseFrom(raw);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Uri getWallpaperUri(@NonNull RecipientId id) {
|
||||
Wallpaper wallpaper = getWallpaper(id);
|
||||
|
||||
if (wallpaper != null && wallpaper.hasFile()) {
|
||||
return Uri.parse(wallpaper.getFile().getUri());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getWallpaperUriUsageCount(@NonNull Uri uri) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
String query = WALLPAPER_URI + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(uri);
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] { "COUNT(*)" }, query, args, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if setting the phone number resulted in changed recipientId, otherwise false.
|
||||
*/
|
||||
@@ -2783,6 +2957,9 @@ public class RecipientDatabase extends Database {
|
||||
private final InsightsBannerTier insightsBannerTier;
|
||||
private final byte[] storageId;
|
||||
private final MentionSetting mentionSetting;
|
||||
private final ChatWallpaper wallpaper;
|
||||
private final String about;
|
||||
private final String aboutEmoji;
|
||||
private final SyncExtras syncExtras;
|
||||
|
||||
RecipientSettings(@NonNull RecipientId id,
|
||||
@@ -2820,6 +2997,9 @@ public class RecipientDatabase extends Database {
|
||||
@NonNull InsightsBannerTier insightsBannerTier,
|
||||
@Nullable byte[] storageId,
|
||||
@NonNull MentionSetting mentionSetting,
|
||||
@Nullable ChatWallpaper wallpaper,
|
||||
@Nullable String about,
|
||||
@Nullable String aboutEmoji,
|
||||
@NonNull SyncExtras syncExtras)
|
||||
{
|
||||
this.id = id;
|
||||
@@ -2859,6 +3039,9 @@ public class RecipientDatabase extends Database {
|
||||
this.insightsBannerTier = insightsBannerTier;
|
||||
this.storageId = storageId;
|
||||
this.mentionSetting = mentionSetting;
|
||||
this.wallpaper = wallpaper;
|
||||
this.about = about;
|
||||
this.aboutEmoji = aboutEmoji;
|
||||
this.syncExtras = syncExtras;
|
||||
}
|
||||
|
||||
@@ -3006,6 +3189,18 @@ public class RecipientDatabase extends Database {
|
||||
return mentionSetting;
|
||||
}
|
||||
|
||||
public @Nullable ChatWallpaper getWallpaper() {
|
||||
return wallpaper;
|
||||
}
|
||||
|
||||
public @Nullable String getAbout() {
|
||||
return about;
|
||||
}
|
||||
|
||||
public @Nullable String getAboutEmoji() {
|
||||
return aboutEmoji;
|
||||
}
|
||||
|
||||
public @NonNull SyncExtras getSyncExtras() {
|
||||
return syncExtras;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -962,6 +963,19 @@ public class ThreadDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<RecipientId, Long> getThreadIdsIfExistsFor(@NonNull RecipientId ... recipientIds) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
SqlUtil.Query query = SqlUtil.buildCollectionQuery(RECIPIENT_ID, Arrays.asList(recipientIds));
|
||||
|
||||
Map<RecipientId, Long> results = new HashMap<>();
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[]{ ID, RECIPIENT_ID }, query.getWhere(), query.getWhereArgs(), null, null, null, "1")) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
results.put(RecipientId.from(CursorUtil.requireString(cursor, RECIPIENT_ID)), CursorUtil.requireLong(cursor, ID));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public long getOrCreateValidThreadId(@NonNull Recipient recipient, long candidateId) {
|
||||
return getOrCreateValidThreadId(recipient, candidateId, DistributionTypes.DEFAULT);
|
||||
}
|
||||
|
||||
@@ -169,8 +169,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
||||
private static final int GV1_MIGRATION_REFACTOR = 85;
|
||||
private static final int CLEAR_PROFILE_KEY_CREDENTIALS = 86;
|
||||
private static final int LAST_RESET_SESSION_TIME = 87;
|
||||
private static final int WALLPAPER = 88;
|
||||
private static final int ABOUT = 89;
|
||||
|
||||
private static final int DATABASE_VERSION = 87;
|
||||
private static final int DATABASE_VERSION = 89;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
@@ -1246,6 +1248,16 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN last_session_reset BLOB DEFAULT NULL");
|
||||
}
|
||||
|
||||
if (oldVersion < WALLPAPER) {
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN wallpaper BLOB DEFAULT NULL");
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN wallpaper_file TEXT DEFAULT NULL");
|
||||
}
|
||||
|
||||
if (oldVersion < ABOUT) {
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN about TEXT DEFAULT NULL");
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN about_emoji TEXT DEFAULT NULL");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.signal.core.util.Conversions;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SessionState;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.File;
|
||||
@@ -64,9 +63,7 @@ class SessionStoreMigrationHelper {
|
||||
|
||||
if (versionMarker == SINGLE_STATE_VERSION) {
|
||||
Log.i(TAG, "Migrating single state version: " + sessionFile.getAbsolutePath());
|
||||
SessionState sessionState = new SessionState(serialized);
|
||||
|
||||
sessionRecord = new SessionRecord(sessionState);
|
||||
sessionRecord = SessionRecord.fromSingleSessionState(serialized);
|
||||
} else if (versionMarker >= ARCHIVE_STATES_VERSION) {
|
||||
Log.i(TAG, "Migrating session: " + sessionFile.getAbsolutePath());
|
||||
sessionRecord = new SessionRecord(serialized);
|
||||
|
||||
@@ -15,9 +15,12 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.LifecycleRecyclerAdapter;
|
||||
import org.thoughtcrime.securesms.util.LifecycleViewHolder;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -166,6 +169,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
final Context context;
|
||||
final AvatarImageView avatar;
|
||||
final TextView recipient;
|
||||
final EmojiTextView about;
|
||||
final CheckBox selected;
|
||||
final PopupMenuView popupMenu;
|
||||
final View popupMenuContainer;
|
||||
@@ -187,6 +191,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
this.context = itemView.getContext();
|
||||
this.avatar = itemView.findViewById(R.id.recipient_avatar);
|
||||
this.recipient = itemView.findViewById(R.id.recipient_name);
|
||||
this.about = itemView.findViewById(R.id.recipient_about);
|
||||
this.selected = itemView.findViewById(R.id.recipient_selected);
|
||||
this.popupMenu = itemView.findViewById(R.id.popupMenu);
|
||||
this.popupMenuContainer = itemView.findViewById(R.id.popupMenuProgressContainer);
|
||||
@@ -201,12 +206,17 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
void bindRecipient(@NonNull Recipient recipient) {
|
||||
String displayName = recipient.isSelf() ? context.getString(R.string.GroupMembersDialog_you)
|
||||
: recipient.getDisplayName(itemView.getContext());
|
||||
bindImageAndText(recipient, displayName);
|
||||
bindImageAndText(recipient, displayName, recipient.getCombinedAboutAndEmoji());
|
||||
}
|
||||
|
||||
void bindImageAndText(@NonNull Recipient recipient, @NonNull String displayText) {
|
||||
void bindImageAndText(@NonNull Recipient recipient, @NonNull String displayText, @Nullable String about) {
|
||||
this.recipient.setText(displayText);
|
||||
this.avatar.setRecipient(recipient);
|
||||
|
||||
if (this.about != null) {
|
||||
this.about.setText(about);
|
||||
this.about.setVisibility(Util.isEmpty(about) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
void bindRecipientClick(@NonNull Recipient recipient) {
|
||||
@@ -333,7 +343,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
|
||||
GroupMemberEntry.PendingMember pendingMember = (GroupMemberEntry.PendingMember) memberEntry;
|
||||
|
||||
bindImageAndText(pendingMember.getInvitee(), pendingMember.getInvitee().getDisplayNameOrUsername(context));
|
||||
bindImageAndText(pendingMember.getInvitee(), pendingMember.getInvitee().getDisplayNameOrUsername(context), pendingMember.getInvitee().getCombinedAboutAndEmoji());
|
||||
bindRecipientClick(pendingMember.getInvitee());
|
||||
|
||||
if (pendingMember.isCancellable() && adminActionsListener != null) {
|
||||
@@ -370,7 +380,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
pendingMembers.getInviteCount(),
|
||||
displayName, pendingMembers.getInviteCount());
|
||||
|
||||
bindImageAndText(inviter, displayText);
|
||||
bindImageAndText(inviter, displayText, inviter.getAbout());
|
||||
|
||||
if (pendingMembers.isCancellable() && adminActionsListener != null) {
|
||||
popupMenu.setMenu(R.menu.others_invite_pending_menu,
|
||||
|
||||
@@ -62,6 +62,7 @@ import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.LifecycleCursorWrapper;
|
||||
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -114,6 +115,7 @@ public class ManageGroupFragment extends LoggingFragment {
|
||||
private View toggleAllMembers;
|
||||
private View groupLinkRow;
|
||||
private TextView groupLinkButton;
|
||||
private View wallpaperButton;
|
||||
|
||||
private final Recipient.FallbackPhotoProvider fallbackPhotoProvider = new Recipient.FallbackPhotoProvider() {
|
||||
@Override
|
||||
@@ -175,6 +177,7 @@ public class ManageGroupFragment extends LoggingFragment {
|
||||
toggleAllMembers = view.findViewById(R.id.toggle_all_members);
|
||||
groupLinkRow = view.findViewById(R.id.group_link_row);
|
||||
groupLinkButton = view.findViewById(R.id.group_link_button);
|
||||
wallpaperButton = view.findViewById(R.id.chat_wallpaper);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -240,6 +243,7 @@ public class ManageGroupFragment extends LoggingFragment {
|
||||
});
|
||||
customNotificationsRow.setOnClickListener(v -> CustomNotificationsDialogFragment.create(groupRecipient.getId())
|
||||
.show(requireFragmentManager(), DIALOG_TAG));
|
||||
wallpaperButton.setOnClickListener(v -> startActivity(ChatWallpaperActivity.createIntent(requireContext(), groupRecipient.getId())));
|
||||
});
|
||||
|
||||
if (groupId.isV2()) {
|
||||
|
||||
@@ -172,7 +172,7 @@ public class HelpFragment extends LoggingFragment {
|
||||
}
|
||||
|
||||
return SupportEmailUtil.generateSupportEmailBody(requireContext(),
|
||||
getString(R.string.HelpFragment__signal_android_support_request),
|
||||
R.string.HelpFragment__signal_android_support_request,
|
||||
problem.getText().toString() + "\n\n",
|
||||
suffix.toString());
|
||||
}
|
||||
|
||||
@@ -61,6 +61,9 @@ public final class ImageEditorView extends FrameLayout {
|
||||
@Nullable
|
||||
private DrawingChangedListener drawingChangedListener;
|
||||
|
||||
@Nullable
|
||||
private SizeChangedListener sizeChangedListener;
|
||||
|
||||
@Nullable
|
||||
private UndoRedoStackListener undoRedoStackListener;
|
||||
|
||||
@@ -93,7 +96,7 @@ public final class ImageEditorView extends FrameLayout {
|
||||
|
||||
private void init() {
|
||||
setWillNotDraw(false);
|
||||
setModel(new EditorModel());
|
||||
setModel(EditorModel.create());
|
||||
|
||||
editText = createAHiddenTextEntryField();
|
||||
|
||||
@@ -170,6 +173,9 @@ public final class ImageEditorView extends FrameLayout {
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
updateViewMatrix();
|
||||
if (sizeChangedListener != null) {
|
||||
sizeChangedListener.onSizeChanged(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateViewMatrix() {
|
||||
@@ -393,6 +399,10 @@ public final class ImageEditorView extends FrameLayout {
|
||||
this.drawingChangedListener = drawingChangedListener;
|
||||
}
|
||||
|
||||
public void setSizeChangedListener(@Nullable SizeChangedListener sizeChangedListener) {
|
||||
this.sizeChangedListener = sizeChangedListener;
|
||||
}
|
||||
|
||||
public void setUndoRedoStackListener(@Nullable UndoRedoStackListener undoRedoStackListener) {
|
||||
this.undoRedoStackListener = undoRedoStackListener;
|
||||
}
|
||||
@@ -463,6 +473,10 @@ public final class ImageEditorView extends FrameLayout {
|
||||
void onDrawingChanged();
|
||||
}
|
||||
|
||||
public interface SizeChangedListener {
|
||||
void onSizeChanged(int newWidth, int newHeight);
|
||||
}
|
||||
|
||||
public interface TapListener {
|
||||
|
||||
void onEntityDown(@Nullable EditorElement editorElement);
|
||||
|
||||
@@ -45,11 +45,15 @@ import org.thoughtcrime.securesms.imageeditor.renderers.OvalGuideRenderer;
|
||||
final class EditorElementHierarchy {
|
||||
|
||||
static @NonNull EditorElementHierarchy create() {
|
||||
return new EditorElementHierarchy(createRoot(false));
|
||||
return new EditorElementHierarchy(createRoot(CropStyle.RECTANGLE));
|
||||
}
|
||||
|
||||
static @NonNull EditorElementHierarchy createForCircleEditing() {
|
||||
return new EditorElementHierarchy(createRoot(true));
|
||||
return new EditorElementHierarchy(createRoot(CropStyle.CIRCLE));
|
||||
}
|
||||
|
||||
static @NonNull EditorElementHierarchy createForPinchAndPanCropping() {
|
||||
return new EditorElementHierarchy(createRoot(CropStyle.PINCH_AND_PAN));
|
||||
}
|
||||
|
||||
static @NonNull EditorElementHierarchy create(@NonNull EditorElement root) {
|
||||
@@ -78,7 +82,24 @@ final class EditorElementHierarchy {
|
||||
this.thumbs = this.cropEditorElement.getChild(1);
|
||||
}
|
||||
|
||||
private static @NonNull EditorElement createRoot(boolean circleEdit) {
|
||||
private enum CropStyle {
|
||||
/**
|
||||
* A rectangular overlay with 8 thumbs, corners and edges.
|
||||
*/
|
||||
RECTANGLE,
|
||||
|
||||
/**
|
||||
* Cropping with a circular template overlay with Corner thumbs only.
|
||||
*/
|
||||
CIRCLE,
|
||||
|
||||
/**
|
||||
* No overlay and no thumbs. Cropping achieved through pinching and panning.
|
||||
*/
|
||||
PINCH_AND_PAN
|
||||
}
|
||||
|
||||
private static @NonNull EditorElement createRoot(@NonNull CropStyle cropStyle) {
|
||||
EditorElement root = new EditorElement(null);
|
||||
|
||||
EditorElement imageRoot = new EditorElement(null);
|
||||
@@ -96,7 +117,8 @@ final class EditorElementHierarchy {
|
||||
EditorElement imageCrop = new EditorElement(null);
|
||||
overlay.addElement(imageCrop);
|
||||
|
||||
EditorElement cropEditorElement = new EditorElement(new CropAreaRenderer(R.color.crop_area_renderer_outer_color, !circleEdit));
|
||||
boolean renderCenterThumbs = cropStyle == CropStyle.RECTANGLE;
|
||||
EditorElement cropEditorElement = new EditorElement(new CropAreaRenderer(R.color.crop_area_renderer_outer_color, renderCenterThumbs));
|
||||
|
||||
cropEditorElement.getFlags()
|
||||
.setRotateLocked(true)
|
||||
@@ -116,14 +138,18 @@ final class EditorElementHierarchy {
|
||||
|
||||
cropEditorElement.addElement(blackout);
|
||||
|
||||
cropEditorElement.addElement(createThumbs(cropEditorElement, !circleEdit));
|
||||
if (cropStyle == CropStyle.PINCH_AND_PAN) {
|
||||
cropEditorElement.addElement(new EditorElement(null));
|
||||
} else {
|
||||
cropEditorElement.addElement(createThumbs(cropEditorElement, renderCenterThumbs));
|
||||
|
||||
if (circleEdit) {
|
||||
EditorElement circle = new EditorElement(new OvalGuideRenderer(R.color.crop_circle_guide_color));
|
||||
circle.getFlags().setSelectable(false)
|
||||
.persist();
|
||||
if (cropStyle == CropStyle.CIRCLE) {
|
||||
EditorElement circle = new EditorElement(new OvalGuideRenderer(R.color.crop_circle_guide_color));
|
||||
circle.getFlags().setSelectable(false)
|
||||
.persist();
|
||||
|
||||
cropEditorElement.addElement(circle);
|
||||
cropEditorElement.addElement(circle);
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
@@ -197,11 +223,14 @@ final class EditorElementHierarchy {
|
||||
return flipRotate;
|
||||
}
|
||||
|
||||
void startCrop(@NonNull Runnable invalidate) {
|
||||
Matrix editor = new Matrix();
|
||||
float scaleInForCrop = 0.8f;
|
||||
/**
|
||||
* @param scaleIn Use 1 for no scale in, use less than 1 and it will zoom the image out
|
||||
* so user can see more of the surrounding image while cropping.
|
||||
*/
|
||||
void startCrop(@NonNull Runnable invalidate, float scaleIn) {
|
||||
Matrix editor = new Matrix();
|
||||
|
||||
editor.postScale(scaleInForCrop, scaleInForCrop);
|
||||
editor.postScale(scaleIn, scaleIn);
|
||||
root.animateEditorTo(editor, invalidate);
|
||||
|
||||
cropEditorElement.getFlags()
|
||||
|
||||
@@ -60,17 +60,21 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
|
||||
private EditorElementHierarchy editorElementHierarchy;
|
||||
|
||||
private final RectF visibleViewPort = new RectF();
|
||||
private final Point size;
|
||||
private final boolean circleEditing;
|
||||
private final RectF visibleViewPort = new RectF();
|
||||
private final Point size;
|
||||
private final EditingPurpose editingPurpose;
|
||||
private float fixedRatio;
|
||||
|
||||
public EditorModel() {
|
||||
this(false, EditorElementHierarchy.create());
|
||||
private enum EditingPurpose {
|
||||
IMAGE,
|
||||
AVATAR_CIRCLE,
|
||||
WALLPAPER
|
||||
}
|
||||
|
||||
private EditorModel(@NonNull Parcel in) {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
this.circleEditing = in.readByte() == 1;
|
||||
this.editingPurpose = EditingPurpose.values()[in.readInt()];
|
||||
this.fixedRatio = in.readFloat();
|
||||
this.size = new Point(in.readInt(), in.readInt());
|
||||
//noinspection ConstantConditions
|
||||
this.editorElementHierarchy = EditorElementHierarchy.create(in.readParcelable(classLoader));
|
||||
@@ -78,8 +82,9 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
this.cropUndoRedoStacks = in.readParcelable(classLoader);
|
||||
}
|
||||
|
||||
public EditorModel(boolean circleEditing, @NonNull EditorElementHierarchy editorElementHierarchy) {
|
||||
this.circleEditing = circleEditing;
|
||||
public EditorModel(@NonNull EditingPurpose editingPurpose, float fixedRatio, @NonNull EditorElementHierarchy editorElementHierarchy) {
|
||||
this.editingPurpose = editingPurpose;
|
||||
this.fixedRatio = fixedRatio;
|
||||
this.size = new Point(1024, 1024);
|
||||
this.editorElementHierarchy = editorElementHierarchy;
|
||||
this.undoRedoStacks = new UndoRedoStacks(50);
|
||||
@@ -87,11 +92,17 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
}
|
||||
|
||||
public static EditorModel create() {
|
||||
return new EditorModel(false, EditorElementHierarchy.create());
|
||||
return new EditorModel(EditingPurpose.IMAGE, 0, EditorElementHierarchy.create());
|
||||
}
|
||||
|
||||
public static EditorModel createForCircleEditing() {
|
||||
EditorModel editorModel = new EditorModel(true, EditorElementHierarchy.createForCircleEditing());
|
||||
EditorModel editorModel = new EditorModel(EditingPurpose.AVATAR_CIRCLE, 1, EditorElementHierarchy.createForCircleEditing());
|
||||
editorModel.setCropAspectLock(true);
|
||||
return editorModel;
|
||||
}
|
||||
|
||||
public static EditorModel createForWallpaperEditing(float fixedRatio) {
|
||||
EditorModel editorModel = new EditorModel(EditingPurpose.WALLPAPER, fixedRatio, EditorElementHierarchy.createForPinchAndPanCropping());
|
||||
editorModel.setCropAspectLock(true);
|
||||
return editorModel;
|
||||
}
|
||||
@@ -260,9 +271,11 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
}
|
||||
|
||||
public void startCrop() {
|
||||
float scaleIn = editingPurpose == EditingPurpose.WALLPAPER ? 1 : 0.8f;
|
||||
|
||||
pushUndoPoint();
|
||||
cropUndoRedoStacks.clear(editorElementHierarchy.getRoot());
|
||||
editorElementHierarchy.startCrop(invalidate);
|
||||
editorElementHierarchy.startCrop(invalidate, scaleIn);
|
||||
inBoundsMemory.push(editorElementHierarchy.getMainImage(), editorElementHierarchy.getCropEditorElement());
|
||||
updateUndoRedoAvailableState(cropUndoRedoStacks);
|
||||
}
|
||||
@@ -538,7 +551,8 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeByte((byte) (circleEditing ? 1 : 0));
|
||||
dest.writeInt(editingPurpose.ordinal());
|
||||
dest.writeFloat(fixedRatio);
|
||||
dest.writeInt(size.x);
|
||||
dest.writeInt(size.y);
|
||||
dest.writeParcelable(editorElementHierarchy.getRoot(), flags);
|
||||
@@ -628,10 +642,10 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
if (imageCropMatrix.isIdentity()) {
|
||||
imageCropMatrix.set(cropMatrix);
|
||||
|
||||
if (circleEditing) {
|
||||
if (editingPurpose == EditingPurpose.AVATAR_CIRCLE || editingPurpose == EditingPurpose.WALLPAPER) {
|
||||
Matrix userCropMatrix = editorElementHierarchy.getCropEditorElement().getLocalMatrix();
|
||||
if (size.x > size.y) {
|
||||
userCropMatrix.setScale(size.y / (float) size.x, 1f);
|
||||
userCropMatrix.setScale(fixedRatio * size.y / (float) size.x, 1f);
|
||||
} else {
|
||||
userCropMatrix.setScale(1f, size.x / (float) size.y);
|
||||
}
|
||||
@@ -643,13 +657,37 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
undoRedoStacks.clear(editorElementHierarchy.getRoot());
|
||||
}
|
||||
|
||||
if (circleEditing) {
|
||||
startCrop();
|
||||
switch (editingPurpose) {
|
||||
case AVATAR_CIRCLE: {
|
||||
startCrop();
|
||||
break;
|
||||
}
|
||||
case WALLPAPER: {
|
||||
setFixedRatio(fixedRatio);
|
||||
startCrop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setFixedRatio(float r) {
|
||||
fixedRatio = r;
|
||||
Matrix userCropMatrix = editorElementHierarchy.getCropEditorElement().getLocalMatrix();
|
||||
float w = size.x;
|
||||
float h = size.y;
|
||||
float imageRatio = w / h;
|
||||
if (imageRatio > r) {
|
||||
userCropMatrix.setScale(r / imageRatio, 1f);
|
||||
} else {
|
||||
userCropMatrix.setScale(1f, imageRatio / r);
|
||||
}
|
||||
|
||||
editorElementHierarchy.doneCrop(visibleViewPort, null);
|
||||
startCrop();
|
||||
}
|
||||
|
||||
private boolean isRendererOfMainImage(@NonNull Renderer renderer) {
|
||||
EditorElement mainImage = editorElementHierarchy.getMainImage();
|
||||
Renderer mainImageRenderer = mainImage != null ? mainImage.getRenderer() : null;
|
||||
@@ -790,7 +828,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
||||
return editorElementHierarchy.getRoot();
|
||||
}
|
||||
|
||||
public EditorElement getMainImage() {
|
||||
public @Nullable EditorElement getMainImage() {
|
||||
return editorElementHierarchy.getMainImage();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobmanager;
|
||||
import android.app.job.JobInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
public interface Constraint {
|
||||
@@ -14,6 +15,15 @@ public interface Constraint {
|
||||
@RequiresApi(26)
|
||||
void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder);
|
||||
|
||||
/**
|
||||
* If you do something in {@link #applyToJobInfo} you should return something here.
|
||||
* <p>
|
||||
* It is sorted and concatenated with other constraints key parts to form a unique job id.
|
||||
*/
|
||||
default @Nullable String getJobSchedulerKeyPart() {
|
||||
return null;
|
||||
}
|
||||
|
||||
interface Factory<T extends Constraint> {
|
||||
T create();
|
||||
}
|
||||
|
||||
@@ -6,26 +6,23 @@ import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.job.JobService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@RequiresApi(26)
|
||||
public class JobSchedulerScheduler implements Scheduler {
|
||||
public final class JobSchedulerScheduler implements Scheduler {
|
||||
|
||||
private static final String TAG = JobSchedulerScheduler.class.getSimpleName();
|
||||
|
||||
private static final String PREF_NAME = "JobSchedulerScheduler_prefs";
|
||||
private static final String PREF_NEXT_ID = "pref_next_id";
|
||||
|
||||
private static final int MAX_ID = 20;
|
||||
private static final String TAG = Log.tag(JobSchedulerScheduler.class);
|
||||
|
||||
private final Application application;
|
||||
|
||||
@@ -37,14 +34,23 @@ public class JobSchedulerScheduler implements Scheduler {
|
||||
@Override
|
||||
public void schedule(long delay, @NonNull List<Constraint> constraints) {
|
||||
JobScheduler jobScheduler = application.getSystemService(JobScheduler.class);
|
||||
int currentId = getCurrentId();
|
||||
|
||||
if (constraints.isEmpty() && jobScheduler.getPendingJob(currentId) != null) {
|
||||
Log.d(TAG, "Skipping JobScheduler enqueue because we have no constraints and there's already one pending.");
|
||||
String constraintNames = constraints.isEmpty() ? ""
|
||||
: Stream.of(constraints)
|
||||
.map(Constraint::getJobSchedulerKeyPart)
|
||||
.withoutNulls()
|
||||
.sorted()
|
||||
.collect(Collectors.joining("-"));
|
||||
|
||||
int jobId = constraintNames.hashCode();
|
||||
|
||||
if (jobScheduler.getPendingJob(jobId) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(getAndUpdateNextId(), new ComponentName(application, SystemService.class))
|
||||
Log.i(TAG, String.format(Locale.US, "JobScheduler enqueue of %s (%d)", constraintNames, jobId));
|
||||
|
||||
JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(jobId, new ComponentName(application, SystemService.class))
|
||||
.setMinimumLatency(delay)
|
||||
.setPersisted(true);
|
||||
|
||||
@@ -55,21 +61,6 @@ public class JobSchedulerScheduler implements Scheduler {
|
||||
jobScheduler.schedule(jobInfoBuilder.build());
|
||||
}
|
||||
|
||||
private int getCurrentId() {
|
||||
SharedPreferences prefs = application.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
return prefs.getInt(PREF_NEXT_ID, 0);
|
||||
}
|
||||
|
||||
private int getAndUpdateNextId() {
|
||||
SharedPreferences prefs = application.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
int returnedId = prefs.getInt(PREF_NEXT_ID, 0);
|
||||
int nextId = returnedId + 1 > MAX_ID ? 0 : returnedId + 1;
|
||||
|
||||
prefs.edit().putInt(PREF_NEXT_ID, nextId).apply();
|
||||
|
||||
return returnedId;
|
||||
}
|
||||
|
||||
@RequiresApi(api = 26)
|
||||
public static class SystemService extends JobService {
|
||||
|
||||
@@ -77,6 +68,8 @@ public class JobSchedulerScheduler implements Scheduler {
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
|
||||
Log.i(TAG, "Waking due to job: " + params.getJobId());
|
||||
|
||||
jobManager.addOnEmptyQueueListener(new JobManager.EmptyQueueListener() {
|
||||
@Override
|
||||
public void onQueueEmpty() {
|
||||
|
||||
@@ -33,6 +33,11 @@ public class ChargingConstraint implements Constraint {
|
||||
jobInfoBuilder.setRequiresCharging(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJobSchedulerKeyPart() {
|
||||
return "CHARGING";
|
||||
}
|
||||
|
||||
public static final class Factory implements Constraint.Factory<ChargingConstraint> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.Constraint;
|
||||
@@ -37,6 +38,11 @@ public class NetworkConstraint implements Constraint {
|
||||
jobInfoBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJobSchedulerKeyPart() {
|
||||
return "NETWORK";
|
||||
}
|
||||
|
||||
public static boolean isMet(@NonNull Context context) {
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.migrations.ProfileMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StickerDayByDayMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StickerLaunchMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageCapabilityMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageServiceMigrationJob;
|
||||
@@ -161,6 +162,7 @@ public final class JobManagerFactories {
|
||||
put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory());
|
||||
put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory());
|
||||
put(StickerAdditionMigrationJob.KEY, new StickerAdditionMigrationJob.Factory());
|
||||
put(StickerDayByDayMigrationJob.KEY, new StickerDayByDayMigrationJob.Factory());
|
||||
put(StorageCapabilityMigrationJob.KEY, new StorageCapabilityMigrationJob.Factory());
|
||||
put(StorageServiceMigrationJob.KEY, new StorageServiceMigrationJob.Factory());
|
||||
put(TrimByLengthSettingsMigrationJob.KEY, new TrimByLengthSettingsMigrationJob.Factory());
|
||||
|
||||
@@ -141,7 +141,7 @@ public final class MmsSendJob extends SendJob {
|
||||
final MmsSendResult result = getSendResult(sendConf, pdu);
|
||||
|
||||
database.markAsSent(messageId, false);
|
||||
markAttachmentsUploaded(messageId, message.getAttachments());
|
||||
markAttachmentsUploaded(messageId, message);
|
||||
|
||||
Log.i(TAG, "Sent message: " + messageId);
|
||||
} catch (UndeliverableMessageException | IOException e) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
@@ -58,10 +59,12 @@ public final class ProfileUploadJob extends BaseJob {
|
||||
|
||||
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
|
||||
ProfileName profileName = Recipient.self().getProfileName();
|
||||
String about = Optional.fromNullable(Recipient.self().getAbout()).or("");
|
||||
String aboutEmoji = Optional.fromNullable(Recipient.self().getAboutEmoji()).or("");
|
||||
String avatarPath;
|
||||
|
||||
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
|
||||
avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), avatar).orNull();
|
||||
avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), about, aboutEmoji, avatar).orNull();
|
||||
}
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileAvatar(Recipient.self().getId(), avatarPath);
|
||||
|
||||
@@ -232,7 +232,7 @@ public final class PushGroupSendJob extends PushSendJob {
|
||||
if (existingNetworkFailures.isEmpty() && networkFailures.isEmpty() && identityMismatches.isEmpty() && existingIdentityMismatches.isEmpty()) {
|
||||
database.markAsSent(messageId, true);
|
||||
|
||||
markAttachmentsUploaded(messageId, message.getAttachments());
|
||||
markAttachmentsUploaded(messageId, message);
|
||||
|
||||
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
|
||||
database.markExpireStarted(messageId);
|
||||
|
||||
@@ -129,7 +129,7 @@ public class PushMediaSendJob extends PushSendJob {
|
||||
boolean unidentified = deliver(message);
|
||||
|
||||
database.markAsSent(messageId, true);
|
||||
markAttachmentsUploaded(messageId, message.getAttachments());
|
||||
markAttachmentsUploaded(messageId, message);
|
||||
database.markUnidentified(messageId, unidentified);
|
||||
|
||||
if (recipient.isSelf()) {
|
||||
|
||||
@@ -75,6 +75,7 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
SignalServiceProfile profile = profileAndCredential.getProfile();
|
||||
|
||||
setProfileName(profile.getName());
|
||||
setProfileAbout(profile.getAbout(), profile.getAboutEmoji());
|
||||
setProfileAvatar(profile.getAvatar());
|
||||
setProfileCapabilities(profile.getCapabilities());
|
||||
Optional<ProfileKeyCredential> profileKeyCredential = profileAndCredential.getProfileKeyCredential();
|
||||
@@ -117,6 +118,18 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void setProfileAbout(@Nullable String encryptedAbout, @Nullable String encryptedEmoji) {
|
||||
try {
|
||||
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
|
||||
String plaintextAbout = ProfileUtil.decryptName(profileKey, encryptedAbout);
|
||||
String plaintextEmoji = ProfileUtil.decryptName(profileKey, encryptedEmoji);
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setAbout(Recipient.self().getId(), plaintextAbout, plaintextEmoji);
|
||||
} catch (InvalidCiphertextException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setProfileAvatar(@Nullable String avatar) {
|
||||
ApplicationDependencies.getJobManager().add(new RetrieveProfileAvatarJob(Recipient.self(), avatar));
|
||||
}
|
||||
|
||||
@@ -328,6 +328,7 @@ public class RetrieveProfileJob extends BaseJob {
|
||||
ProfileKey recipientProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
|
||||
|
||||
setProfileName(recipient, profile.getName());
|
||||
setProfileAbout(recipient, profile.getAbout(), profile.getAboutEmoji());
|
||||
setProfileAvatar(recipient, profile.getAvatar());
|
||||
clearUsername(recipient);
|
||||
setProfileCapabilities(recipient, profile.getCapabilities());
|
||||
@@ -454,6 +455,20 @@ public class RetrieveProfileJob extends BaseJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void setProfileAbout(@NonNull Recipient recipient, @Nullable String encryptedAbout, @Nullable String encryptedEmoji) {
|
||||
try {
|
||||
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
|
||||
if (profileKey == null) return;
|
||||
|
||||
String plaintextAbout = ProfileUtil.decryptName(profileKey, encryptedAbout);
|
||||
String plaintextEmoji = ProfileUtil.decryptName(profileKey, encryptedEmoji);
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setAbout(recipient.getId(), plaintextAbout, plaintextEmoji);
|
||||
} catch (InvalidCiphertextException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setProfileAvatar(Recipient recipient, String profileAvatar) {
|
||||
if (recipient.getProfileKey() == null) return;
|
||||
|
||||
|
||||
@@ -2,15 +2,23 @@ package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.TextSecureExpiredException;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SendJob extends BaseJob {
|
||||
@@ -37,7 +45,17 @@ public abstract class SendJob extends BaseJob {
|
||||
|
||||
protected abstract void onSend() throws Exception;
|
||||
|
||||
protected void markAttachmentsUploaded(long messageId, @NonNull List<Attachment> attachments) {
|
||||
protected void markAttachmentsUploaded(long messageId, @NonNull OutgoingMediaMessage message) {
|
||||
List<Attachment> attachments = new LinkedList<>();
|
||||
|
||||
attachments.addAll(message.getAttachments());
|
||||
attachments.addAll(Stream.of(message.getLinkPreviews()).map(lp -> lp.getThumbnail().orNull()).withoutNulls().toList());
|
||||
attachments.addAll(Stream.of(message.getSharedContacts()).map(Contact::getAvatarAttachment).withoutNulls().toList());
|
||||
|
||||
if (message.getOutgoingQuote() != null) {
|
||||
attachments.addAll(message.getOutgoingQuote().getAttachments());
|
||||
}
|
||||
|
||||
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
|
||||
|
||||
for (Attachment attachment : attachments) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.keyvalue;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceDataStore;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||
|
||||
@@ -28,6 +29,7 @@ public final class SignalStore {
|
||||
private final CertificateValues certificateValues;
|
||||
private final PhoneNumberPrivacyValues phoneNumberPrivacyValues;
|
||||
private final OnboardingValues onboardingValues;
|
||||
private final WallpaperValues wallpaperValues;
|
||||
|
||||
private SignalStore() {
|
||||
this.store = new KeyValueStore(ApplicationDependencies.getApplication());
|
||||
@@ -45,6 +47,7 @@ public final class SignalStore {
|
||||
this.certificateValues = new CertificateValues(store);
|
||||
this.phoneNumberPrivacyValues = new PhoneNumberPrivacyValues(store);
|
||||
this.onboardingValues = new OnboardingValues(store);
|
||||
this.wallpaperValues = new WallpaperValues(store);
|
||||
}
|
||||
|
||||
public static void onFirstEverAppLaunch() {
|
||||
@@ -61,6 +64,7 @@ public final class SignalStore {
|
||||
certificateValues().onFirstEverAppLaunch();
|
||||
phoneNumberPrivacy().onFirstEverAppLaunch();
|
||||
onboarding().onFirstEverAppLaunch();
|
||||
wallpaper().onFirstEverAppLaunch();
|
||||
}
|
||||
|
||||
public static @NonNull KbsValues kbsValues() {
|
||||
@@ -119,6 +123,10 @@ public final class SignalStore {
|
||||
return INSTANCE.onboardingValues;
|
||||
}
|
||||
|
||||
public static @NonNull WallpaperValues wallpaper() {
|
||||
return INSTANCE.wallpaperValues;
|
||||
}
|
||||
|
||||
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() {
|
||||
return new GroupsV2AuthorizationSignalStoreCache(getStore());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory;
|
||||
import org.thoughtcrime.securesms.wallpaper.WallpaperStorage;
|
||||
|
||||
public final class WallpaperValues extends SignalStoreValues {
|
||||
|
||||
private static final String TAG = Log.tag(WallpaperValues.class);
|
||||
|
||||
private static final String KEY_WALLPAPER = "wallpaper.wallpaper";
|
||||
|
||||
WallpaperValues(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onFirstEverAppLaunch() {
|
||||
}
|
||||
|
||||
public void setWallpaper(@NonNull Context context, @Nullable ChatWallpaper wallpaper) {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
Uri currentUri = null;
|
||||
|
||||
if (currentWallpaper != null && currentWallpaper.hasFile()) {
|
||||
currentUri = Uri.parse(currentWallpaper.getFile().getUri());
|
||||
}
|
||||
|
||||
if (wallpaper != null) {
|
||||
putBlob(KEY_WALLPAPER, wallpaper.serialize().toByteArray());
|
||||
} else {
|
||||
getStore().beginWrite().remove(KEY_WALLPAPER).apply();
|
||||
}
|
||||
|
||||
if (currentUri != null) {
|
||||
WallpaperStorage.onWallpaperDeselected(context, currentUri);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable ChatWallpaper getWallpaper() {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
|
||||
if (currentWallpaper != null) {
|
||||
return ChatWallpaperFactory.create(currentWallpaper);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasWallpaperSet() {
|
||||
return getStore().getBlob(KEY_WALLPAPER, null) != null;
|
||||
}
|
||||
|
||||
public void setDimInDarkTheme(boolean enabled) {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
|
||||
if (currentWallpaper != null) {
|
||||
putBlob(KEY_WALLPAPER,
|
||||
currentWallpaper.toBuilder()
|
||||
.setDimLevelInDarkTheme(enabled ? 0.2f : 0)
|
||||
.build()
|
||||
.toByteArray());
|
||||
} else {
|
||||
throw new IllegalStateException("No wallpaper currently set!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the URI of the current wallpaper. Note that this will only return a value if the
|
||||
* wallpaper is both set *and* it's an image.
|
||||
*/
|
||||
public @Nullable Uri getWallpaperUri() {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
|
||||
if (currentWallpaper != null && currentWallpaper.hasFile()) {
|
||||
return Uri.parse(currentWallpaper.getFile().getUri());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Wallpaper getCurrentWallpaper() {
|
||||
byte[] serialized = getBlob(KEY_WALLPAPER, null);
|
||||
|
||||
if (serialized != null) {
|
||||
try {
|
||||
return Wallpaper.parseFrom(serialized);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
Log.w(TAG, "Invalid proto stored for wallpaper!");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
camera.bindToLifecycle(getViewLifecycleOwner());
|
||||
viewModel.onCameraStarted();
|
||||
requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
|
||||
@@ -29,13 +29,19 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
public class MediaPickerFolderFragment extends Fragment implements MediaPickerFolderAdapter.EventListener {
|
||||
|
||||
private static final String KEY_TOOLBAR_TITLE = "toolbar_title";
|
||||
private static final String KEY_HIDE_CAMERA = "hide_camera";
|
||||
|
||||
private String toolbarTitle;
|
||||
private boolean showCamera;
|
||||
private MediaSendViewModel viewModel;
|
||||
private Controller controller;
|
||||
private GridLayoutManager layoutManager;
|
||||
|
||||
public static @NonNull MediaPickerFolderFragment newInstance(@NonNull Context context, @Nullable Recipient recipient) {
|
||||
return newInstance(context, recipient, false);
|
||||
}
|
||||
|
||||
public static @NonNull MediaPickerFolderFragment newInstance(@NonNull Context context, @Nullable Recipient recipient, boolean hideCamera) {
|
||||
String toolbarTitle;
|
||||
|
||||
if (recipient != null) {
|
||||
@@ -45,8 +51,13 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo
|
||||
toolbarTitle = "";
|
||||
}
|
||||
|
||||
return newInstance(toolbarTitle, hideCamera);
|
||||
}
|
||||
|
||||
public static @NonNull MediaPickerFolderFragment newInstance(@NonNull String toolbarTitle, boolean hideCamera) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(KEY_TOOLBAR_TITLE, toolbarTitle);
|
||||
args.putBoolean(KEY_HIDE_CAMERA, hideCamera);
|
||||
|
||||
MediaPickerFolderFragment fragment = new MediaPickerFolderFragment();
|
||||
fragment.setArguments(args);
|
||||
@@ -60,6 +71,7 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
toolbarTitle = getArguments().getString(KEY_TOOLBAR_TITLE);
|
||||
showCamera = !getArguments().getBoolean(KEY_HIDE_CAMERA);
|
||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(requireActivity().getApplication(), new MediaRepository())).get(MediaSendViewModel.class);
|
||||
}
|
||||
|
||||
@@ -105,16 +117,14 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
requireActivity().getMenuInflater().inflate(R.menu.mediapicker_default, menu);
|
||||
if (showCamera) {
|
||||
requireActivity().getMenuInflater().inflate(R.menu.mediapicker_default, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.mediapicker_menu_camera:
|
||||
controller.onCameraSelected();
|
||||
return true;
|
||||
}
|
||||
if (item.getItemId() == R.id.mediapicker_menu_camera) { controller.onCameraSelected(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -36,10 +34,12 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
||||
private static final String KEY_FOLDER_TITLE = "folder_title";
|
||||
private static final String KEY_MAX_SELECTION = "max_selection";
|
||||
private static final String KEY_FORCE_MULTI_SELECT = "force_multi_select";
|
||||
private static final String KEY_HIDE_CAMERA = "hide_camera";
|
||||
|
||||
private String bucketId;
|
||||
private String folderTitle;
|
||||
private int maxSelection;
|
||||
private boolean showCamera;
|
||||
private MediaSendViewModel viewModel;
|
||||
private MediaPickerItemAdapter adapter;
|
||||
private Controller controller;
|
||||
@@ -50,11 +50,16 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
||||
}
|
||||
|
||||
public static MediaPickerItemFragment newInstance(@NonNull String bucketId, @NonNull String folderTitle, int maxSelection, boolean forceMultiSelect) {
|
||||
return newInstance(bucketId, folderTitle, maxSelection, forceMultiSelect, false);
|
||||
}
|
||||
|
||||
public static MediaPickerItemFragment newInstance(@NonNull String bucketId, @NonNull String folderTitle, int maxSelection, boolean forceMultiSelect, boolean hideCamera) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(KEY_BUCKET_ID, bucketId);
|
||||
args.putString(KEY_FOLDER_TITLE, folderTitle);
|
||||
args.putInt(KEY_MAX_SELECTION, maxSelection);
|
||||
args.putBoolean(KEY_FORCE_MULTI_SELECT, forceMultiSelect);
|
||||
args.putBoolean(KEY_HIDE_CAMERA, hideCamera);
|
||||
|
||||
MediaPickerItemFragment fragment = new MediaPickerItemFragment();
|
||||
fragment.setArguments(args);
|
||||
@@ -70,6 +75,7 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
||||
bucketId = getArguments().getString(KEY_BUCKET_ID);
|
||||
folderTitle = getArguments().getString(KEY_FOLDER_TITLE);
|
||||
maxSelection = getArguments().getInt(KEY_MAX_SELECTION);
|
||||
showCamera = !getArguments().getBoolean(KEY_HIDE_CAMERA);
|
||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(requireActivity().getApplication(), new MediaRepository())).get(MediaSendViewModel.class);
|
||||
}
|
||||
|
||||
@@ -120,16 +126,14 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
requireActivity().getMenuInflater().inflate(R.menu.mediapicker_default, menu);
|
||||
if (showCamera) {
|
||||
requireActivity().getMenuInflater().inflate(R.menu.mediapicker_default, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.mediapicker_menu_camera:
|
||||
controller.onCameraSelected();
|
||||
return true;
|
||||
}
|
||||
if (item.getItemId() == R.id.mediapicker_menu_camera) { controller.onCameraSelected(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,11 +111,12 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
|
||||
|
||||
public static final String EXTRA_RESULT = "result";
|
||||
|
||||
private static final String KEY_RECIPIENT = "recipient_id";
|
||||
private static final String KEY_BODY = "body";
|
||||
private static final String KEY_MEDIA = "media";
|
||||
private static final String KEY_TRANSPORT = "transport";
|
||||
private static final String KEY_IS_CAMERA = "is_camera";
|
||||
private static final String KEY_RECIPIENT = "recipient_id";
|
||||
private static final String KEY_RECIPIENTS = "recipient_ids";
|
||||
private static final String KEY_BODY = "body";
|
||||
private static final String KEY_MEDIA = "media";
|
||||
private static final String KEY_TRANSPORT = "transport";
|
||||
private static final String KEY_IS_CAMERA = "is_camera";
|
||||
|
||||
private static final String TAG_FOLDER_PICKER = "folder_picker";
|
||||
private static final String TAG_ITEM_PICKER = "item_picker";
|
||||
@@ -195,6 +196,20 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildShareIntent(@NonNull Context context,
|
||||
@NonNull List<Media> media,
|
||||
@NonNull List<RecipientId> recipientIds,
|
||||
@NonNull CharSequence body,
|
||||
@NonNull TransportOption transportOption)
|
||||
{
|
||||
Intent intent = new Intent(context, MediaSendActivity.class);
|
||||
intent.putParcelableArrayListExtra(KEY_MEDIA, new ArrayList<>(media));
|
||||
intent.putExtra(KEY_TRANSPORT, transportOption);
|
||||
intent.putExtra(KEY_BODY, body == null ? "" : body);
|
||||
intent.putParcelableArrayListExtra(KEY_RECIPIENTS, new ArrayList<>(recipientIds));
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(@NonNull Context newBase) {
|
||||
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
@@ -332,7 +347,18 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
|
||||
initViewModel();
|
||||
|
||||
revealButton.setOnClickListener(v -> viewModel.onRevealButtonToggled());
|
||||
continueButton.setOnClickListener(v -> navigateToContactSelect());
|
||||
|
||||
List<RecipientId> recipientIds = getIntent().getParcelableArrayListExtra(KEY_RECIPIENTS);
|
||||
continueButton.setOnClickListener(v -> {
|
||||
continueButton.setEnabled(false);
|
||||
if (recipientIds == null || recipientIds.isEmpty()) {
|
||||
navigateToContactSelect();
|
||||
} else {
|
||||
SimpleTask.run(getLifecycle(),
|
||||
() -> Stream.of(recipientIds).map(Recipient::resolved).toList(),
|
||||
this::onCameraContactsSendClicked);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -551,7 +577,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
|
||||
SimpleProgressDialog.DismissibleDialog dialog = SimpleProgressDialog.showDelayed(this, 300, 0);
|
||||
viewModel.onSendClicked(buildModelsToTransform(fragment), recipients, composeText.getMentions()).observe(this, result -> {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
setActivityResultAndFinish(result);
|
||||
});
|
||||
} else {
|
||||
throw new AssertionError("No editor fragment available!");
|
||||
|
||||
@@ -88,7 +88,7 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
conversationItem = (ConversationItem) receivedStub.inflate();
|
||||
}
|
||||
}
|
||||
conversationItem.bind(lifecycleOwner, conversationMessage, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), conversationMessage.getMessageRecord().getRecipient(), null, false);
|
||||
conversationItem.bind(lifecycleOwner, conversationMessage, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), conversationMessage.getMessageRecord().getRecipient(), null, false, false);
|
||||
}
|
||||
|
||||
private void bindErrorState(MessageRecord messageRecord) {
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ApplicationMigrations {
|
||||
|
||||
private static final int LEGACY_CANONICAL_VERSION = 455;
|
||||
|
||||
public static final int CURRENT_VERSION = 25;
|
||||
public static final int CURRENT_VERSION = 26;
|
||||
|
||||
private static final class Version {
|
||||
static final int LEGACY = 1;
|
||||
@@ -68,6 +68,7 @@ public class ApplicationMigrations {
|
||||
static final int BACKUP_NOTIFICATION = 23;
|
||||
static final int GV1_MIGRATION = 24;
|
||||
static final int USER_NOTIFICATION = 25;
|
||||
static final int DAY_BY_DAY_STICKERS = 26;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,6 +287,10 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.USER_NOTIFICATION, new UserNotificationMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.DAY_BY_DAY_STICKERS) {
|
||||
jobs.put(Version.DAY_BY_DAY_STICKERS, new StickerDayByDayMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
||||
|
||||
/**
|
||||
* Installs Day by Day blessed pack.
|
||||
*/
|
||||
public class StickerDayByDayMigrationJob extends MigrationJob {
|
||||
|
||||
public static final String KEY = "StickerDayByDayMigrationJob";
|
||||
|
||||
StickerDayByDayMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private StickerDayByDayMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUiBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performMigration() {
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<StickerDayByDayMigrationJob> {
|
||||
@Override
|
||||
public @NonNull StickerDayByDayMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new StickerDayByDayMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
@@ -44,7 +43,6 @@ import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
||||
@@ -223,7 +221,7 @@ public class AttachmentManager {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public ListenableFuture<Boolean> setMedia(@NonNull final GlideRequests glideRequests,
|
||||
@NonNull final Uri uri,
|
||||
@NonNull final MediaType mediaType,
|
||||
@NonNull final SlideFactory.MediaType mediaType,
|
||||
@NonNull final MediaConstraints constraints,
|
||||
final int width,
|
||||
final int height)
|
||||
@@ -286,7 +284,7 @@ public class AttachmentManager {
|
||||
} else {
|
||||
Attachment attachment = slide.asAttachment();
|
||||
result.deferTo(thumbnail.setImageResource(glideRequests, slide, false, true, attachment.getWidth(), attachment.getHeight()));
|
||||
removableMediaView.display(thumbnail, mediaType == MediaType.IMAGE);
|
||||
removableMediaView.display(thumbnail, mediaType == SlideFactory.MediaType.IMAGE);
|
||||
}
|
||||
|
||||
attachmentListener.onAttachmentChanged();
|
||||
@@ -480,58 +478,4 @@ public class AttachmentManager {
|
||||
void onAttachmentChanged();
|
||||
}
|
||||
|
||||
public enum MediaType {
|
||||
IMAGE(MediaUtil.IMAGE_JPEG),
|
||||
GIF(MediaUtil.IMAGE_GIF),
|
||||
AUDIO(MediaUtil.AUDIO_AAC),
|
||||
VIDEO(MediaUtil.VIDEO_MP4),
|
||||
DOCUMENT(MediaUtil.UNKNOWN),
|
||||
VCARD(MediaUtil.VCARD);
|
||||
|
||||
private final String fallbackMimeType;
|
||||
|
||||
MediaType(String fallbackMimeType) {
|
||||
this.fallbackMimeType = fallbackMimeType;
|
||||
}
|
||||
|
||||
|
||||
public @NonNull Slide createSlide(@NonNull Context context,
|
||||
@NonNull Uri uri,
|
||||
@Nullable String fileName,
|
||||
@Nullable String mimeType,
|
||||
@Nullable BlurHash blurHash,
|
||||
long dataSize,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
if (mimeType == null) {
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
|
||||
switch (this) {
|
||||
case IMAGE: return new ImageSlide(context, uri, dataSize, width, height, blurHash);
|
||||
case GIF: return new GifSlide(context, uri, dataSize, width, height);
|
||||
case AUDIO: return new AudioSlide(context, uri, dataSize, false);
|
||||
case VIDEO: return new VideoSlide(context, uri, dataSize);
|
||||
case VCARD:
|
||||
case DOCUMENT: return new DocumentSlide(context, uri, mimeType, dataSize, fileName);
|
||||
default: throw new AssertionError("unrecognized enum");
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable MediaType from(final @Nullable String mimeType) {
|
||||
if (TextUtils.isEmpty(mimeType)) return null;
|
||||
if (MediaUtil.isGif(mimeType)) return GIF;
|
||||
if (MediaUtil.isImageType(mimeType)) return IMAGE;
|
||||
if (MediaUtil.isAudioType(mimeType)) return AUDIO;
|
||||
if (MediaUtil.isVideoType(mimeType)) return VIDEO;
|
||||
if (MediaUtil.isVcard(mimeType)) return VCARD;
|
||||
|
||||
return DOCUMENT;
|
||||
}
|
||||
|
||||
public String toFallbackMimeType() {
|
||||
return fallbackMimeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,22 +15,26 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||
import org.thoughtcrime.securesms.wallpaper.WallpaperStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class PartAuthority {
|
||||
|
||||
private static final String AUTHORITY = BuildConfig.APPLICATION_ID;
|
||||
private static final String PART_URI_STRING = "content://" + AUTHORITY + "/part";
|
||||
private static final String STICKER_URI_STRING = "content://" + AUTHORITY + "/sticker";
|
||||
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
||||
private static final Uri STICKER_CONTENT_URI = Uri.parse(STICKER_URI_STRING);
|
||||
private static final String AUTHORITY = BuildConfig.APPLICATION_ID;
|
||||
private static final String PART_URI_STRING = "content://" + AUTHORITY + "/part";
|
||||
private static final String STICKER_URI_STRING = "content://" + AUTHORITY + "/sticker";
|
||||
private static final String WALLPAPER_URI_STRING = "content://" + AUTHORITY + "/wallpaper";
|
||||
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
||||
private static final Uri STICKER_CONTENT_URI = Uri.parse(STICKER_URI_STRING);
|
||||
private static final Uri WALLPAPER_CONTENT_URI = Uri.parse(WALLPAPER_URI_STRING);
|
||||
|
||||
private static final int PART_ROW = 1;
|
||||
private static final int PERSISTENT_ROW = 2;
|
||||
private static final int BLOB_ROW = 3;
|
||||
private static final int STICKER_ROW = 4;
|
||||
private static final int WALLPAPER_ROW = 5;
|
||||
|
||||
private static final UriMatcher uriMatcher;
|
||||
|
||||
@@ -38,6 +42,7 @@ public class PartAuthority {
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(AUTHORITY, "part/*/#", PART_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "sticker/#", STICKER_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "wallpaper/*", WALLPAPER_ROW);
|
||||
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW);
|
||||
@@ -59,6 +64,7 @@ public class PartAuthority {
|
||||
case STICKER_ROW: return DatabaseFactory.getStickerDatabase(context).getStickerStream(ContentUris.parseId(uri));
|
||||
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
||||
case WALLPAPER_ROW: return WallpaperStorage.read(context, getWallpaperFilename(uri));
|
||||
default: return context.getContentResolver().openInputStream(uri);
|
||||
}
|
||||
} catch (SecurityException se) {
|
||||
@@ -138,6 +144,14 @@ public class PartAuthority {
|
||||
return ContentUris.withAppendedId(STICKER_CONTENT_URI, id);
|
||||
}
|
||||
|
||||
public static Uri getWallpaperUri(String filename) {
|
||||
return Uri.withAppendedPath(WALLPAPER_CONTENT_URI, filename);
|
||||
}
|
||||
|
||||
public static String getWallpaperFilename(Uri uri) {
|
||||
return uri.getPathSegments().get(1);
|
||||
}
|
||||
|
||||
public static boolean isLocalUri(final @NonNull Uri uri) {
|
||||
int match = uriMatcher.match(uri);
|
||||
switch (match) {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SlideFactory encapsulates logic related to constructing slides from a set of paramaeters as defined
|
||||
* by {@link SlideFactory#getSlide}.
|
||||
*/
|
||||
public final class SlideFactory {
|
||||
|
||||
private static final String TAG = Log.tag(SlideFactory.class);
|
||||
|
||||
private SlideFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a slide from the given parameters.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param contentType The contentType of the given Uri
|
||||
* @param uri The Uri pointing to the resource to create a slide out of
|
||||
* @param width (Optional) width, can be 0.
|
||||
* @param height (Optional) height, can be 0.
|
||||
*
|
||||
* @return A Slide with all the information we can gather about it.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static @Nullable Slide getSlide(@NonNull Context context, @Nullable String contentType, @NonNull Uri uri, int width, int height) {
|
||||
MediaType mediaType = MediaType.from(contentType);
|
||||
|
||||
try {
|
||||
if (PartAuthority.isLocalUri(uri)) {
|
||||
return getManuallyCalculatedSlideInfo(context, mediaType, uri, width, height);
|
||||
} else {
|
||||
Slide result = getContentResolverSlideInfo(context, mediaType, uri, width, height);
|
||||
|
||||
if (result == null) return getManuallyCalculatedSlideInfo(context, mediaType, uri, width, height);
|
||||
else return result;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable Slide getContentResolverSlideInfo(@NonNull Context context, @NonNull MediaType mediaType, @NonNull Uri uri, int width, int height) {
|
||||
Cursor cursor = null;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, null, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
|
||||
long fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
|
||||
String mimeType = context.getContentResolver().getType(uri);
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
Pair<Integer, Integer> dimens = MediaUtil.getDimensions(context, mimeType, uri);
|
||||
width = dimens.first;
|
||||
height = dimens.second;
|
||||
}
|
||||
|
||||
Log.d(TAG, "remote slide with size " + fileSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, null, fileSize, width, height);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @NonNull Slide getManuallyCalculatedSlideInfo(@NonNull Context context, @NonNull MediaType mediaType, @NonNull Uri uri, int width, int height) throws IOException {
|
||||
long start = System.currentTimeMillis();
|
||||
Long mediaSize = null;
|
||||
String fileName = null;
|
||||
String mimeType = null;
|
||||
|
||||
if (PartAuthority.isLocalUri(uri)) {
|
||||
mediaSize = PartAuthority.getAttachmentSize(context, uri);
|
||||
fileName = PartAuthority.getAttachmentFileName(context, uri);
|
||||
mimeType = PartAuthority.getAttachmentContentType(context, uri);
|
||||
}
|
||||
|
||||
if (mediaSize == null) {
|
||||
mediaSize = MediaUtil.getMediaSize(context, uri);
|
||||
}
|
||||
|
||||
if (mimeType == null) {
|
||||
mimeType = MediaUtil.getMimeType(context, uri);
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
Pair<Integer, Integer> dimens = MediaUtil.getDimensions(context, mimeType, uri);
|
||||
width = dimens.first;
|
||||
height = dimens.second;
|
||||
}
|
||||
|
||||
Log.d(TAG, "local slide with size " + mediaSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, null, mediaSize, width, height);
|
||||
}
|
||||
|
||||
public enum MediaType {
|
||||
|
||||
IMAGE(MediaUtil.IMAGE_JPEG),
|
||||
GIF(MediaUtil.IMAGE_GIF),
|
||||
AUDIO(MediaUtil.AUDIO_AAC),
|
||||
VIDEO(MediaUtil.VIDEO_MP4),
|
||||
DOCUMENT(MediaUtil.UNKNOWN),
|
||||
VCARD(MediaUtil.VCARD);
|
||||
|
||||
private final String fallbackMimeType;
|
||||
|
||||
MediaType(String fallbackMimeType) {
|
||||
this.fallbackMimeType = fallbackMimeType;
|
||||
}
|
||||
|
||||
|
||||
public @NonNull Slide createSlide(@NonNull Context context,
|
||||
@NonNull Uri uri,
|
||||
@Nullable String fileName,
|
||||
@Nullable String mimeType,
|
||||
@Nullable BlurHash blurHash,
|
||||
long dataSize,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
if (mimeType == null) {
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
|
||||
switch (this) {
|
||||
case IMAGE: return new ImageSlide(context, uri, dataSize, width, height, blurHash);
|
||||
case GIF: return new GifSlide(context, uri, dataSize, width, height);
|
||||
case AUDIO: return new AudioSlide(context, uri, dataSize, false);
|
||||
case VIDEO: return new VideoSlide(context, uri, dataSize);
|
||||
case VCARD:
|
||||
case DOCUMENT: return new DocumentSlide(context, uri, mimeType, dataSize, fileName);
|
||||
default: throw new AssertionError("unrecognized enum");
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable MediaType from(final @Nullable String mimeType) {
|
||||
if (TextUtils.isEmpty(mimeType)) return null;
|
||||
if (MediaUtil.isGif(mimeType)) return GIF;
|
||||
if (MediaUtil.isImageType(mimeType)) return IMAGE;
|
||||
if (MediaUtil.isAudioType(mimeType)) return AUDIO;
|
||||
if (MediaUtil.isVideoType(mimeType)) return VIDEO;
|
||||
if (MediaUtil.isVcard(mimeType)) return VCARD;
|
||||
|
||||
return DOCUMENT;
|
||||
}
|
||||
|
||||
public String toFallbackMimeType() {
|
||||
return fallbackMimeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ public class PinRestoreEntryFragment extends LoggingFragment {
|
||||
}))
|
||||
.setNeutralButton(R.string.PinRestoreEntryFragment_contact_support, (dialog, which) -> {
|
||||
String body = SupportEmailUtil.generateSupportEmailBody(requireContext(),
|
||||
getString(R.string.PinRestoreEntryFragment_signal_registration_need_help_with_pin),
|
||||
R.string.PinRestoreEntryFragment_signal_registration_need_help_with_pin,
|
||||
null,
|
||||
null);
|
||||
CommunicationActions.openEmail(requireContext(),
|
||||
|
||||
@@ -77,6 +77,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
|
||||
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
|
||||
private static final String PREFERENCE_UNIDENTIFIED_LEARN_MORE = "pref_unidentified_learn_more";
|
||||
private static final String PREFERENCE_INCOGNITO_LEARN_MORE = "pref_incognito_learn_more";
|
||||
private static final String PREFERENCE_WHO_CAN_SEE_PHONE_NUMBER = "pref_who_can_see_phone_number";
|
||||
private static final String PREFERENCE_WHO_CAN_FIND_BY_PHONE_NUMBER = "pref_who_can_find_by_phone_number";
|
||||
|
||||
@@ -107,6 +108,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
this.findPreference(TextSecurePreferences.SHOW_UNIDENTIFIED_DELIVERY_INDICATORS).setOnPreferenceChangeListener(new ShowUnidentifiedDeliveryIndicatorsChangedListener());
|
||||
this.findPreference(TextSecurePreferences.UNIVERSAL_UNIDENTIFIED_ACCESS).setOnPreferenceChangeListener(new UniversalUnidentifiedAccessChangedListener());
|
||||
this.findPreference(PREFERENCE_UNIDENTIFIED_LEARN_MORE).setOnPreferenceClickListener(new UnidentifiedLearnMoreClickListener());
|
||||
this.findPreference(PREFERENCE_INCOGNITO_LEARN_MORE).setOnPreferenceClickListener(new IncognitoLearnMoreClickListener());
|
||||
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
|
||||
|
||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||
@@ -463,6 +465,14 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
}
|
||||
}
|
||||
|
||||
private class IncognitoLearnMoreClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
CommunicationActions.openBrowserLink(preference.getContext(), "https://support.signal.org/hc/en-us/articles/360055276112");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class RegistrationLockV2ChangedListener implements Preference.OnPreferenceChangeListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
|
||||
@@ -8,18 +8,27 @@ import androidx.preference.ListPreference;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ActivityTransitionUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class AppearancePreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
|
||||
private static final String WALLPAPER_PREF = "pref_wallpaper";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
this.findPreference(TextSecurePreferences.THEME_PREF).setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
this.findPreference(TextSecurePreferences.LANGUAGE_PREF).setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
this.findPreference(WALLPAPER_PREF).setOnPreferenceClickListener(preference -> {
|
||||
startActivity(ChatWallpaperActivity.createIntent(requireContext()));
|
||||
ActivityTransitionUtil.setSlideInTransition(requireActivity());
|
||||
return true;
|
||||
});
|
||||
initializeListSummary((ListPreference)findPreference(TextSecurePreferences.THEME_PREF));
|
||||
initializeListSummary((ListPreference)findPreference(TextSecurePreferences.LANGUAGE_PREF));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.Objects;
|
||||
public final class ProfileName implements Parcelable {
|
||||
|
||||
public static final ProfileName EMPTY = new ProfileName("", "");
|
||||
public static final int MAX_PART_LENGTH = (ProfileCipher.NAME_PADDED_LENGTH - 1) / 2;
|
||||
public static final int MAX_PART_LENGTH = (ProfileCipher.MAX_POSSIBLE_NAME_LENGTH - 1) / 2;
|
||||
|
||||
private final String givenName;
|
||||
private final String familyName;
|
||||
|
||||
@@ -17,16 +17,17 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.util.DynamicRegistrationTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
/**
|
||||
* Shows editing screen for your profile during registration. Also handles group name editing.
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class EditProfileActivity extends BaseActivity implements EditProfileFragment.Controller {
|
||||
|
||||
public static final String NEXT_INTENT = "next_intent";
|
||||
public static final String EXCLUDE_SYSTEM = "exclude_system";
|
||||
public static final String DISPLAY_USERNAME = "display_username";
|
||||
public static final String NEXT_BUTTON_TEXT = "next_button_text";
|
||||
public static final String SHOW_TOOLBAR = "show_back_arrow";
|
||||
public static final String GROUP_ID = "group_id";
|
||||
public static final String START_AT_USERNAME = "start_at_username";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme();
|
||||
|
||||
@@ -39,7 +40,6 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
|
||||
public static @NonNull Intent getIntentForUserProfileEdit(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, EditProfileActivity.class);
|
||||
intent.putExtra(EditProfileActivity.EXCLUDE_SYSTEM, true);
|
||||
intent.putExtra(EditProfileActivity.DISPLAY_USERNAME, true);
|
||||
intent.putExtra(EditProfileActivity.NEXT_BUTTON_TEXT, R.string.save);
|
||||
return intent;
|
||||
}
|
||||
@@ -52,14 +52,6 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static @NonNull Intent getIntentForUsernameEdit(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, EditProfileActivity.class);
|
||||
intent.putExtra(EditProfileActivity.SHOW_TOOLBAR, true);
|
||||
intent.putExtra(EditProfileActivity.DISPLAY_USERNAME, true);
|
||||
intent.putExtra(EditProfileActivity.START_AT_USERNAME, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
@@ -73,13 +65,6 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
|
||||
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
|
||||
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, extras != null ? extras : new Bundle());
|
||||
|
||||
if (extras != null &&
|
||||
extras.getBoolean(DISPLAY_USERNAME, false) &&
|
||||
extras.getBoolean(START_AT_USERNAME, false)) {
|
||||
NavDirections action = EditProfileFragmentDirections.actionEditUsername();
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).navigate(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,8 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.dd.CircularProgressButton;
|
||||
@@ -39,6 +36,7 @@ import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFrag
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.profiles.manage.EditProfileNameFragment;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
@@ -53,7 +51,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.DISPLAY_USERNAME;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.EXCLUDE_SYSTEM;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.GROUP_ID;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.NEXT_BUTTON_TEXT;
|
||||
@@ -73,9 +70,6 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
private EditText familyName;
|
||||
private View reveal;
|
||||
private TextView preview;
|
||||
private View usernameLabel;
|
||||
private View usernameEditButton;
|
||||
private TextView username;
|
||||
|
||||
private Intent nextIntent;
|
||||
|
||||
@@ -94,26 +88,8 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
}
|
||||
}
|
||||
|
||||
public static EditProfileFragment create(boolean excludeSystem,
|
||||
Intent nextIntent,
|
||||
boolean displayUsernameField,
|
||||
@StringRes int nextButtonText) {
|
||||
|
||||
EditProfileFragment fragment = new EditProfileFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putBoolean(EXCLUDE_SYSTEM, excludeSystem);
|
||||
args.putParcelable(NEXT_INTENT, nextIntent);
|
||||
args.putBoolean(DISPLAY_USERNAME, displayUsernameField);
|
||||
args.putInt(NEXT_BUTTON_TEXT, nextButtonText);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.profile_create_fragment, container, false);
|
||||
}
|
||||
|
||||
@@ -125,13 +101,6 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
initializeViewModel(requireArguments().getBoolean(EXCLUDE_SYSTEM, false), groupId, savedInstanceState != null);
|
||||
initializeProfileAvatar();
|
||||
initializeProfileName();
|
||||
initializeUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
viewModel.refreshUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,17 +169,8 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
this.finishButton = view.findViewById(R.id.finish_button);
|
||||
this.reveal = view.findViewById(R.id.reveal);
|
||||
this.preview = view.findViewById(R.id.name_preview);
|
||||
this.username = view.findViewById(R.id.profile_overview_username);
|
||||
this.usernameEditButton = view.findViewById(R.id.profile_overview_username_edit_button);
|
||||
this.usernameLabel = view.findViewById(R.id.profile_overview_username_label);
|
||||
this.nextIntent = arguments.getParcelable(NEXT_INTENT);
|
||||
|
||||
if (FeatureFlags.usernames() && arguments.getBoolean(DISPLAY_USERNAME, false)) {
|
||||
username.setVisibility(View.VISIBLE);
|
||||
usernameEditButton.setVisibility(View.VISIBLE);
|
||||
usernameLabel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
this.avatar.setOnClickListener(v -> startAvatarSelection());
|
||||
|
||||
view.findViewById(R.id.mms_group_hint)
|
||||
@@ -228,12 +188,14 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
view.findViewById(R.id.description_text).setVisibility(View.GONE);
|
||||
view.<ImageView>findViewById(R.id.avatar_placeholder).setImageResource(R.drawable.ic_group_outline_40);
|
||||
} else {
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(givenName, EditProfileNameFragment.NAME_MAX_GLYPHS);
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(familyName, EditProfileNameFragment.NAME_MAX_GLYPHS);
|
||||
this.givenName.addTextChangedListener(new AfterTextChanged(s -> {
|
||||
trimInPlace(s);
|
||||
EditProfileNameFragment.trimFieldToMaxByteLength(s);
|
||||
viewModel.setGivenName(s.toString());
|
||||
}));
|
||||
this.familyName.addTextChangedListener(new AfterTextChanged(s -> {
|
||||
trimInPlace(s);
|
||||
EditProfileNameFragment.trimFieldToMaxByteLength(s);
|
||||
viewModel.setFamilyName(s.toString());
|
||||
}));
|
||||
LearnMoreTextView descriptionText = view.findViewById(R.id.description_text);
|
||||
@@ -249,11 +211,6 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
|
||||
this.finishButton.setText(arguments.getInt(NEXT_BUTTON_TEXT, R.string.CreateProfileActivity_next));
|
||||
|
||||
this.usernameEditButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditProfileFragmentDirections.actionEditUsername();
|
||||
Navigation.findNavController(v).navigate(action);
|
||||
});
|
||||
|
||||
if (arguments.getBoolean(SHOW_TOOLBAR, true)) {
|
||||
this.toolbar.setVisibility(View.VISIBLE);
|
||||
this.toolbar.setNavigationOnClickListener(v -> requireActivity().finish());
|
||||
@@ -285,10 +242,6 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeUsername() {
|
||||
viewModel.username().observe(getViewLifecycleOwner(), this::onUsernameChanged);
|
||||
}
|
||||
|
||||
private static void updateFieldIfNeeded(@NonNull EditText field, @NonNull String value) {
|
||||
String fieldTrimmed = field.getText().toString().trim();
|
||||
String valueTrimmed = value.trim();
|
||||
@@ -304,10 +257,6 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void onUsernameChanged(@NonNull Optional<String> username) {
|
||||
this.username.setText(username.transform(s -> "@" + s).or(""));
|
||||
}
|
||||
|
||||
private void startAvatarSelection() {
|
||||
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveProfilePhoto(),
|
||||
true,
|
||||
@@ -375,14 +324,6 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
animation.start();
|
||||
}
|
||||
|
||||
private static void trimInPlace(Editable s) {
|
||||
int trimmedLength = StringUtil.trimToFit(s.toString(), ProfileName.MAX_PART_LENGTH).length();
|
||||
|
||||
if (s.length() > trimmedLength) {
|
||||
s.delete(trimmedLength, s.length());
|
||||
}
|
||||
}
|
||||
|
||||
public interface Controller {
|
||||
void onProfileNameUploadCompleted();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ class EditProfileViewModel extends ViewModel {
|
||||
private final LiveData<ProfileName> internalProfileName = LiveDataUtil.combineLatest(trimmedGivenName, trimmedFamilyName, ProfileName::fromParts);
|
||||
private final MutableLiveData<byte[]> internalAvatar = new MutableLiveData<>();
|
||||
private final MutableLiveData<byte[]> originalAvatar = new MutableLiveData<>();
|
||||
private final MutableLiveData<Optional<String>> internalUsername = new MutableLiveData<>();
|
||||
private final MutableLiveData<String> originalDisplayName = new MutableLiveData<>();
|
||||
private final LiveData<Boolean> isFormValid;
|
||||
private final EditProfileRepository repository;
|
||||
@@ -77,10 +76,6 @@ class EditProfileViewModel extends ViewModel {
|
||||
return Transformations.distinctUntilChanged(internalAvatar);
|
||||
}
|
||||
|
||||
public LiveData<Optional<String>> username() {
|
||||
return internalUsername;
|
||||
}
|
||||
|
||||
public boolean hasAvatar() {
|
||||
return internalAvatar.getValue() != null;
|
||||
}
|
||||
@@ -105,10 +100,6 @@ class EditProfileViewModel extends ViewModel {
|
||||
internalAvatar.setValue(avatar);
|
||||
}
|
||||
|
||||
public void refreshUsername() {
|
||||
repository.getCurrentUsername(internalUsername::postValue);
|
||||
}
|
||||
|
||||
public void submitProfile(Consumer<EditProfileRepository.UploadResult> uploadResultConsumer) {
|
||||
ProfileName profileName = isGroup() ? ProfileName.EMPTY : internalProfileName.getValue();
|
||||
String displayName = isGroup() ? givenName.getValue() : "";
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
class EditSelfProfileRepository implements EditProfileRepository {
|
||||
public class EditSelfProfileRepository implements EditProfileRepository {
|
||||
|
||||
private static final String TAG = Log.tag(EditSelfProfileRepository.class);
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.signal.core.util.BreakIteratorCompat;
|
||||
import org.signal.core.util.EditTextUtil;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
|
||||
/**
|
||||
* Let's you edit the 'About' section of your profile.
|
||||
*/
|
||||
public class EditAboutFragment extends Fragment implements ManageProfileActivity.EmojiController {
|
||||
|
||||
public static final int ABOUT_MAX_GLYPHS = 100;
|
||||
public static final int ABOUT_LIMIT_DISPLAY_THRESHOLD = 75;
|
||||
|
||||
private static final String KEY_SELECTED_EMOJI = "selected_emoji";
|
||||
|
||||
private ImageView emojiView;
|
||||
private EditText bodyView;
|
||||
private TextView countView;
|
||||
|
||||
private String selectedEmoji;
|
||||
|
||||
@Override
|
||||
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.edit_about_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
this.emojiView = view.findViewById(R.id.edit_about_emoji);
|
||||
this.bodyView = view.findViewById(R.id.edit_about_body);
|
||||
this.countView = view.findViewById(R.id.edit_about_count);
|
||||
|
||||
|
||||
view.<Toolbar>findViewById(R.id.toolbar)
|
||||
.setNavigationOnClickListener(v -> Navigation.findNavController(view)
|
||||
.popBackStack());
|
||||
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(bodyView, ABOUT_MAX_GLYPHS);
|
||||
this.bodyView.addTextChangedListener(new AfterTextChanged(editable -> {
|
||||
trimFieldToMaxByteLength(editable);
|
||||
presentCount(editable.toString());
|
||||
}));
|
||||
|
||||
this.emojiView.setOnClickListener(v -> {
|
||||
ReactWithAnyEmojiBottomSheetDialogFragment.createForAboutSelection()
|
||||
.show(requireFragmentManager(), "BOTTOM");
|
||||
});
|
||||
|
||||
view.findViewById(R.id.edit_about_save).setOnClickListener(this::onSaveClicked);
|
||||
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_SELECTED_EMOJI)) {
|
||||
onEmojiSelected(savedInstanceState.getString(KEY_SELECTED_EMOJI, ""));
|
||||
} else {
|
||||
this.bodyView.setText(Recipient.self().getAbout());
|
||||
onEmojiSelected(Optional.fromNullable(Recipient.self().getAboutEmoji()).or(""));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putString(KEY_SELECTED_EMOJI, selectedEmoji);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEmojiSelected(@NonNull String emoji) {
|
||||
Drawable drawable = EmojiUtil.convertToDrawable(requireContext(), emoji);
|
||||
if (drawable != null) {
|
||||
this.emojiView.setImageDrawable(drawable);
|
||||
this.selectedEmoji = emoji;
|
||||
}
|
||||
}
|
||||
|
||||
private void presentCount(@NonNull String aboutBody) {
|
||||
BreakIteratorCompat breakIterator = BreakIteratorCompat.getInstance();
|
||||
breakIterator.setText(aboutBody);
|
||||
int glyphCount = breakIterator.countBreaks();
|
||||
|
||||
if (glyphCount >= ABOUT_LIMIT_DISPLAY_THRESHOLD) {
|
||||
this.countView.setVisibility(View.VISIBLE);
|
||||
this.countView.setText(getResources().getString(R.string.EditAboutFragment_count, glyphCount, ABOUT_MAX_GLYPHS));
|
||||
} else {
|
||||
this.countView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void onSaveClicked(View view) {
|
||||
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
|
||||
DatabaseFactory.getRecipientDatabase(requireContext()).setAbout(Recipient.self().getId(), bodyView.getText().toString(), selectedEmoji);
|
||||
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
|
||||
return null;
|
||||
}, (nothing) -> {
|
||||
Navigation.findNavController(view).popBackStack();
|
||||
});
|
||||
}
|
||||
|
||||
public static void trimFieldToMaxByteLength(Editable s) {
|
||||
int trimmedLength = StringUtil.trimToFit(s.toString(), ProfileCipher.MAX_POSSIBLE_ABOUT_LENGTH).length();
|
||||
|
||||
if (s.length() > trimmedLength) {
|
||||
s.delete(trimmedLength, s.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.signal.core.util.EditTextUtil;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
|
||||
/**
|
||||
* Simple fragment to edit your profile name.
|
||||
*/
|
||||
public class EditProfileNameFragment extends Fragment {
|
||||
|
||||
public static final int NAME_MAX_GLYPHS = 26;
|
||||
|
||||
private EditText givenName;
|
||||
private EditText familyName;
|
||||
|
||||
@Override
|
||||
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.edit_profile_name_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
this.givenName = view.findViewById(R.id.edit_profile_name_given_name);
|
||||
this.familyName = view.findViewById(R.id.edit_profile_name_family_name);
|
||||
|
||||
this.givenName.setText(Recipient.self().getProfileName().getGivenName());
|
||||
this.familyName.setText(Recipient.self().getProfileName().getFamilyName());
|
||||
|
||||
view.<Toolbar>findViewById(R.id.toolbar)
|
||||
.setNavigationOnClickListener(v -> Navigation.findNavController(view)
|
||||
.popBackStack());
|
||||
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(givenName, NAME_MAX_GLYPHS);
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(familyName, NAME_MAX_GLYPHS);
|
||||
|
||||
this.givenName.addTextChangedListener(new AfterTextChanged(EditProfileNameFragment::trimFieldToMaxByteLength));
|
||||
this.familyName.addTextChangedListener(new AfterTextChanged(EditProfileNameFragment::trimFieldToMaxByteLength));
|
||||
|
||||
view.findViewById(R.id.edit_profile_name_save).setOnClickListener(this::onSaveClicked);
|
||||
}
|
||||
|
||||
private void onSaveClicked(View view) {
|
||||
ProfileName profileName = ProfileName.fromParts(givenName.getText().toString(), familyName.getText().toString());
|
||||
|
||||
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
|
||||
DatabaseFactory.getRecipientDatabase(requireContext()).setProfileName(Recipient.self().getId(), profileName);
|
||||
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
|
||||
return null;
|
||||
}, (nothing) -> {
|
||||
Navigation.findNavController(view).popBackStack();
|
||||
});
|
||||
}
|
||||
|
||||
public static void trimFieldToMaxByteLength(Editable s) {
|
||||
int trimmedLength = StringUtil.trimToFit(s.toString(), ProfileName.MAX_PART_LENGTH).length();
|
||||
|
||||
if (s.length() > trimmedLength) {
|
||||
s.delete(trimmedLength, s.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.NavGraph;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import org.thoughtcrime.securesms.BaseActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileFragmentDirections;
|
||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
/**
|
||||
* Activity that manages the local user's profile, as accessed via the settings.
|
||||
*/
|
||||
public class ManageProfileActivity extends BaseActivity implements ReactWithAnyEmojiBottomSheetDialogFragment.Callback {
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
|
||||
public static final String START_AT_USERNAME = "start_at_username";
|
||||
|
||||
public static @NonNull Intent getIntent(@NonNull Context context) {
|
||||
return new Intent(context, ManageProfileActivity.class);
|
||||
}
|
||||
|
||||
public static @NonNull Intent getIntentForUsernameEdit(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, ManageProfileActivity.class);
|
||||
intent.putExtra(START_AT_USERNAME, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
dynamicTheme.onCreate(this);
|
||||
|
||||
setContentView(R.layout.manage_profile_activity);
|
||||
|
||||
if (bundle == null) {
|
||||
Bundle extras = getIntent().getExtras();
|
||||
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
|
||||
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, extras != null ? extras : new Bundle());
|
||||
|
||||
if (extras != null && extras.getBoolean(START_AT_USERNAME, false)) {
|
||||
NavDirections action = ManageProfileFragmentDirections.actionManageUsername();
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).navigate(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReactWithAnyEmojiDialogDismissed() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReactWithAnyEmojiPageChanged(int page) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReactWithAnyEmojiSelected(@NonNull String emoji) {
|
||||
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().getPrimaryNavigationFragment();
|
||||
Fragment activeFragment = navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment();
|
||||
|
||||
if (activeFragment instanceof EmojiController) {
|
||||
((EmojiController) activeFragment).onEmojiSelected(emoji);
|
||||
}
|
||||
}
|
||||
|
||||
interface EmojiController {
|
||||
void onEmojiSelected(@NonNull String emoji);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.profiles.manage.ManageProfileViewModel.AvatarState;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class ManageProfileFragment extends LoggingFragment {
|
||||
|
||||
private static final String TAG = Log.tag(ManageProfileFragment.class);
|
||||
private static final short REQUEST_CODE_SELECT_AVATAR = 31726;
|
||||
|
||||
private Toolbar toolbar;
|
||||
private ImageView avatarView;
|
||||
private View avatarPlaceholderView;
|
||||
private TextView profileNameView;
|
||||
private View profileNameContainer;
|
||||
private TextView usernameView;
|
||||
private View usernameContainer;
|
||||
private TextView aboutView;
|
||||
private View aboutContainer;
|
||||
private ImageView aboutEmojiView;
|
||||
private AlertDialog avatarProgress;
|
||||
|
||||
private ManageProfileViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.manage_profile_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
this.toolbar = view.findViewById(R.id.toolbar);
|
||||
this.avatarView = view.findViewById(R.id.manage_profile_avatar);
|
||||
this.avatarPlaceholderView = view.findViewById(R.id.manage_profile_avatar_placeholder);
|
||||
this.profileNameView = view.findViewById(R.id.manage_profile_name);
|
||||
this.profileNameContainer = view.findViewById(R.id.manage_profile_name_container);
|
||||
this.usernameView = view.findViewById(R.id.manage_profile_username);
|
||||
this.usernameContainer = view.findViewById(R.id.manage_profile_username_container);
|
||||
this.aboutView = view.findViewById(R.id.manage_profile_about);
|
||||
this.aboutContainer = view.findViewById(R.id.manage_profile_about_container);
|
||||
this.aboutEmojiView = view.findViewById(R.id.manage_profile_about_icon);
|
||||
|
||||
initializeViewModel();
|
||||
|
||||
this.toolbar.setNavigationOnClickListener(v -> requireActivity().finish());
|
||||
this.avatarView.setOnClickListener(v -> onAvatarClicked());
|
||||
|
||||
this.profileNameContainer.setOnClickListener(v -> {
|
||||
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageProfileName());
|
||||
});
|
||||
|
||||
this.usernameContainer.setOnClickListener(v -> {
|
||||
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageUsername());
|
||||
});
|
||||
|
||||
this.aboutContainer.setOnClickListener(v -> {
|
||||
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageAbout());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQUEST_CODE_SELECT_AVATAR && resultCode == RESULT_OK) {
|
||||
if (data != null && data.getBooleanExtra("delete", false)) {
|
||||
viewModel.onAvatarSelected(requireContext(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
|
||||
|
||||
viewModel.onAvatarSelected(requireContext(), result);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeViewModel() {
|
||||
viewModel = ViewModelProviders.of(this, new ManageProfileViewModel.Factory()).get(ManageProfileViewModel.class);
|
||||
|
||||
viewModel.getAvatar().observe(getViewLifecycleOwner(), this::presentAvatar);
|
||||
viewModel.getProfileName().observe(getViewLifecycleOwner(), this::presentProfileName);
|
||||
viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvent);
|
||||
viewModel.getAbout().observe(getViewLifecycleOwner(), this::presentAbout);
|
||||
viewModel.getAboutEmoji().observe(getViewLifecycleOwner(), this::presentAboutEmoji);
|
||||
|
||||
if (viewModel.shouldShowUsername()) {
|
||||
viewModel.getUsername().observe(getViewLifecycleOwner(), this::presentUsername);
|
||||
} else {
|
||||
usernameContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void presentAvatar(@NonNull AvatarState avatarState) {
|
||||
if (avatarState.getAvatar() == null) {
|
||||
avatarView.setImageDrawable(null);
|
||||
avatarPlaceholderView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
avatarPlaceholderView.setVisibility(View.GONE);
|
||||
Glide.with(this)
|
||||
.load(avatarState.getAvatar())
|
||||
.circleCrop()
|
||||
.into(avatarView);
|
||||
}
|
||||
|
||||
if (avatarProgress == null && avatarState.getLoadingState() == ManageProfileViewModel.LoadingState.LOADING) {
|
||||
avatarProgress = SimpleProgressDialog.show(requireContext());
|
||||
} else if (avatarProgress != null && avatarState.getLoadingState() == ManageProfileViewModel.LoadingState.LOADED) {
|
||||
avatarProgress.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void presentProfileName(@Nullable ProfileName profileName) {
|
||||
if (profileName == null || profileName.isEmpty()) {
|
||||
profileNameView.setText(R.string.ManageProfileFragment_profile_name);
|
||||
} else {
|
||||
profileNameView.setText(profileName.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void presentUsername(@Nullable String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
usernameView.setText(R.string.ManageProfileFragment_username);
|
||||
usernameView.setTextColor(requireContext().getResources().getColor(R.color.signal_text_secondary));
|
||||
} else {
|
||||
usernameView.setText(username);
|
||||
usernameView.setTextColor(requireContext().getResources().getColor(R.color.signal_text_primary));
|
||||
}
|
||||
}
|
||||
|
||||
private void presentAbout(@Nullable String about) {
|
||||
if (about == null || about.isEmpty()) {
|
||||
aboutView.setText(R.string.ManageProfileFragment_about);
|
||||
aboutView.setTextColor(requireContext().getResources().getColor(R.color.signal_text_secondary));
|
||||
} else {
|
||||
aboutView.setText(about);
|
||||
aboutView.setTextColor(requireContext().getResources().getColor(R.color.signal_text_primary));
|
||||
}
|
||||
}
|
||||
|
||||
private void presentAboutEmoji(@NonNull String aboutEmoji) {
|
||||
if (aboutEmoji == null || aboutEmoji.isEmpty()) {
|
||||
aboutEmojiView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
|
||||
} else {
|
||||
Drawable emoji = EmojiUtil.convertToDrawable(requireContext(), aboutEmoji);
|
||||
|
||||
if (emoji != null) {
|
||||
aboutEmojiView.setImageDrawable(emoji);
|
||||
} else {
|
||||
aboutEmojiView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void presentEvent(@NonNull ManageProfileViewModel.Event event) {
|
||||
if (event == ManageProfileViewModel.Event.AVATAR_FAILURE) {
|
||||
Toast.makeText(requireContext(), R.string.ManageProfileFragment_failed_to_set_avatar, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void onAvatarClicked() {
|
||||
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveAvatar(),
|
||||
true,
|
||||
REQUEST_CODE_SELECT_AVATAR,
|
||||
false)
|
||||
.show(getChildFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
class ManageProfileViewModel extends ViewModel {
|
||||
|
||||
private static final String TAG = Log.tag(ManageProfileViewModel.class);
|
||||
|
||||
private final MutableLiveData<AvatarState> avatar;
|
||||
private final MutableLiveData<ProfileName> profileName;
|
||||
private final MutableLiveData<String> username;
|
||||
private final MutableLiveData<String> about;
|
||||
private final MutableLiveData<String> aboutEmoji;
|
||||
private final SingleLiveEvent<Event> events;
|
||||
private final RecipientForeverObserver observer;
|
||||
|
||||
public ManageProfileViewModel() {
|
||||
this.avatar = new MutableLiveData<>();
|
||||
this.profileName = new MutableLiveData<>();
|
||||
this.username = new MutableLiveData<>();
|
||||
this.about = new MutableLiveData<>();
|
||||
this.aboutEmoji = new MutableLiveData<>();
|
||||
this.events = new SingleLiveEvent<>();
|
||||
this.observer = this::onRecipientChanged;
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
onRecipientChanged(Recipient.self().fresh());
|
||||
|
||||
StreamDetails details = AvatarHelper.getSelfProfileAvatarStream(ApplicationDependencies.getApplication());
|
||||
if (details != null) {
|
||||
try {
|
||||
avatar.postValue(AvatarState.loaded(StreamUtil.readFully(details.getStream())));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to read avatar!");
|
||||
avatar.postValue(AvatarState.none());
|
||||
}
|
||||
} else {
|
||||
avatar.postValue(AvatarState.none());
|
||||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().add(RetrieveProfileJob.forRecipient(Recipient.self().getId()));
|
||||
});
|
||||
|
||||
Recipient.self().live().observeForever(observer);
|
||||
}
|
||||
|
||||
public @NonNull LiveData<AvatarState> getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<ProfileName> getProfileName() {
|
||||
return profileName;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<String> getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<String> getAbout() {
|
||||
return about;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<String> getAboutEmoji() {
|
||||
return aboutEmoji;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<Event> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public boolean shouldShowUsername() {
|
||||
return FeatureFlags.usernames();
|
||||
}
|
||||
|
||||
public void onAvatarSelected(@NonNull Context context, @Nullable Media media) {
|
||||
if (media == null) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
AvatarHelper.delete(context, Recipient.self().getId());
|
||||
avatar.postValue(AvatarState.none());
|
||||
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
|
||||
});
|
||||
} else {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
try {
|
||||
InputStream stream = BlobProvider.getInstance().getStream(context, media.getUri());
|
||||
byte[] data = StreamUtil.readFully(stream);
|
||||
|
||||
AvatarHelper.setAvatar(context, Recipient.self().getId(), new ByteArrayInputStream(data));
|
||||
avatar.postValue(AvatarState.loaded(data));
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to save avatar!", e);
|
||||
events.postValue(Event.AVATAR_FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canRemoveAvatar() {
|
||||
return avatar.getValue() != null;
|
||||
}
|
||||
|
||||
private void onRecipientChanged(@NonNull Recipient recipient) {
|
||||
profileName.postValue(recipient.getProfileName());
|
||||
username.postValue(recipient.getUsername().orNull());
|
||||
about.postValue(recipient.getAbout());
|
||||
aboutEmoji.postValue(recipient.getAboutEmoji());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
Recipient.self().live().removeForeverObserver(observer);
|
||||
}
|
||||
|
||||
public static class AvatarState {
|
||||
private final byte[] avatar;
|
||||
private final LoadingState loadingState;
|
||||
|
||||
public AvatarState(@Nullable byte[] avatar, @NonNull LoadingState loadingState) {
|
||||
this.avatar = avatar;
|
||||
this.loadingState = loadingState;
|
||||
}
|
||||
|
||||
private static @NonNull AvatarState none() {
|
||||
return new AvatarState(null, LoadingState.LOADED);
|
||||
}
|
||||
|
||||
private static @NonNull AvatarState loaded(@Nullable byte[] avatar) {
|
||||
return new AvatarState(avatar, LoadingState.LOADED);
|
||||
}
|
||||
|
||||
private static @NonNull AvatarState loading(@Nullable byte[] avatar) {
|
||||
return new AvatarState(avatar, LoadingState.LOADING);
|
||||
}
|
||||
|
||||
public @Nullable byte[] getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public LoadingState getLoadingState() {
|
||||
return loadingState;
|
||||
}
|
||||
}
|
||||
|
||||
public enum LoadingState {
|
||||
LOADING, LOADED
|
||||
}
|
||||
|
||||
enum Event {
|
||||
AVATAR_FAILURE
|
||||
}
|
||||
|
||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||
@Override
|
||||
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
return Objects.requireNonNull(modelClass.cast(new ManageProfileViewModel()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.usernames.username;
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.usernames.username;
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.usernames.username;
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.app.Application;
|
||||
import android.text.TextUtils;
|
||||
@@ -45,9 +45,14 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
|
||||
EmojiPageViewGridAdapter.VariationSelectorListener
|
||||
{
|
||||
|
||||
private static final String REACTION_STORAGE_KEY = "reactions_recent_emoji";
|
||||
private static final String ABOUT_STORAGE_KEY = EmojiKeyboardProvider.RECENT_STORAGE_KEY;
|
||||
|
||||
private static final String ARG_MESSAGE_ID = "arg_message_id";
|
||||
private static final String ARG_IS_MMS = "arg_is_mms";
|
||||
private static final String ARG_START_PAGE = "arg_start_page";
|
||||
private static final String ARG_SHADOWS = "arg_shadows";
|
||||
private static final String ARG_RECENT_KEY = "arg_recent_key";
|
||||
|
||||
private ReactWithAnyEmojiViewModel viewModel;
|
||||
private TextSwitcher categoryLabel;
|
||||
@@ -65,6 +70,22 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
|
||||
args.putLong(ARG_MESSAGE_ID, messageRecord.getId());
|
||||
args.putBoolean(ARG_IS_MMS, messageRecord.isMms());
|
||||
args.putInt(ARG_START_PAGE, startingPage);
|
||||
args.putBoolean(ARG_SHADOWS, false);
|
||||
args.putString(ARG_RECENT_KEY, REACTION_STORAGE_KEY);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public static DialogFragment createForAboutSelection() {
|
||||
DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putLong(ARG_MESSAGE_ID, -1);
|
||||
args.putBoolean(ARG_IS_MMS, false);
|
||||
args.putInt(ARG_START_PAGE, -1);
|
||||
args.putBoolean(ARG_SHADOWS, true);
|
||||
args.putString(ARG_RECENT_KEY, ABOUT_STORAGE_KEY);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
@@ -79,10 +100,13 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
boolean shadows = requireArguments().getBoolean(ARG_SHADOWS);
|
||||
if (ThemeUtil.isDarkTheme(requireContext())) {
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny);
|
||||
setStyle(DialogFragment.STYLE_NORMAL, shadows ? R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny
|
||||
: R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny_Shadowless);
|
||||
} else {
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny);
|
||||
setStyle(DialogFragment.STYLE_NORMAL, shadows ? R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny
|
||||
: R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny_Shadowless);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -168,15 +192,18 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
|
||||
if (savedInstanceState == null) {
|
||||
FrameLayout container = requireDialog().findViewById(R.id.container);
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(requireContext());
|
||||
View statusBarShader = layoutInflater.inflate(R.layout.react_with_any_emoji_status_fade, container, false);
|
||||
TabLayout categoryTabs = (TabLayout) layoutInflater.inflate(R.layout.react_with_any_emoji_tabs, container, false);
|
||||
|
||||
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtil.getStatusBarHeight(container));
|
||||
|
||||
statusBarShader.setLayoutParams(params);
|
||||
container.addView(statusBarShader, 0);
|
||||
if (!requireArguments().getBoolean(ARG_SHADOWS)) {
|
||||
View statusBarShader = layoutInflater.inflate(R.layout.react_with_any_emoji_status_fade, container, false);
|
||||
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtil.getStatusBarHeight(container));
|
||||
|
||||
statusBarShader.setLayoutParams(params);
|
||||
container.addView(statusBarShader, 0);
|
||||
}
|
||||
|
||||
container.addView(categoryTabs);
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(container, (v, insets) -> insets.consumeSystemWindowInsets());
|
||||
|
||||
new TabLayoutMediator(categoryTabs, categoryPager, (tab, position) -> {
|
||||
@@ -203,7 +230,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
|
||||
|
||||
private void initializeViewModel() {
|
||||
Bundle args = requireArguments();
|
||||
ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext());
|
||||
ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext(), args.getString(ARG_RECENT_KEY));
|
||||
ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(reactionsLoader, repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS));
|
||||
|
||||
viewModel = ViewModelProviders.of(this, factory).get(ReactWithAnyEmojiViewModel.class);
|
||||
@@ -212,6 +239,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
|
||||
@Override
|
||||
public void onEmojiSelected(String emoji) {
|
||||
viewModel.onEmojiSelected(emoji);
|
||||
callback.onReactWithAnyEmojiSelected(emoji);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@@ -239,7 +267,8 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
|
||||
}
|
||||
|
||||
private int getStartingPage(boolean firstPageHasContent) {
|
||||
return requireArguments().getInt(ARG_START_PAGE, firstPageHasContent ? 0 : 1);
|
||||
int startPage = requireArguments().getInt(ARG_START_PAGE);
|
||||
return startPage >= 0 ? startPage : (firstPageHasContent ? 0 : 1);
|
||||
}
|
||||
|
||||
private class OnPageChanged extends ViewPager2.OnPageChangeCallback {
|
||||
@@ -253,5 +282,6 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
|
||||
public interface Callback {
|
||||
void onReactWithAnyEmojiDialogDismissed();
|
||||
void onReactWithAnyEmojiPageChanged(int page);
|
||||
void onReactWithAnyEmojiSelected(@NonNull String emoji);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,15 +32,13 @@ final class ReactWithAnyEmojiRepository {
|
||||
|
||||
private static final String TAG = Log.tag(ReactWithAnyEmojiRepository.class);
|
||||
|
||||
private static final String RECENT_STORAGE_KEY = "reactions_recent_emoji";
|
||||
|
||||
private final Context context;
|
||||
private final RecentEmojiPageModel recentEmojiPageModel;
|
||||
private final Context context;
|
||||
private final RecentEmojiPageModel recentEmojiPageModel;
|
||||
private final List<ReactWithAnyEmojiPage> emojiPages;
|
||||
|
||||
ReactWithAnyEmojiRepository(@NonNull Context context) {
|
||||
ReactWithAnyEmojiRepository(@NonNull Context context, @NonNull String storageKey) {
|
||||
this.context = context;
|
||||
this.recentEmojiPageModel = new RecentEmojiPageModel(context, RECENT_STORAGE_KEY);
|
||||
this.recentEmojiPageModel = new RecentEmojiPageModel(context, storageKey);
|
||||
this.emojiPages = new LinkedList<>();
|
||||
|
||||
emojiPages.addAll(Stream.of(EmojiUtil.getDisplayPages())
|
||||
|
||||
@@ -36,8 +36,10 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
void onEmojiSelected(@NonNull String emoji) {
|
||||
SignalStore.emojiValues().setPreferredVariation(emoji);
|
||||
repository.addEmojiToMessage(emoji, messageId, isMms);
|
||||
if (messageId > 0) {
|
||||
SignalStore.emojiValues().setPreferredVariation(emoji);
|
||||
repository.addEmojiToMessage(emoji, messageId, isMms);
|
||||
}
|
||||
}
|
||||
|
||||
static class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.libsignal.util.guava.Preconditions;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
@@ -106,6 +107,9 @@ public class Recipient {
|
||||
private final InsightsBannerTier insightsBannerTier;
|
||||
private final byte[] storageId;
|
||||
private final MentionSetting mentionSetting;
|
||||
private final ChatWallpaper wallpaper;
|
||||
private final String about;
|
||||
private final String aboutEmoji;
|
||||
|
||||
|
||||
/**
|
||||
@@ -339,6 +343,9 @@ public class Recipient {
|
||||
this.groupsV1MigrationCapability = Capability.UNKNOWN;
|
||||
this.storageId = null;
|
||||
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
|
||||
this.wallpaper = null;
|
||||
this.about = null;
|
||||
this.aboutEmoji = null;
|
||||
}
|
||||
|
||||
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
|
||||
@@ -381,6 +388,9 @@ public class Recipient {
|
||||
this.groupsV1MigrationCapability = details.groupsV1MigrationCapability;
|
||||
this.storageId = details.storageId;
|
||||
this.mentionSetting = details.mentionSetting;
|
||||
this.wallpaper = details.wallpaper;
|
||||
this.about = details.about;
|
||||
this.aboutEmoji = details.aboutEmoji;
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getId() {
|
||||
@@ -499,6 +509,15 @@ public class Recipient {
|
||||
return StringUtil.isolateBidi(name);
|
||||
}
|
||||
|
||||
public @NonNull String getShortDisplayNameIncludingUsername(@NonNull Context context) {
|
||||
String name = Util.getFirstNonEmpty(getName(context),
|
||||
getProfileName().getGivenName(),
|
||||
getDisplayName(context),
|
||||
getUsername().orNull());
|
||||
|
||||
return StringUtil.isolateBidi(name);
|
||||
}
|
||||
|
||||
public @NonNull MaterialColor getColor() {
|
||||
if (isGroupInternal()) {
|
||||
return MaterialColor.GROUP;
|
||||
@@ -834,10 +853,51 @@ public class Recipient {
|
||||
return unidentifiedAccessMode;
|
||||
}
|
||||
|
||||
public @Nullable ChatWallpaper getWallpaper() {
|
||||
if (wallpaper != null) {
|
||||
return wallpaper;
|
||||
} else {
|
||||
return SignalStore.wallpaper().getWallpaper();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasOwnWallpaper() {
|
||||
return wallpaper != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A cheap way to check if wallpaper is set without doing any unnecessary proto parsing.
|
||||
*/
|
||||
public boolean hasWallpaper() {
|
||||
return wallpaper != null || SignalStore.wallpaper().hasWallpaperSet();
|
||||
}
|
||||
|
||||
public boolean isSystemContact() {
|
||||
return contactUri != null;
|
||||
}
|
||||
|
||||
public @Nullable String getAbout() {
|
||||
return about;
|
||||
}
|
||||
|
||||
public @Nullable String getAboutEmoji() {
|
||||
return aboutEmoji;
|
||||
}
|
||||
|
||||
public @Nullable String getCombinedAboutAndEmoji() {
|
||||
if (aboutEmoji != null) {
|
||||
if (about != null) {
|
||||
return aboutEmoji + " " + about;
|
||||
} else {
|
||||
return aboutEmoji;
|
||||
}
|
||||
} else if (about != null) {
|
||||
return about;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this recipient is missing crucial data, this will return a populated copy. Otherwise it
|
||||
* returns itself.
|
||||
@@ -952,7 +1012,10 @@ public class Recipient {
|
||||
groupsV1MigrationCapability == other.groupsV1MigrationCapability &&
|
||||
insightsBannerTier == other.insightsBannerTier &&
|
||||
Arrays.equals(storageId, other.storageId) &&
|
||||
mentionSetting == other.mentionSetting;
|
||||
mentionSetting == other.mentionSetting &&
|
||||
Objects.equals(wallpaper, other.wallpaper) &&
|
||||
Objects.equals(about, other.about) &&
|
||||
Objects.equals(aboutEmoji, other.aboutEmoji);
|
||||
}
|
||||
|
||||
private static boolean allContentsAreTheSame(@NonNull List<Recipient> a, @NonNull List<Recipient> b) {
|
||||
@@ -990,7 +1053,6 @@ public class Recipient {
|
||||
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
|
||||
return new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_profile_outline_48);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MissingAddressError extends AssertionError {
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.LinkedList;
|
||||
@@ -65,6 +66,9 @@ public class RecipientDetails {
|
||||
final InsightsBannerTier insightsBannerTier;
|
||||
final byte[] storageId;
|
||||
final MentionSetting mentionSetting;
|
||||
final ChatWallpaper wallpaper;
|
||||
final String about;
|
||||
final String aboutEmoji;
|
||||
|
||||
public RecipientDetails(@Nullable String name,
|
||||
@NonNull Optional<Long> groupAvatarId,
|
||||
@@ -110,6 +114,9 @@ public class RecipientDetails {
|
||||
this.insightsBannerTier = settings.getInsightsBannerTier();
|
||||
this.storageId = settings.getStorageId();
|
||||
this.mentionSetting = settings.getMentionSetting();
|
||||
this.wallpaper = settings.getWallpaper();
|
||||
this.about = settings.getAbout();
|
||||
this.aboutEmoji = settings.getAboutEmoji();
|
||||
|
||||
if (name == null) this.name = settings.getSystemDisplayName();
|
||||
else this.name = name;
|
||||
@@ -157,6 +164,9 @@ public class RecipientDetails {
|
||||
this.groupsV1MigrationCapability = Recipient.Capability.UNKNOWN;
|
||||
this.storageId = null;
|
||||
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
|
||||
this.wallpaper = null;
|
||||
this.about = null;
|
||||
this.aboutEmoji = null;
|
||||
}
|
||||
|
||||
public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientSettings settings) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -55,6 +56,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
private RecipientDialogViewModel viewModel;
|
||||
private AvatarImageView avatar;
|
||||
private TextView fullName;
|
||||
private TextView about;
|
||||
private TextView usernameNumber;
|
||||
private Button messageButton;
|
||||
private Button secureCallButton;
|
||||
@@ -102,6 +104,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
|
||||
avatar = view.findViewById(R.id.rbs_recipient_avatar);
|
||||
fullName = view.findViewById(R.id.rbs_full_name);
|
||||
about = view.findViewById(R.id.rbs_about);
|
||||
usernameNumber = view.findViewById(R.id.rbs_username_number);
|
||||
messageButton = view.findViewById(R.id.rbs_message_button);
|
||||
secureCallButton = view.findViewById(R.id.rbs_secure_call_button);
|
||||
@@ -158,6 +161,14 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
TextViewCompat.setCompoundDrawableTintList(fullName, ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.signal_text_primary)));
|
||||
}
|
||||
|
||||
String aboutText = recipient.getCombinedAboutAndEmoji();
|
||||
if (!Util.isEmpty(aboutText)) {
|
||||
about.setText(aboutText);
|
||||
about.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
about.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
String usernameNumberString = recipient.hasAUserSetDisplayName(requireContext()) && !recipient.isSelf()
|
||||
? recipient.getSmsAddress().transform(PhoneNumberFormatter::prettyPrint).or("").trim()
|
||||
: "";
|
||||
|
||||
@@ -51,9 +51,11 @@ import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.ui.notifications.CustomNotificationsDialogFragment;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.LifecycleCursorWrapper;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
@@ -70,6 +72,7 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||
private GroupMemberListView sharedGroupList;
|
||||
private Toolbar toolbar;
|
||||
private TextView title;
|
||||
private TextView about;
|
||||
private TextView subtitle;
|
||||
private ViewGroup internalDetails;
|
||||
private TextView internalDetailsText;
|
||||
@@ -105,6 +108,7 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||
private View secureCallButton;
|
||||
private View insecureCallButton;
|
||||
private View secureVideoCallButton;
|
||||
private View chatWallpaperButton;
|
||||
|
||||
static ManageRecipientFragment newInstance(@NonNull RecipientId recipientId, boolean fromConversation) {
|
||||
ManageRecipientFragment fragment = new ManageRecipientFragment();
|
||||
@@ -130,6 +134,7 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||
contactText = view.findViewById(R.id.recipient_contact_text);
|
||||
contactIcon = view.findViewById(R.id.recipient_contact_icon);
|
||||
title = view.findViewById(R.id.name);
|
||||
about = view.findViewById(R.id.about);
|
||||
subtitle = view.findViewById(R.id.username_number);
|
||||
internalDetails = view.findViewById(R.id.recipient_internal_details);
|
||||
internalDetailsText = view.findViewById(R.id.recipient_internal_details_text);
|
||||
@@ -161,6 +166,7 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||
secureCallButton = view.findViewById(R.id.recipient_voice_call);
|
||||
insecureCallButton = view.findViewById(R.id.recipient_insecure_voice_call);
|
||||
secureVideoCallButton = view.findViewById(R.id.recipient_video_call);
|
||||
chatWallpaperButton = view.findViewById(R.id.chat_wallpaper);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -270,6 +276,7 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||
secureCallButton.setOnClickListener(v -> viewModel.onSecureCall(requireActivity()));
|
||||
insecureCallButton.setOnClickListener(v -> viewModel.onInsecureCall(requireActivity()));
|
||||
secureVideoCallButton.setOnClickListener(v -> viewModel.onSecureVideoCall(requireActivity()));
|
||||
chatWallpaperButton.setOnClickListener(v -> startActivity(ChatWallpaperActivity.createIntent(requireContext(), recipientId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -299,6 +306,10 @@ public class ManageRecipientFragment extends LoggingFragment {
|
||||
});
|
||||
}
|
||||
|
||||
String aboutText = recipient.getCombinedAboutAndEmoji();
|
||||
about.setText(aboutText);
|
||||
about.setVisibility(Util.isEmpty(aboutText) ? View.GONE : View.VISIBLE);
|
||||
|
||||
disappearingMessagesCard.setVisibility(recipient.isRegistered() ? View.VISIBLE : View.GONE);
|
||||
addToAGroup.setVisibility(recipient.isRegistered() ? View.VISIBLE : View.GONE);
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment
|
||||
|
||||
private void sendEmailToSupport() {
|
||||
String body = SupportEmailUtil.generateSupportEmailBody(requireContext(),
|
||||
getString(R.string.RegistrationActivity_code_support_subject),
|
||||
R.string.RegistrationActivity_code_support_subject,
|
||||
null,
|
||||
null);
|
||||
CommunicationActions.openEmail(requireContext(),
|
||||
|
||||
@@ -293,7 +293,7 @@ public final class EnterPhoneNumberFragment extends BaseRegistrationFragment {
|
||||
|
||||
private void initNumber(@NonNull NumberViewState numberViewState) {
|
||||
int countryCode = numberViewState.getCountryCode();
|
||||
long number = numberViewState.getNationalNumber();
|
||||
String number = numberViewState.getNationalNumber();
|
||||
String regionDisplayName = numberViewState.getCountryDisplayName();
|
||||
|
||||
this.countryCode.setText(String.valueOf(countryCode));
|
||||
@@ -303,7 +303,7 @@ public final class EnterPhoneNumberFragment extends BaseRegistrationFragment {
|
||||
String regionCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(countryCode);
|
||||
setCountryFormatter(regionCode);
|
||||
|
||||
if (number != 0) {
|
||||
if (!TextUtils.isEmpty(number)) {
|
||||
this.number.setText(String.valueOf(number));
|
||||
}
|
||||
}
|
||||
@@ -357,7 +357,7 @@ public final class EnterPhoneNumberFragment extends BaseRegistrationFragment {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
Long number = reformatText(s);
|
||||
String number = reformatText(s);
|
||||
|
||||
if (number == null) return;
|
||||
|
||||
@@ -378,7 +378,7 @@ public final class EnterPhoneNumberFragment extends BaseRegistrationFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private Long reformatText(Editable s) {
|
||||
private String reformatText(Editable s) {
|
||||
if (countryFormatter == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -408,7 +408,7 @@ public final class EnterPhoneNumberFragment extends BaseRegistrationFragment {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Long.parseLong(justDigits.toString());
|
||||
return justDigits.toString();
|
||||
}
|
||||
|
||||
private void setCountryFormatter(@Nullable String regionCode) {
|
||||
|
||||
@@ -34,6 +34,9 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class WelcomeFragment extends BaseRegistrationFragment {
|
||||
|
||||
private static final String TAG = Log.tag(WelcomeFragment.class);
|
||||
@@ -186,15 +189,29 @@ public final class WelcomeFragment extends BaseRegistrationFragment {
|
||||
|
||||
if (Permissions.hasAll(requireContext(), Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS)) {
|
||||
localNumber = Util.getDeviceNumber(requireContext());
|
||||
} else {
|
||||
Log.i(TAG, "No phone permission");
|
||||
}
|
||||
|
||||
if (localNumber.isPresent()) {
|
||||
getModel().onNumberDetected(localNumber.get().getCountryCode(), localNumber.get().getNationalNumber());
|
||||
Log.i(TAG, "Phone number detected");
|
||||
Phonenumber.PhoneNumber phoneNumber = localNumber.get();
|
||||
String nationalNumber = String.valueOf(phoneNumber.getNationalNumber());
|
||||
|
||||
if (phoneNumber.getNumberOfLeadingZeros() != 0) {
|
||||
char[] value = new char[phoneNumber.getNumberOfLeadingZeros()];
|
||||
Arrays.fill(value, '0');
|
||||
nationalNumber = new String(value) + nationalNumber;
|
||||
Log.i(TAG, String.format(Locale.US, "Padded national number with %d zeros", phoneNumber.getNumberOfLeadingZeros()));
|
||||
}
|
||||
|
||||
getModel().onNumberDetected(phoneNumber.getCountryCode(), nationalNumber);
|
||||
} else {
|
||||
Log.i(TAG, "No number detected");
|
||||
Optional<String> simCountryIso = Util.getSimCountryIso(requireContext());
|
||||
|
||||
if (simCountryIso.isPresent() && !TextUtils.isEmpty(simCountryIso.get())) {
|
||||
getModel().onNumberDetected(PhoneNumberUtil.getInstance().getCountryCodeForRegion(simCountryIso.get()), 0);
|
||||
getModel().onNumberDetected(PhoneNumberUtil.getInstance().getCountryCodeForRegion(simCountryIso.get()), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public final class NumberViewState implements Parcelable {
|
||||
|
||||
private final String selectedCountryName;
|
||||
private final int countryCode;
|
||||
private final long nationalNumber;
|
||||
private final String nationalNumber;
|
||||
|
||||
private NumberViewState(Builder builder) {
|
||||
this.selectedCountryName = builder.countryDisplayName;
|
||||
@@ -38,7 +38,7 @@ public final class NumberViewState implements Parcelable {
|
||||
return countryCode;
|
||||
}
|
||||
|
||||
public long getNationalNumber() {
|
||||
public String getNationalNumber() {
|
||||
return nationalNumber;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class NumberViewState implements Parcelable {
|
||||
public int hashCode() {
|
||||
int hash = countryCode;
|
||||
hash *= 31;
|
||||
hash += (int) (nationalNumber ^ (nationalNumber >>> 32));
|
||||
hash += nationalNumber != null ? nationalNumber.hashCode() : 0;
|
||||
hash *= 31;
|
||||
hash += selectedCountryName != null ? selectedCountryName.hashCode() : 0;
|
||||
return hash;
|
||||
@@ -101,7 +101,7 @@ public final class NumberViewState implements Parcelable {
|
||||
NumberViewState other = (NumberViewState) obj;
|
||||
|
||||
return other.countryCode == countryCode &&
|
||||
other.nationalNumber == nationalNumber &&
|
||||
Objects.equals(other.nationalNumber, nationalNumber) &&
|
||||
Objects.equals(other.selectedCountryName, selectedCountryName);
|
||||
}
|
||||
|
||||
@@ -122,8 +122,8 @@ public final class NumberViewState implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
private static String getConfiguredE164Number(int countryCode, long number) {
|
||||
return PhoneNumberFormatter.formatE164(String.valueOf(countryCode), String.valueOf(number));
|
||||
private static String getConfiguredE164Number(int countryCode, String number) {
|
||||
return PhoneNumberFormatter.formatE164(String.valueOf(countryCode), number);
|
||||
}
|
||||
|
||||
private static Phonenumber.PhoneNumber getPhoneNumber(@NonNull PhoneNumberUtil util, @NonNull String e164Number)
|
||||
@@ -135,7 +135,7 @@ public final class NumberViewState implements Parcelable {
|
||||
public static class Builder {
|
||||
private String countryDisplayName;
|
||||
private int countryCode;
|
||||
private long nationalNumber;
|
||||
private String nationalNumber;
|
||||
|
||||
public Builder countryCode(int countryCode) {
|
||||
this.countryCode = countryCode;
|
||||
@@ -147,7 +147,7 @@ public final class NumberViewState implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nationalNumber(long nationalNumber) {
|
||||
public Builder nationalNumber(String nationalNumber) {
|
||||
this.nationalNumber = nationalNumber;
|
||||
return this;
|
||||
}
|
||||
@@ -166,7 +166,7 @@ public final class NumberViewState implements Parcelable {
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(selectedCountryName);
|
||||
parcel.writeInt(countryCode);
|
||||
parcel.writeLong(nationalNumber);
|
||||
parcel.writeString(nationalNumber);
|
||||
}
|
||||
|
||||
public static final Creator<NumberViewState> CREATOR = new Creator<NumberViewState>() {
|
||||
@@ -174,7 +174,7 @@ public final class NumberViewState implements Parcelable {
|
||||
public NumberViewState createFromParcel(Parcel in) {
|
||||
return new Builder().selectedCountryDisplayName(in.readString())
|
||||
.countryCode(in.readInt())
|
||||
.nationalNumber(in.readLong())
|
||||
.nationalNumber(in.readString())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ public final class RegistrationViewModel extends ViewModel {
|
||||
.countryCode(countryCode).build());
|
||||
}
|
||||
|
||||
public void setNationalNumber(long number) {
|
||||
public void setNationalNumber(String number) {
|
||||
NumberViewState numberViewState = getNumber().toBuilder().nationalNumber(number).build();
|
||||
setViewState(numberViewState);
|
||||
}
|
||||
@@ -111,7 +111,7 @@ public final class RegistrationViewModel extends ViewModel {
|
||||
textCodeEntered.setValue(code);
|
||||
}
|
||||
|
||||
public void onNumberDetected(int countryCode, long nationalNumber) {
|
||||
public void onNumberDetected(int countryCode, String nationalNumber) {
|
||||
setViewState(getNumber().toBuilder()
|
||||
.countryCode(countryCode)
|
||||
.nationalNumber(nationalNumber)
|
||||
|
||||
@@ -41,13 +41,16 @@ import java.util.concurrent.ExecutionException;
|
||||
*
|
||||
* The image can be encrypted.
|
||||
*/
|
||||
final class UriGlideRenderer implements Renderer {
|
||||
public final class UriGlideRenderer implements Renderer {
|
||||
|
||||
private static final String TAG = Log.tag(UriGlideRenderer.class);
|
||||
|
||||
private static final int PREVIEW_DIMENSION_LIMIT = 2048;
|
||||
private static final int MAX_BLUR_DIMENSION = 300;
|
||||
|
||||
public static final float WEAK_BLUR = 3f;
|
||||
public static final float STRONG_BLUR = 25f;
|
||||
|
||||
private final Uri imageUri;
|
||||
private final Paint paint = new Paint();
|
||||
private final Matrix imageProjectionMatrix = new Matrix();
|
||||
@@ -56,16 +59,22 @@ final class UriGlideRenderer implements Renderer {
|
||||
private final boolean decryptable;
|
||||
private final int maxWidth;
|
||||
private final int maxHeight;
|
||||
private final float blurRadius;
|
||||
|
||||
@Nullable private Bitmap bitmap;
|
||||
@Nullable private Bitmap blurredBitmap;
|
||||
@Nullable private Paint blurPaint;
|
||||
@Nullable private Bitmap bitmap;
|
||||
@Nullable private Bitmap blurredBitmap;
|
||||
@Nullable private Paint blurPaint;
|
||||
|
||||
UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight) {
|
||||
public UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight) {
|
||||
this(imageUri, decryptable, maxWidth, maxHeight, STRONG_BLUR);
|
||||
}
|
||||
|
||||
public UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight, float blurRadius) {
|
||||
this.imageUri = imageUri;
|
||||
this.decryptable = decryptable;
|
||||
this.maxWidth = maxWidth;
|
||||
this.maxHeight = maxHeight;
|
||||
this.blurRadius = blurRadius;
|
||||
paint.setAntiAlias(true);
|
||||
paint.setFilterBitmap(true);
|
||||
paint.setDither(true);
|
||||
@@ -148,7 +157,7 @@ final class UriGlideRenderer implements Renderer {
|
||||
blurPaint.setMaskFilter(null);
|
||||
|
||||
if (blurredBitmap == null) {
|
||||
blurredBitmap = blur(bitmap, rendererContext.context);
|
||||
blurredBitmap = blur(bitmap, rendererContext.context, blurRadius);
|
||||
|
||||
blurScaleMatrix.setRectToRect(new RectF(0, 0, blurredBitmap.getWidth(), blurredBitmap.getHeight()),
|
||||
new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()),
|
||||
@@ -235,7 +244,7 @@ final class UriGlideRenderer implements Renderer {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
private static @NonNull Bitmap blur(Bitmap bitmap, Context context) {
|
||||
private static @NonNull Bitmap blur(Bitmap bitmap, Context context, float blurRadius) {
|
||||
Point previewSize = scaleKeepingAspectRatio(new Point(bitmap.getWidth(), bitmap.getHeight()), PREVIEW_DIMENSION_LIMIT);
|
||||
Point blurSize = scaleKeepingAspectRatio(new Point(previewSize.x / 2, previewSize.y / 2 ), MAX_BLUR_DIMENSION);
|
||||
Bitmap small = BitmapUtil.createScaledBitmap(bitmap, blurSize.x, blurSize.y);
|
||||
@@ -247,7 +256,7 @@ final class UriGlideRenderer implements Renderer {
|
||||
Allocation output = Allocation.createTyped(rs, input.getType());
|
||||
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
|
||||
|
||||
script.setRadius(25f);
|
||||
script.setRadius(blurRadius);
|
||||
script.setInput(input);
|
||||
script.forEach(output);
|
||||
|
||||
@@ -283,7 +292,8 @@ final class UriGlideRenderer implements Renderer {
|
||||
return new UriGlideRenderer(Uri.parse(in.readString()),
|
||||
in.readInt() == 1,
|
||||
in.readInt(),
|
||||
in.readInt()
|
||||
in.readInt(),
|
||||
in.readFloat()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -304,5 +314,6 @@ final class UriGlideRenderer implements Renderer {
|
||||
dest.writeInt(decryptable ? 1 : 0);
|
||||
dest.writeInt(maxWidth);
|
||||
dest.writeInt(maxHeight);
|
||||
dest.writeFloat(blurRadius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.thoughtcrime.securesms.sharing;
|
||||
|
||||
public enum InterstitialContentType {
|
||||
MEDIA,
|
||||
TEXT,
|
||||
NONE
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package org.thoughtcrime.securesms.sharing;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class MultiShareArgs implements Parcelable {
|
||||
|
||||
private static final String ARGS = "ShareInterstitialArgs";
|
||||
|
||||
private final Set<ShareContactAndThread> shareContactAndThreads;
|
||||
private final ArrayList<Media> media;
|
||||
private final String draftText;
|
||||
private final StickerLocator stickerLocator;
|
||||
private final boolean borderless;
|
||||
private final Uri dataUri;
|
||||
private final String dataType;
|
||||
private final boolean viewOnce;
|
||||
private final LinkPreview linkPreview;
|
||||
|
||||
private MultiShareArgs(@NonNull Builder builder) {
|
||||
shareContactAndThreads = builder.shareContactAndThreads;
|
||||
media = builder.media == null ? new ArrayList<>() : builder.media;
|
||||
draftText = builder.draftText;
|
||||
stickerLocator = builder.stickerLocator;
|
||||
borderless = builder.borderless;
|
||||
dataUri = builder.dataUri;
|
||||
dataType = builder.dataType;
|
||||
viewOnce = builder.viewOnce;
|
||||
linkPreview = builder.linkPreview;
|
||||
}
|
||||
|
||||
protected MultiShareArgs(Parcel in) {
|
||||
shareContactAndThreads = new HashSet<>(in.createTypedArrayList(ShareContactAndThread.CREATOR));
|
||||
media = in.createTypedArrayList(Media.CREATOR);
|
||||
draftText = in.readString();
|
||||
stickerLocator = in.readParcelable(StickerLocator.class.getClassLoader());
|
||||
borderless = in.readByte() != 0;
|
||||
dataUri = in.readParcelable(Uri.class.getClassLoader());
|
||||
dataType = in.readString();
|
||||
viewOnce = in.readByte() != 0;
|
||||
|
||||
LinkPreview preview;
|
||||
try {
|
||||
preview = LinkPreview.deserialize(in.readString());
|
||||
} catch (IOException e) {
|
||||
preview = null;
|
||||
}
|
||||
|
||||
linkPreview = preview;
|
||||
}
|
||||
|
||||
public Set<ShareContactAndThread> getShareContactAndThreads() {
|
||||
return shareContactAndThreads;
|
||||
}
|
||||
|
||||
public ArrayList<Media> getMedia() {
|
||||
return media;
|
||||
}
|
||||
|
||||
public StickerLocator getStickerLocator() {
|
||||
return stickerLocator;
|
||||
}
|
||||
|
||||
public String getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public String getDraftText() {
|
||||
return draftText;
|
||||
}
|
||||
|
||||
public Uri getDataUri() {
|
||||
return dataUri;
|
||||
}
|
||||
|
||||
public boolean isBorderless() {
|
||||
return borderless;
|
||||
}
|
||||
|
||||
public boolean isViewOnce() {
|
||||
return viewOnce;
|
||||
}
|
||||
|
||||
public @Nullable LinkPreview getLinkPreview() {
|
||||
return linkPreview;
|
||||
}
|
||||
|
||||
public @NonNull InterstitialContentType getInterstitialContentType() {
|
||||
if (!requiresInterstitial()) {
|
||||
return InterstitialContentType.NONE;
|
||||
} else if (!this.getMedia().isEmpty() ||
|
||||
(this.getDataUri() != null && this.getDataUri() != Uri.EMPTY && this.getDataType() != null))
|
||||
{
|
||||
return InterstitialContentType.MEDIA;
|
||||
} else if (!TextUtils.isEmpty(this.getDraftText())) {
|
||||
return InterstitialContentType.TEXT;
|
||||
} else {
|
||||
return InterstitialContentType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final Creator<MultiShareArgs> CREATOR = new Creator<MultiShareArgs>() {
|
||||
@Override
|
||||
public MultiShareArgs createFromParcel(Parcel in) {
|
||||
return new MultiShareArgs(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiShareArgs[] newArray(int size) {
|
||||
return new MultiShareArgs[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeTypedList(Stream.of(shareContactAndThreads).toList());
|
||||
dest.writeTypedList(media);
|
||||
dest.writeString(draftText);
|
||||
dest.writeParcelable(stickerLocator, flags);
|
||||
dest.writeByte((byte) (borderless ? 1 : 0));
|
||||
dest.writeParcelable(dataUri, flags);
|
||||
dest.writeString(dataType);
|
||||
dest.writeByte((byte) (viewOnce ? 1 : 0));
|
||||
|
||||
if (linkPreview != null) {
|
||||
try {
|
||||
dest.writeString(linkPreview.serialize());
|
||||
} catch (IOException e) {
|
||||
dest.writeString("");
|
||||
}
|
||||
} else {
|
||||
dest.writeString("");
|
||||
}
|
||||
}
|
||||
|
||||
public Builder buildUpon() {
|
||||
return new Builder(shareContactAndThreads).asBorderless(borderless)
|
||||
.asViewOnce(viewOnce)
|
||||
.withDataType(dataType)
|
||||
.withDataUri(dataUri)
|
||||
.withDraftText(draftText)
|
||||
.withLinkPreview(linkPreview)
|
||||
.withMedia(media)
|
||||
.withStickerLocator(stickerLocator);
|
||||
}
|
||||
|
||||
private boolean requiresInterstitial() {
|
||||
return !media.isEmpty() || !TextUtils.isEmpty(draftText) || MediaUtil.isImageOrVideoType(dataType);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private final Set<ShareContactAndThread> shareContactAndThreads;
|
||||
|
||||
private ArrayList<Media> media;
|
||||
private String draftText;
|
||||
private StickerLocator stickerLocator;
|
||||
private boolean borderless;
|
||||
private Uri dataUri;
|
||||
private String dataType;
|
||||
private LinkPreview linkPreview;
|
||||
private boolean viewOnce;
|
||||
|
||||
public Builder(@NonNull Set<ShareContactAndThread> shareContactAndThreads) {
|
||||
this.shareContactAndThreads = shareContactAndThreads;
|
||||
}
|
||||
|
||||
public @NonNull Builder withMedia(@Nullable ArrayList<Media> media) {
|
||||
this.media = media;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder withDraftText(@Nullable String draftText) {
|
||||
this.draftText = draftText;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder withStickerLocator(@Nullable StickerLocator stickerLocator) {
|
||||
this.stickerLocator = stickerLocator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder asBorderless(boolean borderless) {
|
||||
this.borderless = borderless;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder withDataUri(@Nullable Uri dataUri) {
|
||||
this.dataUri = dataUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder withDataType(@Nullable String dataType) {
|
||||
this.dataType = dataType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder withLinkPreview(@Nullable LinkPreview linkPreview) {
|
||||
this.linkPreview = linkPreview;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Builder asViewOnce(boolean viewOnce) {
|
||||
this.viewOnce = viewOnce;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull MultiShareArgs build() {
|
||||
return new MultiShareArgs(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user