diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java index 1ad5a78321..8b97f322ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java @@ -16,6 +16,7 @@ import android.support.v4.media.session.PlaybackStateCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; @@ -50,7 +51,7 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver { private static final String TAG = Log.tag(VoiceNoteMediaController.class); private MediaBrowserCompat mediaBrowser; - private AppCompatActivity activity; + private FragmentActivity activity; private ProgressEventHandler progressEventHandler; private MutableLiveData voiceNotePlaybackState = new MutableLiveData<>(VoiceNotePlaybackState.NONE); private LiveData> voiceNotePlayerViewState; @@ -58,7 +59,7 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver { private final MediaControllerCompatCallback mediaControllerCompatCallback = new MediaControllerCompatCallback(); - public VoiceNoteMediaController(@NonNull AppCompatActivity activity) { + public VoiceNoteMediaController(@NonNull FragmentActivity activity) { this.activity = activity; this.mediaBrowser = new MediaBrowserCompat(activity, new ComponentName(activity, VoiceNotePlaybackService.class), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteProximityWakeLockManager.kt b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteProximityWakeLockManager.kt index 58d4d393fa..499dea8a2b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteProximityWakeLockManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteProximityWakeLockManager.kt @@ -10,7 +10,7 @@ import android.os.Bundle import android.os.PowerManager import android.support.v4.media.session.MediaControllerCompat import android.support.v4.media.session.PlaybackStateCompat -import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.FragmentActivity import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner @@ -25,7 +25,7 @@ private const val PROXIMITY_THRESHOLD = 5f * Manages the WakeLock while a VoiceNote is playing back in the target activity. */ class VoiceNoteProximityWakeLockManager( - private val activity: AppCompatActivity, + private val activity: FragmentActivity, private val mediaController: MediaControllerCompat ) : DefaultLifecycleObserver { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/BubbleConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/BubbleConversationActivity.java index da449be842..a4577d101c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/BubbleConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/BubbleConversationActivity.java @@ -1,5 +1,8 @@ package org.thoughtcrime.securesms.conversation; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; + /** * Activity which encapsulates a conversation for a Bubble window. * @@ -9,7 +12,11 @@ package org.thoughtcrime.securesms.conversation; */ public class BubbleConversationActivity extends ConversationActivity { @Override - protected boolean isInBubble() { + public boolean isInBubble() { return true; } + + @Override + public void onInitializeToolbar(@NonNull Toolbar toolbar) { + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt new file mode 100644 index 0000000000..a980dd33a8 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt @@ -0,0 +1,76 @@ +package org.thoughtcrime.securesms.conversation + +import android.content.Intent +import android.os.Bundle +import android.view.MotionEvent +import android.view.View +import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.Toolbar +import org.thoughtcrime.securesms.PassphraseRequiredActivity +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.HidingLinearLayout +import org.thoughtcrime.securesms.components.reminder.ReminderView +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme +import org.thoughtcrime.securesms.util.DynamicTheme +import org.thoughtcrime.securesms.util.concurrent.ListenableFuture +import org.thoughtcrime.securesms.util.views.Stub + +open class ConversationActivity : PassphraseRequiredActivity(), ConversationParentFragment.Callback { + + private lateinit var fragment: ConversationParentFragment + + private val dynamicTheme: DynamicTheme = DynamicNoActionBarTheme() + override fun onPreCreate() { + dynamicTheme.onCreate(this) + } + + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { + setContentView(R.layout.conversation_parent_fragment_container) + + fragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as ConversationParentFragment + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + fragment.onNewIntent(intent) + } + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + return fragment.dispatchTouchEvent(ev) || super.dispatchTouchEvent(ev) + } + + override fun onResume() { + super.onResume() + dynamicTheme.onResume(this) + } + + override fun onInitializeToolbar(toolbar: Toolbar) { + toolbar.navigationIcon = AppCompatResources.getDrawable(this, R.drawable.ic_arrow_left_24) + toolbar.setNavigationOnClickListener { finish() } + } + + fun saveDraft(): ListenableFuture { + return fragment.saveDraft() + } + + fun getRecipient(): Recipient { + return fragment.recipient + } + + fun getTitleView(): View { + return fragment.titleView + } + + fun getComposeText(): View { + return fragment.composeText + } + + fun getQuickAttachmentToggle(): HidingLinearLayout { + return fragment.quickAttachmentToggle + } + + fun getReminderView(): Stub { + return fragment.reminderView + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index ab83651f9b..af29551084 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -58,6 +58,7 @@ import androidx.core.view.ViewCompat; import androidx.core.view.ViewKt; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProviders; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -253,7 +254,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActivity.LOCALE_EXTRA); + this.locale = Locale.getDefault(); startupStopwatch = new Stopwatch("conversation-open"); SignalLocalMetrics.ConversationOpen.start(); } @@ -323,9 +324,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect giphyMp4ProjectionRecycler = initializeGiphyMp4(); - this.groupViewModel = ViewModelProviders.of(requireActivity(), new ConversationGroupViewModel.Factory()).get(ConversationGroupViewModel.class); - this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class); - this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class); + this.groupViewModel = new ViewModelProvider(getParentFragment(), new ConversationGroupViewModel.Factory()).get(ConversationGroupViewModel.class); + this.messageCountsViewModel = new ViewModelProvider(getParentFragment()).get(MessageCountsViewModel.class); + this.conversationViewModel = new ViewModelProvider(getParentFragment(), new ConversationViewModel.Factory()).get(ConversationViewModel.class); conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), recyclerViewColorizer::setChatColors); conversationViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> { @@ -381,6 +382,13 @@ public class ConversationFragment extends LoggingFragment implements Multiselect conversationViewModel.getActiveNotificationProfile().observe(getViewLifecycleOwner(), this::updateNotificationProfileStatus); + initializeScrollButtonAnimations(); + initializeResources(); + initializeMessageRequestViewModel(); + initializeListAdapter(); + + // TODO [alex] LargeScreenSupport -- conversationViewModel.getSearchQuery().observe(getViewLifecycleOwner(), this::onSearchQueryUpdated); + return view; } @@ -414,21 +422,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect } @Override - public void onActivityCreated(Bundle bundle) { - super.onActivityCreated(bundle); - - Log.d(TAG, "[onActivityCreated]"); - - initializeScrollButtonAnimations(); - initializeResources(); - initializeMessageRequestViewModel(); - initializeListAdapter(); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - this.listener = (ConversationFragmentListener)activity; + public void onAttach(Context context) { + super.onAttach(context); + this.listener = (ConversationFragmentListener) getParentFragment(); } @Override @@ -525,7 +521,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect private void initializeMessageRequestViewModel() { MessageRequestViewModel.Factory factory = new MessageRequestViewModel.Factory(requireContext()); - messageRequestViewModel = ViewModelProviders.of(requireActivity(), factory).get(MessageRequestViewModel.class); + messageRequestViewModel = new ViewModelProvider(requireParentFragment(), factory).get(MessageRequestViewModel.class); messageRequestViewModel.setConversationInfo(recipient.getId(), threadId); listener.onMessageRequest(messageRequestViewModel); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java similarity index 83% rename from app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java rename to app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index 5a117b3776..33a8dd496a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.conversation; import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; @@ -45,8 +46,8 @@ import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; -import android.view.Gravity; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -55,6 +56,7 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnKeyListener; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.Button; @@ -68,7 +70,6 @@ import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; @@ -78,9 +79,10 @@ import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.IconCompat; +import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; @@ -100,12 +102,10 @@ import org.thoughtcrime.securesms.BlockUnblockDialog; import org.thoughtcrime.securesms.GroupMembersDialog; import org.thoughtcrime.securesms.MainActivity; import org.thoughtcrime.securesms.MuteDialog; -import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.PromptMmsActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.ShortcutLauncherActivity; import org.thoughtcrime.securesms.TransportOption; -import org.thoughtcrime.securesms.verify.VerifyIdentityActivity; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.TombstoneAttachment; import org.thoughtcrime.securesms.audio.AudioRecorder; @@ -227,7 +227,6 @@ 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.SlideFactory.MediaType; import org.thoughtcrime.securesms.mms.StickerSlide; import org.thoughtcrime.securesms.mms.VideoSlide; @@ -267,9 +266,6 @@ import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.ContextUtil; import org.thoughtcrime.securesms.util.ConversationUtil; import org.thoughtcrime.securesms.util.DrawableUtil; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.FullscreenHelper; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.MediaUtil; @@ -289,6 +285,7 @@ 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.verify.VerifyIdentityActivity; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil; import org.whispersystems.libsignal.InvalidMessageException; @@ -314,14 +311,14 @@ import static org.thoughtcrime.securesms.TransportOption.Type; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; /** - * Activity for displaying a message thread, as well as + * Fragment for displaying a message thread, as well as * composing/sending a new message into that thread. * * @author Moxie Marlinspike * */ @SuppressLint("StaticFieldLeak") -public class ConversationActivity extends PassphraseRequiredActivity +public class ConversationParentFragment extends Fragment implements ConversationFragment.ConversationFragmentListener, AttachmentManager.AttachmentListener, OnKeyboardShownListener, @@ -344,7 +341,7 @@ public class ConversationActivity extends PassphraseRequiredActivity private static final int SHORTCUT_ICON_SIZE = Build.VERSION.SDK_INT >= 26 ? ViewUtil.dpToPx(72) : ViewUtil.dpToPx(48 + 16 * 2); - private static final String TAG = Log.tag(ConversationActivity.class); + private static final String TAG = Log.tag(ConversationParentFragment.class); private static final String STATE_REACT_WITH_ANY_PAGE = "STATE_REACT_WITH_ANY_PAGE"; private static final String STATE_IS_SEARCH_REQUESTED = "STATE_IS_SEARCH_REQUESTED"; @@ -432,45 +429,61 @@ public class ConversationActivity extends PassphraseRequiredActivity private volatile boolean screenInitialized = false; - private IdentityRecordList identityRecords = new IdentityRecordList(Collections.emptyList()); - private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); + private IdentityRecordList identityRecords = new IdentityRecordList(Collections.emptyList()); + private Callback callback; @Override - protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); + public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.conversation_activity, container, false); } @Override - protected void onCreate(Bundle state, boolean ready) { - if (ConversationIntents.isInvalid(getIntent())) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if (requireActivity() instanceof Callback) { + callback = (Callback) requireActivity(); + } else if (getParentFragment() instanceof Callback) { + callback = (Callback) getParentFragment(); + } else { + throw new ClassCastException("Cannot cast activity or parent fragment into a Callback object"); + } + + // TODO [alex] LargeScreenSupport -- This check will no longer be valid / necessary + if (ConversationIntents.isInvalid(requireActivity().getIntent())) { Log.w(TAG, "[onCreate] Missing recipientId!"); // TODO [greyson] Navigation - startActivity(MainActivity.clearTop(this)); - finish(); + startActivity(MainActivity.clearTop(requireContext())); + requireActivity().finish(); return; } - isDefaultSms = Util.isDefaultSmsProvider(this); - voiceNoteMediaController = new VoiceNoteMediaController(this); - voiceRecorderWakeLock = new VoiceRecorderWakeLock(this); - new FullscreenHelper(this).showSystemUI(); + isDefaultSms = Util.isDefaultSmsProvider(requireContext()); + voiceNoteMediaController = new VoiceNoteMediaController(requireActivity()); + voiceRecorderWakeLock = new VoiceRecorderWakeLock(requireActivity()); - ConversationIntents.Args args = ConversationIntents.Args.from(getIntent()); + // TODO [alex] LargeScreenSupport -- Should be removed once we move to multi-pane layout. + new FullscreenHelper(requireActivity()).showSystemUI(); + + // TODO [alex] LargeScreenSupport -- This will need to be built from requireArguments() + ConversationIntents.Args args = ConversationIntents.Args.from(requireActivity().getIntent()); isSearchRequested = args.isWithSearchOpen(); reportShortcutLaunch(args.getRecipientId()); - setContentView(R.layout.conversation_activity); - getWindow().getDecorView().setBackgroundResource(R.color.signal_background_primary); + requireActivity().getWindow().getDecorView().setBackgroundResource(R.color.signal_background_primary); - fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale()); + fragment = (ConversationFragment) getChildFragmentManager().findFragmentById(R.id.fragment_content); + if (fragment == null) { + fragment = new ConversationFragment(); + getChildFragmentManager().beginTransaction() + .replace(R.id.fragment_content, fragment) + .commitNow(); + } + + final boolean typingIndicatorsEnabled = TextSecurePreferences.isTypingIndicatorsEnabled(requireContext()); initializeReceivers(); - initializeActionBar(); - initializeViews(); + initializeViews(view); updateWallpaper(args.getWallpaper()); initializeResources(args); initializeLinkPreviewObserver(); @@ -487,7 +500,12 @@ public class ConversationActivity extends PassphraseRequiredActivity initializeSecurity(recipient.get().isRegistered(), isDefaultSms).addListener(new AssertedSuccessListener() { @Override public void onSuccess(Boolean result) { - if (isFinishing()) { + if (getActivity() == null) { + Log.w(TAG, "Activity is not attached. Not proceeding with initialization."); + return; + } + + if (requireActivity().isFinishing()) { Log.w(TAG, "Activity is finishing. Not proceeding with initialization."); return; } @@ -508,7 +526,7 @@ public class ConversationActivity extends PassphraseRequiredActivity }); } - if (TextSecurePreferences.isTypingIndicatorsEnabled(ConversationActivity.this)) { + if (typingIndicatorsEnabled) { composeText.addTextChangedListener(typingTextWatcher); } composeText.setSelection(composeText.length(), composeText.length()); @@ -519,21 +537,21 @@ public class ConversationActivity extends PassphraseRequiredActivity } }); initializeInsightObserver(); + initializeActionBar(); } - @Override + // TODO [alex] LargeScreenSupport -- This needs to be fed a stream of intents protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); Log.i(TAG, "onNewIntent()"); - if (isFinishing()) { + if (requireActivity().isFinishing()) { Log.w(TAG, "Activity is finishing..."); return; } if (!screenInitialized) { Log.w(TAG, "Activity is in the middle of initialization. Restarting."); - finish(); + requireActivity().finish(); startActivity(intent); return; } @@ -549,24 +567,28 @@ public class ConversationActivity extends PassphraseRequiredActivity if (ConversationIntents.isInvalid(intent)) { Log.w(TAG, "[onNewIntent] Missing recipientId!"); // TODO [greyson] Navigation - startActivity(MainActivity.clearTop(this)); - finish(); + startActivity(MainActivity.clearTop(requireContext())); + requireActivity().finish(); return; } - setIntent(intent); + requireActivity().setIntent(intent); ConversationIntents.Args args = ConversationIntents.Args.from(intent); + // TODO [alex] LargeScreenSupport - Set arguments isSearchRequested = args.isWithSearchOpen(); viewModel.setArgs(args); + setVisibleThread(args.getThreadId()); reportShortcutLaunch(viewModel.getArgs().getRecipientId()); initializeResources(viewModel.getArgs()); initializeSecurity(recipient.get().isRegistered(), isDefaultSms).addListener(new AssertedSuccessListener() { @Override public void onSuccess(Boolean result) { - initializeDraft(viewModel.getArgs()); + if (getContext() != null) { + initializeDraft(viewModel.getArgs()); + } } }); @@ -584,13 +606,12 @@ public class ConversationActivity extends PassphraseRequiredActivity } @Override - protected void onResume() { + public void onResume() { super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - WindowUtil.setLightNavigationBarFromTheme(this); - WindowUtil.setLightStatusBarFromTheme(this); + // TODO [alex] LargeScreenSupport -- Remove these lines. + WindowUtil.setLightNavigationBarFromTheme(requireActivity()); + WindowUtil.setLightStatusBarFromTheme(requireActivity()); EventBus.getDefault().register(this); initializeMmsEnabledCheck(); @@ -612,7 +633,7 @@ public class ConversationActivity extends PassphraseRequiredActivity .enqueue(); if (viewModel.getArgs().isFirstTimeInSelfCreatedGroup()) { - groupViewModel.inviteFriendsOneTimeIfJustSelfInGroup(getSupportFragmentManager(), groupId); + groupViewModel.inviteFriendsOneTimeIfJustSelfInGroup(getChildFragmentManager(), groupId); } } @@ -624,18 +645,18 @@ public class ConversationActivity extends PassphraseRequiredActivity ConversationUtil.refreshRecipientShortcuts(); if (SignalStore.rateLimit().needsRecaptcha()) { - RecaptchaProofBottomSheetFragment.show(getSupportFragmentManager()); + RecaptchaProofBottomSheetFragment.show(getChildFragmentManager()); } } @Override - protected void onPause() { + public void onPause() { super.onPause(); if (!isInBubble()) { ApplicationDependencies.getMessageNotifier().clearVisibleThread(); } - if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_end); + if (requireActivity().isFinishing()) requireActivity().overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_end); inputPanel.onPause(); fragment.setLastSeen(System.currentTimeMillis()); @@ -644,7 +665,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } @Override - protected void onStop() { + public void onStop() { super.onStop(); saveDraft(); EventBus.getDefault().unregister(this); @@ -666,14 +687,14 @@ public class ConversationActivity extends PassphraseRequiredActivity } @Override - protected void onDestroy() { - if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver); + public void onDestroy() { + if (securityUpdateReceiver != null) requireActivity().unregisterReceiver(securityUpdateReceiver); super.onDestroy(); } - @Override + // TODO [alex] LargeScreenSupport -- Pipe in events from activity public boolean dispatchTouchEvent(MotionEvent ev) { - return reactionDelegate.applyTouchEvent(ev) || super.dispatchTouchEvent(ev); + return reactionDelegate.applyTouchEvent(ev); } @Override @@ -682,19 +703,19 @@ public class ConversationActivity extends PassphraseRequiredActivity super.onActivityResult(reqCode, resultCode, data); if ((data == null && reqCode != TAKE_PHOTO && reqCode != SMS_DEFAULT) || - (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) + (resultCode != Activity.RESULT_OK && reqCode != SMS_DEFAULT)) { updateLinkPreviewState(); - SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)); + SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(requireContext())); return; } switch (reqCode) { case PICK_DOCUMENT: - setMedia(data.getData(), SlideFactory.MediaType.DOCUMENT); + setMedia(data.getData(), MediaType.DOCUMENT); break; case PICK_AUDIO: - setMedia(data.getData(), SlideFactory.MediaType.AUDIO); + setMedia(data.getData(), MediaType.AUDIO); break; case PICK_CONTACT: if (isSecureText && !isSmsForced()) { @@ -711,7 +732,7 @@ public class ConversationActivity extends PassphraseRequiredActivity onRecipientChanged(recipientSnapshot); titleView.setTitle(glideRequests, recipientSnapshot); - NotificationChannels.updateContactChannelName(this, recipientSnapshot); + NotificationChannels.updateContactChannelName(requireContext(), recipientSnapshot); setBlockedUserState(recipientSnapshot, isSecureText, isDefaultSms); invalidateOptionsMenu(); break; @@ -721,7 +742,7 @@ public class ConversationActivity extends PassphraseRequiredActivity case ADD_CONTACT: SimpleTask.run(() -> { try { - DirectoryHelper.refreshDirectoryFor(this, recipient.get(), false); + DirectoryHelper.refreshDirectoryFor(requireContext(), recipient.get(), false); } catch (IOException e) { Log.w(TAG, "Failed to refresh user after adding to contacts."); } @@ -745,7 +766,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (!Objects.equals(result.getRecipientId(), recipient.getId())) { Log.w(TAG, "Result's recipientId did not match ours! Result: " + result.getRecipientId() + ", Activity: " + recipient.getId()); - Toast.makeText(this, R.string.ConversationActivity_error_sending_media, Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), R.string.ConversationActivity_error_sending_media, Toast.LENGTH_SHORT).show(); return; } @@ -765,17 +786,17 @@ public class ConversationActivity extends PassphraseRequiredActivity for (Media mediaItem : result.getNonUploadedMedia()) { if (MediaUtil.isVideoType(mediaItem.getMimeType())) { - slideDeck.addSlide(new VideoSlide(this, mediaItem.getUri(), mediaItem.getSize(), mediaItem.isVideoGif(), mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull(), mediaItem.getTransformProperties().orNull())); + slideDeck.addSlide(new VideoSlide(requireContext(), mediaItem.getUri(), mediaItem.getSize(), mediaItem.isVideoGif(), mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull(), mediaItem.getTransformProperties().orNull())); } else if (MediaUtil.isGif(mediaItem.getMimeType())) { - slideDeck.addSlide(new GifSlide(this, mediaItem.getUri(), mediaItem.getSize(), mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.isBorderless(), mediaItem.getCaption().orNull())); + slideDeck.addSlide(new GifSlide(requireContext(), mediaItem.getUri(), mediaItem.getSize(), mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.isBorderless(), mediaItem.getCaption().orNull())); } else if (MediaUtil.isImageType(mediaItem.getMimeType())) { - slideDeck.addSlide(new ImageSlide(this, mediaItem.getUri(), mediaItem.getMimeType(), mediaItem.getSize(), mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.isBorderless(), mediaItem.getCaption().orNull(), null, mediaItem.getTransformProperties().orNull())); + slideDeck.addSlide(new ImageSlide(requireContext(), mediaItem.getUri(), mediaItem.getMimeType(), mediaItem.getSize(), mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.isBorderless(), mediaItem.getCaption().orNull(), null, mediaItem.getTransformProperties().orNull())); } else { Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.getMimeType() + "'. Skipping."); } } - final Context context = ConversationActivity.this.getApplicationContext(); + final Context context = requireContext().getApplicationContext(); sendMediaMessage(result.getRecipientId(), result.getTransport().isSms(), @@ -808,7 +829,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { + public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_REACT_WITH_ANY_PAGE, reactWithAnyEmojiStartPage); @@ -816,21 +837,24 @@ public class ConversationActivity extends PassphraseRequiredActivity } @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); + public void onViewStateRestored(Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); - reactWithAnyEmojiStartPage = savedInstanceState.getInt(STATE_REACT_WITH_ANY_PAGE, -1); - isSearchRequested = savedInstanceState.getBoolean(STATE_IS_SEARCH_REQUESTED, false); + if (savedInstanceState != null) { + reactWithAnyEmojiStartPage = savedInstanceState.getInt(STATE_REACT_WITH_ANY_PAGE, -1); + isSearchRequested = savedInstanceState.getBoolean(STATE_IS_SEARCH_REQUESTED, false); + } } private void setVisibleThread(long threadId) { if (!isInBubble()) { + // TODO [alex] LargeScreenSupport -- Inform MainActivityViewModel that the conversation was opened. ApplicationDependencies.getMessageNotifier().setVisibleThread(threadId); } } private void reportShortcutLaunch(@NonNull RecipientId recipientId) { - ShortcutManagerCompat.reportShortcutUsed(this, ConversationUtil.getShortcutId(recipientId)); + ShortcutManagerCompat.reportShortcutUsed(requireContext(), ConversationUtil.getShortcutId(recipientId)); } private void handleImageFromDeviceCameraApp() { @@ -841,13 +865,13 @@ public class ConversationActivity extends PassphraseRequiredActivity try { Uri mediaUri = BlobProvider.getInstance() - .forData(getContentResolver().openInputStream(attachmentManager.getCaptureUri()), 0L) + .forData(requireContext().getContentResolver().openInputStream(attachmentManager.getCaptureUri()), 0L) .withMimeType(MediaUtil.IMAGE_JPEG) - .createForSingleSessionOnDisk(this); + .createForSingleSessionOnDisk(requireContext()); - getContentResolver().delete(attachmentManager.getCaptureUri(), null, null); + requireContext().getContentResolver().delete(attachmentManager.getCaptureUri(), null, null); - setMedia(mediaUri, SlideFactory.MediaType.IMAGE); + setMedia(mediaUri, MediaType.IMAGE); } catch (IOException ioe) { Log.w(TAG, "Could not handle public image", ioe); } @@ -863,13 +887,12 @@ public class ConversationActivity extends PassphraseRequiredActivity super.startActivity(intent); } catch (ActivityNotFoundException e) { Log.w(TAG, e); - Toast.makeText(this, R.string.ConversationActivity_there_is_no_app_available_to_handle_this_link_on_your_device, Toast.LENGTH_LONG).show(); + Toast.makeText(requireContext(), R.string.ConversationActivity_there_is_no_app_available_to_handle_this_link_on_your_device, Toast.LENGTH_LONG).show(); } } @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = this.getMenuInflater(); + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.clear(); GroupActiveState groupActiveState = groupViewModel.getGroupActiveState().getValue(); @@ -887,8 +910,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (recipient != null && recipient.get().isMuted()) inflater.inflate(R.menu.conversation_muted, menu); else inflater.inflate(R.menu.conversation_unmuted, menu); - super.onCreateOptionsMenu(menu); - return true; + super.onCreateOptionsMenu(menu, inflater); } if (isSecureText) { @@ -978,7 +1000,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } hideMenuItem(menu, R.id.menu_create_bubble); - viewModel.canShowAsBubble().observe(this, canShowAsBubble -> { + viewModel.canShowAsBubble().observe(getViewLifecycleOwner(), canShowAsBubble -> { MenuItem item = menu.findItem(R.id.menu_create_bubble); if (item != null) { @@ -998,6 +1020,7 @@ public class ConversationActivity extends PassphraseRequiredActivity public boolean onQueryTextSubmit(String query) { searchViewModel.onQueryUpdated(query, threadId, true); searchNav.showLoading(); + // TODO [alex] LargeScreenSupport -- Set search query on viewModel fragment.onSearchQueryUpdated(query); return true; } @@ -1006,6 +1029,7 @@ public class ConversationActivity extends PassphraseRequiredActivity public boolean onQueryTextChange(String query) { searchViewModel.onQueryUpdated(query, threadId, false); searchNav.showLoading(); + // TODO [alex] LargeScreenSupport -- Set search query on viewModel fragment.onSearchQueryUpdated(query); return true; } @@ -1035,6 +1059,7 @@ public class ConversationActivity extends PassphraseRequiredActivity searchViewModel.onSearchClosed(); searchNav.setVisibility(View.GONE); inputPanel.setHideForSearch(false); + // TODO [alex] LargeScreenSupport -- Set search query on viewModel fragment.onSearchQueryUpdated(null); setBlockedUserState(recipient.get(), isSecureText, isDefaultSms); invalidateOptionsMenu(); @@ -1048,14 +1073,12 @@ public class ConversationActivity extends PassphraseRequiredActivity } } - super.onCreateOptionsMenu(menu); - return true; + super.onCreateOptionsMenu(menu, inflater); } - @Override public void invalidateOptionsMenu() { if (!isSearchRequested) { - super.invalidateOptionsMenu(); + onCreateOptionsMenu(toolbar.getMenu(), requireActivity().getMenuInflater()); } } @@ -1082,35 +1105,13 @@ public class ConversationActivity extends PassphraseRequiredActivity case R.id.menu_expiring_messages_off: case R.id.menu_expiring_messages: handleSelectMessageExpiration(); return true; case R.id.menu_create_bubble: handleCreateBubble(); return true; - case android.R.id.home: super.onBackPressed(); return true; + case android.R.id.home: requireActivity().onBackPressed(); return true; } return false; } - @Override - public boolean onMenuOpened(int featureId, Menu menu) { - if (menu == null) { - return super.onMenuOpened(featureId, null); - } - - if (!SignalStore.uiHints().hasSeenGroupSettingsMenuToast()) { - MenuItem settingsMenuItem = menu.findItem(R.id.menu_group_settings); - - if (settingsMenuItem != null && settingsMenuItem.isVisible()) { - Toast toast = Toast.makeText(this, R.string.ConversationActivity__more_options_now_in_group_settings, Toast.LENGTH_SHORT); - - toast.setGravity(Gravity.CENTER, 0, 0); - toast.show(); - - SignalStore.uiHints().markHasSeenGroupSettingsMenuToast(); - } - } - - return super.onMenuOpened(featureId, menu); - } - - @Override + // TODO [alex] LargeScreenSupport -- Add to a back handler public void onBackPressed() { Log.d(TAG, "onBackPressed()"); if (reactionDelegate.isShowing()) { @@ -1118,7 +1119,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } else if (container.isInputOpen()) { container.hideCurrentInput(composeText); } else { - super.onBackPressed(); + requireActivity().onBackPressed(); } } @@ -1141,7 +1142,7 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override public void onAttachmentMediaClicked(@NonNull Media media) { linkPreviewViewModel.onUserCancel(); - startActivityForResult(MediaSelectionActivity.editor(ConversationActivity.this, sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER); + startActivityForResult(MediaSelectionActivity.editor(requireActivity(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER); container.hideCurrentInput(composeText); } @@ -1149,22 +1150,22 @@ public class ConversationActivity extends PassphraseRequiredActivity public void onAttachmentSelectorClicked(@NonNull AttachmentKeyboardButton button) { switch (button) { case GALLERY: - AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport(), inputPanel.getQuote().isPresent()); + AttachmentManager.selectGallery(requireActivity(), MEDIA_SENDER, recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport(), inputPanel.getQuote().isPresent()); break; case FILE: - AttachmentManager.selectDocument(this, PICK_DOCUMENT); + AttachmentManager.selectDocument(requireActivity(), PICK_DOCUMENT); break; case CONTACT: - AttachmentManager.selectContactInfo(this, PICK_CONTACT); + AttachmentManager.selectContactInfo(requireActivity(), PICK_CONTACT); break; case LOCATION: - AttachmentManager.selectLocation(this, PICK_LOCATION); + AttachmentManager.selectLocation(requireActivity(), PICK_LOCATION); break; case PAYMENT: if (recipient.get().hasProfileKeyCredential()) { - AttachmentManager.selectPayment(this, recipient.getId()); + AttachmentManager.selectPayment(requireActivity(), recipient.getId()); } else { - CanNotSendPaymentDialog.show(this); + CanNotSendPaymentDialog.show(requireActivity()); } break; @@ -1189,11 +1190,11 @@ public class ConversationActivity extends PassphraseRequiredActivity return; } - startActivity(RecipientDisappearingMessagesActivity.forRecipient(this, recipient.getId())); + startActivity(RecipientDisappearingMessagesActivity.forRecipient(requireContext(), recipient.getId())); } private void handleMuteNotifications() { - MuteDialog.show(this, until -> { + MuteDialog.show(requireActivity(), until -> { new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -1213,10 +1214,10 @@ public class ConversationActivity extends PassphraseRequiredActivity if (isInMessageRequest()) return; - Intent intent = ConversationSettingsActivity.forRecipient(this, recipient.getId()); - Bundle bundle = ConversationSettingsActivity.createTransitionBundle(this, titleView.findViewById(R.id.contact_photo_image), toolbar); + Intent intent = ConversationSettingsActivity.forRecipient(requireContext(), recipient.getId()); + Bundle bundle = ConversationSettingsActivity.createTransitionBundle(requireActivity(), titleView.findViewById(R.id.contact_photo_image), toolbar); - ActivityCompat.startActivity(this, intent, bundle); + ActivityCompat.startActivity(requireActivity(), intent, bundle); } private void handleUnmuteNotifications() { @@ -1230,20 +1231,21 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void handleUnblock() { - BlockUnblockDialog.showUnblockFor(this, getLifecycle(), recipient.get(), () -> { + final Context context = requireContext().getApplicationContext(); + BlockUnblockDialog.showUnblockFor(requireContext(), getLifecycle(), recipient.get(), () -> { SignalExecutors.BOUNDED.execute(() -> { - RecipientUtil.unblock(ConversationActivity.this, recipient.get()); + RecipientUtil.unblock(context, recipient.get()); }); }); } @TargetApi(Build.VERSION_CODES.KITKAT) private void handleMakeDefaultSms() { - startActivityForResult(SmsUtil.getSmsRoleIntent(this), SMS_DEFAULT); + startActivityForResult(SmsUtil.getSmsRoleIntent(requireContext()), SMS_DEFAULT); } private void handleRegisterForSignal() { - startActivity(RegistrationNavigationActivity.newIntentForReRegistration(this)); + startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext())); } private void handleInviteLink() { @@ -1261,19 +1263,19 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void handleViewMedia() { - startActivity(MediaOverviewActivity.forThread(this, threadId)); + startActivity(MediaOverviewActivity.forThread(requireContext(), threadId)); } private void handleAddShortcut() { Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.get().getId()); - final Context context = getApplicationContext(); + final Context context = requireContext().getApplicationContext(); final Recipient recipient = this.recipient.get(); GlideApp.with(this) .asBitmap() .load(recipient.getContactPhoto()) - .error(recipient.getFallbackContactPhoto().asDrawable(this, recipient.getAvatarColor(), false)) + .error(recipient.getFallbackContactPhoto().asDrawable(context, recipient.getAvatarColor(), false)) .into(new CustomTarget() { @Override public void onLoadFailed(@Nullable Drawable errorDrawable) { @@ -1303,8 +1305,8 @@ public class ConversationActivity extends PassphraseRequiredActivity private void handleCreateBubble() { ConversationIntents.Args args = viewModel.getArgs(); - BubbleUtil.displayAsBubble(this, args.getRecipientId(), args.getThreadId()); - finish(); + BubbleUtil.displayAsBubble(requireContext(), args.getRecipientId(), args.getThreadId()); + requireActivity().finish(); } private static void addIconToHomeScreen(@NonNull Context context, @@ -1334,19 +1336,19 @@ public class ConversationActivity extends PassphraseRequiredActivity private void handleLeavePushGroup() { if (getRecipient() == null) { - Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient), + Toast.makeText(requireContext(), getString(R.string.ConversationActivity_invalid_recipient), Toast.LENGTH_LONG).show(); return; } - LeaveGroupDialog.handleLeavePushGroup(this, getRecipient().requireGroupId().requirePush(), this::finish); + LeaveGroupDialog.handleLeavePushGroup(requireActivity(), getRecipient().requireGroupId().requirePush(), () -> requireActivity().finish()); } private void handleManageGroup() { - Intent intent = ConversationSettingsActivity.forGroup(this, recipient.get().requireGroupId()); - Bundle bundle = ConversationSettingsActivity.createTransitionBundle(this, titleView.findViewById(R.id.contact_photo_image), toolbar); + Intent intent = ConversationSettingsActivity.forGroup(requireContext(), recipient.get().requireGroupId()); + Bundle bundle = ConversationSettingsActivity.createTransitionBundle(requireContext(), titleView.findViewById(R.id.contact_photo_image), toolbar); - ActivityCompat.startActivity(this, intent, bundle); + ActivityCompat.startActivity(requireContext(), intent, bundle); } private void handleDistributionBroadcastEnabled(MenuItem item) { @@ -1383,9 +1385,9 @@ public class ConversationActivity extends PassphraseRequiredActivity if (recipient == null) return; if (isSecure) { - CommunicationActions.startVoiceCall(this, recipient); + CommunicationActions.startVoiceCall(requireActivity(), recipient); } else { - CommunicationActions.startInsecureCall(this, recipient); + CommunicationActions.startInsecureCall(requireActivity(), recipient); } } @@ -1393,17 +1395,17 @@ public class ConversationActivity extends PassphraseRequiredActivity if (recipient == null) return; if (recipient.isPushV2Group() && groupCallViewModel.hasActiveGroupCall().getValue() == Boolean.FALSE && groupViewModel.isNonAdminInAnnouncementGroup()) { - new MaterialAlertDialogBuilder(this).setTitle(R.string.ConversationActivity_cant_start_group_call) + new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.ConversationActivity_cant_start_group_call) .setMessage(R.string.ConversationActivity_only_admins_of_this_group_can_start_a_call) .setPositiveButton(android.R.string.ok, (d, w) -> d.dismiss()) .show(); } else { - CommunicationActions.startVideoCall(this, recipient); + CommunicationActions.startVideoCall(requireActivity(), recipient); } } private void handleDisplayGroupRecipients() { - new GroupMembersDialog(this, getRecipient()).display(); + new GroupMembersDialog(requireActivity(), getRecipient()).display(); } private void handleAddToContacts() { @@ -1420,7 +1422,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (isInMessageRequest() || recipient.get().isGroup()) return false; if (recipient.get().getContactUri() != null) { - ContactsContract.QuickContact.showQuickContact(ConversationActivity.this, titleView, recipient.get().getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); + ContactsContract.QuickContact.showQuickContact(requireContext(), titleView, recipient.get().getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); } else { handleAddToContacts(); } @@ -1435,7 +1437,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (attachmentKeyboardStub.resolved() && container.isInputOpen() && container.getCurrentInput() == attachmentKeyboardStub.get()) { container.showSoftkey(composeText); } else { - viewModel.getRecentMedia().observe(this, media -> attachmentKeyboardStub.get().onMediaChanged(media)); + viewModel.getRecentMedia().observe(getViewLifecycleOwner(), media -> attachmentKeyboardStub.get().onMediaChanged(media)); attachmentKeyboardStub.get().setCallback(this); attachmentKeyboardStub.get().setWallpaperEnabled(recipient.get().hasWallpaper()); @@ -1470,10 +1472,10 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void handleManualMmsRequired() { - Toast.makeText(this, R.string.MmsDownloader_error_reading_mms_settings, Toast.LENGTH_LONG).show(); + Toast.makeText(requireContext(), R.string.MmsDownloader_error_reading_mms_settings, Toast.LENGTH_LONG).show(); - Bundle extras = getIntent().getExtras(); - Intent intent = new Intent(this, PromptMmsActivity.class); + Bundle extras = requireActivity().getIntent().getExtras(); + Intent intent = new Intent(requireContext(), PromptMmsActivity.class); if (extras != null) intent.putExtras(extras); startActivity(intent); } @@ -1481,7 +1483,7 @@ public class ConversationActivity extends PassphraseRequiredActivity private void handleRecentSafetyNumberChange() { List records = identityRecords.getUnverifiedRecords(); records.addAll(identityRecords.getUntrustedRecords()); - SafetyNumberChangeDialog.show(getSupportFragmentManager(), records); + SafetyNumberChangeDialog.show(getChildFragmentManager(), records); } @Override @@ -1547,9 +1549,9 @@ public class ConversationActivity extends PassphraseRequiredActivity final SettableFuture result = new SettableFuture<>(); final CharSequence draftText = args.getDraftText(); - final Uri draftMedia = getIntent().getData(); - final String draftContentType = getIntent().getType(); - final MediaType draftMediaType = SlideFactory.MediaType.from(draftContentType); + final Uri draftMedia = requireActivity().getIntent().getData(); + final String draftContentType = requireActivity().getIntent().getType(); + final MediaType draftMediaType = MediaType.from(draftContentType); final List mediaList = args.getMedia(); final StickerLocator stickerLocator = args.getStickerLocator(); final boolean borderless = args.isBorderless(); @@ -1569,7 +1571,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (!Util.isEmpty(mediaList)) { Log.d(TAG, "Handling shared Media."); - Intent sendIntent = MediaSelectionActivity.editor(this, sendButton.getSelectedTransport(), mediaList, recipient.getId(), draftText); + Intent sendIntent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), mediaList, recipient.getId(), draftText); startActivityForResult(sendIntent, MEDIA_SENDER); return new SettableFuture<>(false); } @@ -1596,7 +1598,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void initializeEnabledCheck() { - groupViewModel.getSelfMemberLevel().observe(this, selfMembership -> { + groupViewModel.getSelfMemberLevel().observe(getViewLifecycleOwner(), selfMembership -> { boolean canSendMessages; boolean leftGroup; boolean canCancelRequest; @@ -1639,8 +1641,8 @@ public class ConversationActivity extends PassphraseRequiredActivity canSendMessages = false; cannotSendInAnnouncementGroupBanner.get().setVisibility(View.VISIBLE); cannotSendInAnnouncementGroupBanner.get().setMovementMethod(LinkMovementMethod.getInstance()); - cannotSendInAnnouncementGroupBanner.get().setText(SpanUtil.clickSubstring(this, R.string.ConversationActivity_only_s_can_send_messages, R.string.ConversationActivity_admins, v -> { - ShowAdminsBottomSheetDialog.show(getSupportFragmentManager(), getRecipient().requireGroupId().requireV2()); + cannotSendInAnnouncementGroupBanner.get().setText(SpanUtil.clickSubstring(requireContext(), R.string.ConversationActivity_only_s_can_send_messages, R.string.ConversationActivity_admins, v -> { + ShowAdminsBottomSheetDialog.show(getChildFragmentManager(), getRecipient().requireGroupId().requireV2()); })); } else if (cannotSendInAnnouncementGroupBanner.resolved()) { cannotSendInAnnouncementGroupBanner.get().setVisibility(View.GONE); @@ -1660,7 +1662,7 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override public void onError(@Nullable GroupChangeFailureReason error) { Log.d(TAG, "Cancel join request failed " + error); - Toast.makeText(ConversationActivity.this, GroupErrors.getUserDisplayMessage(error), Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), GroupErrors.getUserDisplayMessage(error), Toast.LENGTH_SHORT).show(); } }.toWorkerCallback())); } @@ -1674,21 +1676,22 @@ public class ConversationActivity extends PassphraseRequiredActivity private void initializePendingRequestsBanner() { groupViewModel.getActionableRequestingMembers() - .observe(this, actionablePendingGroupRequests -> updateReminders()); + .observe(getViewLifecycleOwner(), actionablePendingGroupRequests -> updateReminders()); } private void initializeGroupV1MigrationsBanners() { groupViewModel.getGroupV1MigrationSuggestions() - .observe(this, s -> updateReminders()); + .observe(getViewLifecycleOwner(), s -> updateReminders()); } private ListenableFuture initializeDraftFromDatabase() { SettableFuture future = new SettableFuture<>(); + final Context context = requireContext().getApplicationContext(); + new AsyncTask>() { @Override protected Pair doInBackground(Void... params) { - Context context = ConversationActivity.this; DraftDatabase draftDatabase = SignalDatabase.drafts(); Drafts results = draftDatabase.getDrafts(threadId); Draft mentionsDraft = results.getDraftOfType(Draft.MENTION); @@ -1743,13 +1746,13 @@ public class ConversationActivity extends PassphraseRequiredActivity attachmentManager.setLocation(SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints()).addListener(listener); break; case Draft.IMAGE: - setMedia(Uri.parse(draft.getValue()), SlideFactory.MediaType.IMAGE).addListener(listener); + setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE).addListener(listener); break; case Draft.AUDIO: - setMedia(Uri.parse(draft.getValue()), SlideFactory.MediaType.AUDIO).addListener(listener); + setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO).addListener(listener); break; case Draft.VIDEO: - setMedia(Uri.parse(draft.getValue()), SlideFactory.MediaType.VIDEO).addListener(listener); + setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO).addListener(listener); break; case Draft.QUOTE: SettableFuture quoteResult = new SettableFuture<>(); @@ -1775,14 +1778,14 @@ public class ConversationActivity extends PassphraseRequiredActivity private ListenableFuture initializeSecurity(final boolean currentSecureText, final boolean currentIsDefaultSms) { - final SettableFuture future = new SettableFuture<>(); + final SettableFuture future = new SettableFuture<>(); + final Context context = requireContext().getApplicationContext(); handleSecurityChange(currentSecureText || isPushGroupConversation(), currentIsDefaultSms); new AsyncTask() { @Override protected boolean[] doInBackground(Recipient... params) { - Context context = ConversationActivity.this; Recipient recipient = params[0].resolve(); Log.i(TAG, "Resolving registered state..."); RegisteredState registeredState; @@ -1833,43 +1836,48 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void initializeInsightObserver() { - inviteReminderModel = new InviteReminderModel(this, new InviteReminderRepository(this)); + inviteReminderModel = new InviteReminderModel(requireContext(), new InviteReminderRepository(requireContext())); inviteReminderModel.loadReminder(recipient, this::updateReminders); } protected void updateReminders() { + Context context = getContext(); + if (callback.onUpdateReminders() || context == null) { + return; + } + Optional inviteReminder = inviteReminderModel.getReminder(); Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue(); List gv1MigrationSuggestions = groupViewModel.getGroupV1MigrationSuggestions().getValue(); - if (UnauthorizedReminder.isEligible(this)) { - reminderView.get().showReminder(new UnauthorizedReminder(this)); + if (UnauthorizedReminder.isEligible(context)) { + reminderView.get().showReminder(new UnauthorizedReminder(context)); } else if (ExpiredBuildReminder.isEligible()) { - reminderView.get().showReminder(new ExpiredBuildReminder(this)); + reminderView.get().showReminder(new ExpiredBuildReminder(context)); reminderView.get().setOnActionClickListener(this::handleReminderAction); - } else if (ServiceOutageReminder.isEligible(this)) { + } else if (ServiceOutageReminder.isEligible(context)) { ApplicationDependencies.getJobManager().add(new ServiceOutageDetectionJob()); - reminderView.get().showReminder(new ServiceOutageReminder(this)); - } else if (SignalStore.account().isRegistered() && - TextSecurePreferences.isShowInviteReminders(this) && - !isSecureText && - inviteReminder.isPresent() && + reminderView.get().showReminder(new ServiceOutageReminder(context)); + } else if (SignalStore.account().isRegistered() && + TextSecurePreferences.isShowInviteReminders(context) && + !isSecureText && + inviteReminder.isPresent() && !recipient.get().isGroup()) { reminderView.get().setOnActionClickListener(this::handleReminderAction); reminderView.get().setOnDismissListener(() -> inviteReminderModel.dismissReminder()); reminderView.get().showReminder(inviteReminder.get()); } else if (actionableRequestingMembers != null && actionableRequestingMembers > 0) { - reminderView.get().showReminder(PendingGroupJoinRequestsReminder.create(this, actionableRequestingMembers)); + reminderView.get().showReminder(PendingGroupJoinRequestsReminder.create(context, actionableRequestingMembers)); reminderView.get().setOnActionClickListener(id -> { if (id == R.id.reminder_action_review_join_requests) { - startActivity(ManagePendingAndRequestingMembersActivity.newIntent(this, getRecipient().getGroupId().get().requireV2())); + startActivity(ManagePendingAndRequestingMembersActivity.newIntent(context, getRecipient().getGroupId().get().requireV2())); } }); } else if (gv1MigrationSuggestions != null && gv1MigrationSuggestions.size() > 0 && recipient.get().isPushV2Group()) { - reminderView.get().showReminder(new GroupsV1MigrationSuggestionsReminder(this, gv1MigrationSuggestions)); + reminderView.get().showReminder(new GroupsV1MigrationSuggestionsReminder(context, gv1MigrationSuggestions)); reminderView.get().setOnActionClickListener(actionId -> { if (actionId == R.id.reminder_action_gv1_suggestion_add_members) { - GroupsV1MigrationSuggestionsDialog.show(this, recipient.get().requireGroupId().requireV2(), gv1MigrationSuggestions); + GroupsV1MigrationSuggestionsDialog.show(requireActivity(), recipient.get().requireGroupId().requireV2(), gv1MigrationSuggestions); } else if (actionId == R.id.reminder_action_gv1_suggestion_no_thanks) { groupViewModel.onSuggestedMembersBannerDismissed(recipient.get().requireGroupId(), gv1MigrationSuggestions); } @@ -1877,14 +1885,14 @@ public class ConversationActivity extends PassphraseRequiredActivity reminderView.get().setOnDismissListener(() -> { }); } else if (isInBubble() && !SignalStore.tooltips().hasSeenBubbleOptOutTooltip() && Build.VERSION.SDK_INT > 29) { - reminderView.get().showReminder(new BubbleOptOutReminder(this)); + reminderView.get().showReminder(new BubbleOptOutReminder(context)); reminderView.get().setOnActionClickListener(actionId -> { SignalStore.tooltips().markBubbleOptOutTooltipSeen(); reminderView.get().hide(); if (actionId == R.id.reminder_action_turn_off) { Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS) - .putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()) + .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().getPackageName()) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } @@ -1899,9 +1907,9 @@ public class ConversationActivity extends PassphraseRequiredActivity handleInviteLink(); reminderView.get().requestDismiss(); } else if (reminderActionId == R.id.reminder_action_view_insights) { - InsightsLauncher.showInsightsDashboard(getSupportFragmentManager()); + InsightsLauncher.showInsightsDashboard(getChildFragmentManager()); } else if (reminderActionId == R.id.reminder_action_update_now) { - PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(this); + PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); } else { throw new IllegalArgumentException("Unknown ID: " + reminderActionId); } @@ -1913,21 +1921,24 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void initializeMmsEnabledCheck() { + final Context context = requireContext().getApplicationContext(); + new AsyncTask() { @Override protected Boolean doInBackground(Void... params) { - return Util.isMmsCapable(ConversationActivity.this); + return Util.isMmsCapable(context); } @Override protected void onPostExecute(Boolean isMmsEnabled) { - ConversationActivity.this.isMmsEnabled = isMmsEnabled; + ConversationParentFragment.this.isMmsEnabled = isMmsEnabled; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private ListenableFuture initializeIdentityRecords() { - final SettableFuture future = new SettableFuture<>(); + final SettableFuture future = new SettableFuture<>(); + final Context context = requireContext().getApplicationContext(); new AsyncTask>() { @Override @@ -1948,7 +1959,7 @@ public class ConversationActivity extends PassphraseRequiredActivity String message = null; if (identityRecordList.isUnverified()) { - message = IdentityUtil.getUnverifiedBannerDescription(ConversationActivity.this, identityRecordList.getUnverifiedRecipients()); + message = IdentityUtil.getUnverifiedBannerDescription(context, identityRecordList.getUnverifiedRecipients()); } return new Pair<>(identityRecordList, message); @@ -1979,53 +1990,54 @@ public class ConversationActivity extends PassphraseRequiredActivity return future; } - private void initializeViews() { - titleView = findViewById(R.id.conversation_title_view); - buttonToggle = findViewById(R.id.button_toggle); - sendButton = findViewById(R.id.send_button); - attachButton = findViewById(R.id.attach_button); - composeText = findViewById(R.id.embedded_text_editor); - charactersLeft = findViewById(R.id.space_left); - emojiDrawerStub = ViewUtil.findStubById(this, R.id.emoji_drawer_stub); - attachmentKeyboardStub = ViewUtil.findStubById(this, R.id.attachment_keyboard_stub); - unblockButton = findViewById(R.id.unblock_button); - makeDefaultSmsButton = findViewById(R.id.make_default_sms_button); - registerButton = findViewById(R.id.register_button); - container = findViewById(R.id.layout_container); - reminderView = ViewUtil.findStubById(this, R.id.reminder_stub); - unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub); - reviewBanner = ViewUtil.findStubById(this, R.id.review_banner_stub); - quickAttachmentToggle = findViewById(R.id.quick_attachment_toggle); - inlineAttachmentToggle = findViewById(R.id.inline_attachment_container); - inputPanel = findViewById(R.id.bottom_panel); - panelParent = findViewById(R.id.conversation_activity_panel_parent); - searchNav = findViewById(R.id.conversation_search_nav); - messageRequestBottomView = findViewById(R.id.conversation_activity_message_request_bottom_bar); - mentionsSuggestions = ViewUtil.findStubById(this, R.id.conversation_mention_suggestions_stub); - wallpaper = findViewById(R.id.conversation_wallpaper); - wallpaperDim = findViewById(R.id.conversation_wallpaper_dim); - voiceNotePlayerViewStub = ViewUtil.findStubById(this, R.id.voice_note_player_stub); + private void initializeViews(View view) { + toolbar = view.findViewById(R.id.toolbar); + titleView = view.findViewById(R.id.conversation_title_view); + buttonToggle = view.findViewById(R.id.button_toggle); + sendButton = view.findViewById(R.id.send_button); + attachButton = view.findViewById(R.id.attach_button); + composeText = view.findViewById(R.id.embedded_text_editor); + charactersLeft = view.findViewById(R.id.space_left); + emojiDrawerStub = ViewUtil.findStubById(view, R.id.emoji_drawer_stub); + attachmentKeyboardStub = ViewUtil.findStubById(view, R.id.attachment_keyboard_stub); + unblockButton = view.findViewById(R.id.unblock_button); + makeDefaultSmsButton = view.findViewById(R.id.make_default_sms_button); + registerButton = view.findViewById(R.id.register_button); + container = view.findViewById(R.id.layout_container); + reminderView = ViewUtil.findStubById(view, R.id.reminder_stub); + unverifiedBannerView = ViewUtil.findStubById(view, R.id.unverified_banner_stub); + reviewBanner = ViewUtil.findStubById(view, R.id.review_banner_stub); + quickAttachmentToggle = view.findViewById(R.id.quick_attachment_toggle); + inlineAttachmentToggle = view.findViewById(R.id.inline_attachment_container); + inputPanel = view.findViewById(R.id.bottom_panel); + panelParent = view.findViewById(R.id.conversation_activity_panel_parent); + searchNav = view.findViewById(R.id.conversation_search_nav); + messageRequestBottomView = view.findViewById(R.id.conversation_activity_message_request_bottom_bar); + mentionsSuggestions = ViewUtil.findStubById(view, R.id.conversation_mention_suggestions_stub); + wallpaper = view.findViewById(R.id.conversation_wallpaper); + wallpaperDim = view.findViewById(R.id.conversation_wallpaper_dim); + voiceNotePlayerViewStub = ViewUtil.findStubById(view, R.id.voice_note_player_stub); - ImageButton quickCameraToggle = findViewById(R.id.quick_camera_toggle); - ImageButton inlineAttachmentButton = findViewById(R.id.inline_attachment_button); + ImageButton quickCameraToggle = view.findViewById(R.id.quick_camera_toggle); + ImageButton inlineAttachmentButton = view.findViewById(R.id.inline_attachment_button); - Stub reactionOverlayStub = ViewUtil.findStubById(this, R.id.conversation_reaction_scrubber_stub); + Stub reactionOverlayStub = ViewUtil.findStubById(view, R.id.conversation_reaction_scrubber_stub); reactionDelegate = new ConversationReactionDelegate(reactionOverlayStub); - noLongerMemberBanner = findViewById(R.id.conversation_no_longer_member_banner); - cannotSendInAnnouncementGroupBanner = ViewUtil.findStubById(this, R.id.conversation_cannot_send_announcement_stub); - requestingMemberBanner = findViewById(R.id.conversation_requesting_banner); - cancelJoinRequest = findViewById(R.id.conversation_cancel_request); - joinGroupCallButton = findViewById(R.id.conversation_group_call_join); + noLongerMemberBanner = view.findViewById(R.id.conversation_no_longer_member_banner); + cannotSendInAnnouncementGroupBanner = ViewUtil.findStubById(view, R.id.conversation_cannot_send_announcement_stub); + requestingMemberBanner = view.findViewById(R.id.conversation_requesting_banner); + cancelJoinRequest = view.findViewById(R.id.conversation_cancel_request); + joinGroupCallButton = view.findViewById(R.id.conversation_group_call_join); container.setIsBubble(isInBubble()); container.addOnKeyboardShownListener(this); inputPanel.setListener(this); inputPanel.setMediaListener(this); - attachmentManager = new AttachmentManager(this, this); - audioRecorder = new AudioRecorder(this); + attachmentManager = new AttachmentManager(requireActivity(), this); + audioRecorder = new AudioRecorder(requireContext()); typingTextWatcher = new TypingStatusTextWatcher(); SendButtonListener sendButtonListener = new SendButtonListener(); @@ -2076,7 +2088,7 @@ public class ConversationActivity extends PassphraseRequiredActivity joinGroupCallButton.setOnClickListener(v -> handleVideo(getRecipient())); - voiceNoteMediaController.getVoiceNotePlayerViewState().observe(this, state -> { + voiceNoteMediaController.getVoiceNotePlayerViewState().observe(getViewLifecycleOwner(), state -> { if (state.isPresent()) { requireVoiceNotePlayerView().show(); requireVoiceNotePlayerView().setState(state.get()); @@ -2085,7 +2097,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } }); - voiceNoteMediaController.getVoiceNotePlaybackState().observe(ConversationActivity.this, inputPanel.getPlaybackStateObserver()); + voiceNoteMediaController.getVoiceNotePlaybackState().observe(getViewLifecycleOwner(), inputPanel.getPlaybackStateObserver()); } private @NonNull VoiceNotePlayerView requireVoiceNotePlayerView() { @@ -2109,8 +2121,9 @@ public class ConversationActivity extends PassphraseRequiredActivity int toolbarColor = getResources().getColor(R.color.conversation_toolbar_color_wallpaper); toolbar.setBackgroundColor(toolbarColor); + // TODO [alex] LargeScreenSupport -- statusBarBox if (Build.VERSION.SDK_INT > 21) { - WindowUtil.setStatusBarColor(getWindow(), toolbarColor); + WindowUtil.setStatusBarColor(requireActivity().getWindow(), toolbarColor); } } else { wallpaper.setImageDrawable(null); @@ -2122,32 +2135,29 @@ public class ConversationActivity extends PassphraseRequiredActivity int toolbarColor = getResources().getColor(R.color.conversation_toolbar_color); toolbar.setBackgroundColor(toolbarColor); + // TODO [alex] LargeScreenSupport -- statusBarBox if (Build.VERSION.SDK_INT > 21) { - WindowUtil.setStatusBarColor(getWindow(), toolbarColor); + WindowUtil.setStatusBarColor(requireActivity().getWindow(), toolbarColor); } } fragment.onWallpaperChanged(chatWallpaper); } protected void initializeActionBar() { - toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - ActionBar supportActionBar = getSupportActionBar(); - if (supportActionBar == null) throw new AssertionError(); - - supportActionBar.setDisplayHomeAsUpEnabled(true); - supportActionBar.setDisplayShowTitleEnabled(false); + onCreateOptionsMenu(toolbar.getMenu(), requireActivity().getMenuInflater()); + toolbar.setOnMenuItemClickListener(this::onOptionsItemSelected); if (isInBubble()) { - supportActionBar.setHomeAsUpIndicator(DrawableUtil.tint(ContextUtil.requireDrawable(this, R.drawable.ic_notification), - ContextCompat.getColor(this, R.color.signal_accent_primary))); - toolbar.setNavigationOnClickListener(unused -> startActivity(MainActivity.clearTop(this))); + toolbar.setNavigationIcon(DrawableUtil.tint(ContextUtil.requireDrawable(requireContext(), R.drawable.ic_notification), + ContextCompat.getColor(requireContext(), R.color.signal_accent_primary))); + toolbar.setNavigationOnClickListener(unused -> startActivity(MainActivity.clearTop(requireContext()))); } + + callback.onInitializeToolbar(toolbar); } protected boolean isInBubble() { - return false; + return callback.isInBubble(); } private void initializeResources(@NonNull ConversationIntents.Args args) { @@ -2162,13 +2172,13 @@ public class ConversationActivity extends PassphraseRequiredActivity Log.i(TAG, "[initializeResources] Recipient: " + recipient.getId() + ", Thread: " + threadId); - recipient.observe(this, this::onRecipientChanged); + recipient.observe(getViewLifecycleOwner(), this::onRecipientChanged); } private void initializeLinkPreviewObserver() { - linkPreviewViewModel = ViewModelProviders.of(this, new LinkPreviewViewModel.Factory(new LinkPreviewRepository())).get(LinkPreviewViewModel.class); + linkPreviewViewModel = new ViewModelProvider(this, new LinkPreviewViewModel.Factory(new LinkPreviewRepository())).get(LinkPreviewViewModel.class); - linkPreviewViewModel.getLinkPreviewState().observe(this, previewState -> { + linkPreviewViewModel.getLinkPreviewState().observe(getViewLifecycleOwner(), previewState -> { if (previewState == null) return; if (previewState.isLoading()) { @@ -2184,9 +2194,9 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void initializeSearchObserver() { - searchViewModel = ViewModelProviders.of(this).get(ConversationSearchViewModel.class); + searchViewModel = new ViewModelProvider(this).get(ConversationSearchViewModel.class); - searchViewModel.getSearchResults().observe(this, result -> { + searchViewModel.getSearchResults().observe(getViewLifecycleOwner(), result -> { if (result == null) return; if (!result.getResults().isEmpty()) { @@ -2199,23 +2209,23 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void initializeStickerObserver() { - StickerSearchRepository repository = new StickerSearchRepository(this); + StickerSearchRepository repository = new StickerSearchRepository(requireContext()); - stickerViewModel = ViewModelProviders.of(this, new ConversationStickerViewModel.Factory(getApplication(), repository)) + stickerViewModel = new ViewModelProvider(this, new ConversationStickerViewModel.Factory(requireActivity().getApplication(), repository)) .get(ConversationStickerViewModel.class); - stickerViewModel.getStickerResults().observe(this, stickers -> { + stickerViewModel.getStickerResults().observe(getViewLifecycleOwner(), stickers -> { if (stickers == null) return; inputPanel.setStickerSuggestions(stickers); }); - stickerViewModel.getStickersAvailability().observe(this, stickersAvailable -> { + stickerViewModel.getStickersAvailability().observe(getViewLifecycleOwner(), stickersAvailable -> { if (stickersAvailable == null) return; boolean isSystemEmojiPreferred = SignalStore.settings().isPreferSystemEmoji(); - MediaKeyboardMode keyboardMode = TextSecurePreferences.getMediaKeyboardMode(this); - boolean stickerIntro = !TextSecurePreferences.hasSeenStickerIntroTooltip(this); + MediaKeyboardMode keyboardMode = TextSecurePreferences.getMediaKeyboardMode(requireContext()); + boolean stickerIntro = !TextSecurePreferences.hasSeenStickerIntroTooltip(requireContext()); if (stickersAvailable) { inputPanel.showMediaKeyboardToggle(true); @@ -2240,24 +2250,24 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void initializeViewModel(@NonNull ConversationIntents.Args args) { - this.viewModel = ViewModelProviders.of(this, new ConversationViewModel.Factory()).get(ConversationViewModel.class); + this.viewModel = new ViewModelProvider(this, new ConversationViewModel.Factory()).get(ConversationViewModel.class); this.viewModel.setArgs(args); - this.viewModel.getWallpaper().observe(this, this::updateWallpaper); - this.viewModel.getEvents().observe(this, this::onViewModelEvent); + this.viewModel.getWallpaper().observe(getViewLifecycleOwner(), this::updateWallpaper); + this.viewModel.getEvents().observe(getViewLifecycleOwner(), this::onViewModelEvent); } private void initializeGroupViewModel() { - groupViewModel = ViewModelProviders.of(this, new ConversationGroupViewModel.Factory()).get(ConversationGroupViewModel.class); + groupViewModel = new ViewModelProvider(this, new ConversationGroupViewModel.Factory()).get(ConversationGroupViewModel.class); recipient.observe(this, groupViewModel::onRecipientChange); - groupViewModel.getGroupActiveState().observe(this, unused -> invalidateOptionsMenu()); - groupViewModel.getReviewState().observe(this, this::presentGroupReviewBanner); + groupViewModel.getGroupActiveState().observe(getViewLifecycleOwner(), unused -> invalidateOptionsMenu()); + groupViewModel.getReviewState().observe(getViewLifecycleOwner(), this::presentGroupReviewBanner); } private void initializeMentionsViewModel() { - mentionsViewModel = ViewModelProviders.of(this, new MentionsPickerViewModel.Factory()).get(MentionsPickerViewModel.class); + mentionsViewModel = new ViewModelProvider(requireActivity(), new MentionsPickerViewModel.Factory()).get(MentionsPickerViewModel.class); - recipient.observe(this, r -> { + recipient.observe(getViewLifecycleOwner(), r -> { if (r.isPushV2Group() && !mentionsSuggestions.resolved()) { mentionsSuggestions.get(); } @@ -2287,34 +2297,34 @@ public class ConversationActivity extends PassphraseRequiredActivity .toList(); }); - mentionsViewModel.getSelectedRecipient().observe(this, recipient -> { - composeText.replaceTextWithMention(recipient.getDisplayName(this), recipient.getId()); + mentionsViewModel.getSelectedRecipient().observe(getViewLifecycleOwner(), recipient -> { + composeText.replaceTextWithMention(recipient.getDisplayName(requireContext()), recipient.getId()); }); } public void initializeGroupCallViewModel() { - groupCallViewModel = ViewModelProviders.of(this, new GroupCallViewModel.Factory()).get(GroupCallViewModel.class); + groupCallViewModel = new ViewModelProvider(this, new GroupCallViewModel.Factory()).get(GroupCallViewModel.class); recipient.observe(this, r -> { groupCallViewModel.onRecipientChange(r); }); - groupCallViewModel.hasActiveGroupCall().observe(this, hasActiveCall -> { + groupCallViewModel.hasActiveGroupCall().observe(getViewLifecycleOwner(), hasActiveCall -> { invalidateOptionsMenu(); joinGroupCallButton.setVisibility(hasActiveCall ? View.VISIBLE : View.GONE); }); - groupCallViewModel.groupCallHasCapacity().observe(this, hasCapacity -> joinGroupCallButton.setText(hasCapacity ? R.string.ConversationActivity_join : R.string.ConversationActivity_full)); + groupCallViewModel.groupCallHasCapacity().observe(getViewLifecycleOwner(), hasCapacity -> joinGroupCallButton.setText(hasCapacity ? R.string.ConversationActivity_join : R.string.ConversationActivity_full)); } public void initializeDraftViewModel() { - draftViewModel = ViewModelProviders.of(this, new DraftViewModel.Factory(new DraftRepository(getApplicationContext()))).get(DraftViewModel.class); + draftViewModel = new ViewModelProvider(this, new DraftViewModel.Factory(new DraftRepository(requireContext().getApplicationContext()))).get(DraftViewModel.class); - recipient.observe(this, r -> { + recipient.observe(getViewLifecycleOwner(), r -> { draftViewModel.onRecipientChanged(r); }); - draftViewModel.getState().observe(this, + draftViewModel.getState().observe(getViewLifecycleOwner(), state -> { inputPanel.setVoiceNoteDraft(state.getVoiceNoteDraft()); updateToggleButtonState(); @@ -2326,7 +2336,7 @@ public class ConversationActivity extends PassphraseRequiredActivity return; } - View anchor = findViewById(R.id.menu_video_secure); + View anchor = requireView().findViewById(R.id.menu_video_secure); if (anchor == null) { Log.w(TAG, "Video Call tooltip anchor is null. Skipping tooltip..."); return; @@ -2336,7 +2346,7 @@ public class ConversationActivity extends PassphraseRequiredActivity SignalStore.tooltips().markGroupCallSpeakerViewSeen(); TooltipPopup.forTarget(anchor) - .setBackgroundTint(ContextCompat.getColor(this, R.color.signal_accent_green)) + .setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.signal_accent_green)) .setTextColor(getResources().getColor(R.color.core_white)) .setText(R.string.ConversationActivity__tap_here_to_start_a_group_call) .setOnDismissListener(() -> SignalStore.tooltips().markGroupCallingTooltipSeen()) @@ -2344,7 +2354,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void showStickerIntroductionTooltip() { - TextSecurePreferences.setMediaKeyboardMode(this, MediaKeyboardMode.STICKER); + TextSecurePreferences.setMediaKeyboardMode(requireContext(), MediaKeyboardMode.STICKER); inputPanel.setMediaKeyboardToggleMode(KeyboardPage.STICKER); TooltipPopup.forTarget(inputPanel.getMediaKeyboardToggleAnchorView()) @@ -2352,7 +2362,7 @@ public class ConversationActivity extends PassphraseRequiredActivity .setTextColor(getResources().getColor(R.color.core_white)) .setText(R.string.ConversationActivity_new_say_it_with_stickers) .setOnDismissListener(() -> { - TextSecurePreferences.setHasSeenStickerIntroTooltip(this, true); + TextSecurePreferences.setHasSeenStickerIntroTooltip(requireContext(), true); EventBus.getDefault().removeStickyEvent(StickerPackInstallEvent.class); }) .show(TooltipPopup.POSITION_ABOVE); @@ -2360,7 +2370,7 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override public void onReactionSelected(MessageRecord messageRecord, String emoji) { - final Context context = getApplicationContext(); + final Context context = requireContext().getApplicationContext(); reactionDelegate.hide(); @@ -2386,7 +2396,7 @@ public class ConversationActivity extends PassphraseRequiredActivity .orElse(null); if (oldRecord != null && hasAddedCustomEmoji) { - final Context context = getApplicationContext(); + final Context context = requireContext().getApplicationContext(); reactionDelegate.hide(); @@ -2397,7 +2407,7 @@ public class ConversationActivity extends PassphraseRequiredActivity reactionDelegate.hideForReactWithAny(); ReactWithAnyEmojiBottomSheetDialogFragment.createForMessageRecord(messageRecord, reactWithAnyEmojiStartPage) - .show(getSupportFragmentManager(), "BOTTOM"); + .show(getChildFragmentManager(), "BOTTOM"); } } @@ -2483,7 +2493,7 @@ public class ConversationActivity extends PassphraseRequiredActivity @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) public void onStickerPackInstalled(final StickerPackInstallEvent event) { - if (!TextSecurePreferences.hasSeenStickerIntroTooltip(this)) return; + if (!TextSecurePreferences.hasSeenStickerIntroTooltip(requireContext())) return; EventBus.getDefault().removeStickyEvent(event); @@ -2511,9 +2521,9 @@ public class ConversationActivity extends PassphraseRequiredActivity } }; - registerReceiver(securityUpdateReceiver, - new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT), - KeyCachingService.KEY_PERMISSION, null); + requireActivity().registerReceiver(securityUpdateReceiver, + new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT), + KeyCachingService.KEY_PERMISSION, null); } //////// Helper Methods @@ -2527,17 +2537,17 @@ public class ConversationActivity extends PassphraseRequiredActivity return new SettableFuture<>(false); } - if (SlideFactory.MediaType.VCARD.equals(mediaType) && isSecureText) { + if (MediaType.VCARD.equals(mediaType) && isSecureText) { openContactShareEditor(uri); return new SettableFuture<>(false); - } else if (SlideFactory.MediaType.IMAGE.equals(mediaType) || SlideFactory.MediaType.GIF.equals(mediaType) || SlideFactory.MediaType.VIDEO.equals(mediaType)) { - String mimeType = MediaUtil.getMimeType(this, uri); + } else if (MediaType.IMAGE.equals(mediaType) || MediaType.GIF.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) { + String mimeType = MediaUtil.getMimeType(requireContext(), uri); if (mimeType == null) { mimeType = mediaType.toFallbackMimeType(); } Media media = new Media(uri, mimeType, 0, width, height, 0, 0, borderless, videoGif, Optional.absent(), Optional.absent(), Optional.absent()); - startActivityForResult(MediaSelectionActivity.editor(ConversationActivity.this, sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER); + startActivityForResult(MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER); return new SettableFuture<>(false); } else { return attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height); @@ -2545,13 +2555,13 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void openContactShareEditor(Uri contactUri) { - Intent intent = ContactShareEditActivity.getIntent(this, Collections.singletonList(contactUri)); + Intent intent = ContactShareEditActivity.getIntent(requireContext(), Collections.singletonList(contactUri)); startActivityForResult(intent, GET_CONTACT_DETAILS); } private void addAttachmentContactInfo(Uri contactUri) { ContactAccessor contactDataList = ContactAccessor.getInstance(); - ContactData contactData = contactDataList.getContactData(this, contactUri); + ContactData contactData = contactDataList.getContactData(requireContext(), contactUri); if (contactData.numbers.size() == 1) composeText.append(contactData.numbers.get(0).number); else if (contactData.numbers.size() > 1) selectContactInfo(contactData); @@ -2574,7 +2584,7 @@ public class ConversationActivity extends PassphraseRequiredActivity numberItems[i] = contactData.numbers.get(i).type + ": " + contactData.numbers.get(i).number; } - AlertDialog.Builder builder = new AlertDialog.Builder(this); + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); builder.setIcon(R.drawable.ic_account_box); builder.setTitle(R.string.ConversationActivity_select_contact_info); @@ -2610,7 +2620,7 @@ public class ConversationActivity extends PassphraseRequiredActivity drafts.add(new Draft(Draft.QUOTE, new QuoteId(quote.get().getId(), quote.get().getAuthor()).serialize())); } - DraftDatabase.Draft voiceNoteDraft = draftViewModel.getVoiceNoteDraft(); + Draft voiceNoteDraft = draftViewModel.getVoiceNoteDraft(); if (voiceNoteDraft != null) { drafts.add(voiceNoteDraft); } @@ -2626,6 +2636,7 @@ public class ConversationActivity extends PassphraseRequiredActivity return future; } + final Context context = requireContext().getApplicationContext(); final Drafts drafts = getDraftsForCurrentState(); final long thisThreadId = this.threadId; final RecipientId recipientId = this.recipient.getId(); @@ -2653,7 +2664,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (threadId == -1) threadId = threadDatabase.getOrCreateThreadIdFor(getRecipient(), thisDistributionType); draftDatabase.replaceDrafts(threadId, drafts); - threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), + threadDatabase.updateSnippet(threadId, drafts.getSnippet(context), drafts.getUriSnippet(), System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); } else if (threadId > 0) { @@ -2703,7 +2714,7 @@ public class ConversationActivity extends PassphraseRequiredActivity CharacterState characterState = transportOption.calculateCharacters(messageBody); if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) { - charactersLeft.setText(String.format(dynamicLanguage.getCurrentLocale(), + charactersLeft.setText(String.format(Locale.getDefault(), "%d/%d (%d)", characterState.charactersRemaining, characterState.maxTotalMessageSize, @@ -2715,9 +2726,9 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void initializeMediaKeyboardProviders() { - KeyboardPagerViewModel keyboardPagerViewModel = ViewModelProviders.of(this).get(KeyboardPagerViewModel.class); + KeyboardPagerViewModel keyboardPagerViewModel = new ViewModelProvider(this).get(KeyboardPagerViewModel.class); - switch (TextSecurePreferences.getMediaKeyboardMode(this)) { + switch (TextSecurePreferences.getMediaKeyboardMode(requireContext())) { case EMOJI: keyboardPagerViewModel.switchToPage(KeyboardPage.EMOJI); break; @@ -2798,7 +2809,8 @@ public class ConversationActivity extends PassphraseRequiredActivity boolean refreshFragment = (threadId != this.threadId); this.threadId = threadId; - if (fragment == null || !fragment.isVisible() || isFinishing()) { + if (fragment == null || !fragment.isVisible() || requireActivity().isFinishing()) { + callback.onSendComplete(threadId); return; } @@ -2813,6 +2825,7 @@ public class ConversationActivity extends PassphraseRequiredActivity attachmentManager.cleanup(); updateLinkPreviewState(); + callback.onSendComplete(threadId); } private void sendMessage(@Nullable String metricId) { @@ -2823,7 +2836,7 @@ public class ConversationActivity extends PassphraseRequiredActivity Draft voiceNote = draftViewModel.getVoiceNoteDraft(); if (voiceNote != null) { - AudioSlide audioSlide = AudioSlide.createFromVoiceNoteDraft(this, voiceNote); + AudioSlide audioSlide = AudioSlide.createFromVoiceNoteDraft(requireContext(), voiceNote); sendVoiceNote(Objects.requireNonNull(audioSlide.getUri()), audioSlide.getFileSize()); draftViewModel.clearVoiceNoteDraft(); @@ -2864,12 +2877,12 @@ public class ConversationActivity extends PassphraseRequiredActivity sendTextMessage(forceSms, expiresIn, subscriptionId, initiating, metricId); } } catch (RecipientFormattingException ex) { - Toast.makeText(ConversationActivity.this, + Toast.makeText(requireContext(), R.string.ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation, Toast.LENGTH_LONG).show(); Log.w(TAG, ex); } catch (InvalidMessageException ex) { - Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_message_is_empty_exclamation, + Toast.makeText(requireContext(), R.string.ConversationActivity_message_is_empty_exclamation, Toast.LENGTH_SHORT).show(); Log.w(TAG, ex); } @@ -2883,6 +2896,8 @@ public class ConversationActivity extends PassphraseRequiredActivity OutgoingMediaMessage message = new OutgoingMediaMessage(recipient.get(), new SlideDeck(), result.getBody(), System.currentTimeMillis(), -1, expiresIn, result.isViewOnce(), distributionType, quote, Collections.emptyList(), Collections.emptyList(), mentions); OutgoingMediaMessage secureMessage = new OutgoingSecureMediaMessage(message); + final Context context = requireContext().getApplicationContext(); + ApplicationDependencies.getTypingStatusSender().onTypingStopped(thread); inputPanel.clearQuote(); @@ -2892,7 +2907,7 @@ public class ConversationActivity extends PassphraseRequiredActivity long id = fragment.stageOutgoingMessage(secureMessage); SimpleTask.run(() -> { - long resultId = MessageSender.sendPushWithPreUploadedMedia(this, secureMessage, result.getPreUploadResults(), thread, null); + long resultId = MessageSender.sendPushWithPreUploadedMedia(context, secureMessage, result.getPreUploadResults(), thread, null); int deleted = SignalDatabase.attachments().deleteAbandonedPreuploadedAttachments(); Log.i(TAG, "Deleted " + deleted + " abandoned attachments."); @@ -2946,7 +2961,7 @@ public class ConversationActivity extends PassphraseRequiredActivity final long thread = this.threadId; if (sendPush) { - MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(this, body, sendButton.getSelectedTransport().calculateCharacters(body).maxPrimaryMessageSize); + MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(requireContext(), body, sendButton.getSelectedTransport().calculateCharacters(body).maxPrimaryMessageSize); body = splitMessage.getBody(); if (splitMessage.getTextSlide().isPresent()) { @@ -2957,7 +2972,7 @@ public class ConversationActivity extends PassphraseRequiredActivity OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(Recipient.resolved(recipientId), slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, viewOnce, distributionType, quote, contacts, previews, mentions); final SettableFuture future = new SettableFuture<>(); - final Context context = getApplicationContext(); + final Context context = requireContext().getApplicationContext(); final OutgoingMediaMessage outgoingMessage; @@ -3003,7 +3018,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } final long thread = this.threadId; - final Context context = getApplicationContext(); + final Context context = requireContext().getApplicationContext(); final String messageBody = getMessage(); final boolean sendPush = (isSecureText && !forceSms) || recipient.get().isAciOnly(); @@ -3033,7 +3048,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } private void showDefaultSmsPrompt() { - new AlertDialog.Builder(this) + new AlertDialog.Builder(requireContext()) .setMessage(R.string.ConversationActivity_signal_cannot_sent_sms_mms_messages_because_it_is_not_your_default_sms_app) .setNegativeButton(R.string.ConversationActivity_no, (dialog, which) -> dialog.dismiss()) .setPositiveButton(R.string.ConversationActivity_yes, (dialog, which) -> handleMakeDefaultSms()) @@ -3073,7 +3088,7 @@ public class ConversationActivity extends PassphraseRequiredActivity private void onViewModelEvent(@NonNull ConversationViewModel.Event event) { if (event == ConversationViewModel.Event.SHOW_RECAPTCHA) { - RecaptchaProofBottomSheetFragment.show(getSupportFragmentManager()); + RecaptchaProofBottomSheetFragment.show(getChildFragmentManager()); } else { throw new AssertionError("Unexpected event!"); } @@ -3082,7 +3097,7 @@ public class ConversationActivity extends PassphraseRequiredActivity private void updateLinkPreviewState() { if (SignalStore.settings().isLinkPreviewsEnabled() && isSecureText && !sendButton.getSelectedTransport().isSms() && !attachmentManager.isAttachmentPresent()) { linkPreviewViewModel.onEnabled(); - linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed().toString(), composeText.getSelectionStart(), composeText.getSelectionEnd()); + linkPreviewViewModel.onTextChanged(requireContext(), composeText.getTextTrimmed().toString(), composeText.getSelectionStart(), composeText.getSelectionEnd()); } else { linkPreviewViewModel.onUserCancel(); } @@ -3117,11 +3132,11 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override public void onRecorderStarted() { - Vibrator vibrator = ServiceUtil.getVibrator(this); + Vibrator vibrator = ServiceUtil.getVibrator(requireContext()); vibrator.vibrate(20); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); + requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); audioRecorder.startRecording(); } @@ -3130,18 +3145,18 @@ public class ConversationActivity extends PassphraseRequiredActivity public void onRecorderLocked() { voiceRecorderWakeLock.acquire(); updateToggleButtonState(); - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } @Override public void onRecorderFinished() { voiceRecorderWakeLock.release(); updateToggleButtonState(); - Vibrator vibrator = ServiceUtil.getVibrator(this); + Vibrator vibrator = ServiceUtil.getVibrator(requireContext()); vibrator.vibrate(20); - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); ListenableFuture future = audioRecorder.stopRecording(); future.addListener(new ListenableFuture.Listener() { @@ -3152,7 +3167,7 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override public void onFailure(ExecutionException e) { - Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_unable_to_record_audio, Toast.LENGTH_LONG).show(); + Toast.makeText(requireContext(), R.string.ConversationActivity_unable_to_record_audio, Toast.LENGTH_LONG).show(); } }); } @@ -3161,11 +3176,11 @@ public class ConversationActivity extends PassphraseRequiredActivity public void onRecorderCanceled() { voiceRecorderWakeLock.release(); updateToggleButtonState(); - Vibrator vibrator = ServiceUtil.getVibrator(this); + Vibrator vibrator = ServiceUtil.getVibrator(requireContext()); vibrator.vibrate(50); - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); ListenableFuture future = audioRecorder.stopRecording(); if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { @@ -3185,6 +3200,8 @@ public class ConversationActivity extends PassphraseRequiredActivity inputPanel.setMediaKeyboard(emojiDrawerStub.get()); } + emojiDrawerStub.get().setFragmentManager(getChildFragmentManager()); + if (container.getCurrentInput() == emojiDrawerStub.get()) { container.showSoftkey(composeText); } else { @@ -3209,15 +3226,15 @@ public class ConversationActivity extends PassphraseRequiredActivity () -> getKeyboardImageDetails(uri), details -> sendKeyboardImage(uri, contentType, details)); } else if (MediaUtil.isVideoType(contentType)) { - setMedia(uri, SlideFactory.MediaType.VIDEO); + setMedia(uri, MediaType.VIDEO); } else if (MediaUtil.isAudioType(contentType)) { - setMedia(uri, SlideFactory.MediaType.AUDIO); + setMedia(uri, MediaType.AUDIO); } } @Override public void onCursorPositionChanged(int start, int end) { - linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed().toString(), start, end); + linkPreviewViewModel.onTextChanged(requireContext(), composeText.getTextTrimmed().toString(), start, end); } @Override @@ -3227,7 +3244,7 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override public void onStickerManagementClicked() { - startActivity(StickerManagementActivity.getIntent(this)); + startActivity(StickerManagementActivity.getIntent(requireContext())); container.hideAttachedInput(true); } @@ -3236,7 +3253,7 @@ public class ConversationActivity extends PassphraseRequiredActivity boolean initiating = threadId == -1; int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1); long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds()); - AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, uri, size, MediaUtil.AUDIO_AAC, true); + AudioSlide audioSlide = new AudioSlide(requireContext(), uri, size, MediaUtil.AUDIO_AAC, true); SlideDeck slideDeck = new SlideDeck(); slideDeck.addSlide(audioSlide); @@ -3275,7 +3292,7 @@ public class ConversationActivity extends PassphraseRequiredActivity private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull String contentType, @NonNull Uri uri, long size, boolean clearCompose) { if (sendButton.getSelectedTransport().isSms()) { Media media = new Media(uri, contentType, System.currentTimeMillis(), StickerSlide.WIDTH, StickerSlide.HEIGHT, size, 0, false, false, Optional.absent(), Optional.absent(), Optional.absent()); - Intent intent = MediaSelectionActivity.editor(this, sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()); + Intent intent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()); startActivityForResult(intent, MEDIA_SENDER); return; } @@ -3285,7 +3302,7 @@ public class ConversationActivity extends PassphraseRequiredActivity boolean initiating = threadId == -1; TransportOption transport = sendButton.getSelectedTransport(); SlideDeck slideDeck = new SlideDeck(); - Slide stickerSlide = new StickerSlide(this, uri, size, stickerLocator, contentType); + Slide stickerSlide = new StickerSlide(requireContext(), uri, size, stickerLocator, contentType); slideDeck.addSlide(stickerSlide); @@ -3407,17 +3424,17 @@ public class ConversationActivity extends PassphraseRequiredActivity private class QuickCameraToggleListener implements OnClickListener { @Override public void onClick(View v) { - Permissions.with(ConversationActivity.this) + Permissions.with(requireActivity()) .request(Manifest.permission.CAMERA) .ifNecessary() .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_camera_24) .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) .onAllGranted(() -> { composeText.clearFocus(); - startActivityForResult(MediaSelectionActivity.camera(ConversationActivity.this, sendButton.getSelectedTransport(), recipient.getId(), inputPanel.getQuote().isPresent()), MEDIA_SENDER); - overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); + startActivityForResult(MediaSelectionActivity.camera(requireActivity(), sendButton.getSelectedTransport(), recipient.getId(), inputPanel.getQuote().isPresent()), MEDIA_SENDER); + requireActivity().overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); }) - .onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) + .onAnyDenied(() -> Toast.makeText(requireContext(), R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) .execute(); } } @@ -3487,7 +3504,7 @@ public class ConversationActivity extends PassphraseRequiredActivity calculateCharactersRemaining(); if (composeText.getTextTrimmed().length() == 0 || beforeLength == 0) { - composeText.postDelayed(ConversationActivity.this::updateToggleButtonState, 50); + composeText.postDelayed(ConversationParentFragment.this::updateToggleButtonState, 50); } stickerViewModel.onInputTextUpdated(s.toString()); @@ -3538,12 +3555,12 @@ public class ConversationActivity extends PassphraseRequiredActivity messageRequestBottomView.setDeleteOnClickListener(v -> onMessageRequestDeleteClicked(viewModel)); messageRequestBottomView.setBlockOnClickListener(v -> onMessageRequestBlockClicked(viewModel)); messageRequestBottomView.setUnblockOnClickListener(v -> onMessageRequestUnblockClicked(viewModel)); - messageRequestBottomView.setGroupV1MigrationContinueListener(v -> GroupsV1MigrationInitiationBottomSheetDialogFragment.showForInitiation(getSupportFragmentManager(), recipient.getId())); + messageRequestBottomView.setGroupV1MigrationContinueListener(v -> GroupsV1MigrationInitiationBottomSheetDialogFragment.showForInitiation(getChildFragmentManager(), recipient.getId())); - viewModel.getRequestReviewDisplayState().observe(this, this::presentRequestReviewBanner); - viewModel.getMessageData().observe(this, this::presentMessageRequestState); - viewModel.getFailures().observe(this, this::showGroupChangeErrorToast); - viewModel.getMessageRequestStatus().observe(this, status -> { + viewModel.getRequestReviewDisplayState().observe(getViewLifecycleOwner(), this::presentRequestReviewBanner); + viewModel.getMessageData().observe(getViewLifecycleOwner(), this::presentMessageRequestState); + viewModel.getFailures().observe(getViewLifecycleOwner(), this::showGroupChangeErrorToast); + viewModel.getMessageRequestStatus().observe(getViewLifecycleOwner(), status -> { switch (status) { case IDLE: hideMessageRequestBusy(); @@ -3558,12 +3575,12 @@ public class ConversationActivity extends PassphraseRequiredActivity break; case BLOCKED_AND_REPORTED: hideMessageRequestBusy(); - Toast.makeText(this, R.string.ConversationActivity__reported_as_spam_and_blocked, Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), R.string.ConversationActivity__reported_as_spam_and_blocked, Toast.LENGTH_SHORT).show(); break; case DELETED: case BLOCKED: hideMessageRequestBusy(); - finish(); + requireActivity().finish(); } }); } @@ -3579,8 +3596,8 @@ public class ConversationActivity extends PassphraseRequiredActivity reviewBanner.get().setBannerMessage(message); - Drawable drawable = ContextUtil.requireDrawable(this, R.drawable.ic_info_white_24).mutate(); - DrawableCompat.setTint(drawable, ContextCompat.getColor(this, R.color.signal_icon_tint_primary)); + Drawable drawable = ContextUtil.requireDrawable(requireContext(), R.drawable.ic_info_white_24).mutate(); + DrawableCompat.setTint(drawable, ContextCompat.getColor(requireContext(), R.color.signal_icon_tint_primary)); reviewBanner.get().setBannerIcon(drawable); reviewBanner.get().setOnClickListener(unused -> handleReviewRequest(recipient.getId())); @@ -3618,7 +3635,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } ReviewCardDialogFragment.createForReviewMembers(groupId) - .show(getSupportFragmentManager(), null); + .show(getChildFragmentManager(), null); } private void handleReviewRequest(@NonNull RecipientId recipientId) { @@ -3627,11 +3644,11 @@ public class ConversationActivity extends PassphraseRequiredActivity } ReviewCardDialogFragment.createForReviewRequest(recipientId) - .show(getSupportFragmentManager(), null); + .show(getChildFragmentManager(), null); } private void showGroupChangeErrorToast(@NonNull GroupChangeFailureReason e) { - Toast.makeText(this, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show(); + Toast.makeText(requireContext(), GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show(); } @Override @@ -3641,21 +3658,21 @@ public class ConversationActivity extends PassphraseRequiredActivity { reactionDelegate.setOnToolbarItemClickedListener(toolbarListener); reactionDelegate.setOnHideListener(onHideListener); - reactionDelegate.show(this, recipient.get(), conversationMessage, groupViewModel.isNonAdminInAnnouncementGroup()); + reactionDelegate.show(requireActivity(), recipient.get(), conversationMessage, groupViewModel.isNonAdminInAnnouncementGroup()); } @Override public void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord) { if (messageRecord.isIdentityMismatchFailure()) { - SafetyNumberChangeDialog.show(this, messageRecord); + SafetyNumberChangeDialog.show(requireActivity(), messageRecord); } else if (messageRecord.hasFailedWithNetworkFailures()) { - new AlertDialog.Builder(this) + new AlertDialog.Builder(requireContext()) .setMessage(R.string.conversation_activity__message_could_not_be_sent) .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.conversation_activity__send, (dialog, which) -> MessageSender.resend(this, messageRecord)) + .setPositiveButton(R.string.conversation_activity__send, (dialog, which) -> MessageSender.resend(requireContext(), messageRecord)) .show(); } else { - startActivity(MessageDetailsActivity.getIntentForMessageDetails(this, messageRecord, messageRecord.getRecipient().getId(), messageRecord.getThreadId())); + startActivity(MessageDetailsActivity.getIntentForMessageDetails(requireContext(), messageRecord, messageRecord.getRecipient().getId(), messageRecord.getThreadId())); } } @@ -3681,7 +3698,7 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override public void onRegisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver) { - voiceNoteMediaController.getVoiceNotePlaybackState().observe(this, onPlaybackStartObserver); + voiceNoteMediaController.getVoiceNotePlaybackState().observe(getViewLifecycleOwner(), onPlaybackStartObserver); } @Override @@ -3729,7 +3746,7 @@ public class ConversationActivity extends PassphraseRequiredActivity SlideDeck slideDeck = new SlideDeck(); if (contact.getAvatarAttachment() != null) { - slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, contact.getAvatarAttachment())); + slideDeck.addSlide(MediaUtil.getSlideForAttachment(requireContext(), contact.getAvatarAttachment())); } inputPanel.setQuote(GlideApp.with(this), @@ -3743,13 +3760,13 @@ public class ConversationActivity extends PassphraseRequiredActivity SlideDeck slideDeck = new SlideDeck(); if (linkPreview.getThumbnail().isPresent()) { - slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, linkPreview.getThumbnail().get())); + slideDeck.addSlide(MediaUtil.getSlideForAttachment(requireContext(), linkPreview.getThumbnail().get())); } inputPanel.setQuote(GlideApp.with(this), messageRecord.getDateSent(), author, - conversationMessage.getDisplayBody(this), + conversationMessage.getDisplayBody(requireContext()), slideDeck); } else { SlideDeck slideDeck = messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(); @@ -3757,13 +3774,13 @@ public class ConversationActivity extends PassphraseRequiredActivity if (messageRecord.isMms() && ((MmsMessageRecord) messageRecord).isViewOnce()) { Attachment attachment = new TombstoneAttachment(MediaUtil.VIEW_ONCE, true); slideDeck = new SlideDeck(); - slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, attachment)); + slideDeck.addSlide(MediaUtil.getSlideForAttachment(requireContext(), attachment)); } inputPanel.setQuote(GlideApp.with(this), messageRecord.getDateSent(), author, - conversationMessage.getDisplayBody(this), + conversationMessage.getDisplayBody(requireContext()), slideDeck); } @@ -3812,7 +3829,7 @@ public class ConversationActivity extends PassphraseRequiredActivity return; } - AlertDialog.Builder builder = new AlertDialog.Builder(this) + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()) .setNeutralButton(R.string.ConversationActivity_cancel, (d, w) -> d.dismiss()); if (recipient.isGroup() && recipient.isBlocked()) { @@ -3839,7 +3856,7 @@ public class ConversationActivity extends PassphraseRequiredActivity return; } - BlockUnblockDialog.showBlockAndReportSpamFor(this, getLifecycle(), recipient, requestModel::onBlock, requestModel::onBlockAndReportSpam); + BlockUnblockDialog.showBlockAndReportSpamFor(requireContext(), getLifecycle(), recipient, requestModel::onBlock, requestModel::onBlockAndReportSpam); } private void onMessageRequestUnblockClicked(@NonNull MessageRequestViewModel requestModel) { @@ -3849,7 +3866,7 @@ public class ConversationActivity extends PassphraseRequiredActivity return; } - BlockUnblockDialog.showUnblockFor(this, getLifecycle(), recipient, requestModel::onUnblock); + BlockUnblockDialog.showUnblockFor(requireContext(), getLifecycle(), recipient, requestModel::onUnblock); } private static void hideMenuItem(@NonNull Menu menu, @IdRes int menuItem) { @@ -3876,7 +3893,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(SlideFactory.MediaType.from(contentType))); + setMedia(uri, Objects.requireNonNull(MediaType.from(contentType))); return; } @@ -3886,9 +3903,9 @@ public class ConversationActivity extends PassphraseRequiredActivity SlideDeck slideDeck = new SlideDeck(); if (MediaUtil.isGif(contentType)) { - slideDeck.addSlide(new GifSlide(this, uri, 0, details.width, details.height, details.hasTransparency, null)); + slideDeck.addSlide(new GifSlide(requireContext(), uri, 0, details.width, details.height, details.hasTransparency, null)); } else if (MediaUtil.isImageType(contentType)) { - slideDeck.addSlide(new ImageSlide(this, uri, contentType, 0, details.width, details.height, details.hasTransparency, null, null)); + slideDeck.addSlide(new ImageSlide(requireContext(), uri, contentType, 0, details.width, details.height, details.hasTransparency, null, null)); } else { throw new AssertionError("Only images are supported!"); } @@ -3930,19 +3947,19 @@ public class ConversationActivity extends PassphraseRequiredActivity public void onClicked(final List unverifiedIdentities) { Log.i(TAG, "onClicked: " + unverifiedIdentities.size()); if (unverifiedIdentities.size() == 1) { - startActivity(VerifyIdentityActivity.newIntent(ConversationActivity.this, unverifiedIdentities.get(0), false)); + startActivity(VerifyIdentityActivity.newIntent(requireContext(), unverifiedIdentities.get(0), false)); } else { String[] unverifiedNames = new String[unverifiedIdentities.size()]; for (int i=0;i { - startActivity(VerifyIdentityActivity.newIntent(ConversationActivity.this, unverifiedIdentities.get(which), false)); + startActivity(VerifyIdentityActivity.newIntent(requireContext(), unverifiedIdentities.get(which), false)); }); builder.show(); } @@ -3961,13 +3978,13 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override protected ConversationMessage doInBackground(Void... voids) { - QuoteId quoteId = QuoteId.deserialize(ConversationActivity.this, serialized); + QuoteId quoteId = QuoteId.deserialize(ApplicationDependencies.getApplication(), serialized); if (quoteId == null) { return null; } - Context context = getApplicationContext(); + Context context = ApplicationDependencies.getApplication(); MessageRecord messageRecord = SignalDatabase.mmsSms().getMessageFor(quoteId.getId(), quoteId.getAuthor()); if (messageRecord == null) { @@ -4012,8 +4029,8 @@ public class ConversationActivity extends PassphraseRequiredActivity @Override public void onNavigateToMessage(long threadId, @NonNull RecipientId threadRecipientId, @NonNull RecipientId senderId, long messageTimestamp, long messagePositionInThread) { - if (threadId != ConversationActivity.this.threadId) { - startActivity(ConversationIntents.createBuilder(ConversationActivity.this, threadRecipientId, threadId) + if (threadId != ConversationParentFragment.this.threadId) { + startActivity(ConversationIntents.createBuilder(requireActivity(), threadRecipientId, threadId) .withStartingPosition((int) messagePositionInThread) .build()); } else { @@ -4057,4 +4074,23 @@ public class ConversationActivity extends PassphraseRequiredActivity this.hasTransparency = hasTransparency; } } + + public interface Callback { + default void onInitializeToolbar(@NonNull Toolbar toolbar) { + } + + default void onSendComplete(long threadId) { + } + + /** + * @return true to skip built in, otherwise false. + */ + default boolean onUpdateReminders() { + return false; + } + + default boolean isInBubble() { + return false; + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java index 2a5d826eba..ce2c3c04c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java @@ -10,6 +10,7 @@ import android.view.MenuItem; import android.view.View; import android.view.WindowManager; +import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityOptionsCompat; import org.signal.core.util.logging.Log; @@ -48,14 +49,14 @@ public class ConversationPopupActivity extends ConversationActivity { super.onCreate(bundle, ready); - titleView.setOnClickListener(null); + getTitleView().setOnClickListener(null); } @Override protected void onResume() { super.onResume(); - composeText.requestFocus(); - quickAttachmentToggle.disable(); + getComposeText().requestFocus(); + getQuickAttachmentToggle().disable(); } @Override @@ -101,21 +102,20 @@ public class ConversationPopupActivity extends ConversationActivity { } @Override - protected void initializeActionBar() { - super.initializeActionBar(); - getSupportActionBar().setDisplayHomeAsUpEnabled(false); + public void onInitializeToolbar(Toolbar toolbar) { } @Override - protected void sendComplete(long threadId) { - super.sendComplete(threadId); + public void onSendComplete(long threadId) { finish(); } @Override - protected void updateReminders() { - if (reminderView.resolved()) { - reminderView.get().setVisibility(View.GONE); + public boolean onUpdateReminders() { + if (getReminderView().resolved()) { + getReminderView().get().setVisibility(View.GONE); } + + return false; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/gif/GifKeyboardPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/gif/GifKeyboardPageFragment.kt index d053e245ae..90b6bb4c38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyboard/gif/GifKeyboardPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/gif/GifKeyboardPageFragment.kt @@ -9,7 +9,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.RecyclerView import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.conversation.ConversationActivity +import org.thoughtcrime.securesms.conversation.ConversationParentFragment import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Fragment import org.thoughtcrime.securesms.giph.mp4.GiphyMp4SaveResult import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ViewModel @@ -107,7 +107,7 @@ class GifKeyboardPageFragment : LoggingFragment(R.layout.gif_keyboard_page_fragm } private fun openGifSearch() { - AttachmentManager.selectGif(requireActivity(), ConversationActivity.PICK_GIF, host.isMms()) + AttachmentManager.selectGif(requireActivity(), ConversationParentFragment.PICK_GIF, host.isMms()) } interface Host { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java index 703b8e515f..a74cccdda9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java @@ -100,6 +100,10 @@ public final class ViewUtil { return new Stub<>(parent.findViewById(resId)); } + public static Stub findStubById(@NonNull View parent, @IdRes int resId) { + return new Stub<>(parent.findViewById(resId)); + } + private static Animation getAlphaAnimation(float from, float to, int duration) { final Animation anim = new AlphaAnimation(from, to); anim.setInterpolator(new FastOutSlowInInterpolator()); diff --git a/app/src/main/res/layout/conversation_activity.xml b/app/src/main/res/layout/conversation_activity.xml index 03e200ead2..445f21a24f 100644 --- a/app/src/main/res/layout/conversation_activity.xml +++ b/app/src/main/res/layout/conversation_activity.xml @@ -161,14 +161,14 @@ android:layout_width="0dp" android:layout_height="?attr/actionBarSize" android:background="@color/conversation_toolbar_color" - android:theme="?attr/actionBarStyle" - app:contentInsetStartWithNavigation="0dp" - app:layout_constraintStart_toStartOf="@id/parent_start_guideline" - app:layout_constraintEnd_toEndOf="@id/parent_end_guideline" - app:layout_constraintTop_toTopOf="@id/status_bar_guideline" - app:contentInsetStart="46dp" + android:clipChildren="false" android:clipToPadding="false" - android:clipChildren="false"> + android:theme="?attr/actionBarStyle" + app:contentInsetStart="46dp" + app:contentInsetStartWithNavigation="0dp" + app:layout_constraintEnd_toEndOf="@id/parent_end_guideline" + app:layout_constraintStart_toStartOf="@id/parent_start_guideline" + app:layout_constraintTop_toTopOf="@id/status_bar_guideline"> + \ No newline at end of file