kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add first time My Story privacy configuration.
rodzic
3eac397263
commit
78d4d9a3dd
|
@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView
|
|||
import org.thoughtcrime.securesms.components.FromTextView
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem
|
||||
import org.thoughtcrime.securesms.components.menu.SignalContextMenu
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
|
@ -55,7 +56,7 @@ object ContactSearchItems {
|
|||
return MappingModelList(
|
||||
contactSearchData.filterNotNull().map {
|
||||
when (it) {
|
||||
is ContactSearchData.Story -> StoryModel(it, selection.contains(it.contactSearchKey))
|
||||
is ContactSearchData.Story -> StoryModel(it, selection.contains(it.contactSearchKey), SignalStore.storyValues().userHasBeenNotifiedAboutStories)
|
||||
is ContactSearchData.KnownRecipient -> RecipientModel(it, selection.contains(it.contactSearchKey))
|
||||
is ContactSearchData.Expand -> ExpandModel(it)
|
||||
is ContactSearchData.Header -> HeaderModel(it)
|
||||
|
@ -67,18 +68,23 @@ object ContactSearchItems {
|
|||
/**
|
||||
* Story Model
|
||||
*/
|
||||
private class StoryModel(val story: ContactSearchData.Story, val isSelected: Boolean) : MappingModel<StoryModel> {
|
||||
private class StoryModel(val story: ContactSearchData.Story, val isSelected: Boolean, val hasBeenNotified: Boolean) : MappingModel<StoryModel> {
|
||||
|
||||
override fun areItemsTheSame(newItem: StoryModel): Boolean {
|
||||
return newItem.story == story
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(newItem: StoryModel): Boolean {
|
||||
return story.recipient.hasSameContent(newItem.story.recipient) && isSelected == newItem.isSelected
|
||||
return story.recipient.hasSameContent(newItem.story.recipient) &&
|
||||
isSelected == newItem.isSelected &&
|
||||
hasBeenNotified == newItem.hasBeenNotified
|
||||
}
|
||||
|
||||
override fun getChangePayload(newItem: StoryModel): Any? {
|
||||
return if (story.recipient.hasSameContent(newItem.story.recipient) && newItem.isSelected != isSelected) {
|
||||
return if (story.recipient.hasSameContent(newItem.story.recipient) &&
|
||||
hasBeenNotified == newItem.hasBeenNotified &&
|
||||
newItem.isSelected != isSelected
|
||||
) {
|
||||
0
|
||||
} else {
|
||||
null
|
||||
|
@ -100,13 +106,17 @@ object ContactSearchItems {
|
|||
model.story.viewerCount
|
||||
}
|
||||
|
||||
val pluralId = when {
|
||||
model.story.recipient.isGroup -> R.plurals.ContactSearchItems__group_story_d_viewers
|
||||
model.story.recipient.isMyStory -> R.plurals.SelectViewersFragment__d_viewers
|
||||
else -> R.plurals.ContactSearchItems__private_story_d_viewers
|
||||
}
|
||||
if (model.story.recipient.isMyStory && !model.hasBeenNotified) {
|
||||
number.setText(R.string.ContactSearchItems__tap_to_choose_your_viewers)
|
||||
} else {
|
||||
val pluralId = when {
|
||||
model.story.recipient.isGroup -> R.plurals.ContactSearchItems__group_story_d_viewers
|
||||
model.story.recipient.isMyStory -> R.plurals.SelectViewersFragment__d_viewers
|
||||
else -> R.plurals.ContactSearchItems__private_story_d_viewers
|
||||
}
|
||||
|
||||
number.text = context.resources.getQuantityString(pluralId, count, count)
|
||||
number.text = context.resources.getQuantityString(pluralId, count, count)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bindLongPress(model: StoryModel) {
|
||||
|
|
|
@ -9,8 +9,10 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.stories.settings.custom.PrivateStorySettingsFragment
|
||||
import org.thoughtcrime.securesms.stories.settings.my.MyStorySettingsFragment
|
||||
import org.thoughtcrime.securesms.stories.settings.privacy.ChooseInitialMyStoryMembershipBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
|
||||
|
@ -35,7 +37,7 @@ class ContactSearchMediator(
|
|||
mappingAdapter = adapter,
|
||||
displayCheckBox = displayCheckBox,
|
||||
recipientListener = this::toggleSelection,
|
||||
storyListener = this::toggleSelection,
|
||||
storyListener = this::toggleStorySelection,
|
||||
storyContextMenuCallbacks = StoryContextMenuCallbacks(),
|
||||
expandListener = { viewModel.expandSection(it.sectionKey) }
|
||||
)
|
||||
|
@ -87,6 +89,14 @@ class ContactSearchMediator(
|
|||
viewModel.refresh()
|
||||
}
|
||||
|
||||
private fun toggleStorySelection(view: View, contactSearchData: ContactSearchData.Story, isSelected: Boolean) {
|
||||
if (contactSearchData.recipient.isMyStory && !SignalStore.storyValues().userHasBeenNotifiedAboutStories) {
|
||||
ChooseInitialMyStoryMembershipBottomSheetDialogFragment.show(fragment.childFragmentManager)
|
||||
} else {
|
||||
toggleSelection(view, contactSearchData, isSelected)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSelection(view: View, contactSearchData: ContactSearchData, isSelected: Boolean) {
|
||||
return if (isSelected) {
|
||||
viewModel.setKeysNotSelected(setOf(contactSearchData.contactSearchKey))
|
||||
|
|
|
@ -45,10 +45,9 @@ import org.thoughtcrime.securesms.sharing.ShareSelectionAdapter
|
|||
import org.thoughtcrime.securesms.sharing.ShareSelectionMappingModel
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.stories.Stories.getHeaderAction
|
||||
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.stories.settings.privacy.HideStoryFromDialogFragment
|
||||
import org.thoughtcrime.securesms.stories.settings.privacy.ChooseInitialMyStoryMembershipBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper
|
||||
|
@ -77,7 +76,8 @@ class MultiselectForwardFragment :
|
|||
Fragment(R.layout.multiselect_forward_fragment),
|
||||
SafetyNumberChangeDialog.Callback,
|
||||
ChooseStoryTypeBottomSheet.Callback,
|
||||
WrapperDialogFragment.WrapperDialogFragmentCallback {
|
||||
WrapperDialogFragment.WrapperDialogFragmentCallback,
|
||||
ChooseInitialMyStoryMembershipBottomSheetDialogFragment.Callback {
|
||||
|
||||
private val viewModel: MultiselectForwardViewModel by viewModels(factoryProducer = this::createViewModelFactory)
|
||||
private val disposables = LifecycleDisposable()
|
||||
|
@ -285,24 +285,6 @@ class MultiselectForwardFragment :
|
|||
|
||||
private fun onSend(sendButton: View) {
|
||||
sendButton.isEnabled = false
|
||||
|
||||
StoryDialogs.guardWithAddToYourStoryDialog(
|
||||
requireContext(),
|
||||
contactSearchMediator.getSelectedContacts(),
|
||||
onAddToStory = {
|
||||
performSend()
|
||||
},
|
||||
onEditViewers = {
|
||||
sendButton.isEnabled = true
|
||||
HideStoryFromDialogFragment().show(childFragmentManager, null)
|
||||
},
|
||||
onCancel = {
|
||||
sendButton.isEnabled = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun performSend() {
|
||||
viewModel.send(addMessage.text.toString(), contactSearchMediator.getSelectedContacts())
|
||||
}
|
||||
|
||||
|
@ -483,6 +465,15 @@ class MultiselectForwardFragment :
|
|||
CreateStoryFlowDialogFragment().show(parentFragmentManager, CreateStoryWithViewersFragment.REQUEST_KEY)
|
||||
}
|
||||
|
||||
override fun onWrapperDialogFragmentDismissed() {
|
||||
contactSearchMediator.refresh()
|
||||
}
|
||||
|
||||
override fun onMyStoryConfigured(recipientId: RecipientId) {
|
||||
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey.Story(recipientId)))
|
||||
contactSearchMediator.refresh()
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onFinishForwardAction()
|
||||
fun exitFlow()
|
||||
|
@ -544,8 +535,4 @@ class MultiselectForwardFragment :
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWrapperDialogFragmentDismissed() {
|
||||
contactSearchMediator.refresh()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
|||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendRepository
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostView
|
||||
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
|
||||
import org.thoughtcrime.securesms.stories.settings.privacy.HideStoryFromDialogFragment
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
|
@ -134,22 +132,7 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
|||
findNavController().safeNavigate(R.id.action_textStoryPostCreationFragment_to_textStoryPostSendFragment)
|
||||
} else {
|
||||
send.isClickable = false
|
||||
StoryDialogs.guardWithAddToYourStoryDialog(
|
||||
contacts = contacts,
|
||||
context = requireContext(),
|
||||
onAddToStory = {
|
||||
performSend(contacts)
|
||||
},
|
||||
onEditViewers = {
|
||||
send.isClickable = true
|
||||
storyTextPostView.hideCloseButton()
|
||||
HideStoryFromDialogFragment().show(childFragmentManager, null)
|
||||
},
|
||||
onCancel = {
|
||||
send.isClickable = true
|
||||
storyTextPostView.hideCloseButton()
|
||||
}
|
||||
)
|
||||
performSend(contacts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,16 +27,19 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
|||
import org.thoughtcrime.securesms.sharing.ShareSelectionAdapter
|
||||
import org.thoughtcrime.securesms.sharing.ShareSelectionMappingModel
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
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.stories.settings.privacy.HideStoryFromDialogFragment
|
||||
import org.thoughtcrime.securesms.stories.settings.privacy.ChooseInitialMyStoryMembershipBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
|
||||
|
||||
class TextStoryPostSendFragment : Fragment(R.layout.stories_send_text_post_fragment), ChooseStoryTypeBottomSheet.Callback, WrapperDialogFragment.WrapperDialogFragmentCallback {
|
||||
class TextStoryPostSendFragment :
|
||||
Fragment(R.layout.stories_send_text_post_fragment),
|
||||
ChooseStoryTypeBottomSheet.Callback,
|
||||
WrapperDialogFragment.WrapperDialogFragmentCallback,
|
||||
ChooseInitialMyStoryMembershipBottomSheetDialogFragment.Callback {
|
||||
|
||||
private lateinit var shareListWrapper: View
|
||||
private lateinit var shareSelectionRecyclerView: RecyclerView
|
||||
|
@ -87,18 +90,7 @@ class TextStoryPostSendFragment : Fragment(R.layout.stories_send_text_post_fragm
|
|||
|
||||
shareConfirmButton.setOnClickListener {
|
||||
viewModel.onSending()
|
||||
StoryDialogs.guardWithAddToYourStoryDialog(
|
||||
contacts = contactSearchMediator.getSelectedContacts(),
|
||||
context = requireContext(),
|
||||
onAddToStory = { send() },
|
||||
onEditViewers = {
|
||||
viewModel.onSendCancelled()
|
||||
HideStoryFromDialogFragment().show(childFragmentManager, null)
|
||||
},
|
||||
onCancel = {
|
||||
viewModel.onSendCancelled()
|
||||
}
|
||||
)
|
||||
send()
|
||||
}
|
||||
|
||||
disposables += viewModel.untrustedIdentities.subscribe {
|
||||
|
@ -200,4 +192,9 @@ class TextStoryPostSendFragment : Fragment(R.layout.stories_send_text_post_fragm
|
|||
override fun onWrapperDialogFragmentDismissed() {
|
||||
contactSearchMediator.refresh()
|
||||
}
|
||||
|
||||
override fun onMyStoryConfigured(recipientId: RecipientId) {
|
||||
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey.Story(recipientId)))
|
||||
contactSearchMediator.refresh()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,53 +2,14 @@ package org.thoughtcrime.securesms.stories.dialogs
|
|||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
object StoryDialogs {
|
||||
|
||||
/**
|
||||
* Guards onAddToStory with a dialog
|
||||
*/
|
||||
fun guardWithAddToYourStoryDialog(
|
||||
context: Context,
|
||||
contacts: Collection<ContactSearchKey>,
|
||||
onAddToStory: () -> Unit,
|
||||
onEditViewers: () -> Unit,
|
||||
onCancel: () -> Unit = {}
|
||||
) {
|
||||
if (!isFirstSendToMyStory(contacts)) {
|
||||
onAddToStory()
|
||||
} else {
|
||||
SignalStore.storyValues().userHasBeenNotifiedAboutStories = true
|
||||
MaterialAlertDialogBuilder(context, R.style.ThemeOverlay_Signal_MaterialAlertDialog)
|
||||
.setTitle(R.string.StoryDialogs__add_to_story_q)
|
||||
.setMessage(R.string.StoryDialogs__adding_content)
|
||||
.setPositiveButton(R.string.StoryDialogs__add_to_story) { _, _ ->
|
||||
onAddToStory.invoke()
|
||||
}
|
||||
.setNeutralButton(R.string.StoryDialogs__edit_viewers) { _, _ -> Toast.makeText(context, "New flow coming soon", Toast.LENGTH_SHORT).show() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> onCancel.invoke() }
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFirstSendToMyStory(shareContacts: Collection<ContactSearchKey>): Boolean {
|
||||
if (SignalStore.storyValues().userHasBeenNotifiedAboutStories) {
|
||||
return false
|
||||
}
|
||||
|
||||
return shareContacts.any { it is ContactSearchKey.RecipientSearchKey.Story && Recipient.resolved(it.recipientId).isMyStory }
|
||||
}
|
||||
|
||||
fun resendStory(context: Context, resend: () -> Unit) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.StoryDialogs__story_could_not_be_sent)
|
||||
|
|
|
@ -1,27 +1,35 @@
|
|||
package org.thoughtcrime.securesms.stories.settings.my
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyData
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.stories.settings.privacy.ChooseInitialMyStoryMembershipState
|
||||
|
||||
class MyStorySettingsRepository {
|
||||
|
||||
fun getPrivacyState(): Single<MyStoryPrivacyState> {
|
||||
return Single.fromCallable {
|
||||
val privacyData: DistributionListPrivacyData = SignalDatabase.distributionLists.getPrivacyData(DistributionListId.MY_STORY)
|
||||
|
||||
MyStoryPrivacyState(
|
||||
privacyMode = privacyData.privacyMode,
|
||||
connectionCount = if (privacyData.privacyMode == DistributionListPrivacyMode.ALL_EXCEPT) privacyData.rawMemberCount else privacyData.memberCount
|
||||
)
|
||||
getStoryPrivacyState()
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun observeChooseInitialPrivacy(): Observable<ChooseInitialMyStoryMembershipState> {
|
||||
return Single.fromCallable { SignalDatabase.distributionLists.getRecipientId(DistributionListId.MY_STORY)!! }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMapObservable { recipientId ->
|
||||
Recipient.observable(recipientId)
|
||||
.flatMap { Observable.just(ChooseInitialMyStoryMembershipState(recipientId = recipientId, privacyState = getStoryPrivacyState())) }
|
||||
}
|
||||
}
|
||||
|
||||
fun setPrivacyMode(privacyMode: DistributionListPrivacyMode): Completable {
|
||||
return Completable.fromAction {
|
||||
SignalDatabase.distributionLists.setPrivacyMode(DistributionListId.MY_STORY, privacyMode)
|
||||
|
@ -41,4 +49,14 @@ class MyStorySettingsRepository {
|
|||
Stories.onStorySettingsChanged(DistributionListId.MY_STORY)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getStoryPrivacyState(): MyStoryPrivacyState {
|
||||
val privacyData: DistributionListPrivacyData = SignalDatabase.distributionLists.getPrivacyData(DistributionListId.MY_STORY)
|
||||
|
||||
return MyStoryPrivacyState(
|
||||
privacyMode = privacyData.privacyMode,
|
||||
connectionCount = if (privacyData.privacyMode == DistributionListPrivacyMode.ALL_EXCEPT) privacyData.rawMemberCount else privacyData.memberCount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package org.thoughtcrime.securesms.stories.settings.privacy
|
||||
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.WrapperDialogFragment
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.stories.settings.select.BaseStoryRecipientSelectionFragment
|
||||
|
||||
|
@ -20,6 +23,18 @@ abstract class ChangeMyStoryMembershipFragment : BaseStoryRecipientSelectionFrag
|
|||
class AllExceptFragment : ChangeMyStoryMembershipFragment() {
|
||||
override val toolbarTitleId: Int = R.string.ChangeMyStoryMembershipFragment__all_except
|
||||
override val checkboxResource: Int = R.drawable.contact_selection_exclude_checkbox
|
||||
|
||||
class Dialog : WrapperDialogFragment() {
|
||||
override fun getWrappedFragment(): Fragment {
|
||||
return AllExceptFragment()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createAsDialog(): DialogFragment {
|
||||
return Dialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,4 +42,16 @@ class AllExceptFragment : ChangeMyStoryMembershipFragment() {
|
|||
*/
|
||||
class OnlyShareWithFragment : ChangeMyStoryMembershipFragment() {
|
||||
override val toolbarTitleId: Int = R.string.ChangeMyStoryMembershipFragment__only_share_with
|
||||
|
||||
class Dialog : WrapperDialogFragment() {
|
||||
override fun getWrappedFragment(): Fragment {
|
||||
return OnlyShareWithFragment()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createAsDialog(): DialogFragment {
|
||||
return Dialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package org.thoughtcrime.securesms.stories.settings.privacy
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.components.WrapperDialogFragment
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stories.settings.select.BaseStoryRecipientSelectionFragment
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.fragments.findListener
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
/**
|
||||
* Choose the initial settings for My Story when first sending to My Story.
|
||||
*/
|
||||
class ChooseInitialMyStoryMembershipBottomSheetDialogFragment :
|
||||
FixedRoundedCornerBottomSheetDialogFragment(),
|
||||
WrapperDialogFragment.WrapperDialogFragmentCallback,
|
||||
BaseStoryRecipientSelectionFragment.Callback {
|
||||
|
||||
private val viewModel: ChooseInitialMyStoryMembershipViewModel by viewModels()
|
||||
|
||||
private lateinit var lifecycleDisposable: LifecycleDisposable
|
||||
|
||||
private lateinit var allRow: View
|
||||
private lateinit var allExceptRow: View
|
||||
private lateinit var onlyWitRow: View
|
||||
|
||||
private lateinit var allRadio: MaterialRadioButton
|
||||
private lateinit var allExceptRadio: MaterialRadioButton
|
||||
private lateinit var onlyWitRadio: MaterialRadioButton
|
||||
|
||||
private lateinit var allExceptCount: TextView
|
||||
private lateinit var onlyWithCount: TextView
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.choose_initial_my_story_membership_fragment, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
allRow = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_row)
|
||||
allExceptRow = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_except_row)
|
||||
onlyWitRow = view.findViewById(R.id.choose_initial_my_story_only_share_with_row)
|
||||
|
||||
allRadio = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_radio)
|
||||
allExceptRadio = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_except_radio)
|
||||
onlyWitRadio = view.findViewById(R.id.choose_initial_my_story_only_share_with_radio)
|
||||
|
||||
allExceptCount = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_except_count)
|
||||
onlyWithCount = view.findViewById(R.id.choose_initial_my_story_only_share_with_count)
|
||||
|
||||
val save = view.findViewById<View>(R.id.choose_initial_my_story_save).apply {
|
||||
isEnabled = false
|
||||
}
|
||||
|
||||
lifecycleDisposable = LifecycleDisposable().apply { bindTo(viewLifecycleOwner) }
|
||||
|
||||
lifecycleDisposable += viewModel.state
|
||||
.subscribe { state ->
|
||||
allRadio.isChecked = state.privacyState.privacyMode == DistributionListPrivacyMode.ALL
|
||||
allExceptRadio.isChecked = state.privacyState.privacyMode == DistributionListPrivacyMode.ALL_EXCEPT
|
||||
onlyWitRadio.isChecked = state.privacyState.privacyMode == DistributionListPrivacyMode.ONLY_WITH
|
||||
|
||||
allExceptCount.visible = allExceptRadio.isChecked
|
||||
onlyWithCount.visible = onlyWitRadio.isChecked
|
||||
|
||||
when (state.privacyState.privacyMode) {
|
||||
DistributionListPrivacyMode.ALL_EXCEPT -> allExceptCount.text = resources.getQuantityString(R.plurals.MyStorySettingsFragment__d_people_excluded, state.privacyState.connectionCount, state.privacyState.connectionCount)
|
||||
DistributionListPrivacyMode.ONLY_WITH -> onlyWithCount.text = resources.getQuantityString(R.plurals.MyStorySettingsFragment__d_people, state.privacyState.connectionCount, state.privacyState.connectionCount)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
save.isEnabled = state.recipientId != null
|
||||
}
|
||||
|
||||
val clickListener = { v: View ->
|
||||
val selection = when (v) {
|
||||
allRow -> DistributionListPrivacyMode.ALL
|
||||
allExceptRow -> DistributionListPrivacyMode.ALL_EXCEPT
|
||||
onlyWitRow -> DistributionListPrivacyMode.ONLY_WITH
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
|
||||
viewModel
|
||||
.select(selection)
|
||||
.subscribe { confirmedSelection ->
|
||||
when (confirmedSelection) {
|
||||
DistributionListPrivacyMode.ALL_EXCEPT -> AllExceptFragment.createAsDialog().show(childFragmentManager, SELECTION_FRAGMENT)
|
||||
DistributionListPrivacyMode.ONLY_WITH -> OnlyShareWithFragment.createAsDialog().show(childFragmentManager, SELECTION_FRAGMENT)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listOf(allRow, allExceptRow, onlyWitRow).forEach { it.setOnClickListener { v -> clickListener(v) } }
|
||||
|
||||
save.setOnClickListener {
|
||||
lifecycleDisposable += viewModel
|
||||
.save()
|
||||
.subscribe { recipientId ->
|
||||
dismissAllowingStateLoss()
|
||||
findListener<Callback>()?.onMyStoryConfigured(recipientId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun exitFlow() {
|
||||
(childFragmentManager.findFragmentByTag(SELECTION_FRAGMENT) as? DialogFragment)?.dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
override fun onWrapperDialogFragmentDismissed() = Unit
|
||||
|
||||
companion object {
|
||||
private const val SELECTION_FRAGMENT = "selection_fragment"
|
||||
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
val fragment = ChooseInitialMyStoryMembershipBottomSheetDialogFragment()
|
||||
fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onMyStoryConfigured(recipientId: RecipientId)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.thoughtcrime.securesms.stories.settings.privacy
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stories.settings.my.MyStoryPrivacyState
|
||||
|
||||
data class ChooseInitialMyStoryMembershipState(val recipientId: RecipientId? = null, val privacyState: MyStoryPrivacyState = MyStoryPrivacyState())
|
|
@ -0,0 +1,46 @@
|
|||
package org.thoughtcrime.securesms.stories.settings.privacy
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stories.settings.my.MyStorySettingsRepository
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
||||
class ChooseInitialMyStoryMembershipViewModel @JvmOverloads constructor(
|
||||
private val repository: MyStorySettingsRepository = MyStorySettingsRepository()
|
||||
) : ViewModel() {
|
||||
|
||||
private val store = RxStore(ChooseInitialMyStoryMembershipState())
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
val state: Flowable<ChooseInitialMyStoryMembershipState> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
init {
|
||||
disposables += repository.observeChooseInitialPrivacy()
|
||||
.distinctUntilChanged()
|
||||
.subscribe { state -> store.update { state } }
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
fun select(selection: DistributionListPrivacyMode): Single<DistributionListPrivacyMode> {
|
||||
return repository.setPrivacyMode(selection)
|
||||
.toSingleDefault(selection)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun save(): Single<RecipientId> {
|
||||
return Single.fromCallable<RecipientId> {
|
||||
SignalStore.storyValues().userHasBeenNotifiedAboutStories = true
|
||||
store.state.recipientId
|
||||
}.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package org.thoughtcrime.securesms.stories.settings.privacy
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.stories.settings.select.BaseStoryRecipientSelectionFragment
|
||||
|
||||
/**
|
||||
* Embeds HideStoryFromFragment in a full-screen dialog.
|
||||
*/
|
||||
class HideStoryFromDialogFragment : DialogFragment(R.layout.fragment_container), BaseStoryRecipientSelectionFragment.Callback {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// TODO [stories] replace with new bottom sheet
|
||||
}
|
||||
|
||||
override fun exitFlow() {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="48dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/anchor"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="2dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/signal_icon_tint_tab_unselected" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_title"
|
||||
style="@style/Signal.Text.TitleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="32dp"
|
||||
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/ChooseInitialMyStoryMembershipFragment__my_story_privacy" />
|
||||
|
||||
<TextView
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/ChooseInitialMyStoryMembershipFragment__choose_who_can_see_posts_to_my_story_you_can_always_make_changes_in_settings"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="24dp">
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false" />
|
||||
|
||||
<TextView
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/ChooseInitialMyStoryMembershipFragment__all_signal_connections" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_except_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="24dp">
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_except_radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_except_message"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/ChooseInitialMyStoryMembershipFragment__all_signal_connections_except"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/choose_initial_my_story_all_signal_connnections_except_count"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/choose_initial_my_story_all_signal_connnections_except_radio"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_except_count"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/choose_initial_my_story_all_signal_connnections_except_message"
|
||||
app:layout_constraintStart_toStartOf="@+id/choose_initial_my_story_all_signal_connnections_except_message"
|
||||
app:layout_constraintTop_toBottomOf="@+id/choose_initial_my_story_all_signal_connnections_except_message"
|
||||
tools:text="Asdf"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/choose_initial_my_story_only_share_with_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="24dp">
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/choose_initial_my_story_only_share_with_radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_only_share_with_message"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/ChooseInitialMyStoryMembershipFragment__only_share_with"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/choose_initial_my_story_only_share_with_count"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/choose_initial_my_story_only_share_with_radio"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_only_share_with_count"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/choose_initial_my_story_only_share_with_message"
|
||||
app:layout_constraintStart_toStartOf="@+id/choose_initial_my_story_only_share_with_message"
|
||||
app:layout_constraintTop_toBottomOf="@+id/choose_initial_my_story_only_share_with_message"
|
||||
tools:text="Asdf"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/choose_initial_my_story_save"
|
||||
style="@style/Signal.Widget.Button.Medium.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/save" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</ScrollView>
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/action_button"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
style="@style/Signal.Widget.Button.Large.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
|
|
|
@ -4888,6 +4888,8 @@
|
|||
<item quantity="one">Group Story · %1$d viewer</item>
|
||||
<item quantity="other">Group Story · %1$d viewers</item>
|
||||
</plurals>
|
||||
<!-- Label under name for My Story when first sending to my story -->
|
||||
<string name="ContactSearchItems__tap_to_choose_your_viewers">Tap to choose your viewers</string>
|
||||
<!-- Label for context menu item to open story settings -->
|
||||
<string name="ContactSearchItems__story_settings">Story settings</string>
|
||||
<!-- Label for context menu item to remove a group story from contact results -->
|
||||
|
@ -4933,6 +4935,17 @@
|
|||
<!-- Button label to confirm understanding of story navigation -->
|
||||
<string name="StoryFirstTimeNagivationView__got_it">Got it</string>
|
||||
|
||||
<!-- Title of initial My Story settings configuration shown when sending to My Story for the first time -->
|
||||
<string name="ChooseInitialMyStoryMembershipFragment__my_story_privacy">My Story Privacy</string>
|
||||
<!-- Subtitle of initial My Story settings configuration shown when sending to My Story for the first time -->
|
||||
<string name="ChooseInitialMyStoryMembershipFragment__choose_who_can_see_posts_to_my_story_you_can_always_make_changes_in_settings">Choose who can see posts to My Story. You can always make changes in settings.</string>
|
||||
<!-- All connections option for initial My Story settings configuration shown when sending to My Story for the first time -->
|
||||
<string name="ChooseInitialMyStoryMembershipFragment__all_signal_connections">All Signal connections</string>
|
||||
<!-- All connections except option for initial My Story settings configuration shown when sending to My Story for the first time -->
|
||||
<string name="ChooseInitialMyStoryMembershipFragment__all_signal_connections_except">All Signal connections except…</string>
|
||||
<!-- Only with selected connections option for initial My Story settings configuration shown when sending to My Story for the first time -->
|
||||
<string name="ChooseInitialMyStoryMembershipFragment__only_share_with">Only share with…</string>
|
||||
|
||||
<!-- EOF -->
|
||||
|
||||
</resources>
|
||||
|
|
Ładowanie…
Reference in New Issue