From 04e8235cfcd7aada8e1b199c95ad92ab757df21b Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 17 Nov 2022 12:35:17 -0400 Subject: [PATCH] Add group stories education sheet. --- .../forward/MultiselectForwardFragment.kt | 12 +- .../securesms/keyvalue/StoryValues.kt | 10 +- .../storage/AccountRecordProcessor.java | 2 + .../securesms/storage/StorageSyncHelper.java | 2 + .../stories/GroupStoryEducationSheet.kt | 43 ++++++ .../story/StoriesPrivacySettingsFragment.kt | 15 +- app/src/main/res/drawable/group_story.xml | 45 ++++++ .../layout/group_story_education_sheet.xml | 129 ++++++++++++++++++ app/src/main/res/values/strings.xml | 12 ++ .../api/storage/SignalAccountRecord.java | 13 ++ .../src/main/proto/StorageService.proto | 63 ++++----- 11 files changed, 311 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/stories/GroupStoryEducationSheet.kt create mode 100644 app/src/main/res/drawable/group_story.xml create mode 100644 app/src/main/res/layout/group_story_education_sheet.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt index 3725d0402..18acb4df4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt @@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.sharing.ShareSelectionAdapter import org.thoughtcrime.securesms.sharing.ShareSelectionMappingModel +import org.thoughtcrime.securesms.stories.GroupStoryEducationSheet import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.stories.Stories.getHeaderAction import org.thoughtcrime.securesms.stories.settings.create.CreateStoryFlowDialogFragment @@ -80,6 +81,7 @@ class MultiselectForwardFragment : Fragment(R.layout.multiselect_forward_fragment), SafetyNumberBottomSheet.Callbacks, ChooseStoryTypeBottomSheet.Callback, + GroupStoryEducationSheet.Callback, WrapperDialogFragment.WrapperDialogFragmentCallback, ChooseInitialMyStoryMembershipBottomSheetDialogFragment.Callback { @@ -466,13 +468,21 @@ class MultiselectForwardFragment : } override fun onGroupStoryClicked() { - ChooseGroupStoryBottomSheet().show(parentFragmentManager, ChooseGroupStoryBottomSheet.GROUP_STORY) + if (SignalStore.storyValues().userHasSeenGroupStoryEducationSheet) { + onGroupStoryEducationSheetNext() + } else { + GroupStoryEducationSheet().show(childFragmentManager, GroupStoryEducationSheet.KEY) + } } override fun onNewStoryClicked() { CreateStoryFlowDialogFragment().show(parentFragmentManager, CreateStoryWithViewersFragment.REQUEST_KEY) } + override fun onGroupStoryEducationSheetNext() { + ChooseGroupStoryBottomSheet().show(parentFragmentManager, ChooseGroupStoryBottomSheet.GROUP_STORY) + } + override fun onWrapperDialogFragmentDismissed() { contactSearchMediator.refresh() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/StoryValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/StoryValues.kt index 1f7fc7233..3c920e9d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/StoryValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/StoryValues.kt @@ -49,6 +49,11 @@ internal class StoryValues(store: KeyValueStore) : SignalStoreValues(store) { * Whether or not the user will send and receive viewed receipts for stories */ private const val STORY_VIEWED_RECEIPTS = "stories.viewed.receipts" + + /** + * Whether or not the user has seen the group story education sheet + */ + private const val USER_HAS_SEEN_GROUP_STORY_EDUCATION_SHEET = "stories.user.has.seen.group.story.education.sheet" } override fun onFirstEverAppLaunch() { @@ -62,7 +67,8 @@ internal class StoryValues(store: KeyValueStore) : SignalStoreValues(store) { HAS_DOWNLOADED_ONBOARDING_STORY, USER_HAS_VIEWED_ONBOARDING_STORY, USER_HAS_READ_ONBOARDING_STORY, - STORY_VIEWED_RECEIPTS + STORY_VIEWED_RECEIPTS, + USER_HAS_SEEN_GROUP_STORY_EDUCATION_SHEET ) var isFeatureDisabled: Boolean by booleanValue(MANUAL_FEATURE_DISABLE, false) @@ -81,6 +87,8 @@ internal class StoryValues(store: KeyValueStore) : SignalStoreValues(store) { var viewedReceiptsEnabled: Boolean by booleanValue(STORY_VIEWED_RECEIPTS, false) + var userHasSeenGroupStoryEducationSheet: Boolean by booleanValue(USER_HAS_SEEN_GROUP_STORY_EDUCATION_SHEET, false) + fun isViewedReceiptsStateSet(): Boolean { return store.containsKey(STORY_VIEWED_RECEIPTS) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java index 11f0e246f..5113e1f6d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java @@ -124,6 +124,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor(R.id.next).setOnClickListener { + requireListener().onGroupStoryEducationSheetNext() + dismissAllowingStateLoss() + } + } + + interface Callback { + fun onGroupStoryEducationSheetNext() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsFragment.kt index ad25c3131..6f83ef182 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsFragment.kt @@ -16,8 +16,10 @@ import org.thoughtcrime.securesms.components.settings.configure import org.thoughtcrime.securesms.contacts.paged.ContactSearchItems import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.groups.ParcelableGroupId +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseGroupStoryBottomSheet import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseStoryTypeBottomSheet +import org.thoughtcrime.securesms.stories.GroupStoryEducationSheet import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs import org.thoughtcrime.securesms.stories.settings.create.CreateStoryFlowDialogFragment import org.thoughtcrime.securesms.stories.settings.create.CreateStoryWithViewersFragment @@ -34,7 +36,8 @@ class StoriesPrivacySettingsFragment : DSLSettingsFragment( titleId = R.string.preferences__stories ), - ChooseStoryTypeBottomSheet.Callback { + ChooseStoryTypeBottomSheet.Callback, + GroupStoryEducationSheet.Callback { private val viewModel: StoriesPrivacySettingsViewModel by viewModels() private val lifecycleDisposable = LifecycleDisposable() @@ -181,10 +184,18 @@ class StoriesPrivacySettingsFragment : } override fun onGroupStoryClicked() { - ChooseGroupStoryBottomSheet().show(parentFragmentManager, ChooseGroupStoryBottomSheet.GROUP_STORY) + if (SignalStore.storyValues().userHasSeenGroupStoryEducationSheet) { + onGroupStoryEducationSheetNext() + } else { + GroupStoryEducationSheet().show(childFragmentManager, GroupStoryEducationSheet.KEY) + } } override fun onNewStoryClicked() { CreateStoryFlowDialogFragment().show(parentFragmentManager, CreateStoryWithViewersFragment.REQUEST_KEY) } + + override fun onGroupStoryEducationSheetNext() { + ChooseGroupStoryBottomSheet().show(parentFragmentManager, ChooseGroupStoryBottomSheet.GROUP_STORY) + } } diff --git a/app/src/main/res/drawable/group_story.xml b/app/src/main/res/drawable/group_story.xml new file mode 100644 index 000000000..52d196e59 --- /dev/null +++ b/app/src/main/res/drawable/group_story.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/group_story_education_sheet.xml b/app/src/main/res/layout/group_story_education_sheet.xml new file mode 100644 index 000000000..54a6e7600 --- /dev/null +++ b/app/src/main/res/layout/group_story_education_sheet.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e1edd0d7f..1ba41dcbf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5604,6 +5604,18 @@ Signal does not and cannot connect your donation to your Signal account. Thank you for your support! + + + Introducing: Group Stories + + Share story updates to a group chat you\'re already in. + + Anyone in the group chat can add to the story. + + All group chat members can view story replies. + + Next + diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java index c03efa174..dffa9f0f1 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java @@ -191,6 +191,10 @@ public final class SignalAccountRecord implements SignalRecord { diff.add("HasReadOnboardingStory"); } + if (hasSeenGroupStoryEducationSheet() != that.hasSeenGroupStoryEducationSheet()) { + diff.add("HasSeenGroupStoryEducationSheet"); + } + return diff.toString(); } else { return "Different class. " + getClass().getSimpleName() + " | " + other.getClass().getSimpleName(); @@ -317,6 +321,10 @@ public final class SignalAccountRecord implements SignalRecord { return proto.getHasReadOnboardingStory(); } + public boolean hasSeenGroupStoryEducationSheet() { + return proto.getHasSeenGroupStoryEducationSheet(); + } + public AccountRecord toProto() { return proto; } @@ -684,6 +692,11 @@ public final class SignalAccountRecord implements SignalRecord { return this; } + public Builder setHasSeenGroupStoryEducationSheet(boolean hasSeenGroupStoryEducationSheet) { + builder.setHasSeenGroupStoryEducationSheet(hasSeenGroupStoryEducationSheet); + return this; + } + private static AccountRecord.Builder parseUnknowns(byte[] serializedUnknowns) { try { return AccountRecord.parseFrom(serializedUnknowns).toBuilder(); diff --git a/libsignal/service/src/main/proto/StorageService.proto b/libsignal/service/src/main/proto/StorageService.proto index dcb99c4dc..80f02efba 100644 --- a/libsignal/service/src/main/proto/StorageService.proto +++ b/libsignal/service/src/main/proto/StorageService.proto @@ -153,37 +153,38 @@ message AccountRecord { } } - bytes profileKey = 1; - string givenName = 2; - string familyName = 3; - string avatarUrlPath = 4; - bool noteToSelfArchived = 5; - bool readReceipts = 6; - bool sealedSenderIndicators = 7; - bool typingIndicators = 8; - bool proxiedLinkPreviews = 9; - bool noteToSelfMarkedUnread = 10; - bool linkPreviews = 11; - PhoneNumberSharingMode phoneNumberSharingMode = 12; - bool unlistedPhoneNumber = 13; - repeated PinnedConversation pinnedConversations = 14; - bool preferContactAvatars = 15; - Payments payments = 16; - uint32 universalExpireTimer = 17; - bool primarySendsSms = 18; - string e164 = 19; - repeated string preferredReactionEmoji = 20; - bytes subscriberId = 21; - string subscriberCurrencyCode = 22; - bool displayBadgesOnProfile = 23; - bool subscriptionManuallyCancelled = 24; - bool keepMutedChatsArchived = 25; - bool hasSetMyStoriesPrivacy = 26; - bool hasViewedOnboardingStory = 27; - reserved /* storiesDisabled */ 28; - bool storiesDisabled = 29; - OptionalBool storyViewReceiptsEnabled = 30; - bool hasReadOnboardingStory = 31; + bytes profileKey = 1; + string givenName = 2; + string familyName = 3; + string avatarUrlPath = 4; + bool noteToSelfArchived = 5; + bool readReceipts = 6; + bool sealedSenderIndicators = 7; + bool typingIndicators = 8; + bool proxiedLinkPreviews = 9; + bool noteToSelfMarkedUnread = 10; + bool linkPreviews = 11; + PhoneNumberSharingMode phoneNumberSharingMode = 12; + bool unlistedPhoneNumber = 13; + repeated PinnedConversation pinnedConversations = 14; + bool preferContactAvatars = 15; + Payments payments = 16; + uint32 universalExpireTimer = 17; + bool primarySendsSms = 18; + string e164 = 19; + repeated string preferredReactionEmoji = 20; + bytes subscriberId = 21; + string subscriberCurrencyCode = 22; + bool displayBadgesOnProfile = 23; + bool subscriptionManuallyCancelled = 24; + bool keepMutedChatsArchived = 25; + bool hasSetMyStoriesPrivacy = 26; + bool hasViewedOnboardingStory = 27; + reserved /* storiesDisabled */ 28; + bool storiesDisabled = 29; + OptionalBool storyViewReceiptsEnabled = 30; + bool hasReadOnboardingStory = 31; + bool hasSeenGroupStoryEducationSheet = 32; } message StoryDistributionListRecord {