Add dialog protection and remote deletion to disabling stories and deleting lists.

fork-5.53.8
Alex Hart 2022-10-05 15:04:54 -03:00 zatwierdzone przez GitHub
rodzic ad1801108d
commit 4b94509a7a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
15 zmienionych plików z 212 dodań i 19 usunięć

Wyświetl plik

@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.components
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
/**
* Manages the lifecycle of displaying a dialog fragment. Will automatically close and nullify the reference
* if the bound lifecycle is destroyed, and handles repeat calls to show such that no more than one dialog is
* displayed.
*/
class DialogFragmentDisplayManager(private val builder: () -> DialogFragment) : DefaultLifecycleObserver {
private var dialogFragment: DialogFragment? = null
fun show(lifecycleOwner: LifecycleOwner, fragmentManager: FragmentManager, tag: String? = null) {
val fragment = dialogFragment ?: builder()
if (fragment.dialog?.isShowing != true) {
fragment.show(fragmentManager, tag)
dialogFragment = fragment
lifecycleOwner.lifecycle.addObserver(this)
}
}
fun hide() {
dialogFragment?.dismissNow()
dialogFragment = null
}
override fun onDestroy(owner: LifecycleOwner) {
owner.lifecycle.removeObserver(this)
hide()
}
}

Wyświetl plik

@ -0,0 +1,20 @@
package org.thoughtcrime.securesms.components
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import org.thoughtcrime.securesms.R
/**
* Displays a small progress spinner in a card view, as a non-cancellable dialog fragment.
*/
class ProgressCardDialogFragment : DialogFragment(R.layout.progress_card_dialog) {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
isCancelable = false
return super.onCreateDialog(savedInstanceState).apply {
this.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
}
}

Wyświetl plik

@ -44,6 +44,33 @@ class StoryDialogLauncherFragment : DSLSettingsFragment(titleId = R.string.prefe
}
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_turn_off_stories),
onClick = {
StoryDialogs.disableStories(requireContext(), false) {
Toast.makeText(requireContext(), R.string.preferences__internal_turn_off_stories, Toast.LENGTH_SHORT).show()
}
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_turn_off_stories_with_stories_on_disk),
onClick = {
StoryDialogs.disableStories(requireContext(), true) {
Toast.makeText(requireContext(), R.string.preferences__internal_turn_off_stories_with_stories_on_disk, Toast.LENGTH_SHORT).show()
}
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_delete_private_story),
onClick = {
StoryDialogs.deleteDistributionList(requireContext(), "Family") {
Toast.makeText(requireContext(), R.string.preferences__internal_delete_private_story, Toast.LENGTH_SHORT).show()
}
}
)
}
}
}

Wyświetl plik

