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:
parent
0c09f2bfc5
commit
db54bf657e
|
@ -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",
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
LastUpgradeVersion = "1400"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
LastUpgradeVersion = "1400"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
LastUpgradeVersion = "1400"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?) {}
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_link.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -12,8 +12,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
+ (NSString *)formatFileSize:(unsigned long)fileSize;
|
||||
|
||||
+ (NSString *)formatDurationSeconds:(long)timeSeconds;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue