Fixed remaining bugs

Split the date out of the VisibleMessageCell into it's own cell to clean up deletion/insertion animations
Fixed a layout issue with the Open Group
Fixed an issue where the QRCode tinting wasn't working on iOS 16
Implemented a swift version of an ObjC function
This commit is contained in:
Morgan Pretty 2022-10-04 16:21:13 +11:00
parent 0c09f2bfc5
commit db54bf657e
32 changed files with 379 additions and 302 deletions

View File

@ -41,7 +41,7 @@
4520D8D51D417D8E00123472 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4520D8D41D417D8E00123472 /* Photos.framework */; };
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */; };
4535186E1FC635DD00210559 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4535186C1FC635DD00210559 /* MainInterface.storyboard */; };
453518721FC635DD00210559 /* SessionShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 453518681FC635DD00210559 /* SessionShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
453518721FC635DD00210559 /* SessionShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 453518681FC635DD00210559 /* SessionShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; };
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */; };
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454A84032059C787008B8C75 /* MediaTileViewController.swift */; };
@ -146,12 +146,11 @@
7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54CD27ACCEEC003D12F8 /* EmptySearchResultCell.swift */; };
7BAF54D327ACCF01003D12F8 /* ShareAppExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D127ACCF01003D12F8 /* ShareAppExtensionContext.swift */; };
7BAF54D427ACCF01003D12F8 /* SAEScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D227ACCF01003D12F8 /* SAEScreenLockViewController.swift */; };
7BAF54D827ACD0E3003D12F8 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */; };
7BB92B3F28C825FD0082762F /* NewConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB92B3E28C825FD0082762F /* NewConversationViewModel.swift */; };
7BBBDC44286EAD2D00747E59 /* TappableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */; };
7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */; };
7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; };
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; };
7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; };
7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD477A727EC39F5004E2822 /* Atomic.swift */; };
@ -556,7 +555,6 @@
FD17D7A727F41AF000122BE0 /* SSKLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A627F41AF000122BE0 /* SSKLegacy.swift */; };
FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A927F41BF500122BE0 /* SnodeSet.swift */; };
FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */; };
FD17D7B027F4225C00122BE0 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AF27F4225C00122BE0 /* Set+Utilities.swift */; };
FD17D7B327F51E5B00122BE0 /* SSKSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B227F51E5B00122BE0 /* SSKSetting.swift */; };
FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B727F51ECA00122BE0 /* Migration.swift */; };
FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */; };
@ -753,6 +751,8 @@
FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */; };
FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC62838B91E00B7C95D /* LinkPreviewError.swift */; };
FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */; };
FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */; };
FDB7400D28EBEC240094D718 /* DateHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */; };
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908627D7047F005DAE71 /* RoomSpec.swift */; };
FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */; };
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */; };
@ -1003,16 +1003,16 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
453518771FC635DD00210559 /* Embed App Extensions */ = {
453518771FC635DD00210559 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */,
453518721FC635DD00210559 /* SessionShareExtension.appex in Embed App Extensions */,
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed Foundation Extensions */,
453518721FC635DD00210559 /* SessionShareExtension.appex in Embed Foundation Extensions */,
);
name = "Embed App Extensions";
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
4535189F1FC63DBF00210559 /* Embed Frameworks */ = {
@ -1825,6 +1825,8 @@
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Utilities.swift"; sourceTree = "<group>"; };
FDB4BBC62838B91E00B7C95D /* LinkPreviewError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewError.swift; sourceTree = "<group>"; };
FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManagerError.swift; sourceTree = "<group>"; };
FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Utilities.swift"; sourceTree = "<group>"; };
FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateHeaderCell.swift; sourceTree = "<group>"; };
FDC2908627D7047F005DAE71 /* RoomSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSpec.swift; sourceTree = "<group>"; };
FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfoSpec.swift; sourceTree = "<group>"; };
FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequestSpec.swift; sourceTree = "<group>"; };
@ -2388,6 +2390,7 @@
B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */,
7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */,
B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */,
FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */,
);
path = "Message Cells";
sourceTree = "<group>";
@ -2515,6 +2518,7 @@
C33FDB3F255A580C00E217F9 /* String+SSK.swift */,
C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */,
FD7728952849E7E90018502F /* String+Utilities.swift */,
FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */,
C38EF237255B6D65007E1867 /* UIDevice+featureSupport.swift */,
FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */,
FD7728972849E8110018502F /* UITableView+ReusableView.swift */,
@ -4360,7 +4364,7 @@
D221A085169C9E5E00537ABF /* Sources */,
D221A086169C9E5E00537ABF /* Frameworks */,
D221A087169C9E5E00537ABF /* Resources */,
453518771FC635DD00210559 /* Embed App Extensions */,
453518771FC635DD00210559 /* Embed Foundation Extensions */,
4535189F1FC63DBF00210559 /* Embed Frameworks */,
6E75B456D9C7705F6FD9C9D4 /* [CP] Embed Pods Frameworks */,
);
@ -4451,7 +4455,7 @@
DefaultBuildSystemTypeForWorkspace = Original;
LastSwiftUpdateCheck = 1340;
LastTestingUpgradeCheck = 0600;
LastUpgradeCheck = 1320;
LastUpgradeCheck = 1400;
ORGANIZATIONNAME = "Rangeproof Pty Ltd";
TargetAttributes = {
453518671FC635DD00210559 = {
@ -5301,6 +5305,7 @@
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */,
FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */,
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */,
FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */,
@ -5315,7 +5320,6 @@
FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */,
FD17D7C527F5206300122BE0 /* ColumnDefinition+Utilities.swift in Sources */,
FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */,
7BAF54D827ACD0E3003D12F8 /* ReusableView.swift in Sources */,
FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */,
B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */,
FDF2220F281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift in Sources */,
@ -5336,7 +5340,6 @@
C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */,
C3BBE0A72554D4DE0050F1E3 /* Promise+Retrying.swift in Sources */,
B8856D7B256F14F4001CE70E /* UIView+OWS.m in Sources */,
FD17D7B027F4225C00122BE0 /* Set+Utilities.swift in Sources */,
FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */,
B88FA7FB26114EA70049422F /* Hex.swift in Sources */,
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */,
@ -5422,11 +5425,8 @@
FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */,
FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */,
FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */,
C3D9E3BF25676AD70040E4F3 /* (null) in Sources */,
B8BF43BA26CC95FB007828D1 /* WebRTC+Utilities.swift in Sources */,
7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */,
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */,
C3BBE0B52554F0E10050F1E3 /* (null) in Sources */,
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */,
FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */,
FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */,
@ -5614,6 +5614,7 @@
7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */,
B835247925C38D880089A44F /* MessageCell.swift in Sources */,
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */,
FDB7400D28EBEC240094D718 /* DateHeaderCell.swift in Sources */,
B8D0A26925E4A2C200C1835E /* Onboarding.swift in Sources */,
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */,
4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */,
@ -5627,10 +5628,8 @@
FD37EA0328A9FDCC003AE748 /* HelpViewModel.swift in Sources */,
FDFDE124282D04F20098B17F /* MediaDismissAnimationController.swift in Sources */,
7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */,
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */,
7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */,
FD71162228D983ED00B47552 /* QRCodeScanningViewController.swift in Sources */,
C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */,
B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */,
45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */,
B82B408E239DC00D00A248E7 /* DisplayNameVC.swift in Sources */,
@ -6005,7 +6004,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 374;
CURRENT_PROJECT_VERSION = 375;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6030,7 +6029,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.1.0;
MARKETING_VERSION = 2.1.1;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -6078,7 +6077,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 374;
CURRENT_PROJECT_VERSION = 375;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -6108,7 +6107,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.1.0;
MARKETING_VERSION = 2.1.1;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -6144,7 +6143,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 374;
CURRENT_PROJECT_VERSION = 375;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -6167,7 +6166,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.1.0;
MARKETING_VERSION = 2.1.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -6218,7 +6217,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 374;
CURRENT_PROJECT_VERSION = 375;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -6246,7 +6245,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.1.0;
MARKETING_VERSION = 2.1.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -6279,8 +6278,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)";
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6353,8 +6351,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6418,8 +6415,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)";
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6500,8 +6496,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6573,8 +6568,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)";
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6647,8 +6641,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6712,8 +6705,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)";
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6795,8 +6787,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6869,8 +6860,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)";
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -6943,8 +6933,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
@ -7053,7 +7042,7 @@
GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = "";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"-fobjc-arc-exceptions",
@ -7125,7 +7114,7 @@
GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = "";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
ONLY_ACTIVE_ARCH = NO;
OTHER_CFLAGS = (
"-DNS_BLOCK_ASSERTIONS=1",

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
LastUpgradeVersion = "1400"
version = "1.8">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
LastUpgradeVersion = "1400"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
@ -47,7 +47,7 @@
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDC4388027B9FF1E00C60D73"
BlueprintIdentifier = "FD71160828D00BAE00B47552"
BuildableName = "SessionTests.xctest"
BlueprintName = "SessionTests"
ReferencedContainer = "container:Session.xcodeproj">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
LastUpgradeVersion = "1400"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
@ -56,7 +56,7 @@
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDC4388027B9FF1E00C60D73"
BlueprintIdentifier = "FD71160828D00BAE00B47552"
BuildableName = "SessionTests.xctest"
BlueprintName = "SessionTests"
ReferencedContainer = "container:Session.xcodeproj">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1561,7 +1561,7 @@ extension ConversationVC:
func copy(_ cellViewModel: MessageViewModel) {
switch cellViewModel.cellType {
case .typingIndicator: break
case .typingIndicator, .dateHeader: break
case .textOnlyMessage:
UIPasteboard.general.string = cellViewModel.body

View File

@ -148,6 +148,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
trailing: 0
)
result.registerHeaderFooterView(view: UITableViewHeaderFooterView.self)
result.register(view: DateHeaderCell.self)
result.register(view: VisibleMessageCell.self)
result.register(view: InfoMessageCell.self)
result.register(view: TypingIndicatorCell.self)

View File

@ -298,6 +298,20 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
currentUserBlindedPublicKey: threadData.currentUserBlindedPublicKey
)
}
.reduce([]) { result, message in
guard message.shouldShowDateHeader else {
return result.appending(message)
}
return result
.appending(
MessageViewModel(
timestampMs: message.timestampMs,
cellType: .dateHeader
)
)
.appending(message)
}
.appending(typingIndicator)
)
],

