mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 21:18:33 +00:00
Implement new Message Request UI.
This commit is contained in:
parent
88c0e6f8ab
commit
6933ca50a7
6
res/drawable/message_request_button_background_dark.xml
Normal file
6
res/drawable/message_request_button_background_dark.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- TODO - Consolidate using attr if we move away from API 19 -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||||
|
<solid android:color="@color/core_grey_75"/>
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
</shape>
|
6
res/drawable/message_request_button_background_light.xml
Normal file
6
res/drawable/message_request_button_background_light.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- TODO - Consolidate using attr if we move away from API 19 -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||||
|
<solid android:color="@color/core_grey_05"/>
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
</shape>
|
@ -133,4 +133,13 @@
|
|||||||
|
|
||||||
</org.thoughtcrime.securesms.components.InputAwareLayout>
|
</org.thoughtcrime.securesms.components.InputAwareLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fragment_overlay_container"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
|
android:background="?conversation_background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
@ -37,10 +37,10 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/contact_photo_container"
|
android:id="@+id/contact_photo_container"
|
||||||
android:layout_width="@dimen/conversation_item_avatar_size"
|
android:layout_width="@dimen/conversation_item_avatar_size"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/conversation_item_avatar_size"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentBottom="true">
|
android:layout_alignBottom="@id/body_bubble">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
android:id="@+id/contact_photo"
|
android:id="@+id/contact_photo"
|
||||||
|
119
res/layout/message_request_fragment.xml
Normal file
119
res/layout/message_request_fragment.xml
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?conversation_background">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
|
android:id="@+id/message_request_avatar"
|
||||||
|
android:layout_width="112dp"
|
||||||
|
android:layout_height="112dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message_request_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="@style/Signal.Text.MessageRequest.Title"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/message_request_avatar"
|
||||||
|
tools:text="Cayce Pollard" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message_request_subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="@style/Signal.Text.MessageRequest.Subtitle"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/message_request_title"
|
||||||
|
tools:text="\@caycepollard" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
|
android:id="@+id/message_request_description"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="@style/Signal.Text.MessageRequest.Description"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/message_request_subtitle"
|
||||||
|
tools:text="Member of NYC Rock Climbers, Dinner Party and Friends" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/message_request_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_marginTop="27dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/message_request_question"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/message_request_description" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message_request_question"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="11dp"
|
||||||
|
android:textAppearance="@style/Signal.Text.MessageRequest.Description"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/message_request_block"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:text="Do you want to let Cayce Pollard message you? They won't know you've seen their message until you accept." />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/message_request_block"
|
||||||
|
style="@style/Signal.MessageRequest.Button.Deny"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="@string/MessageRequestBottomView_block"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/message_request_delete"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/message_request_delete"
|
||||||
|
style="@style/Signal.MessageRequest.Button.Deny"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="@string/MessageRequestBottomView_delete"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/message_request_block"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/message_request_accept"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/message_request_block"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/message_request_block" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/message_request_accept"
|
||||||
|
style="@style/Signal.MessageRequest.Button.Accept"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="@string/MessageRequestBottomView_accept"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/message_request_block"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/message_request_delete"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/message_request_block" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -181,6 +181,10 @@
|
|||||||
<attr name="advanced_icon" format="reference" />
|
<attr name="advanced_icon" format="reference" />
|
||||||
<attr name="safety_number_icon" format="reference" />
|
<attr name="safety_number_icon" format="reference" />
|
||||||
|
|
||||||
|
<attr name="message_request_dialog_button_background" format="reference" />
|
||||||
|
<attr name="message_request_text_color_primary" format="color" />
|
||||||
|
<attr name="message_request_text_color_secondary" format="color" />
|
||||||
|
|
||||||
<attr name="pref_icon_tint" format="color"/>
|
<attr name="pref_icon_tint" format="color"/>
|
||||||
|
|
||||||
<attr name="pref_divider" format="reference" />
|
<attr name="pref_divider" format="reference" />
|
||||||
|
@ -1661,6 +1661,18 @@
|
|||||||
<string name="RegistrationLockDialog_reminder">Reminder:</string>
|
<string name="RegistrationLockDialog_reminder">Reminder:</string>
|
||||||
<string name="recipient_preferences__about">About</string>
|
<string name="recipient_preferences__about">About</string>
|
||||||
<string name="Recipient_unknown">Unknown</string>
|
<string name="Recipient_unknown">Unknown</string>
|
||||||
|
<string name="MessageRequestBottomView_accept">Accept</string>
|
||||||
|
<string name="MessageRequestBottomView_delete">Delete</string>
|
||||||
|
<string name="MessageRequestBottomView_block">Block</string>
|
||||||
|
<string name="MessageRequestBottomView_do_you_want_to_let">Do you want to receive messages from %1$s?</string>
|
||||||
|
<string name="MessageRequestProfileView_member_of_one_group">Member of %1$s</string>
|
||||||
|
<string name="MessageRequestProfileView_member_of_two_groups">Member of %1$s and %2$s</string>
|
||||||
|
<string name="MessageRequestProfileView_member_of_many_groups">Member of %1$s, %2$s, and %3$s</string>
|
||||||
|
<string name="MessageRequestProfileView_members">%1$d members</string>
|
||||||
|
<plurals name="MessageRequestProfileView_member_of_others">
|
||||||
|
<item quantity="one">%d other</item>
|
||||||
|
<item quantity="other">%d others</item>
|
||||||
|
</plurals>
|
||||||
<!-- EOF -->
|
<!-- EOF -->
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -383,4 +383,19 @@
|
|||||||
<attr name="arcSweepAngle" format="float" />
|
<attr name="arcSweepAngle" format="float" />
|
||||||
<attr name="arcProgress" format="float" />
|
<attr name="arcProgress" format="float" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
<style name="Signal.MessageRequest.Button" parent="Button.Borderless">
|
||||||
|
<item name="android:textAllCaps">false</item>
|
||||||
|
<item name="android:layout_width">0dp</item>
|
||||||
|
<item name="android:layout_height">48dp</item>
|
||||||
|
<item name="android:background">?message_request_dialog_button_background</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Signal.MessageRequest.Button.Deny">
|
||||||
|
<item name="android:textColor">@color/core_red</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Signal.MessageRequest.Button.Accept">
|
||||||
|
<item name="android:textColor">@color/core_blue</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -87,4 +87,19 @@
|
|||||||
<item name="android:textStyle">bold</item>
|
<item name="android:textStyle">bold</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Signal.Text.MessageRequest.Title" parent="Base.TextAppearance.AppCompat.Title">
|
||||||
|
<item name="android:textSize">20sp</item>
|
||||||
|
<item name="android:textColor">?message_request_text_color_primary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Signal.Text.MessageRequest.Subtitle" parent="Base.TextAppearance.AppCompat.Caption">
|
||||||
|
<item name="android:textSize">14sp</item>
|
||||||
|
<item name="android:textColor">?message_request_text_color_secondary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Signal.Text.MessageRequest.Description" parent="Base.TextAppearance.AppCompat.Subhead">
|
||||||
|
<item name="android:textSize">14sp</item>
|
||||||
|
<item name="android:textColor">?message_request_text_color_secondary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -326,6 +326,9 @@
|
|||||||
<item name="linked_devices_icon">@drawable/ic_linked_devices_24</item>
|
<item name="linked_devices_icon">@drawable/ic_linked_devices_24</item>
|
||||||
<item name="advanced_icon">@drawable/ic_advanced_24</item>
|
<item name="advanced_icon">@drawable/ic_advanced_24</item>
|
||||||
<item name="safety_number_icon">@drawable/ic_safety_number_outline_24</item>
|
<item name="safety_number_icon">@drawable/ic_safety_number_outline_24</item>
|
||||||
|
<item name="message_request_dialog_button_background">@drawable/message_request_button_background_light</item>
|
||||||
|
<item name="message_request_text_color_primary">@color/core_grey_90</item>
|
||||||
|
<item name="message_request_text_color_secondary">@color/core_grey_60</item>
|
||||||
|
|
||||||
<item name="conversation_icon_attach_audio">@drawable/ic_audio_light</item>
|
<item name="conversation_icon_attach_audio">@drawable/ic_audio_light</item>
|
||||||
<item name="conversation_icon_attach_video">@drawable/ic_video_light</item>
|
<item name="conversation_icon_attach_video">@drawable/ic_video_light</item>
|
||||||
@ -549,6 +552,9 @@
|
|||||||
<item name="linked_devices_icon">@drawable/ic_linked_devices_24</item>
|
<item name="linked_devices_icon">@drawable/ic_linked_devices_24</item>
|
||||||
<item name="advanced_icon">@drawable/ic_advanced_24</item>
|
<item name="advanced_icon">@drawable/ic_advanced_24</item>
|
||||||
<item name="safety_number_icon">@drawable/ic_safety_number_solid_24</item>
|
<item name="safety_number_icon">@drawable/ic_safety_number_solid_24</item>
|
||||||
|
<item name="message_request_dialog_button_background">@drawable/message_request_button_background_dark</item>
|
||||||
|
<item name="message_request_text_color_primary">@color/core_grey_05</item>
|
||||||
|
<item name="message_request_text_color_secondary">@color/core_grey_25</item>
|
||||||
|
|
||||||
<item name="conversation_icon_attach_audio">@drawable/ic_audio_dark</item>
|
<item name="conversation_icon_attach_audio">@drawable/ic_audio_dark</item>
|
||||||
<item name="conversation_icon_attach_video">@drawable/ic_video_dark</item>
|
<item name="conversation_icon_attach_video">@drawable/ic_video_dark</item>
|
||||||
|
@ -34,9 +34,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
|||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
import android.telephony.PhoneNumberUtils;
|
import android.telephony.PhoneNumberUtils;
|
||||||
@ -48,7 +46,6 @@ import android.view.ViewGroup;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
@ -62,11 +59,9 @@ import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
|||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
|
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
|
||||||
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
|
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
||||||
@ -83,12 +78,12 @@ import org.thoughtcrime.securesms.util.DynamicDarkToolbarTheme;
|
|||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@ -750,38 +745,13 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setBlocked(@NonNull final Context context, final Recipient recipient, final boolean blocked) {
|
private void setBlocked(@NonNull final Context context, final Recipient recipient, final boolean blocked) {
|
||||||
new AsyncTask<Void, Void, Void>() {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
@Override
|
if (blocked) {
|
||||||
protected Void doInBackground(Void... params) {
|
RecipientUtil.block(context, recipient);
|
||||||
DatabaseFactory.getRecipientDatabase(context)
|
} else {
|
||||||
.setBlocked(recipient.getId(), blocked);
|
RecipientUtil.unblock(context, recipient);
|
||||||
|
|
||||||
if (recipient.isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireGroupId())) {
|
|
||||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
|
||||||
Optional<OutgoingGroupMediaMessage> leaveMessage = GroupUtil.createGroupLeaveMessage(context, recipient);
|
|
||||||
|
|
||||||
if (threadId != -1 && leaveMessage.isPresent()) {
|
|
||||||
MessageSender.send(context, leaveMessage.get(), threadId, false, null);
|
|
||||||
|
|
||||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
|
||||||
String groupId = recipient.requireGroupId();
|
|
||||||
groupDatabase.setActive(groupId, false);
|
|
||||||
groupDatabase.remove(groupId, Recipient.self().getId());
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Failed to leave group. Can't block.");
|
|
||||||
Toast.makeText(context, R.string.RecipientPreferenceActivity_error_leaving_group, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blocked && (recipient.resolve().isSystemContact() || recipient.resolve().isProfileSharing())) {
|
|
||||||
ApplicationDependencies.getJobManager().add(new RotateProfileKeyJob());
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().add(new MultiDeviceBlockedUpdateJob());
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ import android.view.View.OnKeyListener;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@ -152,7 +153,6 @@ import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
|||||||
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
||||||
import org.thoughtcrime.securesms.invites.InviteReminderModel;
|
import org.thoughtcrime.securesms.invites.InviteReminderModel;
|
||||||
import org.thoughtcrime.securesms.invites.InviteReminderRepository;
|
import org.thoughtcrime.securesms.invites.InviteReminderRepository;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||||
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
|
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
@ -162,6 +162,8 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
|
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
||||||
|
import org.thoughtcrime.securesms.messagerequests.MessageRequestFragment;
|
||||||
|
import org.thoughtcrime.securesms.messagerequests.MessageRequestFragmentViewModel;
|
||||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||||
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
|
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
|
||||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||||
@ -193,6 +195,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||||
import org.thoughtcrime.securesms.search.model.MessageResult;
|
import org.thoughtcrime.securesms.search.model.MessageResult;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
@ -213,6 +216,7 @@ import org.thoughtcrime.securesms.util.DynamicDarkToolbarTheme;
|
|||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
@ -225,6 +229,7 @@ import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
|||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
import org.whispersystems.libsignal.InvalidMessageException;
|
import org.whispersystems.libsignal.InvalidMessageException;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
@ -307,6 +312,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
private TypingStatusTextWatcher typingTextWatcher;
|
private TypingStatusTextWatcher typingTextWatcher;
|
||||||
private ConversationSearchBottomBar searchNav;
|
private ConversationSearchBottomBar searchNav;
|
||||||
private MenuItem searchViewItem;
|
private MenuItem searchViewItem;
|
||||||
|
private FrameLayout messageRequestOverlay;
|
||||||
|
|
||||||
private AttachmentTypeSelector attachmentTypeSelector;
|
private AttachmentTypeSelector attachmentTypeSelector;
|
||||||
private AttachmentManager attachmentManager;
|
private AttachmentManager attachmentManager;
|
||||||
@ -909,16 +915,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
.setMessage(bodyRes)
|
.setMessage(bodyRes)
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> {
|
.setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> {
|
||||||
new AsyncTask<Void, Void, Void>() {
|
SimpleTask.run(() -> {
|
||||||
@Override
|
RecipientUtil.unblock(ConversationActivity.this, recipient.get());
|
||||||
protected Void doInBackground(Void... params) {
|
return RecipientUtil.isRecipientMessageRequestAccepted(ConversationActivity.this, recipient.get());
|
||||||
DatabaseFactory.getRecipientDatabase(ConversationActivity.this)
|
}, messageRequestAccepted -> {
|
||||||
.setBlocked(recipient.getId(), false);
|
if (!messageRequestAccepted) {
|
||||||
ApplicationDependencies.getJobManager().add(new MultiDeviceBlockedUpdateJob());
|
onMessageRequest();
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
});
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1564,6 +1568,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
|
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
|
||||||
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
|
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||||
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
|
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
|
||||||
|
messageRequestOverlay = ViewUtil.findById(this, R.id.fragment_overlay_container);
|
||||||
|
|
||||||
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
|
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
|
||||||
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
|
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
|
||||||
@ -1977,7 +1982,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setGroupShareProfileReminder(@NonNull Recipient recipient) {
|
private void setGroupShareProfileReminder(@NonNull Recipient recipient) {
|
||||||
if (recipient.isPushGroup() && !recipient.isProfileSharing()) {
|
if (!FeatureFlags.MESSAGE_REQUESTS && recipient.isPushGroup() && !recipient.isProfileSharing()) {
|
||||||
groupShareProfileView.get().setRecipient(recipient);
|
groupShareProfileView.get().setRecipient(recipient);
|
||||||
groupShareProfileView.get().setVisibility(View.VISIBLE);
|
groupShareProfileView.get().setVisibility(View.VISIBLE);
|
||||||
} else if (groupShareProfileView.resolved()) {
|
} else if (groupShareProfileView.resolved()) {
|
||||||
@ -2696,6 +2701,46 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageRequest() {
|
||||||
|
long threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
|
||||||
|
RecipientId recipientId = getIntent().getParcelableExtra(RECIPIENT_EXTRA);
|
||||||
|
|
||||||
|
if (threadId == -1) {
|
||||||
|
throw new IllegalStateException("MessageRequest is not supported here");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipientId == null) {
|
||||||
|
Log.w(TAG, "onMessageRequest: " + threadId + ": null recipient. finishing...");
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "onMessageRequest: " + threadId + ", " + recipientId.serialize());
|
||||||
|
|
||||||
|
MessageRequestFragmentViewModel.Factory factory = new MessageRequestFragmentViewModel.Factory(this, threadId, recipientId);
|
||||||
|
MessageRequestFragmentViewModel viewModel = ViewModelProviders.of(this, factory).get(MessageRequestFragmentViewModel.class);
|
||||||
|
MessageRequestFragment fragment = new MessageRequestFragment();
|
||||||
|
|
||||||
|
messageRequestOverlay.setVisibility(View.VISIBLE);
|
||||||
|
container.setVisibility(View.GONE);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.fragment_overlay_container, fragment)
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
viewModel.getState().observe(this, state -> {
|
||||||
|
switch (state.messageRequestState) {
|
||||||
|
case ACCEPTED:
|
||||||
|
getSupportFragmentManager().popBackStack();
|
||||||
|
messageRequestOverlay.setVisibility(View.GONE);
|
||||||
|
container.setVisibility(View.VISIBLE);
|
||||||
|
return;
|
||||||
|
case DELETED:
|
||||||
|
case BLOCKED:
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setThreadId(long threadId) {
|
public void setThreadId(long threadId) {
|
||||||
this.threadId = threadId;
|
this.threadId = threadId;
|
||||||
|
@ -94,6 +94,7 @@ import org.thoughtcrime.securesms.providers.BlobProvider;
|
|||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageActivity;
|
import org.thoughtcrime.securesms.revealable.ViewOnceMessageActivity;
|
||||||
import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
|
import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
@ -101,6 +102,7 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
|||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
|
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@ -686,10 +688,18 @@ public class ConversationFragment extends Fragment
|
|||||||
setLastSeen(loader.getLastSeen());
|
setLastSeen(loader.getLastSeen());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loader.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isGroup() && recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
|
if (FeatureFlags.MESSAGE_REQUESTS) {
|
||||||
adapter.setHeaderView(unknownSenderView);
|
if (!loader.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isProfileSharing() && !recipient.get().isBlocked() && recipient.get().isRegistered()) {
|
||||||
|
listener.onMessageRequest();
|
||||||
|
} else {
|
||||||
|
clearHeaderIfNotTyping(adapter);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
clearHeaderIfNotTyping(adapter);
|
if (!loader.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isGroup() && recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
|
||||||
|
adapter.setHeaderView(unknownSenderView);
|
||||||
|
} else {
|
||||||
|
clearHeaderIfNotTyping(adapter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loader.hasOffset()) {
|
if (loader.hasOffset()) {
|
||||||
@ -859,6 +869,7 @@ public class ConversationFragment extends Fragment
|
|||||||
void handleReplyMessage(MessageRecord messageRecord);
|
void handleReplyMessage(MessageRecord messageRecord);
|
||||||
void onMessageActionToolbarOpened();
|
void onMessageActionToolbarOpened();
|
||||||
void onForwardClicked();
|
void onForwardClicked();
|
||||||
|
void onMessageRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConversationScrollListener extends OnScrollListener {
|
private class ConversationScrollListener extends OnScrollListener {
|
||||||
|
@ -154,6 +154,26 @@ public class GroupDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getGroupNamesContainingMember(RecipientId recipientId) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
|
List<String> groupNames = new LinkedList<>();
|
||||||
|
String[] projection = new String[]{TITLE, MEMBERS};
|
||||||
|
String query = MEMBERS + " LIKE ?";
|
||||||
|
String[] args = new String[]{"%" + recipientId.serialize() + "%"};
|
||||||
|
|
||||||
|
try (Cursor cursor = database.query(TABLE_NAME, projection, query, args, null, null, null)) {
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
List<String> members = Util.split(cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), ",");
|
||||||
|
|
||||||
|
if (members.contains(recipientId.serialize())) {
|
||||||
|
groupNames.add(cursor.getString(cursor.getColumnIndexOrThrow(TITLE)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupNames;
|
||||||
|
}
|
||||||
|
|
||||||
public Reader getGroups() {
|
public Reader getGroups() {
|
||||||
@SuppressLint("Recycle")
|
@SuppressLint("Recycle")
|
||||||
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null);
|
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null);
|
||||||
|
@ -467,6 +467,11 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
|
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
|
||||||
|
return setMessagesRead(THREAD_ID + " = ?", new String[] {String.valueOf(threadId)});
|
||||||
|
}
|
||||||
|
|
||||||
public List<MarkedMessageInfo> setAllMessagesRead() {
|
public List<MarkedMessageInfo> setAllMessagesRead() {
|
||||||
return setMessagesRead(READ + " = 0", null);
|
return setMessagesRead(READ + " = 0", null);
|
||||||
}
|
}
|
||||||
|
@ -406,6 +406,10 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
return expiring;
|
return expiring;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
|
||||||
|
return setMessagesRead(THREAD_ID + " = ?", new String[] {String.valueOf(threadId)});
|
||||||
|
}
|
||||||
|
|
||||||
public List<MarkedMessageInfo> setMessagesRead(long threadId) {
|
public List<MarkedMessageInfo> setMessagesRead(long threadId) {
|
||||||
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
|
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import android.content.Context;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.MergeCursor;
|
import android.database.MergeCursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@ -279,16 +280,22 @@ public class ThreadDatabase extends Database {
|
|||||||
|
|
||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
|
|
||||||
return new LinkedList<MarkedMessageInfo>() {{
|
return Util.concatenatedList(smsRecords, mmsRecords);
|
||||||
addAll(smsRecords);
|
|
||||||
addAll(mmsRecords);
|
|
||||||
}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCalledSince(@NonNull Recipient recipient, long timestamp) {
|
public boolean hasCalledSince(@NonNull Recipient recipient, long timestamp) {
|
||||||
return DatabaseFactory.getMmsSmsDatabase(context).hasReceivedAnyCallsSince(getThreadIdFor(recipient), timestamp);
|
return DatabaseFactory.getMmsSmsDatabase(context).hasReceivedAnyCallsSince(getThreadIdFor(recipient), timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
|
||||||
|
setRead(threadId, false);
|
||||||
|
|
||||||
|
final List<MarkedMessageInfo> smsRecords = DatabaseFactory.getSmsDatabase(context).setEntireThreadRead(threadId);
|
||||||
|
final List<MarkedMessageInfo> mmsRecords = DatabaseFactory.getMmsDatabase(context).setEntireThreadRead(threadId);
|
||||||
|
|
||||||
|
return Util.concatenatedList(smsRecords, mmsRecords);
|
||||||
|
}
|
||||||
|
|
||||||
public List<MarkedMessageInfo> setRead(long threadId, boolean lastSeen) {
|
public List<MarkedMessageInfo> setRead(long threadId, boolean lastSeen) {
|
||||||
ContentValues contentValues = new ContentValues(1);
|
ContentValues contentValues = new ContentValues(1);
|
||||||
contentValues.put(READ, 1);
|
contentValues.put(READ, 1);
|
||||||
@ -299,6 +306,7 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
|
|
||||||
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""});
|
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""});
|
||||||
|
|
||||||
final List<MarkedMessageInfo> smsRecords = DatabaseFactory.getSmsDatabase(context).setMessagesRead(threadId);
|
final List<MarkedMessageInfo> smsRecords = DatabaseFactory.getSmsDatabase(context).setMessagesRead(threadId);
|
||||||
@ -306,10 +314,7 @@ public class ThreadDatabase extends Database {
|
|||||||
|
|
||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
|
|
||||||
return new LinkedList<MarkedMessageInfo>() {{
|
return Util.concatenatedList(smsRecords, mmsRecords);
|
||||||
addAll(smsRecords);
|
|
||||||
addAll(mmsRecords);
|
|
||||||
}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void incrementUnread(long threadId, int amount) {
|
public void incrementUnread(long threadId, int amount) {
|
||||||
|
@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||||
@ -87,7 +86,12 @@ public class SendReadReceiptJob extends BaseJob {
|
|||||||
public void onRun() throws IOException, UntrustedIdentityException {
|
public void onRun() throws IOException, UntrustedIdentityException {
|
||||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context) || messageIds.isEmpty()) return;
|
if (!TextSecurePreferences.isReadReceiptsEnabled(context) || messageIds.isEmpty()) return;
|
||||||
|
|
||||||
Recipient recipient = Recipient.resolved(recipientId);
|
Recipient recipient = Recipient.resolved(recipientId);
|
||||||
|
if (!RecipientUtil.isRecipientMessageRequestAccepted(context, recipient)) {
|
||||||
|
Log.w(TAG, "Refusing to send receipts to untrusted recipient");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||||
SignalServiceAddress remoteAddress = RecipientUtil.toSignalServiceAddress(context, recipient);
|
SignalServiceAddress remoteAddress = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||||
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp);
|
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp);
|
||||||
|
@ -0,0 +1,172 @@
|
|||||||
|
package org.thoughtcrime.securesms.messagerequests;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.text.HtmlCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||||
|
import org.thoughtcrime.securesms.conversation.ConversationItem;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class MessageRequestFragment extends Fragment {
|
||||||
|
|
||||||
|
private AvatarImageView contactAvatar;
|
||||||
|
private TextView contactTitle;
|
||||||
|
private TextView contactSubtitle;
|
||||||
|
private TextView contactDescription;
|
||||||
|
private FrameLayout messageView;
|
||||||
|
private TextView question;
|
||||||
|
private Button accept;
|
||||||
|
private Button block;
|
||||||
|
private Button delete;
|
||||||
|
private ConversationItem conversationItem;
|
||||||
|
|
||||||
|
private MessageRequestFragmentViewModel viewModel;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
return inflater.inflate(R.layout.message_request_fragment, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
contactAvatar = view.findViewById(R.id.message_request_avatar);
|
||||||
|
contactTitle = view.findViewById(R.id.message_request_title);
|
||||||
|
contactSubtitle = view.findViewById(R.id.message_request_subtitle);
|
||||||
|
contactDescription = view.findViewById(R.id.message_request_description);
|
||||||
|
messageView = view.findViewById(R.id.message_request_message);
|
||||||
|
question = view.findViewById(R.id.message_request_question);
|
||||||
|
accept = view.findViewById(R.id.message_request_accept);
|
||||||
|
block = view.findViewById(R.id.message_request_block);
|
||||||
|
delete = view.findViewById(R.id.message_request_delete);
|
||||||
|
|
||||||
|
initializeViewModel();
|
||||||
|
initializeBottomViewListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViewModel() {
|
||||||
|
viewModel = ViewModelProviders.of(requireActivity()).get(MessageRequestFragmentViewModel.class);
|
||||||
|
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
|
||||||
|
if (state.messageRecord == null || state.recipient == null) return;
|
||||||
|
|
||||||
|
presentConversationItemTo(state.messageRecord, state.recipient);
|
||||||
|
presentMessageRequestBottomViewTo(state.recipient);
|
||||||
|
presentMessageRequestProfileViewTo(state.recipient, state.groups, state.memberCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentConversationItemTo(@NonNull MessageRecord messageRecord, @NonNull Recipient recipient) {
|
||||||
|
if (messageRecord.isGroupAction()) {
|
||||||
|
if (conversationItem != null) {
|
||||||
|
messageView.removeAllViews();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversationItem == null) {
|
||||||
|
conversationItem = (ConversationItem) LayoutInflater.from(requireActivity()).inflate(R.layout.conversation_item_received, messageView, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
conversationItem.bind(messageRecord,
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
GlideApp.with(this),
|
||||||
|
Locale.getDefault(),
|
||||||
|
Collections.emptySet(),
|
||||||
|
recipient,
|
||||||
|
null,
|
||||||
|
false);
|
||||||
|
|
||||||
|
if (messageView.getChildCount() == 0 || messageView.getChildAt(0) != conversationItem) {
|
||||||
|
messageView.removeAllViews();
|
||||||
|
messageView.addView(conversationItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentMessageRequestProfileViewTo(@Nullable Recipient recipient, @Nullable List<String> groups, int memberCount) {
|
||||||
|
if (recipient != null) {
|
||||||
|
contactAvatar.setAvatar(GlideApp.with(this), recipient, false);
|
||||||
|
|
||||||
|
String title = recipient.getDisplayName(requireContext());
|
||||||
|
contactTitle.setText(title);
|
||||||
|
|
||||||
|
if (recipient.isGroup()) {
|
||||||
|
contactSubtitle.setText(getString(R.string.MessageRequestProfileView_members, memberCount));
|
||||||
|
} else {
|
||||||
|
String subtitle = recipient.getUsername().or(recipient.getE164()).orNull();
|
||||||
|
|
||||||
|
if (subtitle == null || subtitle.equals(title)) {
|
||||||
|
contactSubtitle.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
contactSubtitle.setText(subtitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groups == null || groups.isEmpty()) {
|
||||||
|
contactDescription.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
switch (groups.size()) {
|
||||||
|
case 1:
|
||||||
|
description = getString(R.string.MessageRequestProfileView_member_of_one_group, bold(groups.get(0)));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
description = getString(R.string.MessageRequestProfileView_member_of_two_groups, bold(groups.get(0)), bold(groups.get(1)));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
description = getString(R.string.MessageRequestProfileView_member_of_many_groups, bold(groups.get(0)), bold(groups.get(1)), bold(groups.get(2)));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
int others = groups.size() - 2;
|
||||||
|
description = getString(R.string.MessageRequestProfileView_member_of_many_groups,
|
||||||
|
bold(groups.get(0)),
|
||||||
|
bold(groups.get(1)),
|
||||||
|
getResources().getQuantityString(R.plurals.MessageRequestProfileView_member_of_others, others, others));
|
||||||
|
}
|
||||||
|
|
||||||
|
contactDescription.setText(HtmlCompat.fromHtml(description, 0));
|
||||||
|
contactDescription.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull String bold(@NonNull String target) {
|
||||||
|
return "<b>" + target + "</b>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentMessageRequestBottomViewTo(@Nullable Recipient recipient) {
|
||||||
|
if (recipient == null) return;
|
||||||
|
|
||||||
|
question.setText(HtmlCompat.fromHtml(getString(R.string.MessageRequestBottomView_do_you_want_to_let, bold(recipient.getDisplayName(requireContext()))), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeBottomViewListeners() {
|
||||||
|
accept.setOnClickListener(v -> viewModel.accept());
|
||||||
|
delete.setOnClickListener(v -> viewModel.delete());
|
||||||
|
block.setOnClickListener(v -> viewModel.block());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package org.thoughtcrime.securesms.messagerequests;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.util.Consumer;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
||||||
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MessageRequestFragmentRepository {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
private final long threadId;
|
||||||
|
private final LiveRecipient liveRecipient;
|
||||||
|
|
||||||
|
public MessageRequestFragmentRepository(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.threadId = threadId;
|
||||||
|
this.liveRecipient = Recipient.live(recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveRecipient getLiveRecipient() {
|
||||||
|
return liveRecipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshRecipient() {
|
||||||
|
SignalExecutors.BOUNDED.execute(liveRecipient::refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getMessageRecord(@NonNull Consumer<MessageRecord> onMessageRecordLoaded) {
|
||||||
|
SimpleTask.run(() -> {
|
||||||
|
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
|
||||||
|
try (Cursor cursor = mmsSmsDatabase.getConversation(threadId, 0, 1)) {
|
||||||
|
if (!cursor.moveToFirst()) return null;
|
||||||
|
return mmsSmsDatabase.readerFor(cursor).getCurrent();
|
||||||
|
}
|
||||||
|
}, onMessageRecordLoaded::accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getGroups(@NonNull Consumer<List<String>> onGroupsLoaded) {
|
||||||
|
SimpleTask.run(() -> {
|
||||||
|
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||||
|
return groupDatabase.getGroupNamesContainingMember(recipientId);
|
||||||
|
}, onGroupsLoaded::accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getMemberCount(@NonNull Consumer<Integer> onMemberCountLoaded) {
|
||||||
|
SimpleTask.run(() -> {
|
||||||
|
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||||
|
Optional<GroupDatabase.GroupRecord> groupRecord = groupDatabase.getGroup(recipientId);
|
||||||
|
return groupRecord.transform(record -> record.getMembers().size()).or(0);
|
||||||
|
}, onMemberCountLoaded::accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void acceptMessageRequest(@NonNull Runnable onMessageRequestAccepted) {
|
||||||
|
SimpleTask.run(() -> {
|
||||||
|
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||||
|
recipientDatabase.setProfileSharing(recipientId, true);
|
||||||
|
liveRecipient.refresh();
|
||||||
|
|
||||||
|
List<MessagingDatabase.MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context)
|
||||||
|
.setEntireThreadRead(threadId);
|
||||||
|
MessageNotifier.updateNotification(context);
|
||||||
|
MarkReadReceiver.process(context, messageIds);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, v -> onMessageRequestAccepted.run());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteMessageRequest(@NonNull Runnable onMessageRequestDeleted) {
|
||||||
|
SimpleTask.run(() -> {
|
||||||
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
|
threadDatabase.deleteConversation(threadId);
|
||||||
|
return null;
|
||||||
|
}, v -> onMessageRequestDeleted.run());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void blockMessageRequest(@NonNull Runnable onMessageRequestBlocked) {
|
||||||
|
SimpleTask.run(() -> {
|
||||||
|
Recipient recipient = liveRecipient.resolve();
|
||||||
|
RecipientUtil.block(context, recipient);
|
||||||
|
liveRecipient.refresh();
|
||||||
|
return null;
|
||||||
|
}, v -> onMessageRequestBlocked.run());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package org.thoughtcrime.securesms.messagerequests;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MessageRequestFragmentState {
|
||||||
|
|
||||||
|
public enum MessageRequestState {
|
||||||
|
LOADING,
|
||||||
|
PENDING,
|
||||||
|
BLOCKED,
|
||||||
|
DELETED,
|
||||||
|
ACCEPTED
|
||||||
|
}
|
||||||
|
|
||||||
|
public final @NonNull MessageRequestState messageRequestState;
|
||||||
|
public final @Nullable MessageRecord messageRecord;
|
||||||
|
public final @Nullable Recipient recipient;
|
||||||
|
public final @Nullable List<String> groups;
|
||||||
|
public final int memberCount;
|
||||||
|
|
||||||
|
|
||||||
|
public MessageRequestFragmentState(@NonNull MessageRequestState messageRequestState,
|
||||||
|
@Nullable MessageRecord messageRecord,
|
||||||
|
@Nullable Recipient recipient,
|
||||||
|
@Nullable List<String> groups,
|
||||||
|
int memberCount)
|
||||||
|
{
|
||||||
|
this.messageRequestState = messageRequestState;
|
||||||
|
this.messageRecord = messageRecord;
|
||||||
|
this.recipient = recipient;
|
||||||
|
this.groups = groups;
|
||||||
|
this.memberCount = memberCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull MessageRequestFragmentState updateMessageRequestState(@NonNull MessageRequestState messageRequestState) {
|
||||||
|
return new MessageRequestFragmentState(messageRequestState,
|
||||||
|
this.messageRecord,
|
||||||
|
this.recipient,
|
||||||
|
this.groups,
|
||||||
|
this.memberCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull MessageRequestFragmentState updateMessageRecord(@NonNull MessageRecord messageRecord) {
|
||||||
|
return new MessageRequestFragmentState(this.messageRequestState,
|
||||||
|
messageRecord,
|
||||||
|
this.recipient,
|
||||||
|
this.groups,
|
||||||
|
this.memberCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull MessageRequestFragmentState updateRecipient(@NonNull Recipient recipient) {
|
||||||
|
return new MessageRequestFragmentState(this.messageRequestState,
|
||||||
|
this.messageRecord,
|
||||||
|
recipient,
|
||||||
|
this.groups,
|
||||||
|
this.memberCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull MessageRequestFragmentState updateGroups(@NonNull List<String> groups) {
|
||||||
|
return new MessageRequestFragmentState(this.messageRequestState,
|
||||||
|
this.messageRecord,
|
||||||
|
this.recipient,
|
||||||
|
groups,
|
||||||
|
this.memberCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull MessageRequestFragmentState updateMemberCount(int memberCount) {
|
||||||
|
return new MessageRequestFragmentState(this.messageRequestState,
|
||||||
|
this.messageRecord,
|
||||||
|
this.recipient,
|
||||||
|
this.groups,
|
||||||
|
memberCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String toString() {
|
||||||
|
return "MessageRequestFragmentState: [" +
|
||||||
|
messageRequestState.name() + "] [" +
|
||||||
|
messageRecord + "] [" +
|
||||||
|
recipient + "] [" +
|
||||||
|
groups + "] [" +
|
||||||
|
memberCount + "]";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
package org.thoughtcrime.securesms.messagerequests;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.arch.core.util.Function;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
|
||||||
|
public class MessageRequestFragmentViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private static final String TAG = MessageRequestFragmentViewModel.class.getSimpleName();
|
||||||
|
|
||||||
|
private final MutableLiveData<MessageRequestFragmentState> internalState = new MutableLiveData<>();
|
||||||
|
|
||||||
|
private final MessageRequestFragmentRepository repository;
|
||||||
|
|
||||||
|
@SuppressWarnings("CodeBlock2Expr")
|
||||||
|
private final RecipientForeverObserver recipientObserver = recipient -> {
|
||||||
|
updateState(getNewState(s -> s.updateRecipient(recipient)));
|
||||||
|
};
|
||||||
|
|
||||||
|
private MessageRequestFragmentViewModel(@NonNull MessageRequestFragmentRepository repository) {
|
||||||
|
internalState.setValue(new MessageRequestFragmentState(MessageRequestFragmentState.MessageRequestState.LOADING, null, null, null, 0));
|
||||||
|
this.repository = repository;
|
||||||
|
|
||||||
|
loadRecipient();
|
||||||
|
loadMessageRecord();
|
||||||
|
loadGroups();
|
||||||
|
loadMemberCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
repository.getLiveRecipient().removeForeverObserver(recipientObserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull LiveData<MessageRequestFragmentState> getState() {
|
||||||
|
return internalState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
public void accept() {
|
||||||
|
repository.acceptMessageRequest(() -> {
|
||||||
|
MessageRequestFragmentState state = internalState.getValue();
|
||||||
|
updateState(state.updateMessageRequestState(MessageRequestFragmentState.MessageRequestState.ACCEPTED));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
public void delete() {
|
||||||
|
repository.deleteMessageRequest(() -> {
|
||||||
|
MessageRequestFragmentState state = internalState.getValue();
|
||||||
|
updateState(state.updateMessageRequestState(MessageRequestFragmentState.MessageRequestState.DELETED));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
public void block() {
|
||||||
|
repository.blockMessageRequest(() -> {
|
||||||
|
MessageRequestFragmentState state = internalState.getValue();
|
||||||
|
updateState(state.updateMessageRequestState(MessageRequestFragmentState.MessageRequestState.BLOCKED));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(@NonNull MessageRequestFragmentState newState) {
|
||||||
|
Log.i(TAG, "updateState: " + newState);
|
||||||
|
internalState.setValue(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRecipient() {
|
||||||
|
repository.getLiveRecipient().observeForever(recipientObserver);
|
||||||
|
repository.refreshRecipient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMessageRecord() {
|
||||||
|
repository.getMessageRecord(messageRecord -> {
|
||||||
|
MessageRequestFragmentState newState = getNewState(s -> s.updateMessageRecord(messageRecord));
|
||||||
|
updateState(newState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadGroups() {
|
||||||
|
repository.getGroups(groups -> {
|
||||||
|
MessageRequestFragmentState newState = getNewState(s -> s.updateGroups(groups));
|
||||||
|
updateState(newState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMemberCount() {
|
||||||
|
repository.getMemberCount(memberCount -> {
|
||||||
|
MessageRequestFragmentState newState = getNewState(s -> s.updateMemberCount(memberCount == null ? 0 : memberCount));
|
||||||
|
updateState(newState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull MessageRequestFragmentState getNewState(@NonNull Function<MessageRequestFragmentState, MessageRequestFragmentState> stateTransformer) {
|
||||||
|
MessageRequestFragmentState oldState = internalState.getValue();
|
||||||
|
MessageRequestFragmentState newState = stateTransformer.apply(oldState);
|
||||||
|
return newState.updateMessageRequestState(getUpdatedRequestState(newState));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull MessageRequestFragmentState.MessageRequestState getUpdatedRequestState(@NonNull MessageRequestFragmentState state) {
|
||||||
|
if (state.messageRequestState != MessageRequestFragmentState.MessageRequestState.LOADING) {
|
||||||
|
return state.messageRequestState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.messageRecord != null && state.recipient != null && state.groups != null) {
|
||||||
|
return MessageRequestFragmentState.MessageRequestState.PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageRequestFragmentState.MessageRequestState.LOADING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Factory implements ViewModelProvider.Factory {
|
||||||
|
private final Context context;
|
||||||
|
private final long threadId;
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
|
||||||
|
public Factory(@NonNull Context context, long threadId, @NonNull RecipientId recipientId) {
|
||||||
|
this.context = context;
|
||||||
|
this.threadId = threadId;
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||||
|
return (T) new MessageRequestFragmentViewModel(new MessageRequestFragmentRepository(context, recipientId, threadId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,6 +55,7 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
@ -323,7 +324,7 @@ public class MessageNotifier {
|
|||||||
long timestamp = notifications.get(0).getTimestamp();
|
long timestamp = notifications.get(0).getTimestamp();
|
||||||
if (timestamp != 0) builder.setWhen(timestamp);
|
if (timestamp != 0) builder.setWhen(timestamp);
|
||||||
|
|
||||||
if (!KeyCachingService.isLocked(context)) {
|
if (!KeyCachingService.isLocked(context) && RecipientUtil.isRecipientMessageRequestAccepted(context, recipient.resolve())) {
|
||||||
ReplyMethod replyMethod = ReplyMethod.forRecipient(context, recipient);
|
ReplyMethod replyMethod = ReplyMethod.forRecipient(context, recipient);
|
||||||
|
|
||||||
builder.addActions(notificationState.getMarkAsReadIntent(context, notificationId),
|
builder.addActions(notificationState.getMarkAsReadIntent(context, notificationId),
|
||||||
|
@ -19,12 +19,13 @@ import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
|||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
public class ProfilePreference extends Preference {
|
public class ProfilePreference extends Preference {
|
||||||
|
|
||||||
private ImageView avatarView;
|
private ImageView avatarView;
|
||||||
private TextView profileNameView;
|
private TextView profileNameView;
|
||||||
private TextView profileNumberView;
|
private TextView profileSubtextView;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@ -54,15 +55,15 @@ public class ProfilePreference extends Preference {
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(PreferenceViewHolder viewHolder) {
|
public void onBindViewHolder(PreferenceViewHolder viewHolder) {
|
||||||
super.onBindViewHolder(viewHolder);
|
super.onBindViewHolder(viewHolder);
|
||||||
avatarView = (ImageView)viewHolder.findViewById(R.id.avatar);
|
avatarView = (ImageView)viewHolder.findViewById(R.id.avatar);
|
||||||
profileNameView = (TextView)viewHolder.findViewById(R.id.profile_name);
|
profileNameView = (TextView)viewHolder.findViewById(R.id.profile_name);
|
||||||
profileNumberView = (TextView)viewHolder.findViewById(R.id.number);
|
profileSubtextView = (TextView)viewHolder.findViewById(R.id.number);
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
if (profileNumberView == null) return;
|
if (profileSubtextView == null) return;
|
||||||
|
|
||||||
final Recipient self = Recipient.self();
|
final Recipient self = Recipient.self();
|
||||||
final String profileName = TextSecurePreferences.getProfileName(getContext());
|
final String profileName = TextSecurePreferences.getProfileName(getContext());
|
||||||
@ -78,6 +79,6 @@ public class ProfilePreference extends Preference {
|
|||||||
profileNameView.setText(profileName);
|
profileNameView.setText(profileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
profileNumberView.setText(self.requireE164());
|
profileSubtextView.setText(self.getUsername().or(self.getE164()).orNull());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,18 +356,18 @@ public class Recipient {
|
|||||||
public @NonNull String getDisplayName(@NonNull Context context) {
|
public @NonNull String getDisplayName(@NonNull Context context) {
|
||||||
return Util.getFirstNonEmpty(getName(context),
|
return Util.getFirstNonEmpty(getName(context),
|
||||||
getProfileName(),
|
getProfileName(),
|
||||||
getUsername(),
|
getUsername().orNull(),
|
||||||
e164,
|
e164,
|
||||||
email,
|
email,
|
||||||
context.getString(R.string.Recipient_unknown));
|
context.getString(R.string.Recipient_unknown));
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull String getUsername() {
|
public @NonNull Optional<String> getUsername() {
|
||||||
if (FeatureFlags.USERNAMES) {
|
if (FeatureFlags.USERNAMES) {
|
||||||
// TODO [greyson] Replace with actual username
|
// TODO [greyson] Replace with actual username
|
||||||
return "@caycepollard";
|
return Optional.of("@caycepollard");
|
||||||
}
|
}
|
||||||
return "";
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull MaterialColor getColor() {
|
public @NonNull MaterialColor getColor() {
|
||||||
|
@ -1,18 +1,27 @@
|
|||||||
package org.thoughtcrime.securesms.recipients;
|
package org.thoughtcrime.securesms.recipients;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
|
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
||||||
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
@ -54,4 +63,61 @@ public class RecipientUtil {
|
|||||||
Recipient resolved = recipient.resolve();
|
Recipient resolved = recipient.resolve();
|
||||||
return resolved.isPushGroup() || resolved.hasServiceIdentifier();
|
return resolved.isPushGroup() || resolved.hasServiceIdentifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
public static void block(@NonNull Context context, @NonNull Recipient recipient) {
|
||||||
|
if (!isBlockable(recipient)) {
|
||||||
|
throw new AssertionError("Recipient is not blockable!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Recipient resolved = recipient.resolve();
|
||||||
|
|
||||||
|
DatabaseFactory.getRecipientDatabase(context).setBlocked(resolved.getId(), true);
|
||||||
|
|
||||||
|
if (resolved.isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(resolved.requireGroupId())) {
|
||||||
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(resolved);
|
||||||
|
Optional<OutgoingGroupMediaMessage> leaveMessage = GroupUtil.createGroupLeaveMessage(context, resolved);
|
||||||
|
|
||||||
|
if (threadId != -1 && leaveMessage.isPresent()) {
|
||||||
|
MessageSender.send(context, leaveMessage.get(), threadId, false, null);
|
||||||
|
|
||||||
|
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||||
|
String groupId = resolved.requireGroupId();
|
||||||
|
groupDatabase.setActive(groupId, false);
|
||||||
|
groupDatabase.remove(groupId, Recipient.self().getId());
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Failed to leave group. Can't block.");
|
||||||
|
Toast.makeText(context, R.string.RecipientPreferenceActivity_error_leaving_group, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolved.isSystemContact() || resolved.isProfileSharing()) {
|
||||||
|
ApplicationDependencies.getJobManager().add(new RotateProfileKeyJob());
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationDependencies.getJobManager().add(new MultiDeviceBlockedUpdateJob());
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
public static void unblock(@NonNull Context context, @NonNull Recipient recipient) {
|
||||||
|
if (!isBlockable(recipient)) {
|
||||||
|
throw new AssertionError("Recipient is not blockable!");
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseFactory.getRecipientDatabase(context).setBlocked(recipient.getId(), false);
|
||||||
|
ApplicationDependencies.getJobManager().add(new MultiDeviceBlockedUpdateJob());
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
public static boolean isRecipientMessageRequestAccepted(@NonNull Context context, @Nullable Recipient recipient) {
|
||||||
|
if (recipient == null || !FeatureFlags.MESSAGE_REQUESTS) return true;
|
||||||
|
|
||||||
|
Recipient resolved = recipient.resolve();
|
||||||
|
|
||||||
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
|
long threadId = threadDatabase.getThreadIdFor(resolved);
|
||||||
|
boolean hasSentMessage = threadDatabase.getLastSeenAndHasSent(threadId).second() == Boolean.TRUE;
|
||||||
|
|
||||||
|
return hasSentMessage || resolved.isProfileSharing() || resolved.isSystemContact();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,7 @@ public class FeatureFlags {
|
|||||||
|
|
||||||
/** New Profile Display */
|
/** New Profile Display */
|
||||||
public static final boolean PROFILE_DISPLAY = UUIDS;
|
public static final boolean PROFILE_DISPLAY = UUIDS;
|
||||||
|
|
||||||
|
/** MessageRequest stuff */
|
||||||
|
public static final boolean MESSAGE_REQUESTS = UUIDS;
|
||||||
}
|
}
|
||||||
|
@ -562,4 +562,14 @@ public class Util {
|
|||||||
}
|
}
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> concatenatedList(List<T> first, List<T> second) {
|
||||||
|
final List<T> concat = new ArrayList<>(first.size() + second.size());
|
||||||
|
|
||||||
|
concat.addAll(first);
|
||||||
|
concat.addAll(second);
|
||||||
|
|
||||||
|
return concat;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user