Update registration permissions onboarding design & copy
This commit is contained in:
parent
fb3c66074e
commit
9a1082dd89
@ -40,7 +40,8 @@ opt_in_rules:
|
||||
- empty_string
|
||||
- sorted_first_last
|
||||
attributes:
|
||||
always_on_line_above: ["@objc", "@nonobjc"]
|
||||
always_on_line_above: ["@available", "@objc", "@nonobjc"]
|
||||
attributes_with_arguments_always_on_line_above: false
|
||||
inclusive_language:
|
||||
override_allowed_terms: ["master", "whitelist"]
|
||||
large_tuple:
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
05104D162C88EC3A00F8851F /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 05104D142C88CDB300F8851F /* Colors.xcassets */; };
|
||||
05104D182C8A151100F8851F /* AsyncViewTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05104D172C8A151100F8851F /* AsyncViewTask.swift */; };
|
||||
05104E3A2C8B541000F8851F /* AccessibleLayoutMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05104E392C8B540C00F8851F /* AccessibleLayoutMetric.swift */; };
|
||||
0510F69E2C91EB3000FA3FDE /* ScrollBounceBehaviorIfAvailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0510F69D2C91EB2800FA3FDE /* ScrollBounceBehaviorIfAvailable.swift */; };
|
||||
0512145B2C5BCECF0021EEC9 /* CollectionDifference+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0512145A2C5BCECF0021EEC9 /* CollectionDifference+SSK.swift */; };
|
||||
0517B9782BFCFF12002CDE7D /* TSThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0517B9772BFCFF12002CDE7D /* TSThreadTests.swift */; };
|
||||
052647C12C6404DD0076E99D /* ChatListFilterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052647C02C6404D70076E99D /* ChatListFilterStore.swift */; };
|
||||
@ -523,10 +527,6 @@
|
||||
4C8A6DFC22E5499300469AE7 /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8A6DFB22E5499300469AE7 /* MediaZoomAnimationController.swift */; };
|
||||
4C8A6DFE22E54AFA00469AE7 /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8A6DFD22E54AFA00469AE7 /* MediaInteractiveDismiss.swift */; };
|
||||
4C9D347B23679C25006A4307 /* ContactStreamTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9D347923679C13006A4307 /* ContactStreamTest.swift */; };
|
||||
4C9D34972369F0FC006A4307 /* notificationPermission.json in Resources */ = {isa = PBXBuildFile; fileRef = 4C9D34962369F0FC006A4307 /* notificationPermission.json */; };
|
||||
4C9D349B2369F11F006A4307 /* notificationPermission1.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C9D34982369F11E006A4307 /* notificationPermission1.png */; };
|
||||
4C9D349C2369F11F006A4307 /* notificationPermission0.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C9D34992369F11E006A4307 /* notificationPermission0.png */; };
|
||||
4C9D349D2369F11F006A4307 /* notificationPermission2.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C9D349A2369F11F006A4307 /* notificationPermission2.png */; };
|
||||
4CA46F4C219CCC630038ABDE /* MediaCaptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F4B219CCC630038ABDE /* MediaCaptionView.swift */; };
|
||||
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA485BA2232339F004B9E7D /* PhotoCaptureViewController.swift */; };
|
||||
4CB5F26720F6E1E2004D1B42 /* MessageActionsToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MessageActionsToolbar.swift */; };
|
||||
@ -3100,7 +3100,7 @@
|
||||
F9D5BFCD2979A017001737E5 /* OWSRequestFactory+Spam.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D5BFCC2979A017001737E5 /* OWSRequestFactory+Spam.swift */; };
|
||||
F9D5BFCF2979AFF4001737E5 /* URLPathComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D5BFCE2979AFF4001737E5 /* URLPathComponents.swift */; };
|
||||
F9D5BFD12979B027001737E5 /* URLPathComponentsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D5BFD02979B027001737E5 /* URLPathComponentsTest.swift */; };
|
||||
F9D5C39F2993F9FF004891FC /* RegistrationPermissionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D5C39E2993F9FF004891FC /* RegistrationPermissionsViewController.swift */; };
|
||||
F9D5C39F2993F9FF004891FC /* RegistrationPermissionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D5C39E2993F9FF004891FC /* RegistrationPermissionsView.swift */; };
|
||||
F9D83012282DBB1500399363 /* BadgeGiftingChooseBadgeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D83011282DBB1500399363 /* BadgeGiftingChooseBadgeViewController.swift */; };
|
||||
F9DD70B92811AF82000C5960 /* DonationViewsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9DD70B82811AF82000C5960 /* DonationViewsUtil.swift */; };
|
||||
F9E3006129A02D8800DCA219 /* RegistrationPinViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E3006029A02D8800DCA219 /* RegistrationPinViewController.swift */; };
|
||||
@ -3246,6 +3246,10 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
05104D142C88CDB300F8851F /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = "<group>"; };
|
||||
05104D172C8A151100F8851F /* AsyncViewTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncViewTask.swift; sourceTree = "<group>"; };
|
||||
05104E392C8B540C00F8851F /* AccessibleLayoutMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibleLayoutMetric.swift; sourceTree = "<group>"; };
|
||||
0510F69D2C91EB2800FA3FDE /* ScrollBounceBehaviorIfAvailable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollBounceBehaviorIfAvailable.swift; sourceTree = "<group>"; };
|
||||
0512145A2C5BCECF0021EEC9 /* CollectionDifference+SSK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionDifference+SSK.swift"; sourceTree = "<group>"; };
|
||||
0517B9772BFCFF12002CDE7D /* TSThreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSThreadTests.swift; sourceTree = "<group>"; };
|
||||
052647C02C6404D70076E99D /* ChatListFilterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterStore.swift; sourceTree = "<group>"; };
|
||||
@ -3846,10 +3850,6 @@
|
||||
4C9C50FF22F495F60054A33F /* TSAttachmentMultisendJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSAttachmentMultisendJob.swift; sourceTree = "<group>"; };
|
||||
4C9D347923679C13006A4307 /* ContactStreamTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactStreamTest.swift; sourceTree = "<group>"; };
|
||||
4C9D347E23689E06006A4307 /* IncomingContactSyncJobQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingContactSyncJobQueue.swift; sourceTree = "<group>"; };
|
||||
4C9D34962369F0FC006A4307 /* notificationPermission.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = notificationPermission.json; sourceTree = "<group>"; };
|
||||
4C9D34982369F11E006A4307 /* notificationPermission1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = notificationPermission1.png; sourceTree = "<group>"; };
|
||||
4C9D34992369F11E006A4307 /* notificationPermission0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = notificationPermission0.png; sourceTree = "<group>"; };
|
||||
4C9D349A2369F11F006A4307 /* notificationPermission2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = notificationPermission2.png; sourceTree = "<group>"; };
|
||||
4CA46F4B219CCC630038ABDE /* MediaCaptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaCaptionView.swift; sourceTree = "<group>"; };
|
||||
4CA485BA2232339F004B9E7D /* PhotoCaptureViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCaptureViewController.swift; sourceTree = "<group>"; };
|
||||
4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = "<group>"; };
|
||||
@ -6502,7 +6502,7 @@
|
||||
F9D5BFCC2979A017001737E5 /* OWSRequestFactory+Spam.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OWSRequestFactory+Spam.swift"; sourceTree = "<group>"; };
|
||||
F9D5BFCE2979AFF4001737E5 /* URLPathComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLPathComponents.swift; sourceTree = "<group>"; };
|
||||
F9D5BFD02979B027001737E5 /* URLPathComponentsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLPathComponentsTest.swift; sourceTree = "<group>"; };
|
||||
F9D5C39E2993F9FF004891FC /* RegistrationPermissionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationPermissionsViewController.swift; sourceTree = "<group>"; };
|
||||
F9D5C39E2993F9FF004891FC /* RegistrationPermissionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationPermissionsView.swift; sourceTree = "<group>"; };
|
||||
F9D83011282DBB1500399363 /* BadgeGiftingChooseBadgeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeGiftingChooseBadgeViewController.swift; sourceTree = "<group>"; };
|
||||
F9DD70B82811AF82000C5960 /* DonationViewsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationViewsUtil.swift; sourceTree = "<group>"; };
|
||||
F9E3006029A02D8800DCA219 /* RegistrationPinViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationPinViewController.swift; sourceTree = "<group>"; };
|
||||
@ -7659,25 +7659,6 @@
|
||||
path = MediaGallery;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C9D34842369EA3E006A4307 /* NotificationPermission */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C9D348B2369EA69006A4307 /* images */,
|
||||
4C9D34962369F0FC006A4307 /* notificationPermission.json */,
|
||||
);
|
||||
path = NotificationPermission;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C9D348B2369EA69006A4307 /* images */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C9D34992369F11E006A4307 /* notificationPermission0.png */,
|
||||
4C9D34982369F11E006A4307 /* notificationPermission1.png */,
|
||||
4C9D349A2369F11F006A4307 /* notificationPermission2.png */,
|
||||
);
|
||||
path = images;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CD675BF22E7BE47008010D2 /* Transitions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -8938,7 +8919,7 @@
|
||||
6659CCB029CD4650000C24C0 /* RegistrationConfirmModeSwitchViewController.swift */,
|
||||
F92E4C73299E9A0100C6E6C7 /* RegistrationLoadingViewController.swift */,
|
||||
F95A64F2299589CA007FDBDF /* RegistrationNavigationController.swift */,
|
||||
F9D5C39E2993F9FF004891FC /* RegistrationPermissionsViewController.swift */,
|
||||
F9D5C39E2993F9FF004891FC /* RegistrationPermissionsView.swift */,
|
||||
F905DFEA29A534F200BAD034 /* RegistrationPhoneNumberDiscoverabilityViewController.swift */,
|
||||
F9198484299AA7FC007FD5E4 /* RegistrationPhoneNumberInputView.swift */,
|
||||
F95A64F429959065007FDBDF /* RegistrationPhoneNumberViewController.swift */,
|
||||
@ -9964,7 +9945,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
888017822741E5A500346E9A /* Boost */,
|
||||
4C9D34842369EA3E006A4307 /* NotificationPermission */,
|
||||
34848D5B25D43ADD00E5034B /* about-mobilecoin.json */,
|
||||
34848D5C25D43ADD00E5034B /* activate-payments.json */,
|
||||
34848D5D25D43ADD00E5034B /* add-money.json */,
|
||||
@ -10259,6 +10239,9 @@
|
||||
B9D721742C87B8CB007EDA85 /* SwiftUIExtensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05104E392C8B540C00F8851F /* AccessibleLayoutMetric.swift */,
|
||||
05104D172C8A151100F8851F /* AsyncViewTask.swift */,
|
||||
0510F69D2C91EB2800FA3FDE /* ScrollBounceBehaviorIfAvailable.swift */,
|
||||
B9D721752C87B8EB007EDA85 /* SwiftUI+Animations.swift */,
|
||||
);
|
||||
path = SwiftUIExtensions;
|
||||
@ -10470,6 +10453,7 @@
|
||||
5033D46C29DCA8DE007FEADA /* URLs */,
|
||||
D99840BB297A04A300F7ED6D /* Usernames */,
|
||||
052D17872C7E34D00023D56F /* AppIcon.xcassets */,
|
||||
05104D142C88CDB300F8851F /* Colors.xcassets */,
|
||||
B66DBF4919D5BBC8006EA940 /* Images.xcassets */,
|
||||
F0C124B626D4788A0031C96F /* NSE-Images.xcassets */,
|
||||
881FF30623B5B1520023B620 /* Signal-AppStore.entitlements */,
|
||||
@ -13363,7 +13347,7 @@
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
DefaultBuildSystemTypeForWorkspace = Original;
|
||||
LastSwiftUpdateCheck = 1540;
|
||||
LastSwiftUpdateCheck = 1600;
|
||||
LastTestingUpgradeCheck = 0600;
|
||||
LastUpgradeCheck = 1540;
|
||||
ORGANIZATIONNAME = "Open Whisper Systems";
|
||||
@ -13602,6 +13586,7 @@
|
||||
45B74A832044AAB600CD42F8 /* circles.aifc in Resources */,
|
||||
4503F1BE20470A5B00CEE724 /* classic-quiet.aifc in Resources */,
|
||||
4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */,
|
||||
05104D162C88EC3A00F8851F /* Colors.xcassets in Resources */,
|
||||
45B74A872044AAB600CD42F8 /* complete-quiet.aifc in Resources */,
|
||||
45B74A7E2044AAB600CD42F8 /* complete.aifc in Resources */,
|
||||
880FB3EE28CA53D400FA1C10 /* determinate_spinner_44.json in Resources */,
|
||||
@ -13631,10 +13616,6 @@
|
||||
45B74A7F2044AAB600CD42F8 /* note-quiet.aifc in Resources */,
|
||||
45B74A862044AAB600CD42F8 /* note.aifc in Resources */,
|
||||
B9B89EED2C064E760093A2FA /* notification_simple-01.caf in Resources */,
|
||||
4C9D34972369F0FC006A4307 /* notificationPermission.json in Resources */,
|
||||
4C9D349C2369F11F006A4307 /* notificationPermission0.png in Resources */,
|
||||
4C9D349B2369F11F006A4307 /* notificationPermission1.png in Resources */,
|
||||
4C9D349D2369F11F006A4307 /* notificationPermission2.png in Resources */,
|
||||
3406D32E25DD80D600885B14 /* payments_spinner.json in Resources */,
|
||||
3406D33225DD832800885B14 /* payments_spinner_dark.json in Resources */,
|
||||
3406D32B25DD80D600885B14 /* payments_spinner_fail.json in Resources */,
|
||||
@ -14341,11 +14322,13 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
05104E3A2C8B541000F8851F /* AccessibleLayoutMetric.swift in Sources */,
|
||||
3402AA35271D9DCD0084CBAE /* ActionSheetController.swift in Sources */,
|
||||
887F898228FF32A600D3B78E /* AllSignalConnectionsViewController.swift in Sources */,
|
||||
342FFE62271DB2E7000AC89F /* AppContext+SignalUI.swift in Sources */,
|
||||
3402AA4E271D9DCD0084CBAE /* ApprovalFooterView.swift in Sources */,
|
||||
3402AA3F271D9DCD0084CBAE /* ApprovalRailCellView.swift in Sources */,
|
||||
05104D182C8A151100F8851F /* AsyncViewTask.swift in Sources */,
|
||||
3402AA4B271D9DCD0084CBAE /* AttachmentApprovalToolbar.swift in Sources */,
|
||||
763D7DDD27E25DC8002EA7E6 /* AttachmentApprovalTopBar.swift in Sources */,
|
||||
3402AA4A271D9DCD0084CBAE /* AttachmentApprovalViewController.swift in Sources */,
|
||||
@ -14518,6 +14501,7 @@
|
||||
88B987022880890800F8C74D /* SafetyNumberConfirmationSheet.swift in Sources */,
|
||||
88B9870928808A8A00F8C74D /* ScanQRCodeViewController.swift in Sources */,
|
||||
7677E41129F7A60500AC6A75 /* ScreenLockViewController.swift in Sources */,
|
||||
0510F69E2C91EB3000FA3FDE /* ScrollBounceBehaviorIfAvailable.swift in Sources */,
|
||||
50597BBF2B97D629004681E1 /* SearchableNameFinder.swift in Sources */,
|
||||
66FC638E29EDABAC00F00DAC /* SearchDisplayConfigurations.swift in Sources */,
|
||||
66FBC4E328DA82AA00BD9E8B /* SelectMyStoryRecipientsViewController.swift in Sources */,
|
||||
@ -15183,7 +15167,7 @@
|
||||
F92E4C74299E9A0100C6E6C7 /* RegistrationLoadingViewController.swift in Sources */,
|
||||
66533E3A29B9502100E8D928 /* RegistrationMode.swift in Sources */,
|
||||
F95A64F3299589CA007FDBDF /* RegistrationNavigationController.swift in Sources */,
|
||||
F9D5C39F2993F9FF004891FC /* RegistrationPermissionsViewController.swift in Sources */,
|
||||
F9D5C39F2993F9FF004891FC /* RegistrationPermissionsView.swift in Sources */,
|
||||
F905DFEB29A534F200BAD034 /* RegistrationPhoneNumberDiscoverabilityViewController.swift in Sources */,
|
||||
F9198485299AA7FC007FD5E4 /* RegistrationPhoneNumberInputView.swift in Sources */,
|
||||
F95A64F529959065007FDBDF /* RegistrationPhoneNumberViewController.swift in Sources */,
|
||||
@ -18057,6 +18041,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-color AppIcon-bubbles AppIcon-white AppIcon-dark AppIcon-dark-variant AppIcon-chat AppIcon-yellow AppIcon-news AppIcon-notes AppIcon-weather AppIcon-wave";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = NO;
|
||||
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
@ -18310,6 +18295,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-color AppIcon-bubbles AppIcon-white AppIcon-dark AppIcon-dark-variant AppIcon-chat AppIcon-yellow AppIcon-news AppIcon-notes AppIcon-weather AppIcon-wave";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = NO;
|
||||
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
@ -18655,6 +18641,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-color AppIcon-bubbles AppIcon-white AppIcon-dark AppIcon-dark-variant AppIcon-chat AppIcon-yellow AppIcon-news AppIcon-notes AppIcon-weather AppIcon-wave";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = YES;
|
||||
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
@ -18701,6 +18688,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-color AppIcon-bubbles AppIcon-white AppIcon-dark AppIcon-dark-variant AppIcon-chat AppIcon-yellow AppIcon-news AppIcon-notes AppIcon-weather AppIcon-wave";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = NO;
|
||||
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
|
||||
6
Signal/Colors.xcassets/Contents.json
Normal file
6
Signal/Colors.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
Signal/Colors.xcassets/Signal/Background/Contents.json
Normal file
6
Signal/Colors.xcassets/Signal/Background/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x00",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xF0",
|
||||
"green" : "0xEF",
|
||||
"red" : "0xEF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x1E",
|
||||
"green" : "0x1C",
|
||||
"red" : "0x1C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "contrast",
|
||||
"value" : "high"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xE7",
|
||||
"green" : "0xE4",
|
||||
"red" : "0xE4"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
},
|
||||
{
|
||||
"appearance" : "contrast",
|
||||
"value" : "high"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x38",
|
||||
"green" : "0x34",
|
||||
"red" : "0x34"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
9
Signal/Colors.xcassets/Signal/Contents.json
Normal file
9
Signal/Colors.xcassets/Signal/Contents.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
6
Signal/Colors.xcassets/Signal/Label/Contents.json
Normal file
6
Signal/Colors.xcassets/Signal/Label/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x00",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.180",
|
||||
"blue" : "0x43",
|
||||
"green" : "0x3C",
|
||||
"red" : "0x3C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.160",
|
||||
"blue" : "0xF5",
|
||||
"green" : "0xEB",
|
||||
"red" : "0xEB"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "contrast",
|
||||
"value" : "high"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.400",
|
||||
"blue" : "0x43",
|
||||
"green" : "0x3C",
|
||||
"red" : "0x3C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
},
|
||||
{
|
||||
"appearance" : "contrast",
|
||||
"value" : "high"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.260",
|
||||
"blue" : "0xF5",
|
||||
"green" : "0xEB",
|
||||
"red" : "0xEB"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.720",
|
||||
"blue" : "0x43",
|
||||
"green" : "0x3C",
|
||||
"red" : "0x3C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.700",
|
||||
"blue" : "0xF5",
|
||||
"green" : "0xEB",
|
||||
"red" : "0xEB"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "contrast",
|
||||
"value" : "high"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.950",
|
||||
"blue" : "0x43",
|
||||
"green" : "0x3C",
|
||||
"red" : "0x3C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
},
|
||||
{
|
||||
"appearance" : "contrast",
|
||||
"value" : "high"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.800",
|
||||
"blue" : "0xF5",
|
||||
"green" : "0xEB",
|
||||
"red" : "0xEB"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.300",
|
||||
"blue" : "0x43",
|
||||
"green" : "0x3C",
|
||||
"red" : "0x3C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.300",
|
||||
"blue" : "0xF5",
|
||||
"green" : "0xEB",
|
||||
"red" : "0xEB"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "contrast",
|
||||
"value" : "high"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.500",
|
||||
"blue" : "0x43",
|
||||
"green" : "0x3C",
|
||||
"red" : "0x3C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
},
|
||||
{
|
||||
"appearance" : "contrast",
|
||||
"value" : "high"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.400",
|
||||
"blue" : "0xF5",
|
||||
"green" : "0xEB",
|
||||
"red" : "0xEB"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
12
Signal/Images.xcassets/bell-ring.imageset/Contents.json
vendored
Normal file
12
Signal/Images.xcassets/bell-ring.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bell-ring.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Signal/Images.xcassets/bell-ring.imageset/bell-ring.pdf
vendored
Normal file
BIN
Signal/Images.xcassets/bell-ring.imageset/bell-ring.pdf
vendored
Normal file
Binary file not shown.
12
Signal/Images.xcassets/person-circle-large.imageset/Contents.json
vendored
Normal file
12
Signal/Images.xcassets/person-circle-large.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "person-circle.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Signal/Images.xcassets/person-circle-large.imageset/person-circle.pdf
vendored
Normal file
BIN
Signal/Images.xcassets/person-circle-large.imageset/person-circle.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
@ -1 +0,0 @@
|
||||
{"v":"5.5.2","fr":60,"ip":0,"op":180,"w":1080,"h":600,"nm":"Comp 1","ddd":0,"assets":[{"id":"image_0","w":820,"h":232,"u":"images/","p":"notificationPermission0.png","e":0},{"id":"image_1","w":820,"h":232,"u":"images/","p":"notificationPermission1.png","e":0},{"id":"image_2","w":820,"h":232,"u":"images/","p":"notificationPermission2.png","e":0}],"layers":[{"ddd":0,"ind":1,"ty":2,"nm":"Signal Message/notifications.ai","cl":"ai","refId":"image_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":107,"s":[0]},{"t":116,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,110,0],"ix":2},"a":{"a":0,"k":[410,116,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.605,0.605,0.333],"y":[0.517,0.517,0]},"t":107,"s":[87,87,100]},{"i":{"x":[0.534,0.534,0.667],"y":[0.566,0.566,1]},"o":{"x":[0.351,0.351,0.333],"y":[2.108,2.108,0]},"t":116,"s":[101,101,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.448,0.448,0.333],"y":[-0.816,-0.816,0]},"t":125,"s":[99.5,99.5,100]},{"t":131,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":186,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":2,"nm":"Message 2/notifications.ai","cl":"ai","refId":"image_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":64,"s":[540,110,0],"to":[0,0,0],"ti":[0,0,0]},{"t":72,"s":[540,304,0]}],"ix":2},"a":{"a":0,"k":[410,116,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":187,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":2,"nm":"Message 3/notifications.ai","cl":"ai","refId":"image_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":64,"s":[540,308,0],"to":[0,0,0],"ti":[0,0,0]},{"t":72,"s":[540,502,0]}],"ix":2},"a":{"a":0,"k":[410,116,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":185,"st":0,"bm":0}],"markers":[]}
|
||||
@ -1231,7 +1231,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
|
||||
if inMemoryState.needsSomePermissions {
|
||||
// This class is only used for primary device registration
|
||||
// which always needs contacts permissions.
|
||||
return .value(.permissions(RegistrationPermissionsState(shouldRequestAccessToContacts: true)))
|
||||
return .value(.permissions)
|
||||
}
|
||||
if inMemoryState.hasEnteredE164, let e164 = persistedState.e164 {
|
||||
return self.startSession(e164: e164)
|
||||
|
||||
@ -10,7 +10,7 @@ public enum RegistrationStep: Equatable {
|
||||
// MARK: - Opening Steps
|
||||
case registrationSplash
|
||||
case changeNumberSplash
|
||||
case permissions(RegistrationPermissionsState)
|
||||
case permissions
|
||||
|
||||
// MARK: - Actually registering
|
||||
|
||||
|
||||
@ -144,12 +144,10 @@ public class RegistrationNavigationController: OWSNavigationController {
|
||||
// No state to update.
|
||||
update: nil
|
||||
)
|
||||
case .permissions(let state):
|
||||
case .permissions:
|
||||
return Controller(
|
||||
type: RegistrationPermissionsViewController.self,
|
||||
make: { presenter in
|
||||
return RegistrationPermissionsViewController(state: state, presenter: presenter)
|
||||
},
|
||||
make: RegistrationPermissionsViewController.init(presenter:),
|
||||
// The state never changes here. In theory we would build
|
||||
// state update support in the permissions controller,
|
||||
// but its overkill so we have not.
|
||||
@ -419,11 +417,10 @@ extension RegistrationNavigationController: RegistrationConfimModeSwitchPresente
|
||||
extension RegistrationNavigationController: RegistrationChangeNumberSplashPresenter {}
|
||||
|
||||
extension RegistrationNavigationController: RegistrationPermissionsPresenter {
|
||||
|
||||
func requestPermissions() -> Guarantee<Void> {
|
||||
func requestPermissions() async {
|
||||
let guarantee = coordinator.requestPermissions()
|
||||
pushNextController(guarantee, loadingMode: nil)
|
||||
return guarantee.asVoid()
|
||||
await guarantee.asVoid().awaitable()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,197 @@
|
||||
//
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import SignalServiceKit
|
||||
import SignalUI
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
protocol RegistrationPermissionsPresenter {
|
||||
func requestPermissions() async
|
||||
}
|
||||
|
||||
final class RegistrationPermissionsViewController: UIHostingController<RegistrationPermissionsView> {
|
||||
init(presenter: any RegistrationPermissionsPresenter) {
|
||||
super.init(rootView: RegistrationPermissionsView(presenter: presenter))
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
struct RegistrationPermissionsView: View {
|
||||
var presenter: any RegistrationPermissionsPresenter
|
||||
@State private var requestPermissions: RequestPermissionsTask?
|
||||
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@AccessibleLayoutMetric private var headerPadding = 16
|
||||
@AccessibleLayoutMetric private var headerSpacing = 12
|
||||
@AccessibleLayoutMetric(scale: 0.5) private var sectionSpacing = 64
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack(spacing: headerSpacing) {
|
||||
Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_TITLE", comment: "Title of the 'onboarding permissions' view."))
|
||||
.font(.title.weight(.semibold))
|
||||
.lineLimit(1)
|
||||
Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_PREAMBLE", comment: "Preamble of the 'onboarding permissions' view."))
|
||||
.dynamicTypeSize(...DynamicTypeSize.accessibility1)
|
||||
}
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, headerPadding)
|
||||
|
||||
ScrollView {
|
||||
VStack {
|
||||
Spacer(minLength: sectionSpacing)
|
||||
.frame(maxHeight: $sectionSpacing.rawValue)
|
||||
.layoutPriority(-1)
|
||||
|
||||
VStack(alignment: .leading, spacing: 32) {
|
||||
PermissionDescription {
|
||||
Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_NOTIFICATIONS_TITLE", comment: "Title introducing the 'Notifications' permission in the 'onboarding permissions' view."))
|
||||
} description: {
|
||||
Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_NOTIFICATIONS_DESCRIPTION", comment: "Description of the 'Notifications' permission in the 'onboarding permissions' view."))
|
||||
} icon: {
|
||||
PermissionIcon(.bellRing)
|
||||
}
|
||||
|
||||
PermissionDescription {
|
||||
Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_CONTACTS_TITLE", comment: "Title introducing the 'Contacts' permission in the 'onboarding permissions' view."))
|
||||
} description: {
|
||||
Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_CONTACTS_DESCRIPTION", comment: "Description of the 'Contacts' permission in the 'onboarding permissions' view."))
|
||||
} icon: {
|
||||
PermissionIcon(.personCircleLarge)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(minLength: sectionSpacing)
|
||||
.layoutPriority(-1)
|
||||
|
||||
Button(CommonStrings.continueButton) {
|
||||
requestPermissions = RequestPermissionsTask(presenter: presenter)
|
||||
}
|
||||
.buttonStyle(ContinueButtonStyle())
|
||||
.dynamicTypeSize(...DynamicTypeSize.accessibility2)
|
||||
.frame(maxWidth: 400)
|
||||
}
|
||||
.padding(EdgeInsets(.layoutMarginsForRegistration(UIUserInterfaceSizeClass(horizontalSizeClass))))
|
||||
}
|
||||
.scrollBounceBehaviorIfAvailable(.basedOnSize)
|
||||
}
|
||||
.foregroundStyle(Color.Signal.label, Color.Signal.secondaryLabel, Color.Signal.tertiaryLabel)
|
||||
.dynamicTypeSize(...DynamicTypeSize.accessibility3)
|
||||
.minimumScaleFactor(0.9)
|
||||
.navigationBarBackButtonHidden()
|
||||
.task($requestPermissions.animation())
|
||||
// FIXME: Forcing light mode for consistency with the rest of registration
|
||||
.background(Color.Signal.background)
|
||||
.environment(\.colorScheme, .light)
|
||||
}
|
||||
}
|
||||
|
||||
private extension RegistrationPermissionsView {
|
||||
struct ContinueButtonStyle: PrimitiveButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
Button(action: configuration.trigger) {
|
||||
HStack {
|
||||
Spacer()
|
||||
configuration.label
|
||||
.colorScheme(.dark)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
}
|
||||
.frame(minHeight: 32)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
|
||||
struct PermissionDescription<Icon: View, Title: View, Description: View>: View {
|
||||
var icon: Icon
|
||||
var title: Title
|
||||
var description: Description
|
||||
|
||||
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
||||
|
||||
init(@ViewBuilder title: () -> Title, @ViewBuilder description: () -> Description, @ViewBuilder icon: () -> Icon) {
|
||||
self.title = title()
|
||||
self.description = description()
|
||||
self.icon = icon()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if dynamicTypeSize.isAccessibilitySize {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 16) {
|
||||
icon
|
||||
.frame(width: 36)
|
||||
title
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
Spacer()
|
||||
}
|
||||
description
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} else {
|
||||
HStack(alignment: .top, spacing: 16) {
|
||||
icon
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
title
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
description
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.layoutPriority(1)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PermissionIcon: View {
|
||||
var resource: ImageResource
|
||||
|
||||
init(_ resource: ImageResource) {
|
||||
self.resource = resource
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Image(resource)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(maxWidth: 48)
|
||||
}
|
||||
}
|
||||
|
||||
struct RequestPermissionsTask: AsyncViewTask {
|
||||
let id = UUID()
|
||||
let presenter: any RegistrationPermissionsPresenter
|
||||
|
||||
func perform() async {
|
||||
await presenter.requestPermissions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private struct PreviewPermissionsPresenter: RegistrationPermissionsPresenter {
|
||||
func requestPermissions() async {
|
||||
try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack {
|
||||
Color.clear.frame(height: 44)
|
||||
RegistrationPermissionsView(presenter: PreviewPermissionsPresenter())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,180 +0,0 @@
|
||||
//
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Lottie
|
||||
import SignalServiceKit
|
||||
import SignalUI
|
||||
|
||||
// MARK: - RegistrationPermissionsState
|
||||
|
||||
public struct RegistrationPermissionsState: Equatable {
|
||||
let shouldRequestAccessToContacts: Bool
|
||||
}
|
||||
|
||||
// MARK: - RegistrationPermissionsPresenter
|
||||
|
||||
protocol RegistrationPermissionsPresenter: AnyObject {
|
||||
func requestPermissions() -> Guarantee<Void>
|
||||
}
|
||||
|
||||
// MARK: - RegistrationPermissionsViewController
|
||||
|
||||
class RegistrationPermissionsViewController: OWSViewController {
|
||||
private let state: RegistrationPermissionsState
|
||||
private weak var presenter: RegistrationPermissionsPresenter?
|
||||
|
||||
public init(
|
||||
state: RegistrationPermissionsState,
|
||||
presenter: RegistrationPermissionsPresenter?
|
||||
) {
|
||||
self.state = state
|
||||
self.presenter = presenter
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
public override init() {
|
||||
owsFail("This should not be called")
|
||||
}
|
||||
|
||||
// MARK: Rendering
|
||||
|
||||
private lazy var animationView: LottieAnimationView = {
|
||||
let animationView = LottieAnimationView(name: "notificationPermission")
|
||||
animationView.loopMode = .playOnce
|
||||
animationView.backgroundBehavior = .pauseAndRestore
|
||||
animationView.contentMode = .scaleAspectFit
|
||||
return animationView
|
||||
}()
|
||||
|
||||
private var giveAccessButton: OWSFlatButton?
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
navigationItem.setHidesBackButton(true, animated: false)
|
||||
|
||||
view.backgroundColor = Theme.backgroundColor
|
||||
|
||||
let scrollView = UIScrollView()
|
||||
view.addSubview(scrollView)
|
||||
scrollView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .fill
|
||||
stackView.layoutMargins = UIEdgeInsets.layoutMarginsForRegistration(
|
||||
traitCollection.horizontalSizeClass
|
||||
)
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
stackView.setContentHuggingHigh()
|
||||
scrollView.addSubview(stackView)
|
||||
stackView.autoPinEdgesToSuperviewEdges()
|
||||
stackView.autoMatch(
|
||||
.width,
|
||||
to: .width,
|
||||
of: view,
|
||||
withOffset: -view.layoutMargins.totalWidth,
|
||||
relation: .equal
|
||||
)
|
||||
let heightConstraint = stackView.heightAnchor.constraint(
|
||||
greaterThanOrEqualTo: view.layoutMarginsGuide.heightAnchor
|
||||
)
|
||||
heightConstraint.isActive = true
|
||||
|
||||
let titleText: String
|
||||
let explanationText: String
|
||||
let giveAccessText: String
|
||||
if state.shouldRequestAccessToContacts {
|
||||
titleText = OWSLocalizedString(
|
||||
"ONBOARDING_PERMISSIONS_TITLE",
|
||||
comment: "Title of the 'onboarding permissions' view."
|
||||
)
|
||||
explanationText = OWSLocalizedString(
|
||||
"ONBOARDING_PERMISSIONS_EXPLANATION",
|
||||
comment: "Explanation in the 'onboarding permissions' view."
|
||||
)
|
||||
giveAccessText = OWSLocalizedString(
|
||||
"ONBOARDING_PERMISSIONS_ENABLE_PERMISSIONS_BUTTON",
|
||||
comment: "Label for the 'give access' button in the 'onboarding permissions' view."
|
||||
)
|
||||
} else {
|
||||
titleText = OWSLocalizedString(
|
||||
"LINKED_ONBOARDING_PERMISSIONS_TITLE",
|
||||
comment: "Title of the 'onboarding permissions' view."
|
||||
)
|
||||
explanationText = OWSLocalizedString(
|
||||
"LINKED_ONBOARDING_PERMISSIONS_EXPLANATION",
|
||||
comment: "Explanation in the 'onboarding permissions' view."
|
||||
)
|
||||
giveAccessText = OWSLocalizedString(
|
||||
"LINKED_ONBOARDING_PERMISSIONS_ENABLE_PERMISSIONS_BUTTON",
|
||||
comment: "Label for the 'give access' button in the 'onboarding permissions' view."
|
||||
)
|
||||
}
|
||||
|
||||
let titleLabel = UILabel.titleLabelForRegistration(text: titleText)
|
||||
titleLabel.accessibilityIdentifier = "registration.permissions.titleLabel"
|
||||
titleLabel.setCompressionResistanceVerticalHigh()
|
||||
stackView.addArrangedSubview(titleLabel)
|
||||
stackView.setCustomSpacing(20, after: titleLabel)
|
||||
|
||||
let explanationLabel = UILabel.explanationLabelForRegistration(text: explanationText)
|
||||
explanationLabel.accessibilityIdentifier = "registration.permissions.explanationLabel"
|
||||
explanationLabel.setCompressionResistanceVerticalHigh()
|
||||
stackView.addArrangedSubview(explanationLabel)
|
||||
stackView.setCustomSpacing(60, after: explanationLabel)
|
||||
|
||||
stackView.addArrangedSubview(animationView)
|
||||
animationView.setContentHuggingHigh()
|
||||
let animationSize = animationView.intrinsicContentSize
|
||||
animationView.autoPin(toAspectRatio: animationSize.width / animationSize.height)
|
||||
|
||||
stackView.addArrangedSubview(UIView.vStretchingSpacer(minHeight: 60))
|
||||
|
||||
let giveAccessButton = OWSFlatButton.primaryButtonForRegistration(
|
||||
title: giveAccessText,
|
||||
target: self,
|
||||
selector: #selector(giveAccessPressed)
|
||||
)
|
||||
giveAccessButton.accessibilityIdentifier = "registration.permissions.giveAccessButton"
|
||||
stackView.addArrangedSubview(giveAccessButton)
|
||||
giveAccessButton.autoHCenterInSuperview()
|
||||
NSLayoutConstraint.autoSetPriority(.defaultLow) {
|
||||
giveAccessButton.autoPinEdge(toSuperviewEdge: .leading)
|
||||
giveAccessButton.autoPinEdge(toSuperviewEdge: .trailing)
|
||||
}
|
||||
NSLayoutConstraint.autoSetPriority(.required) {
|
||||
giveAccessButton.autoSetDimension(.width, toSize: 280, relation: .greaterThanOrEqual)
|
||||
giveAccessButton.autoSetDimension(.height, toSize: 50, relation: .greaterThanOrEqual)
|
||||
}
|
||||
self.giveAccessButton = giveAccessButton
|
||||
}
|
||||
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
animationView.play()
|
||||
}
|
||||
|
||||
// MARK: Events
|
||||
|
||||
@objc
|
||||
private func giveAccessPressed() {
|
||||
Logger.info("")
|
||||
|
||||
requestPermissions()
|
||||
}
|
||||
|
||||
// MARK: Requesting permissions
|
||||
|
||||
private func requestPermissions() {
|
||||
giveAccessButton?.setEnabled(false)
|
||||
presenter?.requestPermissions().observe(on: DispatchQueue.main) { [weak self] _ in
|
||||
self?.giveAccessButton?.setEnabled(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,9 +120,7 @@ class RegistrationPinAttemptsExhaustedAndMustCreateNewPinViewController: OWSView
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.layoutMargins = UIEdgeInsets.layoutMarginsForRegistration(
|
||||
traitCollection.horizontalSizeClass
|
||||
)
|
||||
stackView.directionalLayoutMargins = .layoutMarginsForRegistration(traitCollection.horizontalSizeClass)
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
|
||||
view.addSubview(stackView)
|
||||
|
||||
@ -164,9 +164,7 @@ class RegistrationReglockTimeoutViewController: OWSViewController {
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.layoutMargins = UIEdgeInsets.layoutMarginsForRegistration(
|
||||
traitCollection.horizontalSizeClass
|
||||
)
|
||||
stackView.directionalLayoutMargins = .layoutMarginsForRegistration(traitCollection.horizontalSizeClass)
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
|
||||
view.addSubview(stackView)
|
||||
|
||||
@ -38,9 +38,9 @@ public class RegistrationSplashViewController: OWSViewController {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .fill
|
||||
stackView.layoutMargins = {
|
||||
stackView.directionalLayoutMargins = {
|
||||
let horizontalSizeClass = traitCollection.horizontalSizeClass
|
||||
var result = UIEdgeInsets.layoutMarginsForRegistration(horizontalSizeClass)
|
||||
var result = NSDirectionalEdgeInsets.layoutMarginsForRegistration(horizontalSizeClass)
|
||||
// We want the hero image a bit closer to the top.
|
||||
result.top = 16
|
||||
return result
|
||||
|
||||
@ -261,9 +261,7 @@ class RegistrationVerificationViewController: OWSViewController {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = 12
|
||||
stackView.layoutMargins = UIEdgeInsets.layoutMarginsForRegistration(
|
||||
traitCollection.horizontalSizeClass
|
||||
)
|
||||
stackView.directionalLayoutMargins = .layoutMarginsForRegistration(traitCollection.horizontalSizeClass)
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
stackView.setContentHuggingHigh()
|
||||
scrollView.addSubview(stackView)
|
||||
|
||||
@ -18,17 +18,17 @@ extension String {
|
||||
|
||||
// MARK: - Layout margins
|
||||
|
||||
extension UIEdgeInsets {
|
||||
extension NSDirectionalEdgeInsets {
|
||||
static func layoutMarginsForRegistration(
|
||||
_ horizontalSizeClass: UIUserInterfaceSizeClass
|
||||
) -> UIEdgeInsets {
|
||||
) -> NSDirectionalEdgeInsets {
|
||||
switch horizontalSizeClass {
|
||||
case .unspecified, .compact:
|
||||
return UIEdgeInsets(allButTop: 32)
|
||||
case .regular:
|
||||
return UIEdgeInsets(allButTop: 112)
|
||||
return NSDirectionalEdgeInsets(top: 0, leading: 112, bottom: 112, trailing: 112)
|
||||
case .unspecified, .compact:
|
||||
fallthrough
|
||||
@unknown default:
|
||||
return UIEdgeInsets(allButTop: 32)
|
||||
return NSDirectionalEdgeInsets(top: 0, leading: 32, bottom: 32, trailing: 32)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -231,15 +231,15 @@ public class RegistrationCoordinatorTest: XCTestCase {
|
||||
}
|
||||
|
||||
// Now we should show the permissions.
|
||||
XCTAssertEqual(nextStep.value, .permissions(Stubs.permissionsState()))
|
||||
XCTAssertEqual(nextStep.value, .permissions)
|
||||
// Doesn't change even if we try and proceed.
|
||||
XCTAssertEqual(coordinator.nextStep().value, .permissions(Stubs.permissionsState()))
|
||||
XCTAssertEqual(coordinator.nextStep().value, .permissions)
|
||||
|
||||
// Once the state is updated we can proceed.
|
||||
nextStep = coordinator.requestPermissions()
|
||||
XCTAssertNotNil(nextStep.value)
|
||||
XCTAssertNotEqual(nextStep.value, .registrationSplash)
|
||||
XCTAssertNotEqual(nextStep.value, .permissions(Stubs.permissionsState()))
|
||||
XCTAssertNotEqual(nextStep.value, .permissions)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3693,7 +3693,7 @@ public class RegistrationCoordinatorTest: XCTestCase {
|
||||
// Now we should show the permissions.
|
||||
nextStep = coordinator.continueFromSplash()
|
||||
scheduler.runUntilIdle()
|
||||
XCTAssertEqual(nextStep.value, .permissions(Stubs.permissionsState()))
|
||||
XCTAssertEqual(nextStep.value, .permissions)
|
||||
|
||||
// Once the state is updated we can proceed.
|
||||
nextStep = coordinator.requestPermissions()
|
||||
@ -3907,10 +3907,6 @@ public class RegistrationCoordinatorTest: XCTestCase {
|
||||
|
||||
// MARK: Step States
|
||||
|
||||
static func permissionsState() -> RegistrationPermissionsState {
|
||||
return RegistrationPermissionsState(shouldRequestAccessToContacts: true)
|
||||
}
|
||||
|
||||
static func pinEntryStateForRegRecoveryPath(
|
||||
mode: RegistrationMode,
|
||||
error: RegistrationPinValidationError? = nil,
|
||||
|
||||
@ -4747,11 +4747,20 @@
|
||||
/* warning to the user that linking a phone is not recommended */
|
||||
"ONBOARDING_MODE_SWITCH_WARNING_REGISTERING" = "Linking your iPhone is not recommended and will limit core functionality.";
|
||||
|
||||
/* Label for the 'give access' button in the 'onboarding permissions' view. */
|
||||
"ONBOARDING_PERMISSIONS_ENABLE_PERMISSIONS_BUTTON" = "Allow Permissions";
|
||||
/* Description of the 'Contacts' permission in the 'onboarding permissions' view. */
|
||||
"ONBOARDING_PERMISSIONS_CONTACTS_DESCRIPTION" = "Find people you know. Your contacts are encrypted and not visible to the Signal service.";
|
||||
|
||||
/* Explanation in the 'onboarding permissions' view. */
|
||||
"ONBOARDING_PERMISSIONS_EXPLANATION" = "Allowing notifications and contacts lets you see when messages arrive and helps you find people you know. Contacts are encrypted so the Signal service can't see them.";
|
||||
/* Title introducing the 'Contacts' permission in the 'onboarding permissions' view. */
|
||||
"ONBOARDING_PERMISSIONS_CONTACTS_TITLE" = "Contacts";
|
||||
|
||||
/* Description of the 'Notifications' permission in the 'onboarding permissions' view. */
|
||||
"ONBOARDING_PERMISSIONS_NOTIFICATIONS_DESCRIPTION" = "Get notified when new messages arrive.";
|
||||
|
||||
/* Title introducing the 'Notifications' permission in the 'onboarding permissions' view. */
|
||||
"ONBOARDING_PERMISSIONS_NOTIFICATIONS_TITLE" = "Notifications";
|
||||
|
||||
/* Preamble of the 'onboarding permissions' view. */
|
||||
"ONBOARDING_PERMISSIONS_PREAMBLE" = "Signal would like to request the following permissions.";
|
||||
|
||||
/* Title of the 'onboarding permissions' view. */
|
||||
"ONBOARDING_PERMISSIONS_TITLE" = "Allow Permissions";
|
||||
|
||||
54
SignalUI/SwiftUIExtensions/AccessibleLayoutMetric.swift
Normal file
54
SignalUI/SwiftUIExtensions/AccessibleLayoutMetric.swift
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// A value that is automatically scaled down at accessibility dynamic type sizes.
|
||||
///
|
||||
/// This is similar to SwiftUI's `ScaledMetric`, which is designed to scale values
|
||||
/// *up*, proportionally with dynamic type size. `AccessibleLayoutMetric` is instead
|
||||
/// designed to make more space for content by tightening up spacing metrics at
|
||||
/// large dynamic type sizes.
|
||||
///
|
||||
/// ```swift
|
||||
/// struct ContentView: View {
|
||||
/// // Automatically scales down to 67% at accessibility dynamic type sizes.
|
||||
/// @AccessibleLayoutMetric private var rowSpacing = 24
|
||||
///
|
||||
/// // The scale used at accessibility sizes can be customized.
|
||||
/// @AccessibleLayoutMetric(scale: 0.5) private var viewPadding = 24
|
||||
///
|
||||
/// var body: some View {
|
||||
/// VStack(spacing: spacing) {
|
||||
/// Text("Moderately long text")
|
||||
/// Text("Very long text…")
|
||||
/// }
|
||||
/// .padding(.horizontal, viewPadding)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
@propertyWrapper
|
||||
public struct AccessibleLayoutMetric<Value: BinaryFloatingPoint>: DynamicProperty {
|
||||
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
||||
private let accessibilityScale: Value
|
||||
|
||||
public let rawValue: Value
|
||||
public private(set) var wrappedValue: Value
|
||||
|
||||
public init(wrappedValue: Value, scale: Value = 0.67) {
|
||||
self.accessibilityScale = scale
|
||||
self.rawValue = wrappedValue
|
||||
self.wrappedValue = wrappedValue
|
||||
}
|
||||
|
||||
public var projectedValue: Self {
|
||||
self
|
||||
}
|
||||
|
||||
public mutating func update() {
|
||||
let scale = dynamicTypeSize.isAccessibilitySize ? accessibilityScale : 1.0
|
||||
wrappedValue = rawValue * scale
|
||||
}
|
||||
}
|
||||
79
SignalUI/SwiftUIExtensions/AsyncViewTask.swift
Normal file
79
SignalUI/SwiftUIExtensions/AsyncViewTask.swift
Normal file
@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - AsyncViewTask
|
||||
|
||||
/// Represents an async operation that can be associated with the lifetime of
|
||||
/// a view using the `task(_:)` view modifier.
|
||||
public protocol AsyncViewTask: Identifiable {
|
||||
/// Optionally provide a custom priority for the task. By default, the task
|
||||
/// will be executed with the `.userInitied` priority.
|
||||
/// See the `task(id:priority:_:)` view modifier for more information.
|
||||
var priority: TaskPriority? { get }
|
||||
|
||||
/// The asynchronous action performed by the task.
|
||||
func perform() async
|
||||
}
|
||||
|
||||
extension AsyncViewTask {
|
||||
public var priority: TaskPriority? { nil }
|
||||
}
|
||||
|
||||
// MARK: - AsyncViewTaskModifier
|
||||
|
||||
extension View {
|
||||
/// Associates a binding to an `AsyncViewTask` with the lifetime of a view.
|
||||
///
|
||||
/// When the binding is `nil`, the task is not executing. To begin the task
|
||||
/// set the value of the binding to an instance of the `AsyncTask` type.
|
||||
///
|
||||
/// Buttons and other controls are automatically disabled while the task is
|
||||
/// executing.
|
||||
///
|
||||
/// To cancel the active task, set the value of the binding to `nil` or a
|
||||
/// new task value. The previous task will automatically be cancelled before
|
||||
/// a new task begins.
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Nap: AsyncViewTask {
|
||||
/// let id = UUID()
|
||||
/// var duration: ContinousClock.Duration
|
||||
///
|
||||
/// func perform() async {
|
||||
/// try? await Task.sleep(for: duration)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct NappingButton: View {
|
||||
/// @State private var nap: Nap?
|
||||
///
|
||||
/// var body: some View {
|
||||
/// Button("Take a Nap") {
|
||||
/// nap = Nap(duration: .seconds(60))
|
||||
/// }
|
||||
/// .task($nap.animation())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
public func task<AsyncTask: AsyncViewTask>(_ task: Binding<AsyncTask?>) -> some View {
|
||||
modifier(AsyncViewTaskModifier(task: task))
|
||||
}
|
||||
}
|
||||
|
||||
public struct AsyncViewTaskModifier<Task: AsyncViewTask>: ViewModifier {
|
||||
@Binding var task: Task?
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
.disabled(task != nil)
|
||||
.task(id: task?.id, priority: task?.priority ?? .userInitiated) {
|
||||
guard let currentTask = task else { return }
|
||||
defer { task = nil }
|
||||
await currentTask.perform()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public struct ScrollBounceBehaviorIfAvailableModifier: ViewModifier {
|
||||
public enum Behavior {
|
||||
case automatic
|
||||
case always
|
||||
case basedOnSize
|
||||
|
||||
@available(iOS 16.4, *)
|
||||
var asScrollBounceBehavior: ScrollBounceBehavior {
|
||||
switch self {
|
||||
case .automatic: .automatic
|
||||
case .always: .always
|
||||
case .basedOnSize: .basedOnSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var behavior: Behavior
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
if #available(iOS 16.4, *) {
|
||||
content.scrollBounceBehavior(behavior.asScrollBounceBehavior)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
public func scrollBounceBehaviorIfAvailable(_ behavior: ScrollBounceBehaviorIfAvailableModifier.Behavior) -> some View {
|
||||
modifier(ScrollBounceBehaviorIfAvailableModifier(behavior: behavior))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user