@ -10,6 +10,38 @@ import org.thoughtcrime.securesms.R
object StoryDialogs {
fun deleteDistributionList(
context: Context,
distributionListName: String,
onDelete: () -> Unit
) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.StoryDialogs__delete_private_story)
.setMessage(context.getString(R.string.StoryDialogs__s_and_updates_shared, distributionListName))
.setPositiveButton(R.string.StoryDialogs__delete) { _, _ -> onDelete() }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
fun disableStories(
context: Context,
userHasStories: Boolean,
onDisable: () -> Unit
) {
val positiveButtonMessage = if (userHasStories) {
R.string.StoryDialogs__turn_off_and_delete
} else {
R.string.StoriesPrivacySettingsFragment__turn_off_stories
}
MaterialAlertDialogBuilder(context)
.setTitle(R.string.StoriesPrivacySettingsFragment__turn_off_stories_question)
.setMessage(R.string.StoriesPrivacySettingsFragment__you_will_no_longer_be_able_to_share)
.setPositiveButton(positiveButtonMessage) { _, _ -> onDisable() }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
fun resendStory(context: Context, onDismiss: () -> Unit = {}, resend: () -> Unit) {
MaterialAlertDialogBuilder(context)
.setMessage(R.string.StoryDialogs__story_could_not_be_sent)

Wyświetl plik

@ -10,6 +10,8 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.DialogFragmentDisplayManager
import org.thoughtcrime.securesms.components.ProgressCardDialogFragment
import org.thoughtcrime.securesms.components.WrapperDialogFragment
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
@ -17,6 +19,7 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.fragments.findListener
@ -28,6 +31,8 @@ class PrivateStorySettingsFragment : DSLSettingsFragment(
menuId = R.menu.story_private_menu
) {
private val progressDisplayManager = DialogFragmentDisplayManager { ProgressCardDialogFragment() }
private val viewModel: PrivateStorySettingsViewModel by viewModels(
factoryProducer = {
PrivateStorySettingsViewModel.Factory(PrivateStorySettingsFragmentArgs.fromBundle(requireArguments()).distributionListId, PrivateStorySettingsRepository())
@ -49,6 +54,12 @@ class PrivateStorySettingsFragment : DSLSettingsFragment(
val toolbar: Toolbar = requireView().findViewById(R.id.toolbar)
viewModel.state.observe(viewLifecycleOwner) { state ->
if (state.isActionInProgress) {
progressDisplayManager.show(viewLifecycleOwner, childFragmentManager)
} else {
progressDisplayManager.hide()
}
toolbar.title = state.privateStory?.name
adapter.submitList(getConfiguration(state).toMappingModelList())
}
@ -88,7 +99,8 @@ class PrivateStorySettingsFragment : DSLSettingsFragment(
clickPref(
title = DSLSettingsText.from(R.string.PrivateStorySettingsFragment__delete_private_story, DSLSettingsText.ColorModifier(ContextCompat.getColor(requireContext(), R.color.signal_alert_primary))),
onClick = {
handleDeletePrivateStory()
val privateStoryName = viewModel.state.value?.privateStory?.name
handleDeletePrivateStory(privateStoryName)
}
)
}
@ -113,13 +125,12 @@ class PrivateStorySettingsFragment : DSLSettingsFragment(
.show()
}
private fun handleDeletePrivateStory() {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.PrivateStorySettingsFragment__are_you_sure)
.setMessage(R.string.PrivateStorySettingsFragment__this_action_cannot)
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.setPositiveButton(R.string.delete) { _, _ -> viewModel.delete().subscribe { findNavController().popBackStack() } }
.show()
private fun handleDeletePrivateStory(privateStoryName: String?) {
val name = privateStoryName ?: return
StoryDialogs.deleteDistributionList(requireContext(), name) {
viewModel.delete().subscribe { findNavController().popBackStack() }
}
}
override fun onToolbarNavigationClicked() {

Wyświetl plik

@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListRecord
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.stories.Stories
class PrivateStorySettingsRepository {
@ -27,6 +28,13 @@ class PrivateStorySettingsRepository {
return Completable.fromAction {
SignalDatabase.distributionLists.deleteList(distributionListId)
Stories.onStorySettingsChanged(distributionListId)
val recipientId = SignalDatabase.recipients.getOrInsertFromDistributionListId(distributionListId)
SignalDatabase.mms.getAllStoriesFor(recipientId, -1).use { reader ->
for (record in reader) {
MessageSender.sendRemoteDelete(record.id, record.isMms)
}
}
}.subscribeOn(Schedulers.io())
}

Wyświetl plik

@ -4,5 +4,6 @@ import org.thoughtcrime.securesms.database.model.DistributionListRecord
data class PrivateStorySettingsState(
val privateStory: DistributionListRecord? = null,
val areRepliesAndReactionsEnabled: Boolean = false
val areRepliesAndReactionsEnabled: Boolean = false,
val isActionInProgress: Boolean = false
)

Wyświetl plik

@ -52,7 +52,9 @@ class PrivateStorySettingsViewModel(private val distributionListId: Distribution
}
fun delete(): Completable {
return repository.delete(distributionListId).observeOn(AndroidSchedulers.mainThread())
return repository.delete(distributionListId)
.doOnSubscribe { store.update { it.copy(isActionInProgress = true) } }
.observeOn(AndroidSchedulers.mainThread())
}
class Factory(private val privateStoryItemData: DistributionListId, private val repository: PrivateStorySettingsRepository) : ViewModelProvider.Factory {

Wyświetl plik

@ -4,9 +4,10 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ConcatAdapter
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.signal.core.util.dp
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.DialogFragmentDisplayManager
import org.thoughtcrime.securesms.components.ProgressCardDialogFragment
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
@ -17,6 +18,7 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.groups.ParcelableGroupId
import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseGroupStoryBottomSheet
import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseStoryTypeBottomSheet
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
import org.thoughtcrime.securesms.stories.settings.create.CreateStoryFlowDialogFragment
import org.thoughtcrime.securesms.stories.settings.create.CreateStoryWithViewersFragment
import org.thoughtcrime.securesms.util.BottomSheetUtil
@ -36,6 +38,7 @@ class StoriesPrivacySettingsFragment :
private val viewModel: StoriesPrivacySettingsViewModel by viewModels()
private val lifecycleDisposable = LifecycleDisposable()
private val progressDisplayManager = DialogFragmentDisplayManager { ProgressCardDialogFragment() }
override fun createAdapters(): Array<MappingAdapter> {
return arrayOf(DSLSettingsAdapter(), PagingMappingAdapter<ContactSearchKey>(), DSLSettingsAdapter())
@ -84,6 +87,12 @@ class StoriesPrivacySettingsFragment :
}
lifecycleDisposable += viewModel.state.subscribe { state ->
if (state.isUpdatingEnabledState) {
progressDisplayManager.show(viewLifecycleOwner, childFragmentManager)
} else {
progressDisplayManager.hide()
}
(top as MappingAdapter).submitList(getTopConfiguration(state).toMappingModelList())
middle.submitList(getMiddleConfiguration(state).toMappingModelList())
(bottom as MappingAdapter).submitList(getBottomConfiguration(state).toMappingModelList())
@ -144,12 +153,9 @@ class StoriesPrivacySettingsFragment :
DSLSettingsText.ColorModifier(ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant))
),
onClick = {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.StoriesPrivacySettingsFragment__turn_off_stories_question)
.setMessage(R.string.StoriesPrivacySettingsFragment__you_will_no_longer_be_able_to)
.setPositiveButton(R.string.StoriesPrivacySettingsFragment__turn_off_stories) { _, _ -> viewModel.setStoriesEnabled(false) }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
StoryDialogs.disableStories(requireContext(), viewModel.userHasActiveStories) {
viewModel.setStoriesEnabled(false)
}
}
)
}

Wyświetl plik

@ -1,12 +1,14 @@
package org.thoughtcrime.securesms.stories.settings.story
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.stories.Stories
@ -23,6 +25,20 @@ class StoriesPrivacySettingsRepository {
return Completable.fromAction {
SignalStore.storyValues().isFeatureDisabled = !isEnabled
Stories.onStorySettingsChanged(Recipient.self().id)
SignalDatabase.mms.getAllOutgoingStories(false, -1).use { reader ->
reader.map { record -> record.id }
}.forEach { messageId ->
MessageSender.sendRemoteDelete(messageId, true)
}
}.subscribeOn(Schedulers.io())
}
fun userHasOutgoingStories(): Single<Boolean> {
return Single.fromCallable {
SignalDatabase.mms.getAllOutgoingStories(false, -1).use {
it.iterator().hasNext()
}
}.subscribeOn(Schedulers.io())
}
}

Wyświetl plik

@ -5,5 +5,6 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchData
data class StoriesPrivacySettingsState(
val areStoriesEnabled: Boolean,
val isUpdatingEnabledState: Boolean = false,
val storyContactItems: List<ContactSearchData> = emptyList()
val storyContactItems: List<ContactSearchData> = emptyList(),
val userHasStories: Boolean = false
)

Wyświetl plik

@ -39,6 +39,7 @@ class StoriesPrivacySettingsViewModel : ViewModel() {
private val headerActionRequestSubject = PublishSubject.create<Unit>()
val state: Flowable<StoriesPrivacySettingsState> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
val userHasActiveStories: Boolean get() = store.state.userHasStories
val pagingController = ProxyPagingController<ContactSearchKey>()
val headerActionRequests: Observable<Unit> = headerActionRequestSubject.debounce(100, TimeUnit.MILLISECONDS)
@ -59,6 +60,8 @@ class StoriesPrivacySettingsViewModel : ViewModel() {
pagingController.set(observablePagedData.controller)
updateUserHasStories()
disposables += store.update(observablePagedData.data.toFlowable(BackpressureStrategy.LATEST)) { data, state ->
state.copy(storyContactItems = data)
}
@ -78,6 +81,7 @@ class StoriesPrivacySettingsViewModel : ViewModel() {
areStoriesEnabled = Stories.isFeatureEnabled()
)
}
updateUserHasStories()
}
}
@ -86,4 +90,10 @@ class StoriesPrivacySettingsViewModel : ViewModel() {
pagingController.onDataInvalidated()
}
}
private fun updateUserHasStories() {
disposables += repository.userHasOutgoingStories().subscribe { userHasActiveStories ->
store.update { it.copy(userHasStories = userHasActiveStories) }
}
}
}

Wyświetl plik

@ -12,5 +12,6 @@
android:layout_gravity="center"
android:layout_margin="24dp"
android:indeterminate="true"
android:background="@color/transparent"
app:indicatorColor="@color/signal_colorPrimary" />
</merge>

Wyświetl plik

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<org.thoughtcrime.securesms.components.ProgressCard
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="18dp" />
</FrameLayout>

Wyświetl plik

@ -2753,6 +2753,9 @@
<string name="configurable_single_select__customize_option">Customize option</string>
<!-- Internal only preferences -->
<string name="preferences__internal_turn_off_stories_with_stories_on_disk" translatable="false">Turn off stories (with stories on disk)</string>
<string name="preferences__internal_turn_off_stories" translatable="false">Turn off stories</string>
<string name="preferences__internal_delete_private_story" translatable="false">Delete private story</string>
<string name="preferences__internal_hide_story" translatable="false">Hide story</string>
<string name="preferences__internal_story_or_profile_selector" translatable="false">Story or profile selector</string>
<string name="preferences__internal_stories_dialog_launcher" translatable="false">Stories dialog launcher</string>
@ -4936,6 +4939,12 @@
<string name="ChangeMyStoryMembershipFragment__only_share_with">Only share with…</string>
<!-- Done button label for hide story from screen -->
<string name="HideStoryFromFragment__done">Done</string>
<!-- Dialog title for deleting a private story -->
<string name="StoryDialogs__delete_private_story">Delete private story?</string>
<!-- Dialog message for deleting a private story -->
<string name="StoryDialogs__s_and_updates_shared">\"%1$s\" and updates shared to this story will be deleted.</string>
<!-- Dialog positive action for deleting a private story -->
<string name="StoryDialogs__delete">Delete</string>
<!-- Dialog title for first time adding something to a story -->
<string name="StoryDialogs__add_to_story_q">Add to story?</string>
<!-- Dialog message for first time adding something to a story -->
@ -4948,6 +4957,8 @@
<string name="StoryDialogs__story_could_not_be_sent">Story could not be sent. Check your connection and try again.</string>
<!-- Error message dialog button to resend a previously failed story send -->
<string name="StoryDialogs__send">Send</string>
<!-- Action button for turning off stories when stories are present on the device -->
<string name="StoryDialogs__turn_off_and_delete">Turn off and delete</string>
<!-- Privacy Settings toggle title for stories -->
<string name="PrivacySettingsFragment__share_and_view_stories">Share &amp; View Stories</string>
<!-- Privacy Settings toggle summary for stories -->
@ -5231,7 +5242,7 @@
<!-- Dialog title to turn off stories -->
<string name="StoriesPrivacySettingsFragment__turn_off_stories_question">Turn off stories?</string>
<!-- Dialog message to turn off stories -->
<string name="StoriesPrivacySettingsFragment__you_will_no_longer_be_able_to">You will no longer be able to share or view stories. Any stories you have recently sent will still be visible by others until they expire.</string>
<string name="StoriesPrivacySettingsFragment__you_will_no_longer_be_able_to_share">You will no longer be able to share or view stories. Story updates you have recently shared will also be deleted.</string>
<!-- Page title when launched from stories landing screen -->
<string name="StoriesPrivacySettingsFragment__story_privacy">Story privacy</string>