Add first time My Story privacy configuration.

fork-5.53.8
Cody Henthorne 2022-07-05 11:58:02 -04:00 zatwierdzone przez Alex Hart
rodzic 3eac397263
commit 78d4d9a3dd
15 zmienionych plików z 485 dodań i 140 usunięć

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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))

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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)
}
}
}

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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)

Wyświetl plik

@ -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
)
}
}

Wyświetl plik

@ -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()
}
}
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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())

Wyświetl plik

@ -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())
}
}

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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"

Wyświetl plik

@ -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>