Merge branch 'dev' into group-chats

pull/80/head
Niels Andriesse 5 years ago
commit 0f0eba9325

@ -1 +1 @@
Subproject commit 0cad06149e4ce0ae92aa3e238916861b4eba894b
Subproject commit 8a4786d9254efa9cba361afc603b60f30157d0a6

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
2400888E239F30A600305217 /* SessionRestorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2400888D239F30A600305217 /* SessionRestorationView.swift */; };
241C6314231F64C000B4198E /* JazzIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C630E231F5AAC00B4198E /* JazzIcon.swift */; };
241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */; };
241C6316231F64CE00B4198E /* UIColor+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6310231F5C4400B4198E /* UIColor+Helper.swift */; };
@ -573,15 +574,19 @@
B82B4094239DF15900A248E7 /* ConversationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B4093239DF15900A248E7 /* ConversationTitleView.swift */; };
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; };
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; };
B847570323D5698100759540 /* LokiPushNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B847570223D5698100759540 /* LokiPushNotificationManager.swift */; };
B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */; };
B85357C123A1B81900AAF6CD /* SeedReminderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85357C023A1B81900AAF6CD /* SeedReminderViewDelegate.swift */; };
B85357C323A1BD1200AAF6CD /* SeedVCV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85357C223A1BD1200AAF6CD /* SeedVCV2.swift */; };
B85357C523A1F13800AAF6CD /* LinkDeviceVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85357C423A1F13800AAF6CD /* LinkDeviceVC.swift */; };
B85357C723A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85357C623A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift */; };
B8544E3123D16CA500299F14 /* DeviceUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8544E3023D16CA500299F14 /* DeviceUtilities.swift */; };
B8544E3323D50E4900299F14 /* AppearanceUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */; };
B8544E3423D51EEF00299F14 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */; };
B8544E3523D5201400299F14 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */; };
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; };
B885D5F62334A32100EE0D8E /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */; };
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; };
B886B4A92398BA1500211ABE /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A82398BA1500211ABE /* QRCode.swift */; };
B891105C2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; };
@ -598,7 +603,6 @@
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; };
B8BB82A9238F62FB00BA5194 /* Gradients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A8238F62FB00BA5194 /* Gradients.swift */; };
B8BB82AB238F669C00BA5194 /* ConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AA238F669C00BA5194 /* ConversationCell.swift */; };
B8BB82AD238F734800BA5194 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */; };
B8BB82B12390C37000BA5194 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82B02390C37000BA5194 /* SearchBar.swift */; };
B8BB82B523947F2D00BA5194 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82B423947F2D00BA5194 /* TextField.swift */; };
B8BB82B92394911B00BA5194 /* Separator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82B82394911B00BA5194 /* Separator.swift */; };
@ -706,6 +710,7 @@
0F94C85CB0B235DA37F68ED0 /* Pods_SignalShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1C93CF3971B64E8B6C1F9AC1 /* Pods-SignalShareExtension.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.test.xcconfig"; sourceTree = "<group>"; };
1CE3CD5C23334683BDD3D78C /* Pods-Signal.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.test.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.test.xcconfig"; sourceTree = "<group>"; };
2400888D239F30A600305217 /* SessionRestorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRestorationView.swift; sourceTree = "<group>"; };
241C630E231F5AAC00B4198E /* JazzIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JazzIcon.swift; sourceTree = "<group>"; };
241C6310231F5C4400B4198E /* UIColor+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helper.swift"; sourceTree = "<group>"; };
241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Rounding.swift"; sourceTree = "<group>"; };
@ -1412,11 +1417,15 @@
B82B4093239DF15900A248E7 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = "<group>"; };
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; };
B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = "<group>"; };
B847570023D568EB00759540 /* SignalServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SignalServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B847570223D5698100759540 /* LokiPushNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiPushNotificationManager.swift; sourceTree = "<group>"; };
B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedReminderView.swift; sourceTree = "<group>"; };
B85357C023A1B81900AAF6CD /* SeedReminderViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedReminderViewDelegate.swift; sourceTree = "<group>"; };
B85357C223A1BD1200AAF6CD /* SeedVCV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedVCV2.swift; sourceTree = "<group>"; };
B85357C423A1F13800AAF6CD /* LinkDeviceVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkDeviceVC.swift; sourceTree = "<group>"; };
B85357C623A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkDeviceVCDelegate.swift; sourceTree = "<group>"; };
B8544E3023D16CA500299F14 /* DeviceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceUtilities.swift; sourceTree = "<group>"; };
B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceUtilities.swift; sourceTree = "<group>"; };
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModal.swift; sourceTree = "<group>"; };
@ -2713,6 +2722,16 @@
sourceTree = "<group>";
};
B846365922B7417900AF1514 /* Loki */ = {
isa = PBXGroup;
children = (
B8544E3623D520F600299F14 /* Jazz Icon */,
B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */,
B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */,
);
path = Loki;
sourceTree = "<group>";
};
B8544E3623D520F600299F14 /* Jazz Icon */ = {
isa = PBXGroup;
children = (
241C630E231F5AAC00B4198E /* JazzIcon.swift */,
@ -2720,7 +2739,7 @@
241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */,
241C6310231F5C4400B4198E /* UIColor+Helper.swift */,
);
path = Loki;
path = "Jazz Icon";
sourceTree = "<group>";
};
B8BB82A3238F356800BA5194 /* Style Guide */ = {
@ -2762,11 +2781,11 @@
B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */,
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */,
B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */,
B8BB82B02390C37000BA5194 /* SearchBar.swift */,
B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */,
B85357C023A1B81900AAF6CD /* SeedReminderViewDelegate.swift */,
B8BB82B82394911B00BA5194 /* Separator.swift */,
2400888D239F30A600305217 /* SessionRestorationView.swift */,
B8CCF638239721E20091D419 /* TabBar.swift */,
B8BB82B423947F2D00BA5194 /* TextField.swift */,
);
@ -2776,8 +2795,10 @@
B8CCF63C239757DB0091D419 /* Utilities */ = {
isa = PBXGroup;
children = (
B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */,
B8544E3023D16CA500299F14 /* DeviceUtilities.swift */,
B847570223D5698100759540 /* LokiPushNotificationManager.swift */,
B84664F4235022F30083A1CD /* MentionUtilities.swift */,
B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */,
B886B4A82398BA1500211ABE /* QRCode.swift */,
);
path = Utilities;
@ -2837,6 +2858,7 @@
D221A08C169C9E5E00537ABF /* Frameworks */ = {
isa = PBXGroup;
children = (
B847570023D568EB00759540 /* SignalServiceKit.framework */,
3496955F21A2FC8100DCFE74 /* CloudKit.framework */,
4C9CA25C217E676900607C63 /* ZXingObjC.framework */,
4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */,
@ -3717,6 +3739,7 @@
34BBC85B220C7ADA00857249 /* OrderedDictionary.swift in Sources */,
346129961FD1E30000532771 /* OWSDatabaseMigration.m in Sources */,
346129FB1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.m in Sources */,
B8544E3523D5201400299F14 /* UIView+Constraints.swift in Sources */,
34AC09EA211B39B100997B47 /* ModalActivityIndicatorViewController.swift in Sources */,
344F248D2007CCD600CFB4F4 /* DisplayableText.swift in Sources */,
450998651FD8A34D00D89EB3 /* DeviceSleepManager.swift in Sources */,
@ -3805,6 +3828,7 @@
4551DB5A205C562300C8AE75 /* Collection+OWS.swift in Sources */,
34BBC84F220B8A0100857249 /* ImageEditorCropViewController.swift in Sources */,
34AC09ED211B39B100997B47 /* ContactFieldView.swift in Sources */,
B8544E3423D51EEF00299F14 /* ProfilePictureView.swift in Sources */,
346129AF1FD1F5D900532771 /* SystemContactsFetcher.swift in Sources */,
34AC09E3211B39B100997B47 /* OWSViewController.m in Sources */,
346129C81FD2072E00532771 /* NSAttributedString+OWS.m in Sources */,
@ -3846,6 +3870,7 @@
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */,
348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */,
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
B847570323D5698100759540 /* LokiPushNotificationManager.swift in Sources */,
34B6A907218B5241007C4606 /* TypingIndicatorCell.swift in Sources */,
4CFD151D22415AA400F2450F /* CallVideoHintView.swift in Sources */,
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */,
@ -3947,20 +3972,22 @@
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
B85357C523A1F13800AAF6CD /* LinkDeviceVC.swift in Sources */,
457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */,
B8544E3123D16CA500299F14 /* DeviceUtilities.swift in Sources */,
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */,
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */,
B8162F0522892C5F00D46544 /* FriendRequestViewDelegate.swift in Sources */,
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */,
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */,
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */,
B8BB82AD238F734800BA5194 /* ProfilePictureView.swift in Sources */,
B8CCF639239721E20091D419 /* TabBar.swift in Sources */,
B8162F0322891AD600D46544 /* FriendRequestView.swift in Sources */,
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */,
34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */,
2400888E239F30A600305217 /* SessionRestorationView.swift in Sources */,
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */,
4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */,
34EA69402194933900702471 /* MediaDownloadView.swift in Sources */,
B8544E3323D50E4900299F14 /* AppearanceUtilities.swift in Sources */,
340FC8AB204DAC8D007AEB0F /* DomainFrontingCountryViewController.m in Sources */,
4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */,
3496744D2076768700080B5F /* OWSMessageBubbleView.m in Sources */,
@ -3973,7 +4000,6 @@
B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */,
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */,
B885D5F62334A32100EE0D8E /* UIView+Constraints.swift in Sources */,
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */,
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */,
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */,
@ -4214,7 +4240,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 31;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -4226,7 +4252,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = SignalShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.0.0;
MTL_ENABLE_DEBUG_INFO = YES;
@ -4276,7 +4302,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 31;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -4293,7 +4319,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = SignalShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.0.0;
MTL_ENABLE_DEBUG_INFO = NO;
@ -4330,7 +4356,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 31;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
@ -4347,7 +4373,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = SignalMessaging/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.0.0;
MTL_ENABLE_DEBUG_INFO = YES;
@ -4399,7 +4425,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 31;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
@ -4421,7 +4447,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = SignalMessaging/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.0.0;
MTL_ENABLE_DEBUG_INFO = NO;
@ -4608,7 +4634,7 @@
CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 31;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -4636,7 +4662,7 @@
"\"$(SRCROOT)/Libraries\"/**",
);
INFOPLIST_FILE = "$(SRCROOT)/Signal/Signal-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
@ -4675,7 +4701,7 @@
CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 31;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -4703,7 +4729,7 @@
"\"$(SRCROOT)/Libraries\"/**",
);
INFOPLIST_FILE = "$(SRCROOT)/Signal/Signal-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",

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

File diff suppressed because one or more lines are too long

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "SessionGreen32.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "SessionGreen32@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "SessionGreen32@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "SessionGreen64.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "SessionGreen64@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "SessionGreen64@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "SessionWhite24.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "SessionWhite24@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "SessionWhite24@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "SessionWhite40.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "SessionWhite40@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "SessionWhite40@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -7,7 +7,7 @@
<key>CarthageVersion</key>
<string>0.34.0</string>
<key>OSXVersion</key>
<string>10.15.2</string>
<string>10.15.1</string>
<key>WebRTCCommit</key>
<string>1445d719bf05280270e9f77576f80f973fd847f8 M73</string>
</dict>

@ -272,7 +272,7 @@ static NSTimeInterval launchStartedAt;
[self versionMigrationsDidComplete];
}];
[UIUtil setupSignalAppearence];
[LKAppearanceUtilities switchToSessionAppearance];
if (CurrentAppContext().isRunningTests) {
return YES;
@ -1097,12 +1097,25 @@ static NSTimeInterval launchStartedAt;
}
[LKLogger print:@"[Loki] Silent push notification received; fetching messages."];
__block AnyPromise *job = [AppEnvironment.shared.messageFetcherJob run].then(^{
job = nil;
__block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{
fetchMessagesPromise = nil;
}).catch(^{
job = nil;
fetchMessagesPromise = nil;
});
[job retainUntilComplete];
[fetchMessagesPromise retainUntilComplete];
__block NSDictionary<NSString *, LKPublicChat *> *publicChats;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
publicChats = [LKDatabaseUtilities getAllPublicChats:transaction];
}];
for (LKPublicChat *publicChat in publicChats) {
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; } // For some reason publicChat is sometimes a base 64 encoded string...
LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat];
[poller stop];
AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages];
[fetchGroupMessagesPromise retainUntilComplete];
}
}
- (void)application:(UIApplication *)application
@ -1120,14 +1133,35 @@ static NSTimeInterval launchStartedAt;
}
[LKLogger print:@"[Loki] Silent push notification received; fetching messages."];
__block AnyPromise *job = [AppEnvironment.shared.messageFetcherJob run].then(^{
completionHandler(UIBackgroundFetchResultNewData);
job = nil;
NSMutableArray *promises = [NSMutableArray new];
__block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{
fetchMessagesPromise = nil;
}).catch(^{
fetchMessagesPromise = nil;
});
[promises addObject:fetchMessagesPromise];
[fetchMessagesPromise retainUntilComplete];
__block NSDictionary<NSString *, LKPublicChat *> *publicChats;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
publicChats = [LKDatabaseUtilities getAllPublicChats:transaction];
}];
for (LKPublicChat *publicChat in publicChats) {
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; } // For some reason publicChat is sometimes a base 64 encoded string...
LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat];
[poller stop];
AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages];
[promises addObject:fetchGroupMessagesPromise];
[fetchGroupMessagesPromise retainUntilComplete];
}
PMKJoin(promises).then(^(id results) {
completionHandler(UIBackgroundFetchResultNewData);
}).catch(^(id error) {
completionHandler(UIBackgroundFetchResultFailed);
job = nil;
});
[job retainUntilComplete];
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
@ -1229,14 +1263,34 @@ static NSTimeInterval launchStartedAt;
NSLog(@"[Loki] Performing background fetch.");
[LKAnalytics.shared track:@"Performed Background Fetch"];
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
__block AnyPromise *job = [AppEnvironment.shared.messageFetcherJob run].then(^{
completionHandler(UIBackgroundFetchResultNewData);
job = nil;
NSMutableArray *promises = [NSMutableArray new];
__block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{
fetchMessagesPromise = nil;
}).catch(^{
fetchMessagesPromise = nil;
});
[promises addObject:fetchMessagesPromise];
[fetchMessagesPromise retainUntilComplete];
__block NSDictionary<NSString *, LKPublicChat *> *publicChats;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
publicChats = [LKDatabaseUtilities getAllPublicChats:transaction];
}];
for (LKPublicChat *publicChat in publicChats) {
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; } // For some reason publicChat is sometimes a base 64 encoded string...
LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat];
[poller stop];
AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages];
[promises addObject:fetchGroupMessagesPromise];
[fetchGroupMessagesPromise retainUntilComplete];
}
PMKJoin(promises).then(^(id results) {
completionHandler(UIBackgroundFetchResultNewData);
}).catch(^(id error) {
completionHandler(UIBackgroundFetchResultFailed);
job = nil;
});
[job retainUntilComplete];
}];
}
@ -1548,12 +1602,12 @@ static NSTimeInterval launchStartedAt;
- (LKRSSFeed *)lokiNewsFeed
{
return [[LKRSSFeed alloc] initWithId:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki News", @"") isDeletable:true];
return [[LKRSSFeed alloc] initWithId:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:@"Loki News" isDeletable:true];
}
- (LKRSSFeed *)lokiMessengerUpdatesFeed
{
return [[LKRSSFeed alloc] initWithId:@"loki.network.messenger-updates.feed" server:@"https://loki.network/category/messenger-updates/feed/" displayName:NSLocalizedString(@"Loki Messenger Updates", @"") isDeletable:false];
return [[LKRSSFeed alloc] initWithId:@"loki.network.messenger-updates.feed" server:@"https://loki.network/category/messenger-updates/feed/" displayName:@"Loki Messenger Updates" isDeletable:false];
}
- (void)setUpDefaultPublicChatsIfNeeded

@ -40,8 +40,6 @@ final class ConversationCell : UITableViewCell {
private lazy var typingIndicatorView = TypingIndicatorView()
private lazy var bottomLabelStackViewSpacer = UIView.hStretchingSpacer()
private lazy var statusIndicatorView: UIImageView = {
let result = UIImageView()
result.contentMode = .center
@ -68,56 +66,79 @@ final class ConversationCell : UITableViewCell {
self.selectedBackgroundView = selectedBackgroundView
// Set up the unread messages indicator view
unreadMessagesIndicatorView.set(.width, to: Values.accentLineThickness)
unreadMessagesIndicatorView.set(.height, to: 72)
// Set up the profile picture view
let profilePictureViewSize = Values.mediumProfilePictureSize
profilePictureView.set(.width, to: profilePictureViewSize)
profilePictureView.set(.height, to: profilePictureViewSize)
profilePictureView.size = profilePictureViewSize
// Set up the label stack view
let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, UIView.hStretchingSpacer(), timestampLabel ])
let topLabelSpacer = UIView.hStretchingSpacer()
let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, topLabelSpacer, timestampLabel ])
topLabelStackView.axis = .horizontal
topLabelStackView.alignment = .center
topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
let snippetLabelContainer = UIView()
snippetLabelContainer.addSubview(snippetLabel)
snippetLabelContainer.addSubview(typingIndicatorView)
let bottomLabelStackView = UIStackView(arrangedSubviews: [ snippetLabelContainer, bottomLabelStackViewSpacer, statusIndicatorView ])
let bottomLabelSpacer = UIView.hStretchingSpacer()
let bottomLabelStackView = UIStackView(arrangedSubviews: [ snippetLabelContainer, bottomLabelSpacer, statusIndicatorView ])
bottomLabelStackView.axis = .horizontal
bottomLabelStackView.alignment = .center
bottomLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
let labelStackView = UIStackView(arrangedSubviews: [ UIView.spacer(withHeight: Values.smallSpacing), topLabelStackView, bottomLabelStackView, UIView.spacer(withHeight: Values.smallSpacing) ])
labelStackView.axis = .vertical
labelStackView.spacing = Values.smallSpacing
let labelContainerView = UIView()
labelContainerView.addSubview(topLabelStackView)
labelContainerView.addSubview(bottomLabelStackView)
// Set up the main stack view
let stackView = UIStackView(arrangedSubviews: [ unreadMessagesIndicatorView, profilePictureView, labelStackView ])
let stackView = UIStackView(arrangedSubviews: [ unreadMessagesIndicatorView, profilePictureView, labelContainerView ])
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Values.mediumSpacing
contentView.addSubview(stackView)
// Set up the constraints
unreadMessagesIndicatorView.pin(.top, to: .top, of: stackView)
unreadMessagesIndicatorView.pin(.bottom, to: .bottom, of: stackView)
unreadMessagesIndicatorView.pin(.top, to: .top, of: contentView)
unreadMessagesIndicatorView.pin(.bottom, to: .bottom, of: contentView)
// The three lines below are part of a workaround for a weird layout bug
topLabelStackView.set(.width, to: UIScreen.main.bounds.width - Values.accentLineThickness - Values.mediumSpacing - profilePictureViewSize - Values.mediumSpacing - Values.mediumSpacing)
topLabelStackView.set(.height, to: 18)
topLabelSpacer.set(.height, to: 18)
timestampLabel.setContentCompressionResistancePriority(.required, for: NSLayoutConstraint.Axis.horizontal)
// The three lines below are part of a workaround for a weird layout bug
bottomLabelStackView.set(.width, to: UIScreen.main.bounds.width - Values.accentLineThickness - Values.mediumSpacing - profilePictureViewSize - Values.mediumSpacing - Values.mediumSpacing)
bottomLabelStackView.set(.height, to: 16)
bottomLabelSpacer.set(.height, to: 16)
statusIndicatorView.set(.width, to: Values.conversationCellStatusIndicatorSize)
statusIndicatorView.set(.height, to: Values.conversationCellStatusIndicatorSize)
snippetLabel.pin(to: snippetLabelContainer)
typingIndicatorView.pin(.leading, to: .leading, of: snippetLabelContainer)
typingIndicatorView.centerYAnchor.constraint(equalTo: snippetLabel.centerYAnchor).isActive = true
// Not using a stack view for this is part of a workaround for a weird layout bug
topLabelStackView.pin(.leading, to: .leading, of: labelContainerView)
topLabelStackView.pin(.top, to: .top, of: labelContainerView, withInset: Values.mediumSpacing)
topLabelStackView.pin(.trailing, to: .trailing, of: labelContainerView)
bottomLabelStackView.pin(.leading, to: .leading, of: labelContainerView)
bottomLabelStackView.pin(.top, to: .bottom, of: topLabelStackView, withInset: Values.smallSpacing)
labelContainerView.pin(.bottom, to: .bottom, of: bottomLabelStackView, withInset: Values.mediumSpacing)
// The two lines below are part of a workaround for a weird layout bug
labelContainerView.set(.width, to: UIScreen.main.bounds.width - Values.accentLineThickness - Values.mediumSpacing - profilePictureViewSize - Values.mediumSpacing - Values.mediumSpacing)
labelContainerView.set(.height, to: 72)
stackView.pin(.leading, to: .leading, of: contentView)
stackView.pin(.top, to: .top, of: contentView)
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.mediumSpacing)
contentView.pin(.bottom, to: .bottom, of: stackView)
stackView.set(.width, to: UIScreen.main.bounds.width - Values.mediumSpacing) // Workaround for weird constraints issue
// The two lines below are part of a workaround for a weird layout bug
stackView.set(.width, to: UIScreen.main.bounds.width - Values.mediumSpacing)
stackView.set(.height, to: 72)
}
// MARK: Updating
private func update() {
LokiAPI.populateUserHexEncodedPublicKeyCacheIfNeeded(for: threadViewModel.threadRecord.uniqueId!) // FIXME: This is a terrible place to do this
unreadMessagesIndicatorView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0
unreadMessagesIndicatorView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0.0001 // Setting the alpha to exactly 0 causes an issue on iOS 12
if threadViewModel.isGroupThread {
let users = LokiAPI.userHexEncodedPublicKeyCache[threadViewModel.threadRecord.uniqueId!] ?? []
let randomUsers = users.sorted().prefix(2) // Sort to provide a level of stability
if !randomUsers.isEmpty {
profilePictureView.hexEncodedPublicKey = randomUsers[0]
profilePictureView.additionalHexEncodedPublicKey = randomUsers.count == 2 ? randomUsers[1] : ""
profilePictureView.additionalHexEncodedPublicKey = randomUsers.count >= 2 ? randomUsers[1] : ""
} else {
profilePictureView.hexEncodedPublicKey = ""
profilePictureView.additionalHexEncodedPublicKey = ""

@ -1,5 +1,6 @@
@objc final class ConversationTitleView : UIView {
@objc(LKConversationTitleView)
final class ConversationTitleView : UIView {
private let thread: TSThread
private var currentStatus: Status? { didSet { updateSubtitleForCurrentStatus() } }
@ -141,8 +142,9 @@
self.currentStatus = nil
}
private func updateSubtitleForCurrentStatus() {
@objc func updateSubtitleForCurrentStatus() {
DispatchQueue.main.async {
self.subtitleLabel.isHidden = false
switch self.currentStatus {
case .calculatingPoW: self.subtitleLabel.text = NSLocalizedString("Encrypting message", comment: "")
case .contactingNetwork: self.subtitleLabel.text = NSLocalizedString("Tracing a path", comment: "")
@ -151,12 +153,40 @@
case .messageFailed: self.subtitleLabel.text = NSLocalizedString("Message failed to send", comment: "")
case nil:
let subtitle = NSMutableAttributedString()
if self.thread.isMuted {
if let muteEndDate = self.thread.mutedUntilDate {
subtitle.append(NSAttributedString(string: "\u{e067} ", attributes: [ .font : UIFont.ows_elegantIconsFont(10), .foregroundColor : Colors.unimportant ]))
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.timeStyle = .medium
dateFormatter.dateStyle = .medium
subtitle.append(NSAttributedString(string: "Muted until " + dateFormatter.string(from: muteEndDate)))
} else if self.thread.isGroupThread() && !(self.thread as! TSGroupThread).isRSSFeed {
let storage = OWSPrimaryStorage.shared()
var userCount: Int?
storage.dbReadConnection.readWrite { transaction in
if let publicChat = LokiDatabaseUtilities.getPublicChat(for: self.thread.uniqueId!, in: transaction) {
userCount = storage.getUserCount(for: publicChat, in: transaction)
}
}
if let userCount = userCount {
if userCount >= 200 {
subtitle.append(NSAttributedString(string: "200+ members"))
} else {
subtitle.append(NSAttributedString(string: "\(userCount) members"))
}
} else if let hexEncodedPublicKey = (self.thread as? TSContactThread)?.contactIdentifier(), ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
subtitle.append(NSAttributedString(string: hexEncodedPublicKey))
} else {
self.subtitleLabel.isHidden = true
}
} else if let hexEncodedPublicKey = (self.thread as? TSContactThread)?.contactIdentifier(), ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
subtitle.append(NSAttributedString(string: hexEncodedPublicKey))
} else {
self.subtitleLabel.isHidden = true
}
subtitle.append(NSAttributedString(string: "26 members")) // TODO: Implement
self.subtitleLabel.attributedText = subtitle
}
self.titleLabel.font = .boldSystemFont(ofSize: self.subtitleLabel.isHidden ? Values.veryLargeFontSize : Values.mediumFontSize)
}
}

@ -9,7 +9,7 @@ final class FakeChatView : UIView {
private lazy var chatBubbles = [
getChatBubble(withText: NSLocalizedString("What's Session?", comment: ""), wasSentByCurrentUser: true),
getChatBubble(withText: NSLocalizedString("It's a secure, decentralized private messaging app", comment: ""), wasSentByCurrentUser: false),
getChatBubble(withText: NSLocalizedString("It's a decentralized, encrypted messaging app.", comment: ""), wasSentByCurrentUser: false),
getChatBubble(withText: NSLocalizedString("So it doesn't collect my personal information or my conversation metadata? How does it work?", comment: ""), wasSentByCurrentUser: true),
getChatBubble(withText: NSLocalizedString("Using a combination of advanced anonymous routing and end-to-end encryption technologies.", comment: ""), wasSentByCurrentUser: false),
getChatBubble(withText: NSLocalizedString("Friends don't let friends use compromised messengers. You're welcome.", comment: ""), wasSentByCurrentUser: false)

@ -114,10 +114,10 @@ final class FriendRequestView : UIView {
let format: String = {
switch (message.friendRequestStatus) {
case .none, .sendingOrFailed: preconditionFailure()
case .pending: return NSLocalizedString("%@ sent you a message request", comment: "")
case .accepted: return NSLocalizedString("You've accepted %@'s message request", comment: "")
case .declined: return NSLocalizedString("You've declined %@'s message request", comment: "")
case .expired: return NSLocalizedString("%@'s message request has expired", comment: "")
case .pending: return NSLocalizedString("%@ sent you a session request", comment: "")
case .accepted: return NSLocalizedString("You've accepted %@'s session request", comment: "")
case .declined: return NSLocalizedString("You've declined %@'s session request", comment: "")
case .expired: return NSLocalizedString("%@'s session request has expired", comment: "")
default: preconditionFailure()
}
}()
@ -130,10 +130,10 @@ final class FriendRequestView : UIView {
switch (message.friendRequestStatus) {
case .none: preconditionFailure()
case .sendingOrFailed: return nil
case .pending: return NSLocalizedString("You've sent %@ a message request", comment: "")
case .accepted: return NSLocalizedString("%@ accepted your message request", comment: "")
case .pending: return NSLocalizedString("You've sent %@ a session request", comment: "")
case .accepted: return NSLocalizedString("%@ accepted your session request", comment: "")
case .declined: preconditionFailure()
case .expired: return NSLocalizedString("Your message request to %@ has expired", comment: "")
case .expired: return NSLocalizedString("Your session request to %@ has expired", comment: "")
default: preconditionFailure()
}
}()

@ -0,0 +1,84 @@
@objc(LKSessionRestorationView)
final class SessionRestorationView : UIView {
private let thread: TSThread
@objc public var onRestore: (() -> Void)?
@objc public var onDismiss: (() -> Void)?
// MARK: Lifecycle
@objc init(thread: TSThread) {
self.thread = thread;
super.init(frame: CGRect.zero)
initialize()
}
required init?(coder: NSCoder) { fatalError("Using SessionRestorationView.init(coder:) isn't allowed. Use SessionRestorationView.init(thread:) instead.") }
override init(frame: CGRect) { fatalError("Using SessionRestorationView.init(frame:) isn't allowed. Use SessionRestorationView.init(thread:) instead.") }
private func initialize() {
// Set up background
backgroundColor = Colors.modalBackground
layer.cornerRadius = Values.modalCornerRadius
layer.masksToBounds = false
layer.borderColor = Colors.modalBorder.cgColor
layer.borderWidth = Values.borderThickness
layer.shadowColor = UIColor.black.cgColor
layer.shadowRadius = 8
layer.shadowOpacity = 0.64
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize)
titleLabel.text = NSLocalizedString("Session Out of Sync", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.textAlignment = .center
// Set up explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("Would you like to restore your session?", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
// Set up restore button
let restoreButton = UIButton()
restoreButton.set(.height, to: Values.mediumButtonHeight)
restoreButton.layer.cornerRadius = Values.modalButtonCornerRadius
restoreButton.backgroundColor = Colors.accent
restoreButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
restoreButton.setTitleColor(Colors.text, for: UIControl.State.normal)
restoreButton.setTitle(NSLocalizedString("Restore", comment: ""), for: UIControl.State.normal)
restoreButton.addTarget(self, action: #selector(restore), for: UIControl.Event.touchUpInside)
// Set up dismiss button
let dismissButton = UIButton()
dismissButton.set(.height, to: Values.mediumButtonHeight)
dismissButton.layer.cornerRadius = Values.modalButtonCornerRadius
dismissButton.backgroundColor = Colors.buttonBackground
dismissButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
dismissButton.setTitleColor(Colors.text, for: UIControl.State.normal)
dismissButton.setTitle(NSLocalizedString("Dismiss", comment: ""), for: UIControl.State.normal)
dismissButton.addTarget(self, action: #selector(dismiss), for: UIControl.Event.touchUpInside)
// Set up button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ dismissButton, restoreButton ])
buttonStackView.axis = .horizontal
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.distribution = .fillEqually
// Set up main stack view
let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, buttonStackView ])
mainStackView.axis = .vertical
mainStackView.spacing = Values.smallSpacing
addSubview(mainStackView)
mainStackView.pin(to: self, withInset: Values.mediumSpacing)
// Update explanation label if possible
if let contactID = thread.contactIdentifier() {
let displayName = DisplayNameUtilities.getPrivateChatDisplayName(for: contactID) ?? contactID
explanationLabel.text = String(format: NSLocalizedString("Would you like to restore your session with %@?", comment: ""), displayName)
}
}
// MARK: Interaction
@objc private func restore() { onRestore?() }
@objc private func dismiss() { onDismiss?() }
}

@ -36,7 +36,7 @@ final class TextField : UITextField {
override func textRect(forBounds bounds: CGRect) -> CGRect {
if usesDefaultHeight {
return bounds.insetBy(dx: Values.largeSpacing, dy: Values.largeSpacing)
return bounds.insetBy(dx: isSmallScreen ? Values.mediumSpacing : Values.largeSpacing, dy: isSmallScreen ? Values.smallSpacing : Values.largeSpacing)
} else {
return bounds.insetBy(dx: Values.mediumSpacing, dy: Values.smallSpacing)
}
@ -44,7 +44,7 @@ final class TextField : UITextField {
override func editingRect(forBounds bounds: CGRect) -> CGRect {
if usesDefaultHeight {
return bounds.insetBy(dx: Values.largeSpacing, dy: Values.largeSpacing)
return bounds.insetBy(dx: isSmallScreen ? Values.mediumSpacing : Values.largeSpacing, dy: isSmallScreen ? Values.smallSpacing : Values.largeSpacing)
} else {
return bounds.insetBy(dx: Values.mediumSpacing, dy: Values.smallSpacing)
}

@ -19,9 +19,9 @@ final class Values : NSObject {
@objc static let massiveFontSize = CGFloat(50)
// MARK: - Element Sizes
@objc static let smallButtonHeight = CGFloat(27)
@objc static let mediumButtonHeight = CGFloat(34)
@objc static let largeButtonHeight = CGFloat(45)
@objc static let smallButtonHeight = isSmallScreen ? CGFloat(24) : CGFloat(27)
@objc static let mediumButtonHeight = isSmallScreen ? CGFloat(30) : CGFloat(34)
@objc static let largeButtonHeight = isSmallScreen ? CGFloat(40) : CGFloat(45)
@objc static let accentLineThickness = CGFloat(4)
@objc static let verySmallProfilePictureSize = CGFloat(26)
@objc static let smallProfilePictureSize = CGFloat(35)
@ -31,18 +31,18 @@ final class Values : NSObject {
@objc static let conversationCellStatusIndicatorSize = CGFloat(14)
@objc static let searchBarHeight = CGFloat(36)
@objc static let newConversationButtonSize = CGFloat(45)
@objc static let textFieldHeight = CGFloat(80)
@objc static let textFieldHeight = isSmallScreen ? CGFloat(48) : CGFloat(80)
@objc static let textFieldCornerRadius = CGFloat(8)
@objc static let separatorLabelHeight = CGFloat(24)
@objc static var separatorThickness: CGFloat { return 1 / UIScreen.main.scale }
@objc static let tabBarHeight = CGFloat(48)
@objc static let settingButtonHeight = CGFloat(75)
@objc static let tabBarHeight = isSmallScreen ? CGFloat(32) : CGFloat(48)
@objc static let settingButtonHeight = isSmallScreen ? CGFloat(52) : CGFloat(75)
@objc static let modalCornerRadius = CGFloat(10)
@objc static let modalButtonCornerRadius = CGFloat(5)
@objc static let fakeChatBubbleWidth = CGFloat(224)
@objc static let fakeChatBubbleCornerRadius = CGFloat(10)
@objc static let fakeChatViewHeight = CGFloat(234)
@objc static var composeViewTextFieldBorderThickness: CGFloat { return 1 / UIScreen.main.scale }
@objc static let composeViewTextFieldBorderThickness = 1 / UIScreen.main.scale
@objc static let messageBubbleCornerRadius: CGFloat = 10
@objc static let progressBarThickness: CGFloat = 2
@ -54,7 +54,7 @@ final class Values : NSObject {
@objc static let veryLargeSpacing = CGFloat(35)
@objc static let massiveSpacing = CGFloat(64)
@objc static let newConversationButtonBottomOffset = CGFloat(52)
@objc static let onboardingButtonBottomOffset = CGFloat(72)
@objc static let onboardingButtonBottomOffset = isSmallScreen ? CGFloat(52) : CGFloat(72)
// MARK: - Animation Values
@objc static let fakeChatStartDelay: TimeInterval = 2

@ -0,0 +1,26 @@
@objc(LKAppearanceUtilities)
final class AppearanceUtilities : NSObject {
@objc static func switchToSessionAppearance() {
if #available(iOS 13, *) {
UINavigationBar.appearance().barTintColor = Colors.navigationBarBackground
UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().tintColor = Colors.text
UIToolbar.appearance().barTintColor = Colors.navigationBarBackground
UIToolbar.appearance().isTranslucent = false
UIToolbar.appearance().tintColor = Colors.text
UISwitch.appearance().onTintColor = Colors.accent
UINavigationBar.appearance().titleTextAttributes = [ NSAttributedString.Key.foregroundColor : Colors.text ]
}
}
@objc static func switchToImagePickerAppearance() {
if #available(iOS 13, *) {
UINavigationBar.appearance().barTintColor = .white
UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().tintColor = .black
UINavigationBar.appearance().titleTextAttributes = [ NSAttributedString.Key.foregroundColor : UIColor.black ]
}
}
}

@ -0,0 +1,4 @@
var isSmallScreen: Bool {
return (UIScreen.main.bounds.height - 568) < 1
}

@ -1,5 +1,7 @@
import UIKit
// Ideally this should be in SignalServiceKit, but somehow linking fails when it is.
@objc(LKPushNotificationManager)
final class LokiPushNotificationManager : NSObject {

@ -1,7 +1,7 @@
enum QRCode {
static func generate(for string: String, hasBackground: Bool = false) -> UIImage {
static func generate(for string: String, isInverted: Bool = false, hasBackground: Bool = false) -> UIImage {
let data = string.data(using: .utf8)
var qrCodeAsCIImage: CIImage
let filter1 = CIFilter(name: "CIQRCodeGenerator")!
@ -10,8 +10,8 @@ enum QRCode {
if hasBackground {
let filter2 = CIFilter(name: "CIFalseColor")!
filter2.setValue(qrCodeAsCIImage, forKey: "inputImage")
filter2.setValue(CIColor(color: UIColor(hex: 0xFFFFFF)), forKey: "inputColor0")
filter2.setValue(CIColor(color: UIColor(hex: 0x1B1B1B)), forKey: "inputColor1")
filter2.setValue(CIColor(color: UIColor(hex: isInverted ? 0xFFFFFF : 0x000000)), forKey: "inputColor0")
filter2.setValue(CIColor(color: UIColor(hex: isInverted ? 0x1B1B1B : 0xFFFFFF)), forKey: "inputColor1")
qrCodeAsCIImage = filter2.outputImage!
} else {
let filter2 = CIFilter(name: "CIColorInvert")!

@ -12,9 +12,20 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
// MARK: Components
private lazy var spinner = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
private lazy var qrCodeImageViewContainer: UIView = {
let result = UIView()
result.addSubview(qrCodeImageView)
qrCodeImageView.pin(.top, to: .top, of: result)
qrCodeImageView.pin(.bottom, to: .bottom, of: result)
qrCodeImageView.center(.horizontal, in: result)
return result
}()
private lazy var qrCodeImageView: UIImageView = {
let result = UIImageView()
result.contentMode = .scaleAspectFit
result.set(.width, to: 128)
result.set(.height, to: 128)
return result
}()
@ -97,7 +108,7 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
override func populateContentView() {
let stackView = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel, mnemonicLabel, buttonStackView ])
switch mode {
case .master: stackView.insertArrangedSubview(qrCodeImageView, at: 0)
case .master: stackView.insertArrangedSubview(qrCodeImageViewContainer, at: 0)
case .slave: stackView.insertArrangedSubview(spinner, at: 0)
}
contentView.addSubview(stackView)
@ -105,9 +116,8 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
stackView.axis = .vertical
switch mode {
case .master:
qrCodeImageView.set(.height, to: 128)
let hexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
qrCodeImageView.image = QRCode.generate(for: hexEncodedPublicKey)
qrCodeImageView.image = QRCode.generate(for: hexEncodedPublicKey, hasBackground: true)
case .slave:
spinner.set(.height, to: 64)
spinner.startAnimating()
@ -120,7 +130,7 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
}()
subtitleLabel.text = {
switch mode {
case .master: return NSLocalizedString("Create a new account on your other device and click \"Link to an existing account\" to start the linking process", comment: "")
case .master: return NSLocalizedString("Open Session on your secondary device and tap \"Link to an existing account\"", comment: "")
case .slave: return NSLocalizedString("Please check that the words below match those shown on your other device", comment: "")
}
}()

@ -23,7 +23,7 @@ final class DeviceLinksVC : UIViewController, UITableViewDataSource, UITableView
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.textAlignment = .center
explanationLabel.text = NSLocalizedString("You don't have any linked devices yet", comment: "")
explanationLabel.text = NSLocalizedString("You haven't linked any devices yet", comment: "")
let linkNewDeviceButton = Button(style: .prominentOutline, size: .medium)
linkNewDeviceButton.setTitle(NSLocalizedString("Link a Device", comment: ""), for: UIControl.State.normal)
linkNewDeviceButton.addTarget(self, action: #selector(linkNewDevice), for: UIControl.Event.touchUpInside)
@ -49,7 +49,7 @@ final class DeviceLinksVC : UIViewController, UITableViewDataSource, UITableView
navigationBar.barTintColor = Colors.navigationBarBackground
// Customize title
let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("Linked Devices", comment: "")
titleLabel.text = NSLocalizedString("Devices", comment: "")
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
navigationItem.titleView = titleLabel

@ -29,7 +29,7 @@ final class DisplayNameVC : UIViewController {
navigationBar.barTintColor = Colors.navigationBarBackground
// Set up logo image view
let logoImageView = UIImageView()
logoImageView.image = #imageLiteral(resourceName: "Session")
logoImageView.image = #imageLiteral(resourceName: "SessionGreen32")
logoImageView.contentMode = .scaleAspectFit
logoImageView.set(.width, to: 32)
logoImageView.set(.height, to: 32)
@ -37,7 +37,7 @@ final class DisplayNameVC : UIViewController {
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("Pick your display name", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
@ -45,15 +45,15 @@ final class DisplayNameVC : UIViewController {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = "This is how others will be able to recognize you."
explanationLabel.text = "This will be your name when you use Session."
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
// Set up spacers
let topSpacer = UIView.vStretchingSpacer()
let spacer1 = UIView()
spacer1HeightConstraint = spacer1.set(.height, to: Values.veryLargeSpacing)
spacer1HeightConstraint = spacer1.set(.height, to: isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing)
let spacer2 = UIView()
spacer2HeightConstraint = spacer2.set(.height, to: Values.veryLargeSpacing)
spacer2HeightConstraint = spacer2.set(.height, to: isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing)
let bottomSpacer = UIView.vStretchingSpacer()
let registerButtonBottomOffsetSpacer = UIView()
registerButtonBottomOffsetConstraint = registerButtonBottomOffsetSpacer.set(.height, to: Values.onboardingButtonBottomOffset)
@ -117,9 +117,9 @@ final class DisplayNameVC : UIViewController {
@objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) {
guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return }
bottomConstraint.constant = -newHeight // Negative due to how the constraint is set up
registerButtonBottomOffsetConstraint.constant = Values.largeSpacing
spacer1HeightConstraint.constant = Values.mediumSpacing
spacer2HeightConstraint.constant = Values.mediumSpacing
registerButtonBottomOffsetConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.largeSpacing
spacer1HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing
spacer2HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
@ -128,8 +128,8 @@ final class DisplayNameVC : UIViewController {
@objc private func handleKeyboardWillHideNotification(_ notification: Notification) {
bottomConstraint.constant = 0
registerButtonBottomOffsetConstraint.constant = Values.onboardingButtonBottomOffset
spacer1HeightConstraint.constant = Values.veryLargeSpacing
spacer2HeightConstraint.constant = Values.veryLargeSpacing
spacer1HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing
spacer2HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}

@ -323,6 +323,17 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat
alert.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in
guard let self = self else { return }
self.editingDatabaseConnection.readWrite { transaction in
if let publicChat = publicChat {
var messageIDs: Set<String> = []
thread.enumerateInteractions(with: transaction) { interaction, _ in
messageIDs.insert(interaction.uniqueId!)
}
OWSPrimaryStorage.shared().updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, in: transaction)
transaction.removeObject(forKey: "\(publicChat.server).\(publicChat.channel)", inCollection: LokiPublicChatAPI.lastMessageServerIDCollection)
transaction.removeObject(forKey: "\(publicChat.server).\(publicChat.channel)", inCollection: LokiPublicChatAPI.lastDeletionServerIDCollection)
let _ = LokiPublicChatAPI.leave(publicChat.channel, on: publicChat.server)
}
thread.removeAllThreadInteractions(with: transaction)
thread.remove(with: transaction)
}
NotificationCenter.default.post(name: .threadDeleted, object: nil, userInfo: [ "threadId" : thread.uniqueId! ])

@ -11,7 +11,7 @@ final class JoinPublicChatVC : UIViewController, UIPageViewControllerDataSource,
// MARK: Components
private lazy var tabBar: TabBar = {
let tabs = [
TabBar.Tab(title: NSLocalizedString("Enter Channel URL", comment: "")) { [weak self] in
TabBar.Tab(title: NSLocalizedString("Open Group URL", comment: "")) { [weak self] in
guard let self = self else { return }
self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil)
},
@ -36,7 +36,7 @@ final class JoinPublicChatVC : UIViewController, UIPageViewControllerDataSource,
}()
private lazy var scanQRCodeWrapperVC: ScanQRCodeWrapperVC = {
let message = NSLocalizedString("Scan the QR code of the channel you'd like to join", comment: "")
let message = NSLocalizedString("Scan the QR code of the open group you'd like to join", comment: "")
let result = ScanQRCodeWrapperVC(message: message)
result.delegate = self
return result
@ -60,7 +60,7 @@ final class JoinPublicChatVC : UIViewController, UIPageViewControllerDataSource,
navigationItem.leftBarButtonItem = closeButton
// Customize title
let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("Join Channel", comment: "")
titleLabel.text = NSLocalizedString("Join Open Group", comment: "")
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
navigationItem.titleView = titleLabel
@ -73,7 +73,13 @@ final class JoinPublicChatVC : UIViewController, UIPageViewControllerDataSource,
// Set up tab bar
view.addSubview(tabBar)
tabBar.pin(.leading, to: .leading, of: view)
tabBar.pin(.top, to: .top, of: view, withInset: navigationBar.height())
let tabBarInset: CGFloat
if #available(iOS 13, *) {
tabBarInset = navigationBar.height()
} else {
tabBarInset = 0
}
tabBar.pin(.top, to: .top, of: view, withInset: tabBarInset)
view.pin(.trailing, to: .trailing, of: tabBar)
// Set up page VC constraints
let pageVCView = pageVC.view!
@ -84,7 +90,13 @@ final class JoinPublicChatVC : UIViewController, UIPageViewControllerDataSource,
view.pin(.bottom, to: .bottom, of: pageVCView)
let screen = UIScreen.main.bounds
pageVCView.set(.width, to: screen.width)
let height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight
let height: CGFloat
if #available(iOS 13, *) {
height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight
} else {
let statusBarHeight = UIApplication.shared.statusBarFrame.height
height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight - statusBarHeight
}
pageVCView.set(.height, to: height)
enterChatURLVC.constrainHeight(to: height)
scanQRCodePlaceholderVC.constrainHeight(to: height)
@ -141,6 +153,7 @@ final class JoinPublicChatVC : UIViewController, UIPageViewControllerDataSource,
.done(on: .main) { [weak self] _ in
let _ = LokiPublicChatAPI.getMessages(for: channelID, on: urlAsString)
let _ = LokiPublicChatAPI.setDisplayName(to: displayName, on: urlAsString)
let _ = LokiPublicChatAPI.join(channelID, on: urlAsString)
self?.presentingViewController!.dismiss(animated: true, completion: nil)
}
.catch(on: .main) { [weak self] _ in
@ -177,7 +190,7 @@ private final class EnterChatURLVC : UIViewController {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("Enter the URL of the channel you'd like to join", comment: "")
explanationLabel.text = NSLocalizedString("Enter an open group URL", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
@ -195,7 +208,7 @@ private final class EnterChatURLVC : UIViewController {
let stackView = UIStackView(arrangedSubviews: [ chatURLTextField, UIView.spacer(withHeight: Values.smallSpacing), explanationLabel, UIView.vStretchingSpacer(), nextButtonContainer ])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: Values.largeSpacing, left: Values.largeSpacing, bottom: Values.mediumSpacing, right: Values.largeSpacing)
stackView.layoutMargins = UIEdgeInsets(top: Values.largeSpacing, left: Values.largeSpacing, bottom: Values.largeSpacing, right: Values.largeSpacing)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.pin(.leading, to: .leading, of: view)

@ -11,7 +11,7 @@ final class LandingVC : UIViewController, LinkDeviceVCDelegate, DeviceLinkingMod
private lazy var registerButton: Button = {
let result = Button(style: .prominentFilled, size: .large)
result.setTitle(NSLocalizedString("Create Account", comment: ""), for: UIControl.State.normal)
result.setTitle(NSLocalizedString("Create Session ID", comment: ""), for: UIControl.State.normal)
result.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.addTarget(self, action: #selector(register), for: UIControl.Event.touchUpInside)
return result
@ -50,7 +50,7 @@ final class LandingVC : UIViewController, LinkDeviceVCDelegate, DeviceLinkingMod
navigationBar.barTintColor = Colors.navigationBarBackground
// Set up logo image view
let logoImageView = UIImageView()
logoImageView.image = #imageLiteral(resourceName: "Session")
logoImageView.image = #imageLiteral(resourceName: "SessionGreen32")
logoImageView.contentMode = .scaleAspectFit
logoImageView.set(.width, to: 32)
logoImageView.set(.height, to: 32)
@ -58,7 +58,7 @@ final class LandingVC : UIViewController, LinkDeviceVCDelegate, DeviceLinkingMod
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("Your Session begins here...", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
@ -79,11 +79,11 @@ final class LandingVC : UIViewController, LinkDeviceVCDelegate, DeviceLinkingMod
linkButton.pin(.leading, to: .leading, of: linkButtonContainer, withInset: Values.massiveSpacing)
linkButton.pin(.top, to: .top, of: linkButtonContainer)
linkButtonContainer.pin(.trailing, to: .trailing, of: linkButton, withInset: Values.massiveSpacing)
linkButtonContainer.pin(.bottom, to: .bottom, of: linkButton, withInset: 10)
linkButtonContainer.pin(.bottom, to: .bottom, of: linkButton, withInset: isSmallScreen ? 6 : 10)
// Set up button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ registerButton, restoreButton ])
buttonStackView.axis = .vertical
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.spacing = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing
buttonStackView.alignment = .fill
// Set up button stack view container
let buttonStackViewContainer = UIView()
@ -93,7 +93,7 @@ final class LandingVC : UIViewController, LinkDeviceVCDelegate, DeviceLinkingMod
buttonStackViewContainer.pin(.trailing, to: .trailing, of: buttonStackView, withInset: Values.massiveSpacing)
buttonStackViewContainer.pin(.bottom, to: .bottom, of: buttonStackView)
// Set up main stack view
let mainStackView = UIStackView(arrangedSubviews: [ topSpacer, titleLabelContainer, UIView.spacer(withHeight: Values.mediumSpacing), fakeChatView, bottomSpacer, buttonStackViewContainer, linkButtonContainer ])
let mainStackView = UIStackView(arrangedSubviews: [ topSpacer, titleLabelContainer, UIView.spacer(withHeight: isSmallScreen ? Values.smallSpacing : Values.mediumSpacing), fakeChatView, bottomSpacer, buttonStackViewContainer, linkButtonContainer ])
mainStackView.axis = .vertical
mainStackView.alignment = .fill
view.addSubview(mainStackView)
@ -110,7 +110,9 @@ final class LandingVC : UIViewController, LinkDeviceVCDelegate, DeviceLinkingMod
override func viewDidDisappear(_ animated: Bool) {
super.viewDidAppear(animated)
fakeChatView.contentOffset = fakeChatViewContentOffset
if let fakeChatViewContentOffset = fakeChatViewContentOffset {
fakeChatView.contentOffset = fakeChatViewContentOffset
}
}
// MARK: Interaction

@ -36,7 +36,7 @@ final class LinkDeviceVC : UIViewController, UIPageViewControllerDataSource, UIP
}()
private lazy var scanQRCodeWrapperVC: ScanQRCodeWrapperVC = {
let message = NSLocalizedString("Link to your existing account by going into your in-app settings and clicking \"Linked Devices\".", comment: "")
let message = NSLocalizedString("Link to your existing account by going into your in-app settings and clicking \"Devices\".", comment: "")
let result = ScanQRCodeWrapperVC(message: message)
result.delegate = self
return result
@ -73,7 +73,13 @@ final class LinkDeviceVC : UIViewController, UIPageViewControllerDataSource, UIP
// Set up tab bar
view.addSubview(tabBar)
tabBar.pin(.leading, to: .leading, of: view)
tabBar.pin(.top, to: .top, of: view, withInset: navigationBar.height())
let tabBarInset: CGFloat
if #available(iOS 13, *) {
tabBarInset = navigationBar.height()
} else {
tabBarInset = 0
}
tabBar.pin(.top, to: .top, of: view, withInset: tabBarInset)
view.pin(.trailing, to: .trailing, of: tabBar)
// Set up page VC constraints
let pageVCView = pageVC.view!
@ -84,7 +90,13 @@ final class LinkDeviceVC : UIViewController, UIPageViewControllerDataSource, UIP
view.pin(.bottom, to: .bottom, of: pageVCView)
let screen = UIScreen.main.bounds
pageVCView.set(.width, to: screen.width)
let height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight
let height: CGFloat
if #available(iOS 13, *) {
height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight
} else {
let statusBarHeight = UIApplication.shared.statusBarFrame.height
height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight - statusBarHeight
}
pageVCView.set(.height, to: height)
enterPublicKeyVC.constrainHeight(to: height)
scanQRCodePlaceholderVC.constrainHeight(to: height)
@ -148,7 +160,7 @@ private final class EnterPublicKeyVC : UIViewController {
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("Link your device", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
@ -165,14 +177,14 @@ private final class EnterPublicKeyVC : UIViewController {
linkButton.addTarget(self, action: #selector(requestDeviceLink), for: UIControl.Event.touchUpInside)
let linkButtonContainer = UIView()
linkButtonContainer.addSubview(linkButton)
linkButton.pin(.leading, to: .leading, of: linkButtonContainer, withInset: 80)
linkButton.pin(.leading, to: .leading, of: linkButtonContainer, withInset: isSmallScreen ? 48 : 80)
linkButton.pin(.top, to: .top, of: linkButtonContainer)
linkButtonContainer.pin(.trailing, to: .trailing, of: linkButton, withInset: 80)
linkButtonBottomConstraint = linkButtonContainer.pin(.bottom, to: .bottom, of: linkButton, withInset: Values.veryLargeSpacing)
linkButtonContainer.pin(.trailing, to: .trailing, of: linkButton, withInset: isSmallScreen ? 48 : 80)
linkButtonBottomConstraint = linkButtonContainer.pin(.bottom, to: .bottom, of: linkButton, withInset: Values.largeSpacing)
// Set up top stack view
let topStackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, publicKeyTextField ])
topStackView.axis = .vertical
topStackView.spacing = Values.largeSpacing
topStackView.spacing = isSmallScreen ? Values.smallSpacing : Values.largeSpacing
// Set up spacers
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
@ -216,7 +228,7 @@ private final class EnterPublicKeyVC : UIViewController {
@objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) {
guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return }
bottomConstraint.constant = -newHeight
linkButtonBottomConstraint.constant = Values.mediumSpacing
linkButtonBottomConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
@ -224,7 +236,7 @@ private final class EnterPublicKeyVC : UIViewController {
@objc private func handleKeyboardWillHideNotification(_ notification: Notification) {
bottomConstraint.constant = 0
linkButtonBottomConstraint.constant = Values.veryLargeSpacing
linkButtonBottomConstraint.constant = Values.largeSpacing
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}

@ -35,7 +35,7 @@ final class NewPrivateChatVC : UIViewController, UIPageViewControllerDataSource,
}()
private lazy var scanQRCodeWrapperVC: ScanQRCodeWrapperVC = {
let message = NSLocalizedString("Users can share their QR code by going into their account settings and tapping \"Share QR Code\".", comment: "")
let message = NSLocalizedString("Scan a users QR code to start a session. QR codes can be found by tapping the QR code icon in account settings.", comment: "")
let result = ScanQRCodeWrapperVC(message: message)
result.delegate = self
return result
@ -63,7 +63,7 @@ final class NewPrivateChatVC : UIViewController, UIPageViewControllerDataSource,
navigationItem.rightBarButtonItem = newPrivateGroupButton
// Customize title
let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("New Conversation", comment: "")
titleLabel.text = NSLocalizedString("New Session", comment: "")
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
navigationItem.titleView = titleLabel
@ -76,7 +76,13 @@ final class NewPrivateChatVC : UIViewController, UIPageViewControllerDataSource,
// Set up tab bar
view.addSubview(tabBar)
tabBar.pin(.leading, to: .leading, of: view)
tabBar.pin(.top, to: .top, of: view, withInset: navigationBar.height())
let tabBarInset: CGFloat
if #available(iOS 13, *) {
tabBarInset = navigationBar.height()
} else {
tabBarInset = 0
}
tabBar.pin(.top, to: .top, of: view, withInset: tabBarInset)
view.pin(.trailing, to: .trailing, of: tabBar)
// Set up page VC constraints
let pageVCView = pageVC.view!
@ -87,7 +93,13 @@ final class NewPrivateChatVC : UIViewController, UIPageViewControllerDataSource,
view.pin(.bottom, to: .bottom, of: pageVCView)
let screen = UIScreen.main.bounds
pageVCView.set(.width, to: screen.width)
let height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight
let height: CGFloat
if #available(iOS 13, *) {
height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight
} else {
let statusBarHeight = UIApplication.shared.statusBarFrame.height
height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight - statusBarHeight
}
pageVCView.set(.height, to: height)
enterPublicKeyVC.constrainHeight(to: height)
scanQRCodePlaceholderVC.constrainHeight(to: height)
@ -138,7 +150,7 @@ final class NewPrivateChatVC : UIViewController, UIPageViewControllerDataSource,
fileprivate func startNewPrivateChatIfPossible(with hexEncodedPublicKey: String) {
if !ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
let alert = UIAlertController(title: NSLocalizedString("Invalid Session ID", comment: ""), message: NSLocalizedString("Please check the Session ID you entered and try again.", comment: ""), preferredStyle: .alert)
let alert = UIAlertController(title: NSLocalizedString("Invalid Session ID", comment: ""), message: NSLocalizedString("Please check the Session ID and try again", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
} else {
@ -161,7 +173,7 @@ private final class EnterPublicKeyVC : UIViewController {
}()
// MARK: Components
private lazy var publicKeyTextField = TextField(placeholder: NSLocalizedString("Enter Session ID of recipient", comment: ""))
private lazy var publicKeyTextField = TextField(placeholder: NSLocalizedString("Enter a Session ID", comment: ""))
private lazy var copyButton: Button = {
let result = Button(style: .unimportant, size: .medium)
@ -178,7 +190,7 @@ private final class EnterPublicKeyVC : UIViewController {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code.", comment: "")
explanationLabel.text = NSLocalizedString("Users can share their Session ID from their account settings, or by sharing their QR code.", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
@ -215,7 +227,7 @@ private final class EnterPublicKeyVC : UIViewController {
let stackView = UIStackView(arrangedSubviews: [ publicKeyTextField, UIView.spacer(withHeight: Values.smallSpacing), explanationLabel, UIView.spacer(withHeight: Values.largeSpacing), separator, UIView.spacer(withHeight: Values.veryLargeSpacing), userPublicKeyLabel, UIView.spacer(withHeight: Values.veryLargeSpacing), buttonContainer, UIView.vStretchingSpacer(), nextButtonContainer ])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: Values.largeSpacing, left: Values.largeSpacing, bottom: Values.mediumSpacing, right: Values.largeSpacing)
stackView.layoutMargins = UIEdgeInsets(top: Values.largeSpacing, left: Values.largeSpacing, bottom: Values.largeSpacing, right: Values.largeSpacing)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.pin(to: view)

@ -16,7 +16,7 @@ final class NukeDataModal : Modal {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("This will delete your entire account, including all data, any messages currently linked to your Session ID, as well as your personal key pair.", comment: "")
explanationLabel.text = NSLocalizedString("This will permanently delete your Session ID, including all messages, sessions, and contacts.", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping

@ -69,7 +69,7 @@ final class QRCodeVC : UIViewController, UIPageViewControllerDataSource, UIPageV
// Set up tab bar
view.addSubview(tabBar)
tabBar.pin(.leading, to: .leading, of: view)
tabBarTopConstraint = tabBar.pin(.top, to: .top, of: view)
tabBarTopConstraint = tabBar.autoPinEdge(toSuperviewSafeArea: .top)
view.pin(.trailing, to: .trailing, of: tabBar)
// Set up page VC constraints
let pageVCView = pageVC.view!
@ -80,7 +80,13 @@ final class QRCodeVC : UIViewController, UIPageViewControllerDataSource, UIPageV
view.pin(.bottom, to: .bottom, of: pageVCView)
let screen = UIScreen.main.bounds
pageVCView.set(.width, to: screen.width)
let height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight
let height: CGFloat
if #available(iOS 13, *) {
height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight
} else {
let statusBarHeight = UIApplication.shared.statusBarFrame.height
height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight - statusBarHeight
}
pageVCView.set(.height, to: height)
viewMyQRCodeVC.constrainHeight(to: height)
scanQRCodePlaceholderVC.constrainHeight(to: height)
@ -130,7 +136,7 @@ final class QRCodeVC : UIViewController, UIPageViewControllerDataSource, UIPageV
fileprivate func startNewPrivateChatIfPossible(with hexEncodedPublicKey: String) {
if !ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
let alert = UIAlertController(title: NSLocalizedString("Invalid Session ID", comment: ""), message: NSLocalizedString("Please check the Session ID you entered and try again.", comment: ""), preferredStyle: .alert)
let alert = UIAlertController(title: NSLocalizedString("Invalid Session ID", comment: ""), message: NSLocalizedString("Please check the Session ID and try again.", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
} else {
@ -160,18 +166,18 @@ private final class ViewMyQRCodeVC : UIViewController {
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.massiveFontSize)
titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? CGFloat(40) : Values.massiveFontSize)
titleLabel.text = NSLocalizedString("Scan Me", comment: "")
titleLabel.numberOfLines = 0
titleLabel.textAlignment = .center
titleLabel.lineBreakMode = .byWordWrapping
// Set up QR code image view
let qrCodeImageView = UIImageView()
let qrCode = QRCode.generate(for: userHexEncodedPublicKey)
let qrCode = QRCode.generate(for: userHexEncodedPublicKey, hasBackground: true)
qrCodeImageView.image = qrCode
qrCodeImageView.contentMode = .scaleAspectFit
qrCodeImageView.set(.height, to: 240)
qrCodeImageView.set(.width, to: 240)
qrCodeImageView.set(.height, to: isSmallScreen ? 180 : 240)
qrCodeImageView.set(.width, to: isSmallScreen ? 180 : 240)
// Set up QR code image view container
let qrCodeImageViewContainer = UIView()
qrCodeImageViewContainer.addSubview(qrCodeImageView)
@ -182,10 +188,11 @@ private final class ViewMyQRCodeVC : UIViewController {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.mediumFontSize)
let text = NSLocalizedString("This is your unique public QR code. Other users can scan this to start a conversation with you.", comment: "")
let attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.mediumFontSize), range: (text as NSString).range(of: "your unique public QR code"))
explanationLabel.attributedText = attributedText
// let text = NSLocalizedString("This is your QR code. Other users can scan it to start a session with you.", comment: "")
// let attributedText = NSMutableAttributedString(string: text)
// attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.mediumFontSize), range: (text as NSString).range(of: "your unique public QR code"))
// explanationLabel.attributedText = attributedText
explanationLabel.text = NSLocalizedString("This is your QR code. Other users can scan it to start a session with you.", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
@ -203,9 +210,9 @@ private final class ViewMyQRCodeVC : UIViewController {
// Set up stack view
let stackView = UIStackView(arrangedSubviews: [ titleLabel, qrCodeImageViewContainer, explanationLabel, shareButtonContainer, UIView.vStretchingSpacer() ])
stackView.axis = .vertical
stackView.spacing = Values.largeSpacing
stackView.spacing = isSmallScreen ? Values.mediumSpacing : Values.largeSpacing
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: Values.mediumSpacing, left: Values.largeSpacing, bottom: Values.mediumSpacing, right: Values.largeSpacing)
stackView.layoutMargins = UIEdgeInsets(top: Values.largeSpacing, left: Values.largeSpacing, bottom: Values.largeSpacing, right: Values.largeSpacing)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.pin(.leading, to: .leading, of: view)

@ -7,7 +7,7 @@ final class RegisterVC : UIViewController {
private lazy var publicKeyLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = Fonts.spaceMono(ofSize: Values.largeFontSize)
result.font = Fonts.spaceMono(ofSize: isSmallScreen ? Values.mediumFontSize : Values.largeFontSize)
result.numberOfLines = 0
result.lineBreakMode = .byCharWrapping
return result
@ -16,6 +16,7 @@ final class RegisterVC : UIViewController {
private lazy var copyPublicKeyButton: Button = {
let result = Button(style: .prominentOutline, size: .large)
result.setTitle(NSLocalizedString("Copy", comment: ""), for: UIControl.State.normal)
result.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.addTarget(self, action: #selector(copyPublicKey), for: UIControl.Event.touchUpInside)
return result
}()
@ -52,7 +53,7 @@ final class RegisterVC : UIViewController {
navigationBar.barTintColor = Colors.navigationBarBackground
// Set up logo image view
let logoImageView = UIImageView()
logoImageView.image = #imageLiteral(resourceName: "Session")
logoImageView.image = #imageLiteral(resourceName: "SessionGreen32")
logoImageView.contentMode = .scaleAspectFit
logoImageView.set(.width, to: 32)
logoImageView.set(.height, to: 32)
@ -60,7 +61,7 @@ final class RegisterVC : UIViewController {
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("Say hello to your Session ID", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
@ -68,7 +69,7 @@ final class RegisterVC : UIViewController {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("Your Session ID is the unique address that people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design.", comment: "")
explanationLabel.text = NSLocalizedString("Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design.", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
// Set up public key label container
@ -89,7 +90,7 @@ final class RegisterVC : UIViewController {
// Set up button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ registerButton, copyPublicKeyButton ])
buttonStackView.axis = .vertical
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.spacing = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing
buttonStackView.alignment = .fill
// Set up button stack view container
let buttonStackViewContainer = UIView()
@ -109,11 +110,11 @@ final class RegisterVC : UIViewController {
legalLabel.pin(.leading, to: .leading, of: legalLabelContainer, withInset: Values.massiveSpacing)
legalLabel.pin(.top, to: .top, of: legalLabelContainer)
legalLabelContainer.pin(.trailing, to: .trailing, of: legalLabel, withInset: Values.massiveSpacing)
legalLabelContainer.pin(.bottom, to: .bottom, of: legalLabel, withInset: 10)
legalLabelContainer.pin(.bottom, to: .bottom, of: legalLabel, withInset: isSmallScreen ? 6 : 10)
// Set up top stack view
let topStackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, publicKeyLabelContainer ])
topStackView.axis = .vertical
topStackView.spacing = Values.veryLargeSpacing
topStackView.spacing = isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing
topStackView.alignment = .fill
// Set up top stack view container
let topStackViewContainer = UIView()
@ -184,6 +185,8 @@ final class RegisterVC : UIViewController {
databaseConnection.setObject(seed.toHexString(), forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
databaseConnection.setObject(keyPair!, forKey: OWSPrimaryStorageIdentityKeyStoreIdentityKey, inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = keyPair!.hexEncodedPublicKey
OWSPrimaryStorage.shared().setRestorationTime(0)
UserDefaults.standard.set(false, forKey: "hasViewedSeed")
let displayNameVC = DisplayNameVC()
navigationController!.pushViewController(displayNameVC, animated: true)
}

@ -45,7 +45,7 @@ final class RestoreVC : UIViewController {
navigationBar.barTintColor = Colors.navigationBarBackground
// Set up logo image view
let logoImageView = UIImageView()
logoImageView.image = #imageLiteral(resourceName: "Session")
logoImageView.image = #imageLiteral(resourceName: "SessionGreen32")
logoImageView.contentMode = .scaleAspectFit
logoImageView.set(.width, to: 32)
logoImageView.set(.height, to: 32)
@ -53,7 +53,7 @@ final class RestoreVC : UIViewController {
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("Restore your account", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
@ -71,11 +71,11 @@ final class RestoreVC : UIViewController {
// Set up spacers
let topSpacer = UIView.vStretchingSpacer()
let spacer1 = UIView()
spacer1HeightConstraint = spacer1.set(.height, to: Values.veryLargeSpacing)
spacer1HeightConstraint = spacer1.set(.height, to: isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing)
let spacer2 = UIView()
spacer2HeightConstraint = spacer2.set(.height, to: Values.veryLargeSpacing)
spacer2HeightConstraint = spacer2.set(.height, to: isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing)
let spacer3 = UIView()
spacer3HeightConstraint = spacer3.set(.height, to: Values.veryLargeSpacing)
spacer3HeightConstraint = spacer3.set(.height, to: isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing)
let bottomSpacer = UIView.vStretchingSpacer()
let restoreButtonBottomOffsetSpacer = UIView()
restoreButtonBottomOffsetConstraint = restoreButtonBottomOffsetSpacer.set(.height, to: Values.onboardingButtonBottomOffset)
@ -139,10 +139,10 @@ final class RestoreVC : UIViewController {
@objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) {
guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return }
bottomConstraint.constant = -newHeight // Negative due to how the constraint is set up
restoreButtonBottomOffsetConstraint.constant = Values.largeSpacing
spacer1HeightConstraint.constant = Values.mediumSpacing
spacer2HeightConstraint.constant = Values.mediumSpacing
spacer3HeightConstraint.constant = Values.mediumSpacing
restoreButtonBottomOffsetConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.largeSpacing
spacer1HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing
spacer2HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing
spacer3HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
@ -151,9 +151,9 @@ final class RestoreVC : UIViewController {
@objc private func handleKeyboardWillHideNotification(_ notification: Notification) {
bottomConstraint.constant = 0
restoreButtonBottomOffsetConstraint.constant = Values.onboardingButtonBottomOffset
spacer1HeightConstraint.constant = Values.veryLargeSpacing
spacer2HeightConstraint.constant = Values.veryLargeSpacing
spacer3HeightConstraint.constant = Values.veryLargeSpacing
spacer1HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing
spacer2HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing
spacer3HeightConstraint.constant = isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
@ -176,6 +176,8 @@ final class RestoreVC : UIViewController {
databaseConnection.setObject(seed.toHexString(), forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
databaseConnection.setObject(keyPair, forKey: OWSPrimaryStorageIdentityKeyStoreIdentityKey, inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = keyPair.hexEncodedPublicKey
OWSPrimaryStorage.shared().setRestorationTime(Date().timeIntervalSince1970)
UserDefaults.standard.set(true, forKey: "hasViewedSeed")
mnemonicTextField.resignFirstResponder()
Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in
let displayNameVC = DisplayNameVC()

@ -34,7 +34,7 @@ final class SeedModal : Modal {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("This is your personal recovery phrase. It can be used to restore your account or migrate your account to a new device.", comment: "")
explanationLabel.text = NSLocalizedString("This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device.", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.textAlignment = .center

@ -30,7 +30,7 @@ final class SeedVCV2 : UIViewController {
let attributedTitle = NSMutableAttributedString(string: title)
attributedTitle.addAttribute(.foregroundColor, value: Colors.accent, range: (title as NSString).range(of: "90%"))
result.title = attributedTitle
result.subtitle = NSLocalizedString("Press the redacted words to view your recovery phrase and secure your account", comment: "")
result.subtitle = NSLocalizedString("Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID.", comment: "")
result.setProgress(0.9, animated: false)
return result
}()
@ -80,7 +80,7 @@ final class SeedVCV2 : UIViewController {
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("Meet your recovery phrase", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
@ -88,7 +88,7 @@ final class SeedVCV2 : UIViewController {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("Think of this as the crypto-equivalent of a social security number. This allows whomever has it complete access to your account.", comment: "")
explanationLabel.text = NSLocalizedString("Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and dont give it to anyone. To restore your Session ID, launch Session and tap Continue your Session.", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
// Set up mnemonic label
@ -100,14 +100,14 @@ final class SeedVCV2 : UIViewController {
// Set up mnemonic label container
let mnemonicLabelContainer = UIView()
mnemonicLabelContainer.addSubview(mnemonicLabel)
mnemonicLabel.pin(to: mnemonicLabelContainer, withInset: Values.mediumSpacing)
mnemonicLabel.pin(to: mnemonicLabelContainer, withInset: isSmallScreen ? Values.smallSpacing : Values.mediumSpacing)
mnemonicLabelContainer.layer.cornerRadius = Values.textFieldCornerRadius
mnemonicLabelContainer.layer.borderWidth = Values.borderThickness
mnemonicLabelContainer.layer.borderColor = Colors.text.cgColor
// Set up call to action label
let callToActionLabel = UILabel()
callToActionLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
callToActionLabel.font = .systemFont(ofSize: Values.mediumFontSize)
callToActionLabel.font = .systemFont(ofSize: isSmallScreen ? Values.smallFontSize : Values.mediumFontSize)
callToActionLabel.text = NSLocalizedString("Hold to reveal", comment: "")
callToActionLabel.textAlignment = .center
let callToActionLabelGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(revealMnemonic))
@ -127,7 +127,7 @@ final class SeedVCV2 : UIViewController {
// Set up top stack view
let topStackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, mnemonicLabelContainer, callToActionLabel ])
topStackView.axis = .vertical
topStackView.spacing = Values.largeSpacing
topStackView.spacing = isSmallScreen ? Values.smallSpacing : Values.largeSpacing
topStackView.alignment = .fill
// Set up top stack view container
let topStackViewContainer = UIView()
@ -145,7 +145,7 @@ final class SeedVCV2 : UIViewController {
let mainStackView = UIStackView(arrangedSubviews: [ topSpacer, topStackViewContainer, bottomSpacer, copyButtonContainer ])
mainStackView.axis = .vertical
mainStackView.alignment = .fill
mainStackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: Values.mediumSpacing, trailing: 0)
mainStackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: isSmallScreen ? Values.smallSpacing : Values.mediumSpacing, trailing: 0)
mainStackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: view)

@ -103,7 +103,7 @@ final class SettingsVC : UIViewController, AvatarViewHelperDelegate {
// Set up public key label
let publicKeyLabel = UILabel()
publicKeyLabel.textColor = Colors.text
publicKeyLabel.font = Fonts.spaceMono(ofSize: Values.largeFontSize)
publicKeyLabel.font = Fonts.spaceMono(ofSize: isSmallScreen ? Values.mediumFontSize : Values.largeFontSize)
publicKeyLabel.numberOfLines = 0
publicKeyLabel.textAlignment = .center
publicKeyLabel.lineBreakMode = .byCharWrapping
@ -183,9 +183,9 @@ final class SettingsVC : UIViewController, AvatarViewHelperDelegate {
let isMasterDevice = (UserDefaults.standard.string(forKey: "masterDeviceHexEncodedPublicKey") == nil)
if isMasterDevice {
result.append(getSeparator())
result.append(getSettingButton(withTitle: NSLocalizedString("Linked Devices", comment: ""), color: Colors.text, action: #selector(showLinkedDevices)))
result.append(getSettingButton(withTitle: NSLocalizedString("Devices", comment: ""), color: Colors.text, action: #selector(showLinkedDevices)))
result.append(getSeparator())
result.append(getSettingButton(withTitle: NSLocalizedString("Show Recovery Phrase", comment: ""), color: Colors.text, action: #selector(showSeed)))
result.append(getSettingButton(withTitle: NSLocalizedString("Recovery Phrase", comment: ""), color: Colors.text, action: #selector(showSeed)))
}
result.append(getSeparator())
result.append(getSettingButton(withTitle: NSLocalizedString("Clear All Data", comment: ""), color: Colors.destructive, action: #selector(clearAllData)))

@ -106,7 +106,7 @@
[weakSelf.navigationController pushViewController:vc animated:YES];
}]];
backgroundSection.footerTitle
= NSLocalizedString(@"Notifications can appear while your phone is locked. You may wish to limit what is shown in these notifications.", @"");
= NSLocalizedString(@"The information shown in notifications when your phone is locked.", @"");
[contents addSection:backgroundSection];
self.contents = contents;

@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (atomic) ZXCapture *capture;
@property (nonatomic) BOOL captureEnabled;
@property (nonatomic) UIView *maskingView;
@property (nonatomic) dispatch_queue_t captureQueue;
@end
@ -34,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
}
_captureEnabled = NO;
_captureQueue = dispatch_get_main_queue();
return self;
}
@ -46,6 +48,7 @@ NS_ASSUME_NONNULL_BEGIN
}
_captureEnabled = NO;
_captureQueue = dispatch_get_main_queue();
return self;
}
@ -102,9 +105,9 @@ NS_ASSUME_NONNULL_BEGIN
{
self.captureEnabled = YES;
if (!self.capture) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(self.captureQueue, ^{
self.capture = [[ZXCapture alloc] init];
self.capture.invert = YES;
// self.capture.invert = YES;
self.capture.camera = self.capture.back;
self.capture.focusMode = AVCaptureFocusModeContinuousAutoFocus;
self.capture.delegate = self;
@ -124,7 +127,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)stopCapture
{
self.captureEnabled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(self.captureQueue, ^{
[self.capture stop];
});
}

@ -137,8 +137,7 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
OWSTableSection *typingIndicatorsSection = [OWSTableSection new];
typingIndicatorsSection.headerTitle
= NSLocalizedString(@"SETTINGS_TYPING_INDICATORS", @"Label for the 'typing indicators' setting.");
typingIndicatorsSection.footerTitle = NSLocalizedString(
@"SETTINGS_TYPING_INDICATORS_FOOTER", @"An explanation of the 'typing indicators' setting.");
typingIndicatorsSection.footerTitle = NSLocalizedString(@"See and share when messages are being typed (applies to all sessions).", @"");
[typingIndicatorsSection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_TYPING_INDICATORS",
@"Label for the 'typing indicators' setting.")
@ -156,7 +155,7 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
OWSTableSection *screenLockSection = [OWSTableSection new];
screenLockSection.headerTitle = NSLocalizedString(
@"SETTINGS_SCREEN_LOCK_SECTION_TITLE", @"Title for the 'screen lock' section of the privacy settings.");
screenLockSection.footerTitle = NSLocalizedString(@"Unlock Loki Messenger's screen using Touch ID, Face ID, or your iOS device passcode. You can still receive message notifications while Screen Lock is enabled. Loki Messenger's notification settings allow you to customize the information that is displayed.", @"");
screenLockSection.footerTitle = NSLocalizedString(@"Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.", @"");
[screenLockSection
addItem:[OWSTableItem
switchItemWithText:NSLocalizedString(@"SETTINGS_SCREEN_LOCK_SWITCH_LABEL",
@ -192,9 +191,9 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
OWSTableSection *screenSecuritySection = [OWSTableSection new];
screenSecuritySection.headerTitle = NSLocalizedString(@"SETTINGS_SECURITY_TITLE", @"Section header");
screenSecuritySection.footerTitle = NSLocalizedString(@"Prevent Loki Messenger previews from appearing in the app switcher.", nil);
screenSecuritySection.footerTitle = NSLocalizedString(@"Prevent Session previews from appearing in the app switcher.", nil);
[screenSecuritySection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_SCREEN_SECURITY", @"")
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"Disable Preview in App Switcher", @"")
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"screen_security"]
isOnBlock:^{
return [Environment.shared.preferences screenSecurityIsEnabled];
@ -441,7 +440,7 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
{
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:nil
message:NSLocalizedString(@"SETTINGS_DELETE_HISTORYLOG_CONFIRMATION",
message:NSLocalizedString(@"Are you sure? This cannot be undone.",
@"Alert message before user confirms clearing history")
preferredStyle:UIAlertControllerStyleAlert];

@ -70,6 +70,8 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertIsOnMainThread();
OWSAssertDebug(self.delegate);
[LKAppearanceUtilities switchToImagePickerAppearance];
[self.delegate.fromViewController ows_askForCameraPermissions:^(BOOL granted) {
if (!granted) {
OWSLogWarn(@"Camera permission denied.");
@ -91,6 +93,8 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertIsOnMainThread();
OWSAssertDebug(self.delegate);
[LKAppearanceUtilities switchToImagePickerAppearance];
[self.delegate.fromViewController ows_askForMediaLibraryPermissions:^(BOOL granted) {
if (!granted) {
OWSLogWarn(@"Media Library permission denied.");
@ -115,6 +119,8 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertIsOnMainThread();
OWSAssertDebug(self.delegate);
[LKAppearanceUtilities switchToSessionAppearance];
[self.delegate.fromViewController dismissViewControllerAnimated:YES completion:nil];
}
@ -126,6 +132,8 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertIsOnMainThread();
OWSAssertDebug(self.delegate);
[LKAppearanceUtilities switchToSessionAppearance];
UIImage *rawAvatar = [info objectForKey:UIImagePickerControllerOriginalImage];
[self.delegate.fromViewController

@ -431,12 +431,8 @@ typedef void (^SystemMessageActionBlock)(void);
}];
case TSErrorMessageMissingKeyId:
case TSErrorMessageNoSession:
return nil;
case TSErrorMessageInvalidMessage:
return [SystemMessageAction actionWithTitle:NSLocalizedString(@"FINGERPRINT_SHRED_KEYMATERIAL_BUTTON", @"")
block:^{
[weakSelf.delegate tappedCorruptedMessage:message];
}];
return nil;
case TSErrorMessageDuplicateMessage:
case TSErrorMessageInvalidVersion:
return nil;

@ -160,8 +160,9 @@ typedef enum : NSUInteger {
@property (nonatomic, nullable) NSTimer *readTimer;
@property (nonatomic) NSCache *cellMediaCache;
@property (nonatomic) ConversationTitleView *headerView;
@property (nonatomic) LKConversationTitleView *headerView;
@property (nonatomic, nullable) UIView *bannerView;
@property (nonatomic, nullable) UIView *restoreSessionBannerView;
@property (nonatomic, nullable) OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration;
// Back Button Unread Count
@ -261,7 +262,7 @@ typedef enum : NSUInteger {
_recordVoiceNoteAudioActivity = [[OWSAudioActivity alloc] initWithAudioDescription:audioActivityDescription behavior:OWSAudioBehavior_PlayAndRecord];
self.scrollContinuity = kScrollContinuityBottom;
_currentMentionStartIndex = -1;
_mentions = [NSMutableArray new];
_oldText = @"";
@ -418,6 +419,10 @@ typedef enum : NSUInteger {
selector:@selector(handleThreadFriendRequestStatusChangedNotification:)
name:NSNotification.threadFriendRequestStatusChanged
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleThreadSessionRestoreDevicesChangedNotifiaction:)
name:NSNotification.threadSessionRestoreDevicesChanged
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleCalculatingPoWNotification:)
name:NSNotification.calculatingPoW
@ -509,6 +514,17 @@ typedef enum : NSUInteger {
[self resetContentAndLayout];
}
- (void)handleThreadSessionRestoreDevicesChangedNotifiaction:(NSNotification *)notification
{
// Check thread
NSString *threadID = (NSString *)notification.object;
if (![threadID isEqualToString:self.thread.uniqueId]) { return; }
// Ensure thread instance is up to date
[self.thread reload];
// Update UI
[self updateSessionRestoreBanner];
}
- (void)peekSetup
{
_peek = YES;
@ -547,7 +563,7 @@ typedef enum : NSUInteger {
selector:@selector(reloadTimerDidFire)
userInfo:nil
repeats:YES];
[LKAPI populateUserHexEncodedPublicKeyCacheIfNeededFor:thread.uniqueId in:nil];
}
@ -622,7 +638,7 @@ typedef enum : NSUInteger {
[super viewDidLoad];
[self createContents];
[self registerCellClasses];
[self createConversationScrollButtons];
@ -641,20 +657,20 @@ typedef enum : NSUInteger {
[self loadDraftInCompose];
[self applyTheme];
[self.conversationViewModel viewDidLoad];
// Loki: Set gradient background
self.collectionView.backgroundColor = UIColor.clearColor;
LKGradient *gradient = LKGradients.defaultLokiBackground;
self.view.backgroundColor = UIColor.clearColor;
[self.view setGradient:gradient];
// Loki: Set navigation bar background color
UINavigationBar *navigationBar = self.navigationController.navigationBar;
[navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
navigationBar.shadowImage = [UIImage new];
[navigationBar setTranslucent:NO];
navigationBar.barTintColor = LKColors.navigationBarBackground;
// Loki: Set up navigation bar buttons
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Back", "") style:UIBarButtonItemStylePlain target:nil action:nil];
backButton.tintColor = LKColors.text;
@ -662,6 +678,19 @@ typedef enum : NSUInteger {
UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"Gear"] style:UIBarButtonItemStylePlain target:self action:@selector(showConversationSettings)];
settingsButton.tintColor = LKColors.text;
self.navigationItem.rightBarButtonItem = settingsButton;
if (self.thread.isGroupThread) {
TSGroupThread *thread = (TSGroupThread *)self.thread;
if (thread.isRSSFeed) { return; }
__block LKPublicChat *publicChat;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
publicChat = [LKDatabaseUtilities getPublicChatForThreadID:thread.uniqueId transaction:transaction];
}];
[LKPublicChatAPI getUserCountForGroup:publicChat.channel onServer:publicChat.server]
.thenOn(dispatch_get_main_queue(), ^(id userCount) {
[self.headerView updateSubtitleForCurrentStatus];
});
}
}
- (void)createContents
@ -706,7 +735,7 @@ typedef enum : NSUInteger {
[self.progressIndicatorView autoPinEdgeToSuperviewEdge:ALEdgeTop];
[self.progressIndicatorView autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
[self.progressIndicatorView autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
[self.collectionView applyScrollViewInsetsFix];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _collectionView);
@ -715,7 +744,7 @@ typedef enum : NSUInteger {
self.inputToolbar.inputTextViewDelegate = self;
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _inputToolbar);
[self updateInputToolbar];
self.loadMoreHeader = [UILabel new];
self.loadMoreHeader.text = NSLocalizedString(@"CONVERSATION_VIEW_LOADING_MORE_MESSAGES", @"Indicates that the app is loading more messages in this conversation.");
self.loadMoreHeader.textColor = [LKColors.text colorWithAlphaComponent:0.8];
@ -822,6 +851,7 @@ typedef enum : NSUInteger {
OWSLogDebug(@"viewWillAppear");
[self ensureBannerState];
[self updateSessionRestoreBanner];
[super viewWillAppear:animated];
@ -979,6 +1009,38 @@ typedef enum : NSUInteger {
return [result copy];
}
- (void)updateSessionRestoreBanner {
BOOL isContactThread = [self.thread isKindOfClass:[TSContactThread class]];
BOOL shouldRemoveBanner = !isContactThread;
if (isContactThread) {
TSContactThread *thread = (TSContactThread *)self.thread;
if (thread.sessionRestoreDevices.count > 0) {
if (self.restoreSessionBannerView == nil) {
LKSessionRestorationView *bannerView = [[LKSessionRestorationView alloc] initWithThread:thread];
[self.view addSubview:bannerView];
[bannerView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:LKValues.mediumSpacing];
[bannerView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:LKValues.largeSpacing];
[bannerView autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:LKValues.mediumSpacing];
[self.view layoutSubviews];
self.restoreSessionBannerView = bannerView;
[bannerView setOnRestore:^{
[self restoreSession];
}];
[bannerView setOnDismiss:^{
[thread removeAllSessionRestoreDevicesWithTransaction:nil];
}];
}
} else {
shouldRemoveBanner = true;
}
}
if (shouldRemoveBanner && self.restoreSessionBannerView) {
[self.restoreSessionBannerView removeFromSuperview];
self.restoreSessionBannerView = nil;
}
}
- (void)ensureBannerState
{
// This method should be called rarely, so it's simplest to discard and
@ -1150,6 +1212,27 @@ typedef enum : NSUInteger {
}];
}
- (void)restoreSession {
if ([self.thread isKindOfClass:[TSContactThread class]]) {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
TSContactThread *thread = (TSContactThread *)self.thread;
NSArray *devices = thread.sessionRestoreDevices;
for (NSString *device in devices) {
if (device.length == 0) { continue; }
OWSMessageSend *sessionRestoreMessage = [messageSender getSessionRestoreMessageForHexEncodedPublicKey:device];
if (sessionRestoreMessage) {
dispatch_async(OWSDispatch.sendingQueue, ^{
[messageSender sendMessage:sessionRestoreMessage];
});
}
}
[[[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageType:TSInfoMessageTypeLokiSessionResetInProgress] save];
thread.sessionResetState = TSContactThreadSessionResetStateRequestReceived;
[thread save];
[thread removeAllSessionRestoreDevicesWithTransaction:nil];
}
}
- (void)noLongerVerifiedBannerViewWasTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
@ -1320,7 +1403,7 @@ typedef enum : NSUInteger {
// - Begin presenting another view, e.g. swipe-left for details or swipe-right to go back, but quit part way, so that you remain on the conversation view
// - toolbar will be not be visible unless we reaquire first responder.
if (!self.isFirstResponder) {
// We don't have to worry about the input toolbar being visible if the inputToolbar.textView is first responder
// In fact doing so would unnecessarily dismiss the keyboard which is probably not desirable and at least
// a distracting animation.
@ -1410,7 +1493,7 @@ typedef enum : NSUInteger {
- (void)createHeaderViews
{
ConversationTitleView *headerView = [[ConversationTitleView alloc] initWithThread:self.thread];
LKConversationTitleView *headerView = [[LKConversationTitleView alloc] initWithThread:self.thread];
self.headerView = headerView;
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, headerView);
@ -1468,7 +1551,7 @@ typedef enum : NSUInteger {
- (void)updateBarButtonItems
{
return; // Loki: Re-enable later?
self.navigationItem.hidesBackButton = NO;
if (self.customBackButton) {
self.navigationItem.leftBarButtonItem = self.customBackButton;
@ -1495,7 +1578,7 @@ typedef enum : NSUInteger {
UIButton *callButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *image = [[UIImage imageNamed:@"button_phone_white"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[callButton setImage:image forState:UIControlStateNormal];
if (OWSWindowManager.sharedManager.hasCall) {
callButton.enabled = NO;
callButton.userInteractionEnabled = NO;
@ -1507,7 +1590,7 @@ typedef enum : NSUInteger {
}
UIEdgeInsets imageEdgeInsets = UIEdgeInsetsZero;
// We normally would want to use left and right insets that ensure the button
// is square and the icon is centered. However UINavigationBar doesn't offer us
// control over the margins and spacing of its content, and the buttons end up
@ -1591,7 +1674,7 @@ typedef enum : NSUInteger {
isAttachmentButtonHidden = false;
}
[self.inputToolbar setUserInteractionEnabled:isEnabled];
NSString *placeholderText = isEnabled ? NSLocalizedString(@"Message", "") : NSLocalizedString(@"Pending message request", "");
NSString *placeholderText = isEnabled ? NSLocalizedString(@"Message", "") : NSLocalizedString(@"Pending session request", "");
[self.inputToolbar setPlaceholderText:placeholderText];
[self.inputToolbar setAttachmentButtonHidden:isAttachmentButtonHidden];
}
@ -1860,14 +1943,14 @@ typedef enum : NSUInteger {
// Before 2.13 we didn't track the recipient id in the identity change error.
OWSLogWarn(@"Ignoring tap on legacy nonblocking identity change since it has no signal id");
return;
} else {
OWSLogInfo(@"Assuming tap on legacy nonblocking identity change corresponds to current contact thread: %@",
self.thread.contactIdentifier);
signalIdParam = self.thread.contactIdentifier;
}
}
NSString *signalId = signalIdParam;
[self showFingerprintWithRecipientId:signalId];
@ -2395,7 +2478,7 @@ typedef enum : NSUInteger {
self.audioAttachmentPlayer =
[[OWSAudioPlayer alloc] initWithMediaUrl:attachmentStream.originalMediaURL audioBehavior:OWSAudioBehavior_AudioMessagePlayback delegate:viewItem];
// Associate the player with this media adapter.
self.audioAttachmentPlayer.owner = viewItem;
[self.audioAttachmentPlayer play];
@ -4235,18 +4318,24 @@ typedef enum : NSUInteger {
[searchBar setImage:searchImage forSearchBarIcon:UISearchBarIconSearch state:UIControlStateNormal];
UIImage *clearImage = [[UIImage imageNamed:@"searchbar_clear"] asTintedImageWithColor:LKColors.searchBarPlaceholder];
[searchBar setImage:clearImage forSearchBarIcon:UISearchBarIconClear state:UIControlStateNormal];
searchBar.searchTextField.backgroundColor = LKColors.searchBarBackground;
searchBar.searchTextField.textColor = LKColors.text;
searchBar.searchTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Search", @"") attributes:@{ NSForegroundColorAttributeName : LKColors.searchBarPlaceholder }];
UITextField *searchTextField;
if (@available(iOS 13, *)) {
searchTextField = searchBar.searchTextField;
} else {
searchTextField = (UITextField *)[searchBar valueForKey:@"_searchField"];
}
searchTextField.backgroundColor = LKColors.searchBarBackground;
searchTextField.textColor = LKColors.text;
searchTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Search", @"") attributes:@{ NSForegroundColorAttributeName : LKColors.searchBarPlaceholder }];
searchBar.keyboardAppearance = UIKeyboardAppearanceDark;
[searchBar setPositionAdjustment:UIOffsetMake(4, 0) forSearchBarIcon:UISearchBarIconSearch];
[searchBar setSearchTextPositionAdjustment:UIOffsetMake(2, 0)];
[searchBar setPositionAdjustment:UIOffsetMake(-4, 0) forSearchBarIcon:UISearchBarIconClear];
// Note: setting a searchBar as the titleView causes UIKit to render the navBar
// *slightly* taller (44pt -> 56pt)
self.navigationItem.titleView = searchBar;
[self updateBarButtonItems];
// Hack so that the ResultsBar stays on the screen when dismissing the search field
@ -4725,7 +4814,7 @@ typedef enum : NSUInteger {
targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
if (@available(iOS 13, *)) {
} else {
if (self.menuActionsViewController != nil) {
NSValue *_Nullable contentOffset = [self contentOffsetForMenuActionInteraction];

@ -115,8 +115,13 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
target: self,
action: #selector(donePressed))
self.navigationItem.title = NSLocalizedString("GIF_PICKER_VIEW_TITLE",
comment: "Title for the 'GIF picker' dialog.")
// Loki: Customize title
let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("GIF", comment: "")
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
navigationItem.titleView = titleLabel
createViews()

@ -16,14 +16,18 @@ public class LoadingViewController: UIViewController {
override public func loadView() {
self.view = UIView()
view.backgroundColor = UIColor.lokiDarkestGray()
// Loki: Set gradient background
view.backgroundColor = .clear
let gradient = Gradients.defaultLokiBackground
view.setGradient(gradient)
self.logoView = UIImageView(image: #imageLiteral(resourceName: "Session"))
self.logoView = UIImageView(image: #imageLiteral(resourceName: "SessionGreen64"))
view.addSubview(logoView)
logoView.autoCenterInSuperview()
logoView.autoSetDimension(.width, toSize: 75)
logoView.autoSetDimension(.height, toSize: 75)
logoView.autoSetDimension(.width, toSize: 64)
logoView.autoSetDimension(.height, toSize: 64)
logoView.contentMode = .scaleAspectFit
self.topLabel = buildLabel()

@ -107,16 +107,10 @@ NS_ASSUME_NONNULL_BEGIN
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
self.view.backgroundColor = LKColors.navigationBarBackground;
[self updateContents];
// Loki: Set gradient background
self.view.backgroundColor = UIColor.clearColor;
LKGradient *gradient = LKGradients.defaultLokiBackground;
self.view.backgroundColor = UIColor.clearColor;
[self.view setGradient:gradient];
// Loki: Set navigation bar background color
UINavigationBar *navigationBar = self.navigationController.navigationBar;
[navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];

@ -152,8 +152,9 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
}
// Views
view.backgroundColor = Theme.darkThemeBackgroundColor
pagerScrollView.backgroundColor = Colors.navigationBarBackground
view.backgroundColor = Colors.navigationBarBackground
captionContainerView.delegate = self
updateCaptionContainerVisibility()

@ -59,7 +59,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
// ensure images at the end of the list can be scrolled above the bottom buttons
let bottomButtonInset = -1 * SendMediaNavigationController.bottomButtonsCenterOffset + SendMediaNavigationController.bottomButtonWidth / 2
collectionView.contentInset.bottom = bottomButtonInset + 8
view.backgroundColor = Colors.navigationBarBackground
view.backgroundColor = .white
// The PhotoCaptureVC needs a shadow behind it's cancel button, so we use a custom icon.
// This VC has a visible navbar so doesn't need the shadow, but because the user can
@ -69,19 +69,12 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
let cancelImage = UIImage(imageLiteralResourceName: "X")
let cancelButton = UIBarButtonItem(image: cancelImage, style: .plain, target: self, action: #selector(didPressCancel))
cancelButton.tintColor = Colors.text
cancelButton.tintColor = .black
navigationItem.leftBarButtonItem = cancelButton
let titleView = TitleView()
titleView.delegate = self
titleView.text = photoCollection.localizedTitle()
// Loki: Set navigation bar background color
let navigationBar = navigationController!.navigationBar
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = Colors.navigationBarBackground
if #available(iOS 11, *) {
// do nothing
@ -93,7 +86,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
navigationItem.titleView = titleView
self.titleView = titleView
collectionView.backgroundColor = Colors.navigationBarBackground
collectionView.backgroundColor = .white
let selectionPanGesture = DirectionalPanGestureRecognizer(direction: [.horizontal], target: self, action: #selector(didPanSelection))
selectionPanGesture.delegate = self
@ -106,7 +99,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
case select, deselect
}
var selectionPanGestureMode: BatchSelectionGestureMode = .select
@objc
func didPanSelection(_ selectionPanGesture: UIPanGestureRecognizer) {
guard let collectionView = collectionView else {
@ -194,6 +187,17 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Loki: Set navigation bar background color
let navigationBar = navigationController!.navigationBar
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = .white
(navigationBar as! OWSNavigationBar).respectsTheme = false
navigationBar.backgroundColor = .white
let backgroundImage = UIImage(color: .white)
navigationBar.setBackgroundImage(backgroundImage, for: .default)
// Determine the size of the thumbnails to request
let scale = UIScreen.main.scale
let cellSize = collectionViewFlowLayout.itemSize
@ -219,12 +223,12 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
scrollToBottom(animated: false)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
hasEverAppeared = true
// Since we're presenting *over* the ConversationVC, we need to `becomeFirstResponder`.
//
// Otherwise, the `ConversationVC.inputAccessoryView` will appear over top of us whenever
@ -440,6 +444,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
collectionPickerView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
collectionPickerView.autoPinEdge(toSuperviewSafeArea: .top)
collectionPickerView.layoutIfNeeded()
collectionPickerView.backgroundColor = .white
// Initially position offscreen, we'll animate it in.
collectionPickerView.frame = collectionPickerView.frame.offsetBy(dx: 0, dy: collectionPickerView.frame.height)
@ -602,10 +607,10 @@ class TitleView: UIView {
addSubview(stackView)
stackView.autoPinEdgesToSuperviewEdges()
label.textColor = Colors.text
label.textColor = .black
label.font = .boldSystemFont(ofSize: Values.mediumFontSize)
iconView.tintColor = Colors.text
iconView.tintColor = .black
iconView.image = UIImage(named: "navbar_disclosure_down")?.withRenderingMode(.alwaysTemplate)
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(titleTapped)))

@ -34,8 +34,8 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Colors.navigationBarBackground
tableView.backgroundColor = Colors.navigationBarBackground
view.backgroundColor = .white
tableView.backgroundColor = .white
tableView.separatorColor = .clear
library.add(delegate: self)
@ -68,21 +68,21 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg
private func buildTableCell(collection: PhotoCollection) -> UITableViewCell {
let cell = OWSTableItem.newCell()
cell.backgroundColor = Theme.darkThemeBackgroundColor
cell.contentView.backgroundColor = Theme.darkThemeBackgroundColor
cell.backgroundColor = .white
cell.contentView.backgroundColor = .white
cell.selectedBackgroundView?.backgroundColor = UIColor(white: 0.2, alpha: 1)
let contents = collection.contents()
let titleLabel = UILabel()
titleLabel.text = collection.localizedTitle()
titleLabel.font = UIFont.ows_dynamicTypeBody
titleLabel.textColor = Theme.darkThemePrimaryColor
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
titleLabel.textColor = .black
let countLabel = UILabel()
countLabel.text = numberFormatter.string(for: contents.assetCount)
countLabel.font = UIFont.ows_dynamicTypeCaption1
countLabel.textColor = Theme.darkThemePrimaryColor
countLabel.font = .systemFont(ofSize: Values.smallFontSize)
countLabel.textColor = .black
let textStack = UIStackView(arrangedSubviews: [titleLabel, countLabel])
textStack.axis = .vertical
@ -98,7 +98,7 @@ class PhotoCollectionPickerController: OWSTableViewController, PhotoLibraryDeleg
let hStackView = UIStackView(arrangedSubviews: [imageView, textStack])
hStackView.axis = .horizontal
hStackView.alignment = .center
hStackView.spacing = 11
hStackView.spacing = Values.mediumSpacing
let photoMediaSize = PhotoMediaSize(thumbnailSize: CGSize(width: kImageSize, height: kImageSize))
if let assetItem = contents.lastAssetItem(photoMediaSize: photoMediaSize) {

@ -257,13 +257,13 @@ class SendMediaNavigationController: OWSNavigationController {
extension SendMediaNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if let navbarTheme = preferredNavbarTheme(viewController: viewController) {
if let owsNavBar = navigationBar as? OWSNavigationBar {
owsNavBar.overrideTheme(type: navbarTheme)
} else {
owsFailDebug("unexpected navigationBar: \(navigationBar)")
}
}
// if let navbarTheme = preferredNavbarTheme(viewController: viewController) {
// if let owsNavBar = navigationBar as? OWSNavigationBar {
// owsNavBar.overrideTheme(type: navbarTheme)
// } else {
// owsFailDebug("unexpected navigationBar: \(navigationBar)")
// }
// }
switch viewController {
case is PhotoCaptureViewController:
@ -286,13 +286,13 @@ extension SendMediaNavigationController: UINavigationControllerDelegate {
// In case back navigation was canceled, we re-apply whatever is showing.
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if let navbarTheme = preferredNavbarTheme(viewController: viewController) {
if let owsNavBar = navigationBar as? OWSNavigationBar {
owsNavBar.overrideTheme(type: navbarTheme)
} else {
owsFailDebug("unexpected navigationBar: \(navigationBar)")
}
}
// if let navbarTheme = preferredNavbarTheme(viewController: viewController) {
// if let owsNavBar = navigationBar as? OWSNavigationBar {
// owsNavBar.overrideTheme(type: navbarTheme)
// } else {
// owsFailDebug("unexpected navigationBar: \(navigationBar)")
// }
// }
self.updateButtons(topViewController: viewController)
}

@ -532,8 +532,7 @@ const CGFloat kIconViewLength = 24;
[topRow autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeBottom];
UILabel *subtitleLabel = [UILabel new];
subtitleLabel.text = NSLocalizedString(
@"DISAPPEARING_MESSAGES_DESCRIPTION", @"subheading in conversation settings");
subtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"When enabled, messages between you and %@ will disappear after they have been seen.", ""), [LKDisplayNameUtilities getPrivateChatDisplayNameFor:self.thread.contactIdentifier]];
subtitleLabel.textColor = LKColors.text;
subtitleLabel.font = [UIFont systemFontOfSize:LKValues.smallFontSize];
subtitleLabel.numberOfLines = 0;
@ -1263,8 +1262,7 @@ const CGFloat kIconViewLength = 24;
- (void)updateDisappearingMessagesDurationLabel
{
if (self.disappearingMessagesConfiguration.isEnabled) {
NSString *keepForFormat = NSLocalizedString(@"KEEP_MESSAGES_DURATION",
@"Slider label embeds {{TIME_AMOUNT}}, e.g. '2 hours'. See *_TIME_AMOUNT strings for examples.");
NSString *keepForFormat = NSLocalizedString(@"Disappear after %@", @"");
self.disappearingMessagesDurationLabel.text =
[NSString stringWithFormat:keepForFormat, self.disappearingMessagesConfiguration.durationString];
} else {

@ -19,11 +19,11 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Session" translatesAutoresizingMaskIntoConstraints="NO" id="b92-yg-YYQ">
<rect key="frame" x="169.5" y="410.5" width="75" height="75"/>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SessionGreen64" translatesAutoresizingMaskIntoConstraints="NO" id="b92-yg-YYQ">
<rect key="frame" x="175" y="416" width="64" height="64"/>
<constraints>
<constraint firstAttribute="width" constant="75" id="edg-p1-IeH"/>
<constraint firstAttribute="height" constant="75" id="sRe-w6-smO"/>
<constraint firstAttribute="width" constant="64" id="edg-p1-IeH"/>
<constraint firstAttribute="height" constant="64" id="sRe-w6-smO"/>
</constraints>
</imageView>
</subviews>
@ -40,6 +40,6 @@
</scene>
</scenes>
<resources>
<image name="Session" width="303" height="337"/>
<image name="SessionGreen64" width="57.5" height="64"/>
</resources>
</document>

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "قفل الشاشة";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";

@ -2184,7 +2184,7 @@
"SETTINGS_NAV_BAR_TITLE" = "Settings";
/* table section footer */
"SETTINGS_NOTIFICATION_CONTENT_DESCRIPTION" = "Notifications can appear while your phone is locked. You may wish to limit what is shown in these notifications.";
"SETTINGS_NOTIFICATION_CONTENT_DESCRIPTION" = "The information shown in notifications when your phone is locked.";
/* table section header */
"SETTINGS_NOTIFICATION_CONTENT_TITLE" = "Notification Content";
@ -2220,7 +2220,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";
@ -2582,33 +2582,33 @@
"Calculating proof of work" = "Calculating proof of work";
"Failed to calculate proof of work." = "Failed to calculate proof of work.";
"Share Public Key" = "Share Public Key";
"%@ sent you a message request" = "%@ sent you a message request";
"%@ sent you a session request" = "%@ sent you a session request";
"Accept" = "Accept";
"Decline" = "Decline";
"Pending message request" = "Pending message request";
"Pending session request" = "Pending session request";
"New Message" = "New Message";
"Secure session reset in progress" = "Secure session reset in progress";
"Secure session reset done" = "Secure session reset done";
"Session" = "Session";
"You've sent %@ a message request" = "You've sent %@ a message request";
"You've declined %@'s message request" = "You've declined %@'s message request";
"You've accepted %@'s message request" = "You've accepted %@'s message request";
"%@ accepted your message request" = "%@ accepted your message request";
"%@'s message request has expired" = "%@'s message request has expired";
"Your message request to %@ has expired" = "Your message request to %@ has expired";
"You've sent %@ a session request" = "You've sent %@ a session request";
"You've declined %@'s session request" = "You've declined %@'s session request";
"You've accepted %@'s session request" = "You've accepted %@'s session request";
"%@ accepted your session request" = "%@ accepted your session request";
"%@'s session request has expired" = "%@'s session request has expired";
"Your session request to %@ has expired" = "Your session request to %@ has expired";
"Show Seed" = "Show Seed";
"Your Seed" = "Your Seed";
"Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed." = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications." = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
"Prevent Session previews from appearing in the app switcher." = "Prevent Session previews from appearing in the app switcher.";
"Session" = "Session";
"Privacy Policy" = "Privacy Policy";
"New Conversation" = "New Conversation";
"New Session" = "New Session";
"Add Public Chat Server" = "Add Public Chat Server";
"Enter a Public Key" = "Enter a Public Key";
"Enter a Server URL" = "Enter a Server URL";
"For example: 059abcf223aa8c10e3dc2d623688b75dd25896794717e4a9c486772664fc95e41e." = "For example: 059abcf223aa8c10e3dc2d623688b75dd25896794717e4a9c486772664fc95e41e.";
"Invalid Session ID" = "Invalid Session ID";
"Please check the Session ID you entered and try again." = "Please check the Session ID you entered and try again.";
"Please check the Session ID and try again" = "Please check the Session ID and try again";
"Looks like you don't have any conversations yet. Get started by messaging a friend." = "Looks like you don't have any conversations yet. Get started by messaging a friend.";
"Enter the public key of the person you'd like to securely message. They can share their public key with you by going into Session's in-app settings and clicking \"Share Public Key\"." = "Enter the public key of the person you'd like to securely message. They can share their public key with you by going into Session's in-app settings and clicking \"Share Public Key\".";
"Unlock Session" = "Unlock Session";
@ -2663,8 +2663,8 @@
"Clear Profile Picture" = "Clear Profile Picture";
"Invalid QR Code" = "Invalid QR Code";
"Please make sure the QR code you scanned is correct and try again." = "Please make sure the QR code you scanned is correct and try again.";
"Linked Devices" = "Linked Devices";
"You don't have any linked devices yet" = "You don't have any linked devices yet";
"Devices" = "Devices";
"You haven't linked any devices yet" = "You haven't linked any devices yet";
"Link a Device" = "Link a Device";
"Unlink" = "Unlink";
"Change Name" = "Change Name";
@ -2680,6 +2680,8 @@
"Your device was unlinked successfully" = "Your device was unlinked successfully";
"Unnamed Device" = "Unnamed Device";
"Linked device (%@)" = "Linked device (%@)";
"Restore session" = "Restore session";
"Would you like to start a new session with %@?" = "Would you like to start a new session with %@?";
// MARK: - Redesign
"Messages" = "Messages";
@ -2687,10 +2689,10 @@
"New Group" = "New Group";
"Delete" = "Delete";
"Search" = "Search";
"New Conversation" = "New Conversation";
"Enter Session ID of recipient" = "Enter Session ID of recipient";
"Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code." = "Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code.";
"Users can share their QR code by going into their account settings and tapping \"Share QR Code\"." = "Users can share their QR code by going into their account settings and tapping \"Share QR Code\".";
"New Session" = "New Session";
"Enter a Session ID" = "Enter a Session ID";
"Users can share their Session ID from their account settings, or by sharing their QR code." = "Users can share their Session ID from their account settings, or by sharing their QR code.";
"Scan a users QR code to start a session. QR codes can be found by tapping the QR code icon in account settings." = "Scan a users QR code to start a session. QR codes can be found by tapping the QR code icon in account settings.";
"Your Session ID" = "Your Session ID";
"Copy" = "Copy";
"Copied" = "Copied";
@ -2700,31 +2702,31 @@
"Enable Camera Access" = "Enable Camera Access";
"Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Session's in-app settings and tapping \"Show QR Code\"." = "Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Session's in-app settings and tapping \"Show QR Code\".";
"Enter Session ID" = "Enter Session ID";
"Enter Channel URL" = "Enter Channel URL";
"Open Group URL" = "Open Group URL";
"Scan QR Code" = "Scan QR Code";
"Scan the QR code of the channel you'd like to join" = "Scan the QR code of the channel you'd like to join";
"Join Channel" = "Join Channel";
"Enter the URL of the channel you'd like to join" = "Enter the URL of the channel you'd like to join";
"Scan the QR code of the open group you'd like to join" = "Scan the QR code of the open group you'd like to join";
"Join Open Group" = "Join Open Group";
"Enter an open group URL" = "Enter an open group URL";
"Invalid URL" = "Invalid URL";
"Please check the URL you entered and try again" = "Please check the URL you entered and try again";
"Couldn't Join" = "Couldn't Join";
"Settings" = "Settings";
"Privacy" = "Privacy";
"Notifications" = "Notifications";
"Linked Devices" = "Linked Devices";
"Show Recovery Phrase" = "Show Recovery Phrase";
"Devices" = "Devices";
"Recovery Phrase" = "Recovery Phrase";
"Clear All Data" = "Clear All Data";
"This will delete your entire account, including all data, any messages currently linked to your Session ID, as well as your personal key pair." = "This will delete your entire account, including all data, any messages currently linked to your Session ID, as well as your personal key pair.";
"This will permanently delete your Session ID, including all messages, sessions, and contacts." = "This will permanently delete your Session ID, including all messages, sessions, and contacts.";
"Delete" = "Delete";
"This is your personal recovery phrase. It can be used to restore your account or migrate your account to a new device." = "This is your personal recovery phrase. It can be used to restore your account or migrate your account to a new device.";
"Notifications can appear while your phone is locked. You may wish to limit what is shown in these notifications." = "Notifications can appear while your phone is locked. You may wish to limit what is shown in these notifications.";
"This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device." = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device.";
"The information shown in notifications when your phone is locked." = "The information shown in notifications when your phone is locked.";
"Notifications" = "Notifications";
"Back" = "Back";
"View My QR Code" = "View My QR Code";
"Scan someone's QR code to start a conversation with them" = "Scan someone's QR code to start a conversation with them";
"QR Code" = "QR Code";
"Scan Me" = "Scan Me";
"This is your unique public QR code. Other users can scan this to start a conversation with you." = "This is your unique public QR code. Other users can scan this to start a conversation with you.";
"This is your QR code. Other users can scan it to start a session with you." = "This is your QR code. Other users can scan it to start a session with you.";
"Privacy" = "Privacy";
"Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed." = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"Sound" = "Sound";
@ -2735,11 +2737,11 @@
"Enter a display name" = "Enter a display name";
"Your Session begins here..." = "Your Session begins here...";
"What's Session?" = "What's Session?";
"It's a secure, decentralized private messaging app" = "It's a secure, decentralized private messaging app";
"It's a decentralized, encrypted messaging app." = "It's a decentralized, encrypted messaging app.";
"So it doesn't collect my personal information or my conversation metadata? How does it work?" = "So it doesn't collect my personal information or my conversation metadata? How does it work?";
"Using a combination of advanced anonymous routing and end-to-end encryption technologies." = "Using a combination of advanced anonymous routing and end-to-end encryption technologies.";
"Friends don't let friends use compromised messengers. You're welcome." = "Friends don't let friends use compromised messengers. You're welcome.";
"Create Account" = "Create Account";
"Create Session ID" = "Create Session ID";
"Continue your Session" = "Continue your Session";
"Say hello to your Session ID" = "Say hello to your Session ID";
"Continue" = "Continue";
@ -2759,18 +2761,30 @@
"Continue" = "Continue";
"Your Recovery Phrase" = "Your Recovery Phrase";
"Meet your recovery phrase" = "Meet your recovery phrase";
"Think of this as the crypto-equivalent of a social security number. This allows whomever has it complete access to your account." = "Think of this as the crypto-equivalent of a social security number. This allows whomever has it complete access to your account.";
"Press the redacted words to view your recovery phrase and secure your account" = "Press the redacted words to view your recovery phrase and secure your account";
"Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and dont give it to anyone. To restore your Session ID, launch Session and tap Continue your Session." = "Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and dont give it to anyone. To restore your Session ID, launch Session and tap Continue your Session.";
"Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID." = "Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID.";
"Hold to reveal" = "Hold to reveal";
"Make sure to store your recovery phrase in a safe place" = "Make sure to store your recovery phrase in a safe place";
"Link to an existing account" = "Link to an existing account";
"Enter your public key" = "Enter your public key";
"Link to your existing account by going into your in-app settings and clicking \"Linked Devices\"." = "Link to your existing account by going into your in-app settings and clicking \"Linked Devices\".";
"Create a new account on your other device and click \"Link to an existing account\" to start the linking process" = "Create a new account on your other device and click \"Link to an existing account\" to start the linking process";
"Link to your existing account by going into your in-app settings and clicking \"Devices\"." = "Link to your existing account by going into your in-app settings and clicking \"Devices\".";
"Open Session on your secondary device and tap \"Link to an existing account\"" = "Open Session on your secondary device and tap \"Link to an existing account\"";
"Group Settings" = "Group Settings";
"Your Session ID is the unique address that people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design." = "Your Session ID is the unique address that people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design.";
"Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design." = "Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design.";
"Enter the recovery phrase that was given to you when you signed up to restore your account." = "Enter the recovery phrase that was given to you when you signed up to restore your account.";
"Enter Session ID" = "Enter Session ID";
"Link your device" = "Link your device";
"Enter your Session ID to start the linking process." = "Enter your Session ID to start the linking process.";
"Enter your Session ID" = "Enter your Session ID";
"Recent Chats" = "Recent Chats";
"Other Chats" = "Other Chats";
"See and share when messages are being typed (applies to all sessions)." = "See and share when messages are being typed (applies to all sessions).";
"Disable Preview in App Switcher" = "Disable Preview in App Switcher";
"Are you sure? This cannot be undone." = "Are you sure? This cannot be undone.";
"When enabled, messages between you and %@ will disappear after they have been seen." = "When enabled, messages between you and %@ will disappear after they have been seen.";
"This will be your name when you use Session." = "This will be your name when you use Session.";
"Session Out of Sync" = "Session Out of Sync";
"Would you like to restore your session?" = "Would you like to restore your session?";
"Would you like to restore your session with %@?" = "Would you like to restore your session with %@?";
"Restore" = "Restore";
"Dismiss" = "Dismiss";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Zaključaj zaslon nakon";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Zaključavanje zaslona";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Kijelző zárolási ideje";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Kijelző zárolás";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "Screen Lock";

@ -2211,7 +2211,7 @@
"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "หมดเวลาการล็อกหน้าจอ";
/* Footer for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still answer incoming calls and receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed.";
"SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.";
/* Title for the 'screen lock' section of the privacy settings. */
"SETTINGS_SCREEN_LOCK_SECTION_TITLE" = "ล็อกหน้าจอ";

@ -1,33 +1,24 @@
@objc(LKProfilePictureView)
final class ProfilePictureView : UIView {
public final class ProfilePictureView : UIView {
private var imageViewWidthConstraint: NSLayoutConstraint!
private var imageViewHeightConstraint: NSLayoutConstraint!
@objc var size: CGFloat = 0 // Not an implicitly unwrapped optional due to Obj-C limitations
@objc var isRSSFeed = false
@objc var hexEncodedPublicKey: String!
@objc var additionalHexEncodedPublicKey: String?
@objc public var size: CGFloat = 0 // Not an implicitly unwrapped optional due to Obj-C limitations
@objc public var isRSSFeed = false
@objc public var hexEncodedPublicKey: String!
@objc public var additionalHexEncodedPublicKey: String?
// MARK: Components
private lazy var imageView = getImageView()
private lazy var additionalImageView = getImageView()
private lazy var rssLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .systemFont(ofSize: Values.smallFontSize)
result.textAlignment = .center
result.text = "RSS"
return result
}()
// MARK: Lifecycle
override init(frame: CGRect) {
public override init(frame: CGRect) {
super.init(frame: frame)
setUpViewHierarchy()
}
required init?(coder: NSCoder) {
public required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
}
@ -37,60 +28,56 @@ final class ProfilePictureView : UIView {
addSubview(imageView)
imageView.pin(.leading, to: .leading, of: self)
imageView.pin(.top, to: .top, of: self)
let imageViewSize = CGFloat(45) // Values.mediumProfilePictureSize
imageViewWidthConstraint = imageView.set(.width, to: imageViewSize)
imageViewHeightConstraint = imageView.set(.height, to: imageViewSize)
// Set up additional image view
addSubview(additionalImageView)
additionalImageView.pin(.trailing, to: .trailing, of: self)
additionalImageView.pin(.bottom, to: .bottom, of: self)
let additionalImageViewSize = Values.smallProfilePictureSize
let additionalImageViewSize = CGFloat(35) // Values.smallProfilePictureSize
additionalImageView.set(.width, to: additionalImageViewSize)
additionalImageView.set(.height, to: additionalImageViewSize)
additionalImageView.layer.cornerRadius = additionalImageViewSize / 2
// Set up RSS label
addSubview(rssLabel)
rssLabel.pin(.leading, to: .leading, of: self)
rssLabel.pin(.top, to: .top, of: self)
rssLabel.autoPinWidth(toWidthOf: imageView)
rssLabel.autoPinHeight(toHeightOf: imageView)
}
// MARK: Updating
@objc func update() {
if let imageViewWidthConstraint = imageViewWidthConstraint, let imageViewHeightConstraint = imageViewHeightConstraint {
imageView.removeConstraint(imageViewWidthConstraint)
imageView.removeConstraint(imageViewHeightConstraint)
}
@objc public func update() {
func getProfilePicture(of size: CGFloat, for hexEncodedPublicKey: String) -> UIImage? {
guard !hexEncodedPublicKey.isEmpty else { return nil }
return OWSProfileManager.shared().profileAvatar(forRecipientId: hexEncodedPublicKey) ?? Identicon.generateIcon(string: hexEncodedPublicKey, size: size)
}
let size: CGFloat
if let additionalHexEncodedPublicKey = additionalHexEncodedPublicKey, !isRSSFeed {
size = Values.smallProfilePictureSize
imageViewWidthConstraint = imageView.set(.width, to: size)
imageViewHeightConstraint = imageView.set(.height, to: size)
size = 35 // Values.smallProfilePictureSize
imageViewWidthConstraint.constant = size
imageViewHeightConstraint.constant = size
additionalImageView.isHidden = false
additionalImageView.image = getProfilePicture(of: size, for: additionalHexEncodedPublicKey)
} else {
size = self.size
imageViewWidthConstraint = imageView.pin(.trailing, to: .trailing, of: self)
imageViewHeightConstraint = imageView.pin(.bottom, to: .bottom, of: self)
imageViewWidthConstraint.constant = size
imageViewHeightConstraint.constant = size
additionalImageView.isHidden = true
additionalImageView.image = nil
}
guard hexEncodedPublicKey != nil else { return } // Can happen in rare cases
imageView.image = isRSSFeed ? nil : getProfilePicture(of: size, for: hexEncodedPublicKey)
imageView.backgroundColor = isRSSFeed ? UIColor(hex: 0x353535) : Colors.unimportant
imageView.backgroundColor = isRSSFeed ? UIColor(rgbHex: 0x353535) : UIColor(rgbHex: 0xD8D8D8) // UIColor(rgbHex: 0xD8D8D8) = Colors.unimportant
imageView.layer.cornerRadius = size / 2
rssLabel.isHidden = !isRSSFeed
rssLabel.font = size == (Values.largeProfilePictureSize) ? .systemFont(ofSize: Values.largeFontSize) : .systemFont(ofSize: Values.smallFontSize)
imageView.contentMode = isRSSFeed ? .center : .scaleAspectFit
if isRSSFeed {
imageView.image = (size == 45) ? #imageLiteral(resourceName: "SessionWhite24") : #imageLiteral(resourceName: "SessionWhite40")
}
}
// MARK: Convenience
private func getImageView() -> UIImageView {
let result = UIImageView()
result.layer.masksToBounds = true
result.backgroundColor = Colors.unimportant
result.layer.borderColor = Colors.border.cgColor
result.layer.borderWidth = Values.borderThickness
result.backgroundColor = UIColor(rgbHex: 0xD8D8D8) // Colors.unimportant
result.layer.borderColor = UIColor(rgbHex: 0x979797).cgColor // Colors.border
result.layer.borderWidth = 1 // Values.borderThickness
result.contentMode = .scaleAspectFit
return result
}

@ -1,10 +1,10 @@
extension UIView {
public extension UIView {
enum HorizontalEdge { case left, leading, right, trailing }
enum VerticalEdge { case top, bottom }
enum Direction { case horizontal, vertical }
enum Dimension { case width, height }
public enum HorizontalEdge { case left, leading, right, trailing }
public enum VerticalEdge { case top, bottom }
public enum Direction { case horizontal, vertical }
public enum Dimension { case width, height }
private func anchor(from edge: HorizontalEdge) -> NSLayoutXAxisAnchor {
switch edge {
@ -23,7 +23,7 @@ extension UIView {
}
@discardableResult
func pin(_ constraineeEdge: HorizontalEdge, to constrainerEdge: HorizontalEdge, of view: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
public func pin(_ constraineeEdge: HorizontalEdge, to constrainerEdge: HorizontalEdge, of view: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint = anchor(from: constraineeEdge).constraint(equalTo: view.anchor(from: constrainerEdge), constant: inset)
constraint.isActive = true
@ -31,19 +31,19 @@ extension UIView {
}
@discardableResult
func pin(_ constraineeEdge: VerticalEdge, to constrainerEdge: VerticalEdge, of view: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
public func pin(_ constraineeEdge: VerticalEdge, to constrainerEdge: VerticalEdge, of view: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint = anchor(from: constraineeEdge).constraint(equalTo: view.anchor(from: constrainerEdge), constant: inset)
constraint.isActive = true
return constraint
}
func pin(to view: UIView) {
public func pin(to view: UIView) {
[ HorizontalEdge.leading, HorizontalEdge.trailing ].forEach { pin($0, to: $0, of: view) }
[ VerticalEdge.top, VerticalEdge.bottom ].forEach { pin($0, to: $0, of: view) }
}
func pin(to view: UIView, withInset inset: CGFloat) {
public func pin(to view: UIView, withInset inset: CGFloat) {
pin(.leading, to: .leading, of: view, withInset: inset)
pin(.top, to: .top, of: view, withInset: inset)
view.pin(.trailing, to: .trailing, of: self, withInset: inset)
@ -51,7 +51,7 @@ extension UIView {
}
@discardableResult
func center(_ direction: Direction, in view: UIView) -> NSLayoutConstraint {
public func center(_ direction: Direction, in view: UIView) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint: NSLayoutConstraint = {
switch direction {
@ -63,13 +63,13 @@ extension UIView {
return constraint
}
func center(in view: UIView) {
public func center(in view: UIView) {
center(.horizontal, in: view)
center(.vertical, in: view)
}
@discardableResult
func set(_ dimension: Dimension, to size: CGFloat) -> NSLayoutConstraint {
public func set(_ dimension: Dimension, to size: CGFloat) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint: NSLayoutConstraint = {
switch dimension {

@ -92,13 +92,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
vc.approvalDelegate = approvalDelegate
let navController = OWSNavigationController(rootViewController: vc)
navController.ows_prefersStatusBarHidden = true
guard let navigationBar = navController.navigationBar as? OWSNavigationBar else {
owsFailDebug("navigationBar was nil or unexpected class")
return navController
}
navigationBar.overrideTheme(type: .clear)
return navController
}
@ -138,7 +131,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
override public func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .black
self.view.backgroundColor = .red
// avoid an unpleasant "bounce" which doesn't make sense in the context of a single item.
pagerScrollView?.isScrollEnabled = attachmentItems.count > 1
@ -176,7 +169,16 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
owsFailDebug("navigationBar was nil or unexpected class")
return
}
navigationBar.overrideTheme(type: .clear)
// Loki: Set navigation bar background color
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = UIColor(rgbHex: 0x161616) // Colors.navigationBarBackground
navigationBar.respectsTheme = true
navigationBar.backgroundColor = UIColor(rgbHex: 0x161616) // Colors.navigationBarBackground
let backgroundImage = UIImage(color: UIColor(rgbHex: 0x161616)) // Colors.navigationBarBackground
navigationBar.setBackgroundImage(backgroundImage, for: .default)
updateContents()
}

@ -70,8 +70,8 @@ public class ModalActivityIndicatorViewController: OWSViewController {
super.loadView()
self.view.backgroundColor = (Theme.isDarkThemeEnabled
? UIColor(white: 0.35, alpha: 0.35)
: UIColor(white: 0, alpha: 0.25))
? UIColor(white: 0, alpha: 0.5)
: UIColor(white: 0, alpha: 0.5))
self.view.isOpaque = false
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)

@ -36,8 +36,13 @@ NSString *NSStringForScreenLockUIState(ScreenLockUIState value)
- (void)loadView
{
[super loadView];
self.view.backgroundColor = UIColor.lokiDarkestGray;
// Loki: Set gradient background
self.view.backgroundColor = UIColor.clearColor;
CAGradientLayer *layer = [CAGradientLayer new];
layer.frame = UIScreen.mainScreen.bounds;
layer.colors = @[ (id)[UIColor colorWithRGBHex:0x171717].CGColor, (id)[UIColor colorWithRGBHex:0x121212].CGColor ];
[self.view.layer insertSublayer:layer atIndex:0];
UIView *edgesView = [UIView containerView];
[self.view addSubview:edgesView];
@ -45,20 +50,20 @@ NSString *NSStringForScreenLockUIState(ScreenLockUIState value)
[edgesView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[edgesView autoPinWidthToSuperview];
UIImage *image = [UIImage imageNamed:@"Session"];
UIImage *image = [UIImage imageNamed:@"SessionGreen64"];
UIImageView *imageView = [UIImageView new];
imageView.image = image;
imageView.contentMode = UIViewContentModeScaleAspectFit;
[edgesView addSubview:imageView];
[imageView autoHCenterInSuperview];
[imageView autoSetDimension:ALDimensionWidth toSize:75];
[imageView autoSetDimension:ALDimensionHeight toSize:75];
[imageView autoSetDimension:ALDimensionWidth toSize:64];
[imageView autoSetDimension:ALDimensionHeight toSize:64];
const CGFloat kButtonHeight = 40.f;
OWSFlatButton *button =
[OWSFlatButton buttonWithTitle:NSLocalizedString(@"Unlock Session", @"")
font:[OWSFlatButton fontForHeight:kButtonHeight]
font:[UIFont boldSystemFontOfSize:15] // Values.mediumFontSize
titleColor:UIColor.whiteColor
backgroundColor:UIColor.clearColor
target:self

@ -51,13 +51,10 @@ NS_ASSUME_NONNULL_BEGIN
{
[super loadView];
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(dismissPressed:)];
self.view.backgroundColor = Theme.backgroundColor;
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"X"] style:UIBarButtonItemStylePlain target:self action:@selector(dismissPressed:)];
closeButton.tintColor = [UIColor colorWithRGBHex:0xFFFFFF]; // Colors.text
self.navigationItem.leftBarButtonItem = closeButton;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_fullTextSearcher = FullTextSearcher.shared;
_threadViewHelper = [ThreadViewHelper new];
@ -78,6 +75,21 @@ NS_ASSUME_NONNULL_BEGIN
object:nil];
[self createViews];
// Loki: Set gradient background
self.tableViewController.tableView.backgroundColor = UIColor.clearColor;
self.tableViewController.view.backgroundColor = UIColor.clearColor;
CAGradientLayer *layer = [CAGradientLayer new];
layer.frame = UIScreen.mainScreen.bounds;
layer.colors = @[ (id)[UIColor colorWithRGBHex:0x171717].CGColor, (id)[UIColor colorWithRGBHex:0x121212].CGColor ];
[self.tableViewController.view.layer insertSublayer:layer atIndex:0];
// Loki: Set navigation bar background color
UINavigationBar *navigationBar = self.navigationController.navigationBar;
[navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
navigationBar.shadowImage = [UIImage new];
[navigationBar setTranslucent:NO];
navigationBar.barTintColor = [UIColor colorWithRGBHex:0x161616]; // Colors.navigationBarBackground
[self updateTableContents];
}
@ -87,6 +99,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(self.selectThreadViewDelegate);
// Search
/*
UISearchBar *searchBar = [OWSSearchBar new];
_searchBar = searchBar;
searchBar.delegate = self;
@ -102,6 +115,7 @@ NS_ASSUME_NONNULL_BEGIN
[header autoPinToTopLayoutGuideOfViewController:self withInset:0];
[header setCompressionResistanceVerticalHigh];
[header setContentHuggingVerticalHigh];
*/
// Table
_tableViewController = [OWSTableViewController new];
@ -109,7 +123,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.view addSubview:self.tableViewController.view];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
[_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:header];
[_tableViewController.view autoPinEdgeToSuperviewEdge:ALEdgeTop];
[_tableViewController.view autoPinEdgeToSuperviewEdge:ALEdgeBottom];
self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableViewController.tableView.estimatedRowHeight = 60;
@ -168,6 +182,7 @@ NS_ASSUME_NONNULL_BEGIN
ContactsViewHelper *helper = self.contactsViewHelper;
OWSTableContents *contents = [OWSTableContents new];
/*
OWSTableSection *findByPhoneSection = [OWSTableSection new];
[findByPhoneSection
addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"NEW_CONVERSATION_FIND_BY_PHONE_NUMBER",
@ -182,11 +197,11 @@ NS_ASSUME_NONNULL_BEGIN
animated:YES];
}]];
[contents addSection:findByPhoneSection];
*/
// Existing threads are listed first, ordered by most recently active
OWSTableSection *recentChatsSection = [OWSTableSection new];
recentChatsSection.headerTitle = NSLocalizedString(
@"SELECT_THREAD_TABLE_RECENT_CHATS_TITLE", @"Table section header for recently active conversations");
recentChatsSection.headerTitle = NSLocalizedString(@"Recent Chats", @"");
for (TSThread *thread in [self filteredThreadsWithSearchText]) {
[recentChatsSection
addItem:[OWSTableItem
@ -221,7 +236,7 @@ NS_ASSUME_NONNULL_BEGIN
[[DisappearingTimerConfigurationView alloc]
initWithDurationSeconds:disappearingMessagesConfiguration.durationSeconds];
disappearingTimerConfigurationView.tintColor = Theme.middleGrayColor;
disappearingTimerConfigurationView.tintColor = [UIColor colorWithRGBHex:0xFFFFFF]; // Colors.text
[disappearingTimerConfigurationView autoSetDimensionsToSize:CGSizeMake(44, 44)];
[cell ows_setAccessoryView:disappearingTimerConfigurationView];
@ -262,8 +277,7 @@ NS_ASSUME_NONNULL_BEGIN
// Contacts who don't yet have a thread are listed last
OWSTableSection *otherContactsSection = [OWSTableSection new];
otherContactsSection.headerTitle = NSLocalizedString(
@"SELECT_THREAD_TABLE_OTHER_CHATS_TITLE", @"Table section header for conversations you haven't recently used.");
otherContactsSection.headerTitle = NSLocalizedString(@"Other Chats", @"");
NSArray<SignalAccount *> *filteredSignalAccounts = [self filteredSignalAccountsWithSearchText];
for (SignalAccount *signalAccount in filteredSignalAccounts) {
[otherContactsSection

@ -84,6 +84,13 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
selector:@selector(attachmentUploadProgress:)
name:kAttachmentUploadProgressNotification
object:nil];
// Loki: Customize title
UILabel *titleLabel = [UILabel new];
titleLabel.text = NSLocalizedString(@"Share", @"");
titleLabel.textColor = [UIColor colorWithRGBHex:0xFFFFFF]; // Colors.text
titleLabel.font = [UIFont boldSystemFontOfSize:25];
self.navigationItem.titleView = titleLabel;
}
- (BOOL)canSelectBlockedContact
@ -98,8 +105,8 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
const CGFloat contentVMargin = 0;
UIView *header = [UIView new];
header.backgroundColor = Theme.backgroundColor;
header.backgroundColor = [UIColor colorWithRGBHex:0x161616]; // Colors.navigationBarBackground
UIButton *cancelShareButton = [UIButton buttonWithType:UIButtonTypeSystem];
[header addSubview:cancelShareButton];

@ -22,7 +22,7 @@ const CGFloat kContactCellAvatarTextMargin = 12;
@property (nonatomic) UILabel *nameLabel;
@property (nonatomic) UILabel *profileNameLabel;
@property (nonatomic) UIImageView *avatarView;
@property (nonatomic) LKProfilePictureView *profilePictureView;
@property (nonatomic) UILabel *subtitleLabel;
@property (nonatomic) UILabel *accessoryLabel;
@property (nonatomic) UIStackView *nameContainerView;
@ -76,9 +76,11 @@ const CGFloat kContactCellAvatarTextMargin = 12;
self.layoutMargins = UIEdgeInsetsZero;
_avatarView = [AvatarImageView new];
[_avatarView autoSetDimension:ALDimensionWidth toSize:kStandardAvatarSize];
[_avatarView autoSetDimension:ALDimensionHeight toSize:kStandardAvatarSize];
_profilePictureView = [LKProfilePictureView new];
CGFloat profilePictureSize = 45; // Values.mediumProfilePictureSize
[self.profilePictureView autoSetDimension:ALDimensionWidth toSize:profilePictureSize];
[self.profilePictureView autoSetDimension:ALDimensionHeight toSize:profilePictureSize];
self.profilePictureView.size = profilePictureSize;
self.nameLabel = [UILabel new];
self.nameLabel.lineBreakMode = NSLineBreakByTruncatingTail;
@ -100,14 +102,13 @@ const CGFloat kContactCellAvatarTextMargin = 12;
]];
self.nameContainerView.axis = UILayoutConstraintAxisVertical;
[self.avatarView setContentHuggingHorizontalHigh];
[self.nameContainerView setContentHuggingHorizontalLow];
[self.accessoryViewContainer setContentHuggingHorizontalHigh];
self.axis = UILayoutConstraintAxisHorizontal;
self.spacing = kContactCellAvatarTextMargin;
self.spacing = 16; // Values.mediumSpacing
self.alignment = UIStackViewAlignmentCenter;
[self addArrangedSubview:self.avatarView];
[self addArrangedSubview:self.profilePictureView];
[self addArrangedSubview:self.nameContainerView];
[self addArrangedSubview:self.accessoryViewContainer];
@ -116,7 +117,7 @@ const CGFloat kContactCellAvatarTextMargin = 12;
- (void)configureFontsAndColors
{
self.nameLabel.font = [UIFont ows_dynamicTypeBodyFont];
self.nameLabel.font = [UIFont boldSystemFontOfSize:15];
self.profileNameLabel.font = [UIFont ows_regularFontWithSize:11.f];
self.subtitleLabel.font = [UIFont ows_regularFontWithSize:11.f];
self.accessoryLabel.font = [UIFont ows_mediumFontWithSize:13.f];
@ -187,12 +188,12 @@ const CGFloat kContactCellAvatarTextMargin = 12;
threadName = NSLocalizedString(@"NOTE_TO_SELF", @"Label for 1:1 conversation with yourself.");
}
NSAttributedString *attributedText =
[[NSAttributedString alloc] initWithString:threadName
attributes:@{
NSForegroundColorAttributeName : [Theme primaryColor],
}];
self.nameLabel.attributedText = attributedText;
// NSAttributedString *attributedText =
// [[NSAttributedString alloc] initWithString:threadName
// attributes:@{
// NSForegroundColorAttributeName : [Theme primaryColor],
// }];
// self.nameLabel.attributedText = attributedText;
if ([thread isKindOfClass:[TSContactThread class]]) {
self.recipientId = thread.contactIdentifier;
@ -203,7 +204,8 @@ const CGFloat kContactCellAvatarTextMargin = 12;
object:nil];
[self updateProfileName];
}
self.avatarView.image = [OWSAvatarBuilder buildImageForThread:thread diameter:kStandardAvatarSize];
[self updateAvatar];
if (self.accessoryMessage) {
self.accessoryLabel.text = self.accessoryMessage;
@ -216,25 +218,21 @@ const CGFloat kContactCellAvatarTextMargin = 12;
- (void)updateAvatar
{
NSString *recipientId = self.recipientId;
if (recipientId.length == 0) {
OWSFailDebug(@"recipientId should not be nil");
self.avatarView.image = nil;
return;
}
ConversationColorName colorName = ^{
if (self.thread) {
return self.thread.conversationColorName;
} else {
OWSAssertDebug(self.recipientId);
return [TSThread stableColorNameForNewConversationWithString:self.recipientId];
if (self.thread.isGroupThread) {
NSMutableArray<NSString *> *sortedUsers = @[].mutableCopy;
NSSet<NSString *> *users = LKAPI.userHexEncodedPublicKeyCache[self.thread.uniqueId];
if (users != nil) {
for (NSString *user in users) {
[sortedUsers addObject:user];
}
}
}();
self.avatarView.image =
[[[OWSContactAvatarBuilder alloc] initWithSignalId:recipientId colorName:colorName diameter:kStandardAvatarSize]
build];
sortedUsers = [sortedUsers sortedArrayUsingSelector:@selector(compare:)].mutableCopy;
self.profilePictureView.hexEncodedPublicKey = (sortedUsers.count > 0) ? sortedUsers[0] : @"";
self.profilePictureView.isRSSFeed = ((TSGroupThread *)self.thread).isRSSFeed;
} else {
self.profilePictureView.hexEncodedPublicKey = self.thread.contactIdentifier;
}
[self.profilePictureView update];
}
- (void)updateProfileName
@ -242,26 +240,31 @@ const CGFloat kContactCellAvatarTextMargin = 12;
OWSContactsManager *contactsManager = self.contactsManager;
if (contactsManager == nil) {
OWSFailDebug(@"contactsManager should not be nil");
self.profileNameLabel.text = nil;
self.nameLabel.text = self.recipientId;
return;
}
NSString *recipientId = self.recipientId;
if (recipientId.length == 0) {
OWSFailDebug(@"recipientId should not be nil");
self.profileNameLabel.text = nil;
self.nameLabel.text = nil;
return;
}
if ([contactsManager hasNameInSystemContactsForRecipientId:recipientId]) {
// Don't display profile name when we have a veritas name in system Contacts
self.profileNameLabel.text = nil;
self.nameLabel.text = nil;
} else {
// Use profile name, if any is available
self.profileNameLabel.text = [contactsManager formattedProfileNameForRecipientId:recipientId];
BOOL isNoteToSelf = (!self.thread.isGroupThread && [self.thread.contactIdentifier isEqualToString:self.tsAccountManager.localNumber]);
if (isNoteToSelf) {
self.nameLabel.text = NSLocalizedString(@"NOTE_TO_SELF", @"Label for 1:1 conversation with yourself.");
} else {
self.nameLabel.text = [contactsManager formattedProfileNameForRecipientId:recipientId];
}
}
[self.profileNameLabel setNeedsLayout];
[self.nameLabel setNeedsLayout];
}
- (void)prepareForReuse

@ -46,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
self.preservesSuperviewLayoutMargins = YES;
self.contentView.preservesSuperviewLayoutMargins = YES;
self.cellView = [ContactCellView new];
[self.contentView addSubview:self.cellView];
[self.cellView autoPinEdgesToSuperviewMargins];

@ -169,7 +169,7 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel
canvasView.autoPinEdgesToSuperviewEdges()
let tintView = UIView()
tintView.backgroundColor = UIColor(white: 0, alpha: 0.33)
tintView.backgroundColor = .clear
tintView.isOpaque = false
self.view.addSubview(tintView)
tintView.autoPinEdgesToSuperviewEdges()

@ -35,7 +35,7 @@ public extension UIViewController {
stackView.axis = .horizontal
stackView.spacing = spacing
stackView.alignment = .center
// Ensure layout works on older versions of iOS.
var stackSize = CGSize.zero
for item in navigationBarItems {
@ -49,5 +49,15 @@ public extension UIViewController {
stackView.frame = CGRect(origin: .zero, size: stackSize)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: stackView)
// Loki: Set navigation bar background color
let navigationBar = navigationController!.navigationBar
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = UIColor(rgbHex: 0x161616) // Colors.navigationBarBackground
navigationBar.backgroundColor = UIColor(rgbHex: 0x161616) // Colors.navigationBarBackground
let backgroundImage = UIImage(color: UIColor(rgbHex: 0x161616)) // Colors.navigationBarBackground
navigationBar.setBackgroundImage(backgroundImage, for: .default)
}
}

@ -75,6 +75,10 @@ public class OWSNavigationBar: UINavigationBar {
return
}
backgroundColor = UIColor(rgbHex: 0x161616) // Colors.navigationBarBackground
tintColor = UIColor(rgbHex: 0xFFFFFF) // Colors.text
if UIAccessibility.isReduceTransparencyEnabled {
blurEffectView?.isHidden = true
let color = Theme.navbarBackgroundColor
@ -86,7 +90,8 @@ public class OWSNavigationBar: UINavigationBar {
let color = Theme.navbarBackgroundColor
let backgroundImage = UIImage(color: color)
self.setBackgroundImage(backgroundImage, for: .default)
/*
let blurEffect = Theme.barBlurEffect
let blurEffectView: UIVisualEffectView = {
@ -109,13 +114,14 @@ public class OWSNavigationBar: UINavigationBar {
}()
blurEffectView.effect = blurEffect
*/
// remove hairline below bar.
self.shadowImage = UIImage()
// On iOS11, despite inserting the blur at 0, other views are later inserted into the navbar behind the blur,
// so we have to set a zindex to avoid obscuring navbar title/buttons.
blurEffectView.layer.zPosition = -1
// blurEffectView.layer.zPosition = -1
}
}

@ -734,7 +734,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
NSString *profileNameFormatString = NSLocalizedString(@"PROFILE_NAME_LABEL_FORMAT",
@"Prepend a simple marker to differentiate the profile name, embeds the contact's {{profile name}}.");
return [NSString stringWithFormat:profileNameFormatString, profileName];
return profileName;
}
- (nullable NSString *)profileNameForRecipientId:(NSString *)recipientId

@ -264,6 +264,15 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
return [self syncContactsForSignalAccounts:@[ signalAccount ]];
}
- (AnyPromise *)syncContact:(NSString *)hexEncodedPubKey transaction:(YapDatabaseReadTransaction *)transaction
{
TSContactThread *thread = [TSContactThread getThreadWithContactId:hexEncodedPubKey transaction:transaction];
if (thread != nil && thread.isContactFriend) {
return [self syncContactsForSignalAccounts:@[[[SignalAccount alloc] initWithRecipientId:hexEncodedPubKey]]];
}
return [AnyPromise promiseWithValue:@1];
}
- (AnyPromise *)syncAllContacts
{
NSMutableArray<SignalAccount *> *friends = @[].mutableCopy;

@ -364,6 +364,7 @@ public class FullTextSearcher: NSObject {
@objc(filterThreads:withSearchText:)
public func filterThreads(_ threads: [TSThread], searchText: String) -> [TSThread] {
let threads = threads.filter { $0.name() != "Loki Messenger Updates" && $0.name() != "Loki News" }
guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
return threads
}

@ -27,26 +27,19 @@
+ (void)setupSignalAppearence
{
UINavigationBar.appearance.barTintColor = [UIColor colorWithRGBHex:0x161616]; // Colors.navigationBarBackground
UINavigationBar.appearance.tintColor = [UIColor colorWithRGBHex:0xFFFFFF]; // Colors.text
UIToolbar.appearance.barTintColor = Theme.navbarBackgroundColor;
UIToolbar.appearance.tintColor = Theme.navbarIconColor;
UIBarButtonItem.appearance.tintColor = [UIColor colorWithRGBHex:0xFFFFFF]; // Colors.text
// Using the keyboardAppearance causes crashes due to a bug in UIKit.
// UITextField.appearance.keyboardAppearance = (Theme.isDarkThemeEnabled
// ? UIKeyboardAppearanceDark
// : UIKeyboardAppearanceDefault);
// UITextView.appearance.keyboardAppearance = (Theme.isDarkThemeEnabled
// ? UIKeyboardAppearanceDark
// : UIKeyboardAppearanceDefault);
[[UISwitch appearance] setOnTintColor:[UIColor colorWithRGBHex:0x00F782]]; // Colors.accent
[[UIToolbar appearance] setTintColor:[UIColor ows_materialBlueColor]];
UINavigationBar.appearance.barTintColor = UIColor.whiteColor;
UINavigationBar.appearance.translucent = NO;
UINavigationBar.appearance.tintColor = UIColor.blackColor;
UIToolbar.appearance.barTintColor = UIColor.blackColor;
UIToolbar.appearance.translucent = NO;
UIToolbar.appearance.tintColor = UIColor.whiteColor;
UIBarButtonItem.appearance.tintColor = UIColor.blackColor;
[UISwitch.appearance setOnTintColor:[UIColor colorWithRGBHex:0x00F782]]; // Colors.accent
[UIToolbar.appearance setTintColor:[UIColor colorWithRGBHex:0x00F782]]; // Colors.accent
// If we set NSShadowAttributeName, the NSForegroundColorAttributeName value is ignored.
UINavigationBar.appearance.titleTextAttributes = @{ NSForegroundColorAttributeName : [UIColor colorWithRGBHex:0xFFFFFF] }; // Colors.text
UINavigationBar.appearance.titleTextAttributes = @{ NSForegroundColorAttributeName : UIColor.blackColor };
}
@end

@ -148,6 +148,7 @@ message DataMessage {
END_SESSION = 1;
EXPIRATION_TIMER_UPDATE = 2;
PROFILE_KEY_UPDATE = 4;
SESSION_RESTORE = 64;
UNLINK_DEVICE = 128;
SESSION_REQUEST = 256;
}

@ -22,6 +22,7 @@ extern NSString *const TSContactThreadPrefix;
// Loki: The current session reset state for this thread
@property (atomic) TSContactThreadSessionResetState sessionResetState;
@property (atomic, readonly) NSArray<NSString *> *sessionRestoreDevices;
@property (nonatomic) BOOL hasDismissedOffers;
@ -47,6 +48,11 @@ extern NSString *const TSContactThreadPrefix;
+ (NSString *)conversationColorNameForRecipientId:(NSString *)recipientId
transaction:(YapDatabaseReadTransaction *)transaction;
#pragma mark - Loki Session Restore
- (void)addSessionRestoreDevice:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction;
- (void)removeAllSessionRestoreDevicesWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction;
@end
NS_ASSUME_NONNULL_END

@ -8,6 +8,7 @@
#import "NotificationsProtocol.h"
#import "OWSIdentityManager.h"
#import "SSKEnvironment.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <YapDatabase/YapDatabaseConnection.h>
#import <YapDatabase/YapDatabaseTransaction.h>
@ -26,6 +27,7 @@ NSString *const TSContactThreadPrefix = @"c";
// No session reset ongoing
_sessionResetState = TSContactThreadSessionResetStateNone;
_sessionRestoreDevices = @[];
return self;
}
@ -107,6 +109,34 @@ NSString *const TSContactThreadPrefix = @"c";
return [self stableColorNameForNewConversationWithString:recipientId];
}
#pragma mark - Loki Session Restore
- (void)addSessionRestoreDevice:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction
{
NSMutableSet *set = [[NSMutableSet alloc] initWithArray:_sessionRestoreDevices];
[set addObject:hexEncodedPublicKey];
[self setSessionRestoreDevices:set.allObjects transaction:transaction];
}
- (void)removeAllSessionRestoreDevicesWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction
{
[self setSessionRestoreDevices:@[] transaction:transaction];
}
- (void)setSessionRestoreDevices:(NSArray<NSString *> *)sessionRestoreDevices transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction {
_sessionRestoreDevices = sessionRestoreDevices;
void (^postNotification)() = ^() {
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.threadSessionRestoreDevicesChanged object:self.uniqueId];
};
if (transaction == nil) {
[self save];
[self.dbReadWriteConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{ postNotification(); }];
} else {
[self saveWithTransaction:transaction];
[transaction.connection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{ postNotification(); }];
}
}
@end
NS_ASSUME_NONNULL_END

@ -47,7 +47,7 @@ public extension LokiAPI {
}
// MARK: Internal API
private static func getRandomSnode() -> Promise<LokiAPITarget> {
internal static func getRandomSnode() -> Promise<LokiAPITarget> {
if randomSnodePool.isEmpty {
let target = seedNodePool.randomElement()!
let url = URL(string: "\(target)/json_rpc")!
@ -58,7 +58,9 @@ public extension LokiAPI {
"limit" : 24,
"fields" : [
"public_ip" : true,
"storage_port" : true
"storage_port" : true,
"pubkey_ed25519": true,
"pubkey_x25519": true
]
]
])
@ -67,11 +69,11 @@ public extension LokiAPI {
let rawResponse = intermediate.responseObject
guard let json = rawResponse as? JSON, let intermediate = json["result"] as? JSON, let rawTargets = intermediate["service_node_states"] as? [JSON] else { throw "Failed to update random snode pool from: \(rawResponse)." }
randomSnodePool = try Set(rawTargets.flatMap { rawTarget in
guard let address = rawTarget["public_ip"] as? String, let port = rawTarget["storage_port"] as? Int, address != "0.0.0.0" else {
print("Failed to update random snode pool from: \(rawTarget).")
guard let address = rawTarget["public_ip"] as? String, let port = rawTarget["storage_port"] as? Int, let idKey = rawTarget["pubkey_ed25519"] as? String, let encryptionKey = rawTarget["pubkey_x25519"] as? String, address != "0.0.0.0" else {
print("[Loki] Failed to parse target from: \(rawTarget).")
return nil
}
return LokiAPITarget(address: "https://\(address)", port: UInt16(port))
return LokiAPITarget(address: "https://\(address)", port: UInt16(port), publicKeySet: LokiAPITarget.KeySet(idKey: idKey, encryptionKey: encryptionKey))
})
return randomSnodePool.randomElement()!
}.recover(on: DispatchQueue.global()) { error -> Promise<LokiAPITarget> in
@ -103,16 +105,16 @@ public extension LokiAPI {
// MARK: Parsing
private static func parseTargets(from rawResponse: Any) -> [LokiAPITarget] {
guard let json = rawResponse as? JSON, let rawSnodes = json["snodes"] as? [JSON] else {
guard let json = rawResponse as? JSON, let rawTargets = json["snodes"] as? [JSON] else {
print("[Loki] Failed to parse targets from: \(rawResponse).")
return []
}
return rawSnodes.flatMap { rawSnode in
guard let address = rawSnode["ip"] as? String, let portAsString = rawSnode["port"] as? String, let port = UInt16(portAsString), address != "0.0.0.0" else {
print("[Loki] Failed to parse target from: \(rawSnode).")
return rawTargets.flatMap { rawTarget in
guard let address = rawTarget["ip"] as? String, let portAsString = rawTarget["port"] as? String, let port = UInt16(portAsString), let idKey = rawTarget["pubkey_ed25519"] as? String, let encryptionKey = rawTarget["pubkey_x25519"] as? String, address != "0.0.0.0" else {
print("[Loki] Failed to parse target from: \(rawTarget).")
return nil
}
return LokiAPITarget(address: "https://\(address)", port: port)
return LokiAPITarget(address: "https://\(address)", port: port, publicKeySet: LokiAPITarget.KeySet(idKey: idKey, encryptionKey: encryptionKey))
}
}
}
@ -121,8 +123,8 @@ public extension LokiAPI {
internal extension Promise {
internal func handlingSwarmSpecificErrorsIfNeeded(for target: LokiAPITarget, associatedWith hexEncodedPublicKey: String) -> Promise<T> {
return recover(on: DispatchQueue.global()) { error -> Promise<T> in
if let error = error as? NetworkManagerError {
return recover(on: LokiAPI.errorHandlingQueue) { error -> Promise<T> in
if let error = error as? LokiHTTPClient.HTTPError {
switch error.statusCode {
case 0, 400, 500, 503:
// The snode is unreachable
@ -130,7 +132,6 @@ internal extension Promise {
let newFailureCount = oldFailureCount + 1
LokiAPI.failureCount[target] = newFailureCount
print("[Loki] Couldn't reach snode at: \(target); setting failure count to \(newFailureCount).")
Analytics.shared.track("Unreachable Snode")
if newFailureCount >= LokiAPI.failureThreshold {
print("[Loki] Failure threshold reached for: \(target); dropping it.")
LokiAPI.dropIfNeeded(target, hexEncodedPublicKey: hexEncodedPublicKey) // Remove it from the swarm cache associated with the given public key
@ -140,13 +141,10 @@ internal extension Promise {
case 421:
// The snode isn't associated with the given public key anymore
print("[Loki] Invalidating swarm for: \(hexEncodedPublicKey).")
Analytics.shared.track("Migrated Snode")
LokiAPI.dropIfNeeded(target, hexEncodedPublicKey: hexEncodedPublicKey)
case 432:
// The PoW difficulty is too low
if case NetworkManagerError.taskError(_, let underlyingError) = error, let nsError = underlyingError as? NSError,
let data = nsError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? Data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON,
let powDifficulty = json["difficulty"] as? Int {
if case LokiHTTPClient.HTTPError.networkError(_, let result, _) = error, let json = result as? JSON, let powDifficulty = json["difficulty"] as? Int {
print("[Loki] Setting proof of work difficulty to \(powDifficulty).")
LokiAPI.powDifficulty = UInt(powDifficulty)
} else {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save