View File

@ -2,6 +2,7 @@
import UIKit
import SessionUIKit
import SessionUtilitiesKit
import SignalUtilitiesKit
final class VoiceMessageRecordingView: UIView {
@ -207,7 +208,7 @@ final class VoiceMessageRecordingView: UIView {
@objc private func updateDurationLabel() {
let interval = Date().timeIntervalSince(recordingStartDate)
durationLabel.text = OWSFormat.formatDurationSeconds(Int(interval))
durationLabel.text = interval.formatted(format: .hoursMinutesSeconds)
}
// MARK: - Animation

View File

@ -4,6 +4,7 @@ import UIKit
import NVActivityIndicatorView
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
public final class VoiceMessageView: UIView {
private static let width: CGFloat = 160
@ -180,7 +181,8 @@ public final class VoiceMessageView: UIView {
toggleImageView.image = (isPlaying ? UIImage(named: "Pause") : UIImage(named: "Play"))?
.withRenderingMode(.alwaysTemplate)
countdownLabel.text = OWSFormat.formatDurationSeconds(max(0, Int(floor(attachment.duration.defaulting(to: 0) - progress))))
countdownLabel.text = max(0, (floor(attachment.duration.defaulting(to: 0) - progress)))
.formatted(format: .hoursMinutesSeconds)
guard let duration: TimeInterval = attachment.duration, duration > 0, progress > 0 else {
return progressViewRightConstraint.constant = -VoiceMessageView.width

View File

@ -0,0 +1,54 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SignalUtilitiesKit
import SessionUtilitiesKit
import SessionMessagingKit
final class DateHeaderCell: MessageCell {
// MARK: - UI
private lazy var dateLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .boldSystemFont(ofSize: Values.verySmallFontSize)
result.themeTextColor = .textPrimary
result.textAlignment = .center
return result
}()
// MARK: - Lifecycle
override func setUpViewHierarchy() {
super.setUpViewHierarchy()
contentView.addSubview(dateLabel)
dateLabel.pin(.top, to: .top, of: contentView, withInset: Values.mediumSpacing)
dateLabel.pin(.leading, to: .leading, of: contentView)
dateLabel.pin(.trailing, to: .trailing, of: contentView)
dateLabel.pin(.bottom, to: .bottom, of: contentView, withInset: -Values.smallSpacing)
}
// MARK: - Updating
override func prepareForReuse() {
super.prepareForReuse()
dateLabel.text = ""
}
override func update(
with cellViewModel: MessageViewModel,
mediaCache: NSCache<NSString, AnyObject>,
playbackInfo: ConversationViewModel.PlaybackInfo?,
showExpandedReactions: Bool,
lastSearchText: String?
) {
guard cellViewModel.cellType == .dateHeader else { return }
dateLabel.text = cellViewModel.dateForUI.formattedForDisplay
}
override func dynamicUpdate(with cellViewModel: MessageViewModel, playbackInfo: ConversationViewModel.PlaybackInfo?) {}
}

View File

@ -63,6 +63,7 @@ public class MessageCell: UITableViewCell {
static func cellType(for viewModel: MessageViewModel) -> MessageCell.Type {
guard viewModel.cellType != .typingIndicator else { return TypingIndicatorCell.self }
guard viewModel.cellType != .dateHeader else { return DateHeaderCell.self }
switch viewModel.variant {
case .standardOutgoing, .standardIncoming, .standardIncomingDeleted:

View File

@ -16,7 +16,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
var audioStateChanged: ((TimeInterval, Bool) -> ())?
// Constraints
private lazy var headerViewTopConstraint = headerView.pin(.top, to: .top, of: self, withInset: 1)
private lazy var authorLabelTopConstraint = authorLabel.pin(.top, to: .top, of: self)
private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0)
private lazy var profilePictureViewLeftConstraint = profilePictureView.pin(.left, to: .left, of: self, withInset: VisibleMessageCell.groupThreadHSpacing)
private lazy var profilePictureViewWidthConstraint = profilePictureView.set(.width, to: Values.verySmallProfilePictureSize)
@ -77,9 +77,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
result.set(.width, greaterThanOrEqualTo: VisibleMessageCell.largeCornerRadius * 2)
return result
}()
private lazy var headerView = UIView()
private lazy var authorLabel: UILabel = {
let result = UILabel()
result.font = .boldSystemFont(ofSize: Values.smallFontSize)
@ -166,15 +164,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
override func setUpViewHierarchy() {
super.setUpViewHierarchy()
// Header view
addSubview(headerView)
headerViewTopConstraint.isActive = true
headerView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: self)
// Author label
addSubview(authorLabel)
authorLabelTopConstraint.isActive = true
authorLabelHeightConstraint.isActive = true
authorLabel.pin(.top, to: .bottom, of: headerView)
// Profile picture view
addSubview(profilePictureView)
@ -197,7 +190,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
// Bubble background view
bubbleBackgroundView.addSubview(bubbleView)
bubbleBackgroundView.pin(to: bubbleView)
bubbleView.pin(to: bubbleBackgroundView)
// Timer view
addSubview(timerView)
@ -253,14 +246,16 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
) {
self.viewModel = cellViewModel
let isGroupThread: Bool = (cellViewModel.threadVariant == .openGroup || cellViewModel.threadVariant == .closedGroup)
let shouldInsetHeader: Bool = (
cellViewModel.previousVariant?.isInfoMessage != true &&
(
// We want to add spacing between "clusters" of messages to indicate that time has
// passed (even if there wasn't enough time to warrant showing a date header)
let shouldAddTopInset: Bool = (
!cellViewModel.shouldShowDateHeader &&
cellViewModel.previousVariant?.isInfoMessage != true && (
cellViewModel.positionInCluster == .top ||
cellViewModel.isOnlyMessageInCluster
)
)
let isGroupThread: Bool = (cellViewModel.threadVariant == .openGroup || cellViewModel.threadVariant == .closedGroup)
// Profile picture view
profilePictureViewLeftConstraint.constant = (isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0)
@ -309,12 +304,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
reactionContainerViewRightConstraint.isActive = (cellViewModel.variant == .standardOutgoing)
populateReaction(for: cellViewModel, showExpandedReactions: showExpandedReactions)
// Date break
headerViewTopConstraint.constant = (shouldInsetHeader ? Values.mediumSpacing : 1)
headerView.subviews.forEach { $0.removeFromSuperview() }
populateHeader(for: cellViewModel, shouldInsetHeader: shouldInsetHeader)
// Author label
authorLabelTopConstraint.constant = (shouldAddTopInset ? Values.mediumSpacing : 0)
authorLabel.isHidden = (cellViewModel.senderName == nil)
authorLabel.text = cellViewModel.senderName
authorLabel.themeTextColor = .textPrimary
@ -378,27 +369,6 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
}
}
private func populateHeader(for cellViewModel: MessageViewModel, shouldInsetHeader: Bool) {
guard cellViewModel.shouldShowDateHeader else { return }
let dateBreakLabel: UILabel = UILabel()
dateBreakLabel.font = .boldSystemFont(ofSize: Values.verySmallFontSize)
dateBreakLabel.text = cellViewModel.dateForUI.formattedForDisplay
dateBreakLabel.themeTextColor = .textPrimary
dateBreakLabel.textAlignment = .center
headerView.addSubview(dateBreakLabel)
dateBreakLabel.pin(.top, to: .top, of: headerView, withInset: Values.smallSpacing)
let additionalBottomInset = (shouldInsetHeader ? Values.mediumSpacing : 1)
headerView.pin(.bottom, to: .bottom, of: dateBreakLabel, withInset: Values.smallSpacing + additionalBottomInset)
dateBreakLabel.center(.horizontal, in: headerView)
let availableWidth = VisibleMessageCell.getMaxWidth(for: cellViewModel)
let availableSpace = CGSize(width: availableWidth, height: .greatestFiniteMagnitude)
let dateBreakLabelSize = dateBreakLabel.sizeThatFits(availableSpace)
dateBreakLabel.set(.height, to: dateBreakLabelSize.height)
}
private func populateContentView(
for cellViewModel: MessageViewModel,
mediaCache: NSCache<NSString, AnyObject>,
@ -444,7 +414,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
}
switch cellViewModel.cellType {
case .typingIndicator: break
case .typingIndicator, .dateHeader: break
case .textOnlyMessage:
let inset: CGFloat = 12

View File

@ -117,10 +117,7 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
].appending(
contentsOf: DisappearingMessagesConfiguration.validDurationsSeconds
.map { duration in
let title: String = NSString.formatDurationSeconds(
UInt32(duration),
useShortFormat: false
)
let title: String = duration.formatted(format: .long)
return SessionCell.Info(
id: Item(title: title),

View File

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

View File

@ -1,119 +0,0 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
13.180191 13.115493 m
12.402289 13.892069 11.323112 14.042969 9.946011 14.042969 c
4.134184 14.042969 l
2.776993 14.042969 1.696493 13.892069 0.918559 13.115493 c
0.141978 12.338914 0.000000 11.268697 0.000000 9.914982 c
0.000000 4.146585 l
0.000000 2.766711 0.141978 1.702695 0.918559 0.927503 c
1.697854 0.149537 2.776993 -0.000006 4.146584 -0.000006 c
9.946011 -0.000006 l
11.323112 -0.000006 12.403645 0.149537 13.180191 0.927503 c
13.956801 1.704049 14.098795 2.766711 14.098795 4.146585 c
14.098795 9.896383 l
14.098795 11.274897 13.956801 12.346476 13.180191 13.115493 c
h
12.617570 10.108065 m
12.617570 3.941111 l
12.617570 3.149404 12.517962 2.432404 12.093529 2.006616 c
11.675289 1.589731 10.943192 1.481156 10.159034 1.481156 c
3.941103 1.481156 l
3.156964 1.481156 2.423473 1.589731 1.999047 2.006616 c
1.582175 2.432404 1.481173 3.149404 1.481173 3.941111 c
1.481173 10.089472 l
1.481173 10.887365 1.582175 11.616772 1.999047 12.035005 c
2.423473 12.459431 3.163163 12.561794 3.959702 12.561794 c
10.159034 12.561794 l
10.943192 12.561794 11.675289 12.453232 12.093529 12.035005 c
12.517962 11.610572 12.617570 10.893565 12.617570 10.108065 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 0.000000 8.103119 cm
0.000000 0.000000 0.000000 scn
9.412357 -3.042511 m
9.788599 -3.042511 10.030975 -2.756910 10.030975 -2.357380 c
10.030975 1.161995 l
10.030975 1.678799 9.741376 1.911201 9.272816 1.911201 c
5.733462 1.911201 l
5.323707 1.911201 5.062879 1.668818 5.062879 1.292628 c
5.062879 0.916439 5.328545 0.674062 5.741778 0.674062 c
7.018399 0.674062 l
8.055644 0.795024 l
6.932081 -0.231801 l
4.283275 -2.886775 l
4.155662 -3.014382 4.070536 -3.192824 4.070536 -3.369849 c
4.070536 -3.768606 4.344208 -4.028660 4.727204 -4.028660 c
4.931782 -4.028660 5.099916 -3.953823 5.235697 -3.819378 c
7.877652 -1.178787 l
8.898316 -0.067582 l
8.787611 -1.159884 l
8.787611 -2.367121 l
8.787611 -2.775490 9.029987 -3.042511 9.412357 -3.042511 c
h
f
n
Q
endstream
endobj
3 0 obj
2078
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 14.098755 14.042969 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002168 00000 n
0000002191 00000 n
0000002364 00000 n
0000002438 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2497
%%EOF

View File

@ -345,13 +345,23 @@ class LastRowCenteredLayout: UICollectionViewFlowLayout {
// If we have an odd number of items then we want to center the last one horizontally
let elementAttributes: [UICollectionViewLayoutAttributes]? = super.layoutAttributesForElements(in: rect)
// It looks like on "max" devices the rect we are given can be much larger than the size of the
// collection view, as a result we need to try and use the collectionView width here instead
let targetViewWidth: CGFloat = {
guard let collectionView: UICollectionView = self.collectionView, collectionView.frame.width > 0 else {
return rect.width
}
return collectionView.frame.width
}()
guard
(elementAttributes?.count ?? 0) % 2 == 1,
let lastItemAttributes: UICollectionViewLayoutAttributes = elementAttributes?.last
else { return elementAttributes }
lastItemAttributes.frame = CGRect(
x: ((rect.width - lastItemAttributes.frame.size.width) / 2),
x: ((targetViewWidth - lastItemAttributes.frame.size.width) / 2),
y: lastItemAttributes.frame.origin.y,
width: lastItemAttributes.frame.size.width,
height: lastItemAttributes.frame.size.height

View File

@ -60,9 +60,9 @@ class HelpViewModel: SessionTableViewModel<NoNav, HelpViewModel.Section, HelpVie
id: .translate,
title: "HELP_TRANSLATE_TITLE".localized(),
rightAccessory: .icon(
UIImage(named: "icon_link")?
UIImage(systemName: "arrow.up.forward.app")?
.withRenderingMode(.alwaysTemplate),
size: .fit
size: .small
),
onTap: {
guard let url: URL = URL(string: "https://crowdin.com/project/session-ios") else {
@ -81,9 +81,9 @@ class HelpViewModel: SessionTableViewModel<NoNav, HelpViewModel.Section, HelpVie
id: .feedback,
title: "HELP_FEEDBACK_TITLE".localized(),
rightAccessory: .icon(
UIImage(named: "icon_link")?
UIImage(systemName: "arrow.up.forward.app")?
.withRenderingMode(.alwaysTemplate),
size: .fit
size: .small
),
onTap: {
guard let url: URL = URL(string: "https://getsession.org/survey") else {
@ -102,9 +102,9 @@ class HelpViewModel: SessionTableViewModel<NoNav, HelpViewModel.Section, HelpVie
id: .faq,
title: "HELP_FAQ_TITLE".localized(),
rightAccessory: .icon(
UIImage(named: "icon_link")?
UIImage(systemName: "arrow.up.forward.app")?
.withRenderingMode(.alwaysTemplate),
size: .fit
size: .small
),
onTap: {
guard let url: URL = URL(string: "https://getsession.org/faq") else {
@ -123,9 +123,9 @@ class HelpViewModel: SessionTableViewModel<NoNav, HelpViewModel.Section, HelpVie
id: .support,
title: "HELP_SUPPORT_TITLE".localized(),
rightAccessory: .icon(
UIImage(named: "icon_link")?
UIImage(systemName: "arrow.up.forward.app")?
.withRenderingMode(.alwaysTemplate),
size: .fit
size: .small
),
onTap: {
guard let url: URL = URL(string: "https://sessionapp.zendesk.com/hc/en-us") else {

View File

@ -259,8 +259,8 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
let pathView: PathStatusView = PathStatusView(size: .large)
result.addSubview(pathView)
result.set(.width, to: IconSize.small.size)
result.set(.height, to: IconSize.small.size)
result.set(.width, to: IconSize.medium.size)
result.set(.height, to: IconSize.medium.size)
pathView.center(in: result)
return result

View File

@ -157,11 +157,11 @@ extension SessionCell.Accessory {
// MARK: - .icon Variants
public static func icon(_ image: UIImage?) -> SessionCell.Accessory {
return .icon(image, size: .small, customTint: nil, shouldFill: false)
return .icon(image, size: .medium, customTint: nil, shouldFill: false)
}
public static func icon(_ image: UIImage?, customTint: ThemeValue) -> SessionCell.Accessory {
return .icon(image, size: .small, customTint: customTint, shouldFill: false)
return .icon(image, size: .medium, customTint: customTint, shouldFill: false)
}
public static func icon(_ image: UIImage?, size: IconSize) -> SessionCell.Accessory {
@ -173,17 +173,17 @@ extension SessionCell.Accessory {
}
public static func icon(_ image: UIImage?, shouldFill: Bool) -> SessionCell.Accessory {
return .icon(image, size: .small, customTint: nil, shouldFill: shouldFill)
return .icon(image, size: .medium, customTint: nil, shouldFill: shouldFill)
}
// MARK: - .iconAsync Variants
public static func iconAsync(_ setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory {
return .iconAsync(size: .small, customTint: nil, shouldFill: false, setter: setter)
return .iconAsync(size: .medium, customTint: nil, shouldFill: false, setter: setter)
}
public static func iconAsync(customTint: ThemeValue, _ setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory {
return .iconAsync(size: .small, customTint: customTint, shouldFill: false, setter: setter)
return .iconAsync(size: .medium, customTint: customTint, shouldFill: false, setter: setter)
}
public static func iconAsync(size: IconSize, setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory {
@ -191,7 +191,7 @@ extension SessionCell.Accessory {
}
public static func iconAsync(shouldFill: Bool, setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory {
return .iconAsync(size: .small, customTint: nil, shouldFill: shouldFill, setter: setter)
return .iconAsync(size: .medium, customTint: nil, shouldFill: shouldFill, setter: setter)
}
public static func iconAsync(size: IconSize, customTint: ThemeValue, setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory {

View File

@ -3,30 +3,42 @@
import UIKit
enum QRCode {
static func generate(for string: String, hasBackground: Bool = false) -> UIImage {
/// Generates a QRCode for the give string
///
/// **Note:** If the `hasBackground` value is true then the QRCode will be black and white and
/// the `withRenderingMode(.alwaysTemplate)` won't work correctly on some iOS versions (eg. iOS 16)
static func generate(for string: String, hasBackground: Bool) -> UIImage {
let data = string.data(using: .utf8)
var qrCodeAsCIImage: CIImage
let filter1 = CIFilter(name: "CIQRCodeGenerator")!
filter1.setValue(data, forKey: "inputMessage")
qrCodeAsCIImage = filter1.outputImage!
if hasBackground {
guard !hasBackground else {
let filter2 = CIFilter(name: "CIFalseColor")!
filter2.setValue(qrCodeAsCIImage, forKey: "inputImage")
filter2.setValue(CIColor(color: .black), forKey: "inputColor0")
filter2.setValue(CIColor(color: .white), forKey: "inputColor1")
qrCodeAsCIImage = filter2.outputImage!
}
else {
let filter2 = CIFilter(name: "CIColorInvert")!
filter2.setValue(qrCodeAsCIImage, forKey: "inputImage")
qrCodeAsCIImage = filter2.outputImage!
let filter3 = CIFilter(name: "CIMaskToAlpha")!
filter3.setValue(qrCodeAsCIImage, forKey: "inputImage")
qrCodeAsCIImage = filter3.outputImage!
let scaledQRCodeAsCIImage = qrCodeAsCIImage.transformed(by: CGAffineTransform(scaleX: 6.4, y: 6.4))
return UIImage(ciImage: scaledQRCodeAsCIImage)
}
let filter2 = CIFilter(name: "CIColorInvert")!
filter2.setValue(qrCodeAsCIImage, forKey: "inputImage")
qrCodeAsCIImage = filter2.outputImage!
let filter3 = CIFilter(name: "CIMaskToAlpha")!
filter3.setValue(qrCodeAsCIImage, forKey: "inputImage")
qrCodeAsCIImage = filter3.outputImage!
let scaledQRCodeAsCIImage = qrCodeAsCIImage.transformed(by: CGAffineTransform(scaleX: 6.4, y: 6.4))
return UIImage(ciImage: scaledQRCodeAsCIImage)
// Note: It looks like some internal method was changed in iOS 16.0 where images
// generated from a CIImage don't have the same color information as normal images
// as a result tinting using the `alwaysTemplate` rendering mode won't work - to
// work around this we convert the image to data and then back into an image
let imageData: Data = UIImage(ciImage: scaledQRCodeAsCIImage).pngData()!
return UIImage(data: imageData)!
}
}

View File

@ -69,7 +69,7 @@ public extension DisappearingMessagesConfiguration {
return String(
format: "YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION".localized(),
NSString.formatDurationSeconds(UInt32(floor(durationSeconds)), useShortFormat: false)
floor(durationSeconds).formatted(format: .long)
)
}
@ -80,13 +80,13 @@ public extension DisappearingMessagesConfiguration {
return String(
format: "OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION".localized(),
senderName,
NSString.formatDurationSeconds(UInt32(floor(durationSeconds)), useShortFormat: false)
floor(durationSeconds).formatted(format: .long)
)
}
}
var durationString: String {
NSString.formatDurationSeconds(UInt32(durationSeconds), useShortFormat: false)
floor(durationSeconds).formatted(format: .long)
}
func messageInfoString(with senderName: String?) -> String? {
@ -176,7 +176,7 @@ public class SMKDisappearingMessagesConfiguration: NSObject {
DisappearingMessagesConfiguration.validDurationsSeconds[0]
)
return NSString.formatDurationSeconds(UInt32(durationSeconds), useShortFormat: false)
return floor(durationSeconds).formatted(format: .long)
}
@objc(update:isEnabled:durationIndex:)

View File

@ -52,6 +52,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
case audio
case genericAttachment
case typingIndicator
case dateHeader
}
public var differenceIdentifier: Int64 { id }
@ -239,9 +240,10 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
name: self.authorNameInternal,
nickname: nil // Folded into 'authorName' within the Query
)
let shouldShowDateOnThisModel: Bool = {
let shouldShowDateBeforeThisModel: Bool = {
guard self.isTypingIndicator != true else { return false }
guard self.variant != .infoCall else { return true } // Always show on calls
guard !self.variant.isInfoMessage else { return false } // Never show on info messages
guard let prevModel: ViewModel = prevModel else { return true }
return MessageViewModel.shouldShowDateBreak(
@ -249,7 +251,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
and: self.timestampMs
)
}()
let shouldShowDateOnNextModel: Bool = {
let shouldShowDateBeforeNextModel: Bool = {
// Should be nothing after a typing indicator
guard self.isTypingIndicator != true else { return false }
guard let nextModel: ViewModel = nextModel else { return false }
@ -262,7 +264,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
let (positionInCluster, isOnlyMessageInCluster): (Position, Bool) = {
let isFirstInCluster: Bool = (
prevModel == nil ||
shouldShowDateOnThisModel || (
shouldShowDateBeforeThisModel || (
self.variant == .standardOutgoing &&
prevModel?.variant != .standardOutgoing
) || (
@ -278,7 +280,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
)
let isLastInCluster: Bool = (
nextModel == nil ||
shouldShowDateOnNextModel || (
shouldShowDateBeforeNextModel || (
self.variant == .standardOutgoing &&
nextModel?.variant != .standardOutgoing
) || (
@ -371,7 +373,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
}
// Only if there is a date header or the senders are different
guard shouldShowDateOnThisModel || self.authorId != prevModel?.authorId else {
guard shouldShowDateBeforeThisModel || self.authorId != prevModel?.authorId else {
return nil
}
@ -388,13 +390,13 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
(
self.authorId != nextModel?.authorId ||
(nextModel?.variant != .standardIncoming && nextModel?.variant != .standardIncomingDeleted) ||
shouldShowDateOnNextModel
shouldShowDateBeforeNextModel
) &&
// Need a profile to be able to show it
self.profile != nil
),
shouldShowDateHeader: shouldShowDateOnThisModel,
shouldShowDateHeader: shouldShowDateBeforeThisModel,
containsOnlyEmoji: self.body?.containsOnlyEmoji,
glyphCount: self.body?.glyphCount,
previousVariant: prevModel?.variant,
@ -491,6 +493,7 @@ public extension MessageViewModel {
// Note: This init method is only used system-created cells or empty states
init(
variant: Interaction.Variant = .standardOutgoing,
timestampMs: Int64 = Int64.max,
body: String? = nil,
quote: Quote? = nil,
cellType: CellType = .typingIndicator,
@ -514,7 +517,7 @@ public extension MessageViewModel {
self.rowId = targetId
self.id = targetId
self.variant = variant
self.timestampMs = Int64.max
self.timestampMs = timestampMs
self.authorId = ""
self.authorNameInternal = nil
self.body = body

View File

@ -90,10 +90,9 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec {
)
)
let title: String = NSString.formatDurationSeconds(
UInt32(DisappearingMessagesConfiguration.validDurationsSeconds.last ?? -1),
useShortFormat: false
)
let title: String = (DisappearingMessagesConfiguration.validDurationsSeconds.last?
.formatted(format: .long))
.defaulting(to: "")
expect(viewModel.settingsData.first?.elements.last)
.to(
equal(
@ -146,10 +145,9 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec {
)
)
let title: String = NSString.formatDurationSeconds(
UInt32(DisappearingMessagesConfiguration.validDurationsSeconds.last ?? -1),
useShortFormat: false
)
let title: String = (DisappearingMessagesConfiguration.validDurationsSeconds.last?
.formatted(format: .long))
.defaulting(to: "")
expect(viewModel.settingsData.first?.elements.last)
.to(
equal(

View File

@ -6,14 +6,16 @@ import DifferenceKit
public enum IconSize: Differentiable {
case small
case medium
case large
case veryLarge
case fit
public var size: CGFloat {
switch self {
case .small: return 24
case .medium: return 32
case .small: return 20
case .medium: return 24
case .large: return 32
case .veryLarge: return 80
case .fit: return 0
}

View File

@ -28,7 +28,7 @@ public extension Dictionary.Values {
// MARK: - Functional Convenience
public extension Dictionary {
public subscript(_ key: Key?) -> Value? {
subscript(_ key: Key?) -> Value? {
guard let key: Key = key else { return nil }
return self[key]

View File

@ -87,3 +87,157 @@ public extension String {
return text.replacingOccurrences(of: "%", with: "%%")
}
}
// MARK: - Formatting
public extension String {
static func formattedDuration(_ duration: TimeInterval, format: TimeInterval.DurationFormat = .short) -> String {
let secondsPerMinute: TimeInterval = 60
let secondsPerHour: TimeInterval = (secondsPerMinute * 60)
let secondsPerDay: TimeInterval = (secondsPerHour * 24)
let secondsPerWeek: TimeInterval = (secondsPerDay * 7)
switch format {
case .hoursMinutesSeconds:
let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60))
let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60))
let hours: Int = Int(duration / 3600)
guard hours > 0 else { return String(format: "%ld:%02ld", minutes, seconds) }
return String(format: "%ld:%02ld:%02ld", hours, minutes, seconds)
case .short:
switch duration {
case 0..<secondsPerMinute: // Seconds
return String(
format: "TIME_AMOUNT_SECONDS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration)),
number: .none
)
)
case secondsPerMinute..<secondsPerHour: // Minutes
return String(
format: "TIME_AMOUNT_MINUTES_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
number: .none
)
)
case secondsPerHour..<secondsPerDay: // Hours
return String(
format: "TIME_AMOUNT_HOURS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
number: .none
)
)
case secondsPerDay..<secondsPerWeek: // Days
return String(
format: "TIME_AMOUNT_DAYS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
number: .none
)
)
default: // Weeks
return String(
format: "TIME_AMOUNT_WEEKS_SHORT_FORMAT".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
number: .none
)
)
}
case .long:
switch duration {
case 0..<secondsPerMinute: // XX Seconds
return String(
format: "TIME_AMOUNT_SECONDS".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration)),
number: .none
)
)
case secondsPerMinute..<(secondsPerMinute * 1.5): // 1 Minute
return String(
format: "TIME_AMOUNT_SINGLE_MINUTE".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
number: .none
)
)
case (secondsPerMinute * 1.5)..<secondsPerHour: // Multiple Minutes
return String(
format: "TIME_AMOUNT_MINUTES".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
number: .none
)
)
case secondsPerHour..<(secondsPerHour * 1.5): // 1 Hour
return String(
format: "TIME_AMOUNT_SINGLE_HOUR".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
number: .none
)
)
case (secondsPerHour * 1.5)..<secondsPerDay: // Multiple Hours
return String(
format: "TIME_AMOUNT_HOURS".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
number: .none
)
)
case secondsPerDay..<(secondsPerDay * 1.5): // 1 Day
return String(
format: "TIME_AMOUNT_SINGLE_DAY".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
number: .none
)
)
case (secondsPerDay * 1.5)..<secondsPerWeek: // Multiple Days
return String(
format: "TIME_AMOUNT_DAYS".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
number: .none
)
)
case secondsPerWeek..<(secondsPerWeek * 1.5): // 1 Week
return String(
format: "TIME_AMOUNT_SINGLE_WEEK".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
number: .none
)
)
default: // Multiple Weeks
return String(
format: "TIME_AMOUNT_WEEKS".localized(),
NumberFormatter.localizedString(
from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
number: .none
)
)
}
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
public extension TimeInterval {
enum DurationFormat {
case short
case long
case hoursMinutesSeconds
}
func formatted(format: DurationFormat) -> String {
return String.formattedDuration(self, format: format)
}
}

View File

@ -12,8 +12,6 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)formatFileSize:(unsigned long)fileSize;
+ (NSString *)formatDurationSeconds:(long)timeSeconds;
@end
NS_ASSUME_NONNULL_END

View File

@ -42,19 +42,6 @@ NS_ASSUME_NONNULL_BEGIN
}
}
+ (NSString *)formatDurationSeconds:(long)timeSeconds
{
long seconds = timeSeconds % 60;
long minutes = (timeSeconds / 60) % 60;
long hours = timeSeconds / 3600;
if (hours > 0) {
return [NSString stringWithFormat:@"%ld:%02ld:%02ld", hours, minutes, seconds];
} else {
return [NSString stringWithFormat:@"%ld:%02ld", minutes, seconds];
}
}
@end
NS_ASSUME_NONNULL_END