Update registration permissions onboarding design & copy

This commit is contained in:
Adam Sharp 2024-09-11 17:28:13 -04:00 committed by GitHub
parent fb3c66074e
commit 9a1082dd89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 869 additions and 257 deletions

View File

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

View File

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

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

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

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "bell-ring.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "person-circle.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

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

View File

@ -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":[]}

View File

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

View File

@ -10,7 +10,7 @@ public enum RegistrationStep: Equatable {
// MARK: - Opening Steps
case registrationSplash
case changeNumberSplash
case permissions(RegistrationPermissionsState)
case permissions
// MARK: - Actually registering

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}
}

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

View File

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