diff --git a/Permanent.xcodeproj/project.pbxproj b/Permanent.xcodeproj/project.pbxproj index e74f9d3b..632453b4 100644 --- a/Permanent.xcodeproj/project.pbxproj +++ b/Permanent.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 5E051C0A2B6AFAD800274657 /* CustomSimpleListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E051C092B6AFAD800274657 /* CustomSimpleListItemView.swift */; }; 5E051C0C2B6BB90C00274657 /* CustomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E051C0B2B6BB90B00274657 /* CustomHeaderView.swift */; }; 5E051C0E2B6C39E900274657 /* CustomBarProgressGradientStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E051C0D2B6C39E900274657 /* CustomBarProgressGradientStyle.swift */; }; + 5E05D39C2CECDCE700CD9837 /* LoginSecurityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E05D39B2CECDCDB00CD9837 /* LoginSecurityView.swift */; }; + 5E05D39E2CECDDB000CD9837 /* LoginSecurityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E05D39D2CECDDAD00CD9837 /* LoginSecurityViewModel.swift */; }; 5E07D44C28915C8E008C0A6B /* LoginPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07D44B28915C8E008C0A6B /* LoginPage.swift */; }; 5E07D44E28915CE0008C0A6B /* SignUpPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07D44D28915CE0008C0A6B /* SignUpPage.swift */; }; 5E07D45028915FB7008C0A6B /* RightSideMenuPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07D44F28915FB7008C0A6B /* RightSideMenuPage.swift */; }; @@ -106,6 +108,9 @@ 5E2114932B026CB300944386 /* ViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2114912B026CB300944386 /* ViewExtension.swift */; }; 5E218BF925A86C9E00B56625 /* PasswordElementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E218BF725A86C9E00B56625 /* PasswordElementView.swift */; }; 5E218BFA25A86C9E00B56625 /* PasswordElementView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5E218BF825A86C9E00B56625 /* PasswordElementView.xib */; }; + 5E24A2262CF67E6C003F22AE /* CustomToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E24A2242CF67E6C003F22AE /* CustomToggleView.swift */; }; + 5E24A22B2CF73845003F22AE /* IDPUserMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E24A22A2CF73845003F22AE /* IDPUserMethodModel.swift */; }; + 5E24A22C2CF73845003F22AE /* IDPUserMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E24A22A2CF73845003F22AE /* IDPUserMethodModel.swift */; }; 5E29C1D525AEF22D00C2A230 /* SecurityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E29C1D425AEF22D00C2A230 /* SecurityViewModel.swift */; }; 5E2C5D1B24D98EE100E2B95F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2C5D1A24D98EE100E2B95F /* AppDelegate.swift */; }; 5E2C5D2424D98EE300E2B95F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E2C5D2324D98EE300E2B95F /* Assets.xcassets */; }; @@ -995,6 +1000,8 @@ 5E051C092B6AFAD800274657 /* CustomSimpleListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSimpleListItemView.swift; sourceTree = ""; }; 5E051C0B2B6BB90B00274657 /* CustomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomHeaderView.swift; sourceTree = ""; }; 5E051C0D2B6C39E900274657 /* CustomBarProgressGradientStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBarProgressGradientStyle.swift; sourceTree = ""; }; + 5E05D39B2CECDCDB00CD9837 /* LoginSecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginSecurityView.swift; sourceTree = ""; }; + 5E05D39D2CECDDAD00CD9837 /* LoginSecurityViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginSecurityViewModel.swift; sourceTree = ""; }; 5E07D44B28915C8E008C0A6B /* LoginPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPage.swift; sourceTree = ""; }; 5E07D44D28915CE0008C0A6B /* SignUpPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpPage.swift; sourceTree = ""; }; 5E07D44F28915FB7008C0A6B /* RightSideMenuPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RightSideMenuPage.swift; sourceTree = ""; }; @@ -1043,6 +1050,8 @@ 5E2114912B026CB300944386 /* ViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtension.swift; sourceTree = ""; }; 5E218BF725A86C9E00B56625 /* PasswordElementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordElementView.swift; sourceTree = ""; }; 5E218BF825A86C9E00B56625 /* PasswordElementView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PasswordElementView.xib; sourceTree = ""; }; + 5E24A2242CF67E6C003F22AE /* CustomToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomToggleView.swift; sourceTree = ""; }; + 5E24A22A2CF73845003F22AE /* IDPUserMethodModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IDPUserMethodModel.swift; sourceTree = ""; }; 5E29C1D425AEF22D00C2A230 /* SecurityViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityViewModel.swift; sourceTree = ""; }; 5E2C5D1724D98EE100E2B95F /* Permanent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Permanent.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5E2C5D1A24D98EE100E2B95F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -1784,6 +1793,7 @@ 5ED0856924E3EBBB00CDB4D3 /* TextStyle.swift */, BC566CE92546FE00000249E1 /* UploadFileMetaResponse.swift */, BC4526DF251CAB7F00E24A51 /* VerifyCodeResponse.swift */, + 5E24A22A2CF73845003F22AE /* IDPUserMethodModel.swift */, 5E63944D29702F830043D952 /* ForgotPasswordResponse.swift */, BC4526FA251E1D7500E24A51 /* AuthResponse.swift */, BC8945102524B24000FA8D7A /* SignUpResponse.swift */, @@ -2042,10 +2052,19 @@ children = ( 5E218BF725A86C9E00B56625 /* PasswordElementView.swift */, 5E218BF825A86C9E00B56625 /* PasswordElementView.xib */, + 5E05D39B2CECDCDB00CD9837 /* LoginSecurityView.swift */, ); path = Views; sourceTree = ""; }; + 5E24A2252CF67E6C003F22AE /* CustomViews */ = { + isa = PBXGroup; + children = ( + 5E24A2242CF67E6C003F22AE /* CustomToggleView.swift */, + ); + path = CustomViews; + sourceTree = ""; + }; 5E2C5D0E24D98EE100E2B95F = { isa = PBXGroup; children = ( @@ -2167,6 +2186,7 @@ isa = PBXGroup; children = ( 5E29C1D425AEF22D00C2A230 /* SecurityViewModel.swift */, + 5E05D39D2CECDDAD00CD9837 /* LoginSecurityViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -3050,6 +3070,7 @@ 5E991EE02A4AD29F006229C0 /* SwiftUIViews */ = { isa = PBXGroup; children = ( + 5E24A2252CF67E6C003F22AE /* CustomViews */, 5ECD1D9D2C1339B50049A03F /* LoadingAnimations */, 5E92165D2C0DC118002AACA3 /* LabelViews */, 5E051C082B6AFA8C00274657 /* CustomListViews */, @@ -4585,6 +4606,7 @@ 9220ACCF2A20E6CE003797C9 /* RoundedImageView.swift in Sources */, BCFFD293252B506B009485C1 /* UIViewControllerExtension.swift in Sources */, BC42EDD325C014CC0031B965 /* InviteEndpoint.swift in Sources */, + 5E24A2262CF67E6C003F22AE /* CustomToggleView.swift in Sources */, BC42EDC325BEE9310031B965 /* InvitationTableViewCell.swift in Sources */, 5EB053D32BD120C400AFE66D /* TextField+Extension.swift in Sources */, BC59BAB925C2B7D6005A45D3 /* ShareStatus.swift in Sources */, @@ -4597,6 +4619,7 @@ F557A64927A1C81600C061D4 /* OnlinePresenceTableViewCell.swift in Sources */, BC62D580254181BD00E84DA9 /* DataExtension.swift in Sources */, BC6358A42536D8EA00EEC48C /* FilesViewModel.swift in Sources */, + 5E05D39E2CECDDB000CD9837 /* LoginSecurityViewModel.swift in Sources */, F5E793D7294B5AB000CFCCA5 /* AuthTextField.swift in Sources */, BC42EDCD25C011290031B965 /* InviteVO.swift in Sources */, BCA120CC2562C64300ECAD7B /* HighlightColor.swift in Sources */, @@ -4648,6 +4671,7 @@ BCE8DA7525652F4400842ABD /* FileAction.swift in Sources */, 5E92165F2C0DC14C002AACA3 /* OnboardingTitleTextView.swift in Sources */, 5EE9D8C427A188C100CE5F9C /* ProfilePageData.swift in Sources */, + 5E05D39C2CECDCE700CD9837 /* LoginSecurityView.swift in Sources */, 5E6CCEF02B72D65800D192FF /* ActivityFeedViewControllerRepresentable.swift in Sources */, BCC5501F255AB31800CA1574 /* FileHelper.swift in Sources */, 5EB45A462943403600277800 /* UIDeviceExtension.swift in Sources */, @@ -4944,6 +4968,7 @@ 5E3E124B2A431F9600682DE5 /* EnvVars.generated.swift in Sources */, 5EB620292784B038001B9AFD /* BirthInfoProfileItem.swift in Sources */, 5EC043A72924432E00072933 /* ShareManagementEmptyHeaderCollectionReusableView.swift in Sources */, + 5E24A22B2CF73845003F22AE /* IDPUserMethodModel.swift in Sources */, 5ECBAFA32A1B640900FACFDF /* ArchiveStewardResponseTriggerType.swift in Sources */, BCE8DA7F256674D300842ABD /* BottomActionSheet.swift in Sources */, BC3DF862252DB8BA003D3829 /* LocalAuthErrors.swift in Sources */, @@ -5268,6 +5293,7 @@ F559F89828FEEA020015A522 /* SortOption.swift in Sources */, 5E1CCC1C287F04DE00913EEA /* ViewModelInterface.swift in Sources */, 5E4739F62A4187AB00A20D85 /* DescriptionProfileItem.swift in Sources */, + 5E24A22C2CF73845003F22AE /* IDPUserMethodModel.swift in Sources */, F5ADBF43290BC0920007A516 /* UIViewControllerExtension.swift in Sources */, 5E4739CE2A410B9600A20D85 /* BaseViewController.swift in Sources */, F51B3326288B0BB300EA15DA /* MinFolderVO.swift in Sources */, @@ -5547,7 +5573,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -5577,7 +5603,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; @@ -5995,7 +6021,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = "DEV-Debug"; }; @@ -6082,7 +6108,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = "DEV-Release"; }; diff --git a/Permanent/Common/Base/SwiftUINavigationViews/CustomNavigationView.swift b/Permanent/Common/Base/SwiftUINavigationViews/CustomNavigationView.swift index adafcab5..4db28802 100644 --- a/Permanent/Common/Base/SwiftUINavigationViews/CustomNavigationView.swift +++ b/Permanent/Common/Base/SwiftUINavigationViews/CustomNavigationView.swift @@ -18,7 +18,10 @@ struct CustomNavigationView: self.rightButtons = rightButton() UINavigationBar.appearance().backgroundColor = .darkBlue - UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.white] + UINavigationBar.appearance().titleTextAttributes = [ + .foregroundColor: UIColor.white, + NSAttributedString.Key.font: TextFontStyle.style51.font + ] UINavigationBar.appearance().isTranslucent = false UIScrollView.appearance().bounces = false @@ -26,7 +29,8 @@ struct CustomNavigationView: let navigationBarAppearance = UINavigationBarAppearance() navigationBarAppearance.configureWithOpaqueBackground() navigationBarAppearance.titleTextAttributes = [ - NSAttributedString.Key.foregroundColor : UIColor.white + NSAttributedString.Key.foregroundColor : UIColor.white, + NSAttributedString.Key.font: TextFontStyle.style51.font ] navigationBarAppearance.backgroundColor = UIColor.darkBlue UINavigationBar.appearance().standardAppearance = navigationBarAppearance diff --git a/Permanent/Common/Base/SwiftUIViews/CustomListViews/CustomListItemView.swift b/Permanent/Common/Base/SwiftUIViews/CustomListViews/CustomListItemView.swift index 436a9f26..04fec7ef 100644 --- a/Permanent/Common/Base/SwiftUIViews/CustomListViews/CustomListItemView.swift +++ b/Permanent/Common/Base/SwiftUIViews/CustomListViews/CustomListItemView.swift @@ -6,10 +6,72 @@ import SwiftUI +/// A SwiftUI view that represents a customizable list item with a consistent design pattern. +/// +/// This view creates a horizontal layout that includes: +/// - A leading icon/image +/// - A content section with title and description +/// - An optional badge (for highlighting new or special items) +/// - A trailing chevron indicator +/// +/// The view is designed for use in lists where each item needs to display structured information +/// in a visually appealing way. It's particularly useful for navigation menus, settings pages, +/// or any list where items need to show both primary and secondary information. +/// +/// - Parameters: +/// - image: The leading image/icon to display (24x24 points, automatically tinted) +/// - titleText: The primary text displayed in a semi-bold style +/// - descText: Secondary text shown below the title (limited to 2 lines) +/// - showBadge: When true, displays a badge next to the title. Defaults to `false` +/// - badgeText: Custom text for the badge. Defaults to "NEW" if not specified +/// - badgeColor: Custom color for the badge. Defaults to yellow if not specified +/// +/// # Example Usage +/// ```swift +/// // Basic usage +/// CustomListItemView( +/// image: Image(systemName: "folder.fill"), +/// titleText: "Documents", +/// descText: "Access all your stored documents" +/// ) +/// +/// // With badge +/// CustomListItemView( +/// image: Image(systemName: "gift.fill"), +/// titleText: "Special Offer", +/// descText: "Limited time storage upgrade available", +/// showBadge: true, +/// badgeText: "NEW", +/// badgeColor: .red +/// ) +/// ``` +/// +/// The view automatically handles proper spacing, alignment, and color styling +/// to maintain consistency across the app's interface. + + struct CustomListItemView: View { var image: Image var titleText: String var descText: String + var showBadge: Bool = false + var badgeText: String? + var badgeColor: Color? + var showToggle: Bool = false + @Binding var isToggleOn: Bool + + init(image: Image, titleText: String, descText: String, + showBadge: Bool = false, badgeText: String? = nil, badgeColor: Color? = nil, + showToggle: Bool = false, isToggleOn: Binding = .constant(false)) { + self.image = image + self.titleText = titleText + self.descText = descText + self.showBadge = showBadge + self.badgeText = badgeText + self.badgeColor = badgeColor + self.showToggle = showToggle + self._isToggleOn = isToggleOn + } var body: some View { VStack { @@ -23,22 +85,32 @@ struct CustomListItemView: View { VStack(alignment: .leading, spacing: 8) { HStack(spacing: 10) { Text(titleText) - .textStyle(SmallXSemiBoldTextStyle()) + .textStyle(UsualSmallXMediumTextStyle()) .foregroundColor(.blue900) - if titleText == "Redeem code" { - NewBadgeView() + if showBadge { + NewBadgeView(badgeText: badgeText ?? "NEW", badgeColor: badgeColor ?? .yellow) + .transition(.opacity) + } else { + NewBadgeView(badgeText: "", badgeColor: .clear) + .opacity(0) } } Text(descText) - .textStyle(SmallXXXRegularTextStyle()) + .textStyle(UsualSmallXXXRegularTextStyle()) .foregroundColor(.blue400) .lineLimit(2) .multilineTextAlignment(.leading) } Spacer() - Image(systemName: "chevron.right") - .foregroundColor(.blue400) - .padding(.trailing, 10) + if showToggle { + CustomToggleView(isOn: $isToggleOn, height: 24, width: 36) + .padding(.trailing, 10) + } else { + Image(.settingsNextArrowIcon) + .frame(width: 24, height: 24) + .foregroundColor(.blue400) + .padding(.trailing, 10) + } } .padding(10) } diff --git a/Permanent/Common/Base/SwiftUIViews/CustomListViews/CustomSimpleListItemView.swift b/Permanent/Common/Base/SwiftUIViews/CustomListViews/CustomSimpleListItemView.swift index 45577657..339c5720 100644 --- a/Permanent/Common/Base/SwiftUIViews/CustomListViews/CustomSimpleListItemView.swift +++ b/Permanent/Common/Base/SwiftUIViews/CustomListViews/CustomSimpleListItemView.swift @@ -5,11 +5,17 @@ // Created by Lucian Cerbu on 01.02.2024. import SwiftUI - /// Description: Simple list element containing an image and a text +/// - Parameters: +/// - image: Image displayed on the left +/// - titleText: String text +/// - color: Color, default is blue900 +/// - notificationIcon: An red explamation icon shown on the right, default is false +/// - Returns: View struct CustomSimpleListItemView: View { var image: Image var titleText: String + var notificationIcon: Bool = false var color: Color = .blue900 var body: some View { @@ -26,6 +32,13 @@ struct CustomSimpleListItemView: View { Text(titleText) .textStyle(UsualSmallXRegularTextStyle()) .foregroundColor(color) + if notificationIcon { + Image(.authNotificationError) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + .foregroundColor(.red) + } } } } diff --git a/Permanent/Common/Base/SwiftUIViews/CustomViews/CustomToggleView.swift b/Permanent/Common/Base/SwiftUIViews/CustomViews/CustomToggleView.swift new file mode 100644 index 00000000..90ad49a9 --- /dev/null +++ b/Permanent/Common/Base/SwiftUIViews/CustomViews/CustomToggleView.swift @@ -0,0 +1,66 @@ +// +// CustomToggleView.swift +// Permanent +// +// Created by Lucian Cerbu on 27.11.2024. + +import SwiftUI + +/// A SwiftUI view that represents a customizable toggle switch with configurable dimensions. +/// +/// This view creates a toggle switch that: +/// - Can be customized with specific width and height +/// - Automatically scales to maintain proper proportions +/// - Includes smooth spring animation when toggled +/// - Wraps the toggle in a button for improved tap target size +/// +/// The view is designed for use in settings, preferences, or any interface where +/// users need to toggle between two states. It maintains a consistent look while +/// allowing size customization to fit different design requirements. +/// +/// - Parameters: +/// - isOn: Binding to a Boolean value that determines the toggle's state +/// - height: The height of the toggle (defaults to 20 points) +/// - width: The width of the toggle (defaults to 36 points) +/// +/// # Example Usage +/// ```swift +/// // Basic usage with default size +/// CustomToggleView(isOn: $someToggleState) +/// +/// // Custom sized toggle +/// CustomToggleView( +/// isOn: $someToggleState, +/// height: 24, +/// width: 44 +/// ) +/// ``` +/// +/// The view automatically handles proper scaling and touch target sizing +/// to ensure a good user experience regardless of the specified dimensions. + +struct CustomToggleView: View { + @Binding var isOn: Bool + var height: CGFloat = 20 + var width: CGFloat = 36 + + var body: some View { + Button(action: { + isOn.toggle() + }) { + Toggle("", isOn: $isOn) + .labelsHidden() + .frame(width: width, height: height) + .scaleEffect(min(height / 31, width / 51)) + .animation(.spring(), value: isOn) + } + .buttonStyle(PlainButtonStyle()) + .frame(width: width + 20, height: height + 20, alignment: .top) + } +} + +struct CustomToggleView_Previews: PreviewProvider { + static var previews: some View { + CustomToggleView(isOn: .constant(true)) + } +} diff --git a/Permanent/Common/Base/SwiftUIViews/NewBadgeView.swift b/Permanent/Common/Base/SwiftUIViews/NewBadgeView.swift index 0b7ce274..709dc082 100644 --- a/Permanent/Common/Base/SwiftUIViews/NewBadgeView.swift +++ b/Permanent/Common/Base/SwiftUIViews/NewBadgeView.swift @@ -6,18 +6,36 @@ import SwiftUI +/// A view that displays a badge with a text and a color. +/// - Parameters: +/// - badgeText: The text to be displayed on the badge. +/// - badgeColor: The color of the badge. +/// - Returns: View +/// Example: +/// ``` +/// NewBadgeView(badgeText: "New", badgeColor: .blue) +/// ``` + struct NewBadgeView: View { + var badgeText: String + var badgeColor: Color + + init(badgeText: String, badgeColor: Color) { + self.badgeText = badgeText + self.badgeColor = badgeColor + } + var body: some View { HStack { if #available(iOS 16, *) { - Text("NEW") - .textStyle(SmallXXXXXSemiBoldTextStyle()) + Text("\(badgeText)") + .textStyle(UsualSmallXXXXXMediumTextStyle()) .kerning(1.6) .multilineTextAlignment(.center) .foregroundColor(.white) } else { - Text("NEW") - .textStyle(SmallXXXXXSemiBoldTextStyle()) + Text("\(badgeText)") + .textStyle(UsualSmallXXXXXMediumTextStyle()) .multilineTextAlignment(.center) .foregroundColor(.white) } @@ -25,7 +43,7 @@ struct NewBadgeView: View { .frame(height: 24) .padding(.horizontal, 8) .padding(.vertical, 0) - .background(Color(.yellow)) + .background(badgeColor) .cornerRadius(20) } } diff --git a/Permanent/Common/Constants/Constants.swift b/Permanent/Common/Constants/Constants.swift index 2990ce98..5d00caa8 100644 --- a/Permanent/Common/Constants/Constants.swift +++ b/Permanent/Common/Constants/Constants.swift @@ -57,6 +57,7 @@ struct TextFontStyle { static let style48 = TextStyle(UIFont(name: "Usual-Light", size: 56)!, TextStyle.calculateSpacing(fontSize: CGFloat(14), lineHeight: CGFloat(19)), NSTextAlignment.natural) static let style49 = TextStyle(UIFont(name: "Usual-Bold", size: 56)!, TextStyle.calculateSpacing(fontSize: CGFloat(14), lineHeight: CGFloat(19)), NSTextAlignment.natural) static let style50 = TextStyle(UIFont(name: "Usual-Medium", size: 14)!, TextStyle.calculateSpacing(fontSize: CGFloat(14), lineHeight: CGFloat(19)), NSTextAlignment.natural) + static let style51 = TextStyle(UIFont(name: "Usual-Medium", size: 16)!, TextStyle.calculateSpacing(fontSize: CGFloat(16), lineHeight: CGFloat(19)), NSTextAlignment.natural) } struct Constants { diff --git a/Permanent/Common/Models/IDPUserMethodModel.swift b/Permanent/Common/Models/IDPUserMethodModel.swift new file mode 100644 index 00000000..c7e2d61c --- /dev/null +++ b/Permanent/Common/Models/IDPUserMethodModel.swift @@ -0,0 +1,14 @@ +// +// IDPUserMethodModel.swift +// Permanent +// +// Created by Lucian Cerbu on 27.11.2024. + + +import Foundation + +struct IDPUserMethodModel: Codable { + let methodId: String + let method: String + let value: String +} diff --git a/Permanent/Common/Network/AuthenticationEndpoint.swift b/Permanent/Common/Network/AuthenticationEndpoint.swift index a7f023e8..9d95a9bb 100644 --- a/Permanent/Common/Network/AuthenticationEndpoint.swift +++ b/Permanent/Common/Network/AuthenticationEndpoint.swift @@ -30,6 +30,7 @@ enum AuthenticationEndpoint { case createCredentials(credentials: CreateCredentials) /// Logs out the user. case logout + case getIDPUser } extension AuthenticationEndpoint: RequestProtocol { @@ -61,6 +62,8 @@ extension AuthenticationEndpoint: RequestProtocol { var path: String { switch self { + case .getIDPUser: + return "" case .verifyAuth: return "/auth/loggedin" case .login: @@ -77,7 +80,12 @@ extension AuthenticationEndpoint: RequestProtocol { } var method: RequestMethod { - return .post + switch self { + case .getIDPUser: + return .get + default: + return .post + } } var progressHandler: ProgressHandler? { @@ -88,7 +96,15 @@ extension AuthenticationEndpoint: RequestProtocol { var bodyData: Data? { nil } - var customURL: String? { nil } + var customURL: String? { + let endpointPath = APIEnvironment.defaultEnv.apiServer + switch self { + case .getIDPUser: + return "\(endpointPath)api/v2/idpuser" + default: + return nil + } + } var headers: RequestHeaders? { if method == .post { @@ -100,6 +116,11 @@ extension AuthenticationEndpoint: RequestProtocol { } else { return ["content-type": "application/json; charset=utf-8"] } + } else if case .getIDPUser = self { + return [ + "content-type": "application/json; charset=utf-8", + "Request-Version": "2" + ] } else { return nil } diff --git a/Permanent/Common/ViewModifiers/TextViewModifiers.swift b/Permanent/Common/ViewModifiers/TextViewModifiers.swift index 36684a66..c076e3a3 100644 --- a/Permanent/Common/ViewModifiers/TextViewModifiers.swift +++ b/Permanent/Common/ViewModifiers/TextViewModifiers.swift @@ -118,6 +118,14 @@ struct UsualSmallXXXXXRegularTextStyle: ViewModifier { } } +struct UsualSmallXXXXXMediumTextStyle: ViewModifier { + func body(content: Content) -> some View { + content.font(.custom(FontName.usualMedium.rawValue, + size: FontSize.xxxxxSmall.rawValue, + relativeTo: .largeTitle)) + } +} + struct SmallXXXXXItalicTextStyle: ViewModifier { func body(content: Content) -> some View { content.font(.custom(FontName.openSansItalic.rawValue, diff --git a/Permanent/Modules/AccountSecurity/ViewModel/LoginSecurityViewModel.swift b/Permanent/Modules/AccountSecurity/ViewModel/LoginSecurityViewModel.swift new file mode 100644 index 00000000..7c421656 --- /dev/null +++ b/Permanent/Modules/AccountSecurity/ViewModel/LoginSecurityViewModel.swift @@ -0,0 +1,124 @@ +// +// LoginSecurityViewModel.swift +// Permanent +// +// Created by Lucian Cerbu on 19.11.2024. + +import Foundation +import SwiftUI + +/// ViewModel that handles login security settings including two-factor authentication +/// and biometric authentication management +class LoginSecurityViewModel: ObservableObject { + var accountData: AccountVOData? + + @Published var addStorageIsPresented: Bool = false + @Published var redeemStorageIspresented: Bool = false + @Published var isTwoStepVerificationToggleOn: Bool = false + @Published var isSecurityToggleOn: Bool = false + @Published var twoFactorBadgeStatus: SecurityBadgeStatus? = nil + + init() { + checkTwoFactorStatus() + isSecurityToggleOn = getAuthToggleStatus() + } + + /// Checks the current status of two-factor authentication by fetching + /// the user's IDP methods from the server + private func checkTwoFactorStatus() { + let operation = APIOperation(AuthenticationEndpoint.getIDPUser) + + operation.execute(in: APIRequestDispatcher()) { [weak self] result in + DispatchQueue.main.async { + switch result { + case .json(let response, _): + guard let methods: [IDPUserMethodModel] = JSONHelper.convertToModel(from: response) else { + self?.updateTwoFactorStatus(enabled: false) + return + } + + // If we have any methods, 2FA is enabled + self?.updateTwoFactorStatus(enabled: !methods.isEmpty) + + case .error: + self?.updateTwoFactorStatus(enabled: false) + + default: + self?.updateTwoFactorStatus(enabled: false) + } + } + } + } + + /// Updates the UI state based on whether two-factor authentication is enabled + /// - Parameter enabled: Boolean indicating if 2FA is enabled + private func updateTwoFactorStatus(enabled: Bool) { + isTwoStepVerificationToggleOn = enabled + if isTwoStepVerificationToggleOn { + twoFactorBadgeStatus = SecurityBadgeStatus(text: "ON", color: .green) + } else { + twoFactorBadgeStatus = SecurityBadgeStatus(text: "OFF", color: .red) + } + } + + /// Returns the appropriate authentication type text based on the device's + /// biometric capabilities (Face ID or Touch ID) + /// - Returns: A localized string representing the auth type + func getAuthTypeText() -> String { + let authType = BiometryUtils.biometryInfo.name + var authTextType: String + switch authType { + case "Touch ID": + authTextType = .LogInTouchId + case "Face ID": + authTextType = .LogInFaceId + default: + authTextType = authType + } + return authTextType + } + + /// Checks if the device has biometric authentication hardware available + /// - Returns: Boolean indicating if biometric authentication is available + func getUserBiomericsStatus() -> Bool { + let authStatus = PermanentLocalAuthentication.instance.canAuthenticate() + return !(authStatus.error?.statusCode == LocalAuthErrors.localHardwareUnavailableError.statusCode) + } + + /// Updates and saves the biometric authentication toggle status + /// - Parameter isEnabled: Boolean indicating if biometric auth should be enabled + func updateBiometricsStatus(isEnabled: Bool) { + PreferencesManager.shared.set(isEnabled, forKey: Constants.Keys.StorageKeys.biometricsAuthEnabled) + isSecurityToggleOn = isEnabled + } + + /// Retrieves the current biometric authentication toggle status + /// - Returns: Boolean indicating if biometric auth is enabled in user preferences + func getAuthToggleStatus() -> Bool { + let authStatus = PermanentLocalAuthentication.instance.canAuthenticate() + var biometricsAuthEnabled: Bool = PreferencesManager.shared.getValue(forKey: Constants.Keys.StorageKeys.biometricsAuthEnabled) ?? true + + if authStatus.error?.statusCode == LocalAuthErrors.localHardwareUnavailableError.statusCode { + biometricsAuthEnabled = false + // Make sure to save the disabled state if hardware is unavailable + updateBiometricsStatus(isEnabled: false) + return false + } + + isSecurityToggleOn = biometricsAuthEnabled + return biometricsAuthEnabled + } +} + +/// Represents the possible states after a password change attempt +enum PasswordChangeStatus { + case success(message: String?) + case error(message: String?) +} + +/// Represents the visual status of a security feature (like 2FA) +/// with associated text and color +struct SecurityBadgeStatus: Equatable { + let text: String + let color: Color +} diff --git a/Permanent/Modules/AccountSecurity/ViewModel/SecurityViewModel.swift b/Permanent/Modules/AccountSecurity/ViewModel/SecurityViewModel.swift index e13cf83e..3b0fd6bf 100644 --- a/Permanent/Modules/AccountSecurity/ViewModel/SecurityViewModel.swift +++ b/Permanent/Modules/AccountSecurity/ViewModel/SecurityViewModel.swift @@ -27,9 +27,9 @@ protocol SecurityViewModelViewDelegate: ViewModelDelegateInterface { extension SecurityViewModel: SecurityViewModelDelegate { func changePassword(with accountId: Int, data: ChangePasswordCredentials, then handler: @escaping (PasswordChangeStatus) -> Void) { let changePasswordOperation = APIOperation(AccountEndpoint.changePassword(accountId: accountId, passwordDetails: data)) - + changePasswordOperation.execute(in: APIRequestDispatcher()) { result in - + switch result { case .json(let response, _): guard @@ -55,13 +55,13 @@ extension SecurityViewModel: SecurityViewModelDelegate { case .error: handler(.error(message: .errorMessage)) self.viewDelegate?.passwordUpdated(success: false) - + default: break } } } - + func getAuthTypeText() -> String { let authType = BiometryUtils.biometryInfo.name var authTextType: String @@ -75,24 +75,19 @@ extension SecurityViewModel: SecurityViewModelDelegate { } return authTextType } - + func getUserBiomericsStatus() -> Bool { let authStatus = PermanentLocalAuthentication.instance.canAuthenticate() return !(authStatus.error?.statusCode == LocalAuthErrors.localHardwareUnavailableError.statusCode) } - + func getAuthToggleStatus() -> Bool { let authStatus = PermanentLocalAuthentication.instance.canAuthenticate() var biometricsAuthEnabled: Bool = PreferencesManager.shared.getValue(forKey: Constants.Keys.StorageKeys.biometricsAuthEnabled) ?? true if authStatus.error?.statusCode == LocalAuthErrors.localHardwareUnavailableError.statusCode { biometricsAuthEnabled = false } - + return biometricsAuthEnabled } } - -enum PasswordChangeStatus { - case success(message: String?) - case error(message: String?) -} diff --git a/Permanent/Modules/AccountSecurity/Views/LoginSecurityView.swift b/Permanent/Modules/AccountSecurity/Views/LoginSecurityView.swift new file mode 100644 index 00000000..667faaf4 --- /dev/null +++ b/Permanent/Modules/AccountSecurity/Views/LoginSecurityView.swift @@ -0,0 +1,99 @@ +// +// LoginSecurityView.swift +// Permanent +// +// Created by Lucian Cerbu on 19.11.2024. +import SwiftUI + +struct LoginSecurityView: View { + @Environment(\.presentationMode) var presentationMode + @StateObject var viewModel: LoginSecurityViewModel + + init(viewModel: StateObject) { + self._viewModel = viewModel + } + + var dismissAction: ((Bool) -> Void)? + + var body: some View { + ZStack { + CustomNavigationView { + ZStack { + backgroundView + contentView + } + .ignoresSafeArea(.all) + } leftButton: { + backButton + } rightButton: { + EmptyView() + } + } + } + + var backgroundView: some View { + Color.whiteGray + .frame(maxWidth: .infinity, maxHeight: .infinity) + .edgesIgnoringSafeArea(.all) + } + + var contentView: some View { + ZStack(alignment: .bottom) { + VStack(spacing: 10) { + Button { + viewModel.addStorageIsPresented = true + } label: { + CustomListItemView( + image: Image(.securityChangePass), + titleText: "Change password", + descText: "Update your password to keep your account secure." + ) + } + Divider() + Button { + viewModel.addStorageIsPresented = true + } label: { + CustomListItemView( + image: Image(.securityTwoStepVerify), + titleText: "Two-step verification", + descText: "Enhance account security by requiring a login verification code.", + showBadge: viewModel.twoFactorBadgeStatus != nil, + badgeText: viewModel.twoFactorBadgeStatus?.text ?? "", + badgeColor: viewModel.twoFactorBadgeStatus?.color ?? .clear + ) + .animation(.spring(response: 0.3, dampingFraction: 0.6), value: viewModel.twoFactorBadgeStatus) + } + Divider() + CustomListItemView( + image: Image(.securityFaceId), + titleText: "Sign in with \(viewModel.getAuthTypeText())", + descText: "This option lets you securely sign in with just a glance.", + showToggle: true, + isToggleOn: $viewModel.isSecurityToggleOn + ) + Spacer() + } + } + .navigationBarTitle("Login & Security", displayMode: .inline) + .padding(.top, 10) + .onChange(of: viewModel.isSecurityToggleOn) { newValue in + viewModel.updateBiometricsStatus(isEnabled: newValue) + } + } + + var backButton: some View { + Button(action: { + dismissView() + }) { + HStack { + Image(.settingsNavigationBarBackIcon) + .foregroundColor(.white) + } + } + } + + func dismissView() { + dismissAction?(false) + presentationMode.wrappedValue.dismiss() + } +} diff --git a/Permanent/Modules/Main/Navigation/Routers/SettingsRouter.swift b/Permanent/Modules/Main/Navigation/Routers/SettingsRouter.swift index f7dfb04b..12c903bf 100644 --- a/Permanent/Modules/Main/Navigation/Routers/SettingsRouter.swift +++ b/Permanent/Modules/Main/Navigation/Routers/SettingsRouter.swift @@ -15,7 +15,7 @@ class SettingsRouter { case myArchives case invitations case activityFeed - case security + case loginAndSecurity case legacyPlanning case contactSupport case signUp @@ -64,8 +64,8 @@ class SettingsRouter { let host = UIHostingController(rootView: screenView) host.modalPresentationStyle = .fullScreen self.rootViewController.present(host, animated: true, completion: nil) - case .security: - let screenView = ViewRepresentableContainer(viewRepresentable: AccountSettingsViewControllerRepresentable(), title: AccountSettingsViewControllerRepresentable().title) + case .loginAndSecurity: + let screenView = LoginSecurityView(viewModel: StateObject(wrappedValue: LoginSecurityViewModel())) let host = UIHostingController(rootView: screenView) host.modalPresentationStyle = .fullScreen self.rootViewController.present(host, animated: true, completion: nil) diff --git a/Permanent/Modules/Main/Navigation/ViewControllerRepresentables/AccountSettingsViewControllerRepresentable.swift b/Permanent/Modules/Main/Navigation/ViewControllerRepresentables/AccountSettingsViewControllerRepresentable.swift index aa013857..ccbe63a1 100644 --- a/Permanent/Modules/Main/Navigation/ViewControllerRepresentables/AccountSettingsViewControllerRepresentable.swift +++ b/Permanent/Modules/Main/Navigation/ViewControllerRepresentables/AccountSettingsViewControllerRepresentable.swift @@ -8,7 +8,7 @@ import SwiftUI import UIKit struct AccountSettingsViewControllerRepresentable: UIViewControllerRepresentable { - let title: String = "Security" + let title: String = "Login & Security" func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController.create(withIdentifier: .accountSettings, from: .settings) diff --git a/Permanent/Modules/Main/Navigation/Views/ViewRepresentableContainer.swift b/Permanent/Modules/Main/Navigation/Views/ViewRepresentableContainer.swift index c621b91c..281d97da 100644 --- a/Permanent/Modules/Main/Navigation/Views/ViewRepresentableContainer.swift +++ b/Permanent/Modules/Main/Navigation/Views/ViewRepresentableContainer.swift @@ -47,7 +47,7 @@ struct ViewRepresentableContainer: View { dismissView() }) { HStack { - Image(.backArrowNewDesign) + Image(.settingsNavigationBarBackIcon) .foregroundColor(.white) } } diff --git a/Permanent/Modules/SideMenus/Views/SettingsScreenView.swift b/Permanent/Modules/SideMenus/Views/SettingsScreenView.swift index 3d473a7b..2e7034bc 100644 --- a/Permanent/Modules/SideMenus/Views/SettingsScreenView.swift +++ b/Permanent/Modules/SideMenus/Views/SettingsScreenView.swift @@ -80,9 +80,9 @@ struct SettingsScreenView: View { CustomSimpleListItemView(image: Image(.activityFeedSettings), titleText: "Activity feed") } Button { - settingsRouter.navigate(to: .security, router: settingsRouter) + settingsRouter.navigate(to: .loginAndSecurity, router: settingsRouter) } label: { - CustomSimpleListItemView(image: Image(.securitySettings), titleText: "Security") + CustomSimpleListItemView(image: Image(.securitySettings), titleText: "Login & Security", notificationIcon: true) } Button { settingsRouter.navigate(to: .legacyPlanning, router: settingsRouter) diff --git a/Permanent/Modules/Storage/Views/AddStorageView.swift b/Permanent/Modules/Storage/Views/AddStorageView.swift index 80049602..f34dfdf9 100644 --- a/Permanent/Modules/Storage/Views/AddStorageView.swift +++ b/Permanent/Modules/Storage/Views/AddStorageView.swift @@ -45,7 +45,7 @@ struct AddStorageView: View { dismissView() }) { HStack { - Image(.backArrowNewDesign) + Image(.settingsNavigationBarBackIcon) .foregroundColor(.white) } } diff --git a/Permanent/Modules/Storage/Views/GiftStorageView.swift b/Permanent/Modules/Storage/Views/GiftStorageView.swift index 5d314e2a..bda5fb15 100644 --- a/Permanent/Modules/Storage/Views/GiftStorageView.swift +++ b/Permanent/Modules/Storage/Views/GiftStorageView.swift @@ -56,7 +56,7 @@ struct GiftStorageView: View { dismissView() }) { HStack { - Image(.backArrowNewDesign) + Image(.settingsNavigationBarBackIcon) .foregroundColor(.white) } } diff --git a/Permanent/Modules/Storage/Views/RedeemCodeView.swift b/Permanent/Modules/Storage/Views/RedeemCodeView.swift index b5d36931..88d01e57 100644 --- a/Permanent/Modules/Storage/Views/RedeemCodeView.swift +++ b/Permanent/Modules/Storage/Views/RedeemCodeView.swift @@ -100,7 +100,7 @@ struct RedeemCodeView: View { dismissView() }) { HStack { - Image(.backArrowNewDesign) + Image(.settingsNavigationBarBackIcon) .foregroundColor(.white) } } diff --git a/Permanent/Modules/Storage/Views/StorageView.swift b/Permanent/Modules/Storage/Views/StorageView.swift index 1ee2a04b..ed9478ad 100644 --- a/Permanent/Modules/Storage/Views/StorageView.swift +++ b/Permanent/Modules/Storage/Views/StorageView.swift @@ -85,7 +85,7 @@ struct StorageView: View { Button { viewModel.redeemStorageIspresented = true } label: { - CustomListItemView(image: Image(.storageRedeem), titleText: "Redeem code", descText: "Enter codes to unlock special storage benefits just for you.") + CustomListItemView(image: Image(.storageRedeem), titleText: "Redeem code", descText: "Enter codes to unlock special storage benefits just for you.", showBadge: true) } Spacer() } @@ -99,7 +99,7 @@ struct StorageView: View { dismissView() }) { HStack { - Image(.backArrowNewDesign) + Image(.settingsNavigationBarBackIcon) .foregroundColor(.white) } } diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/Contents.json b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/Contents.json b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityChangePass.imageset/Contents.json b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityChangePass.imageset/Contents.json new file mode 100644 index 00000000..7bbb7089 --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityChangePass.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "securityChangePass.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityChangePass.imageset/securityChangePass.svg b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityChangePass.imageset/securityChangePass.svg new file mode 100644 index 00000000..d12ab05f --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityChangePass.imageset/securityChangePass.svg @@ -0,0 +1,3 @@ + + + diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityFaceId.imageset/Contents.json b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityFaceId.imageset/Contents.json new file mode 100644 index 00000000..252d46a0 --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityFaceId.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "securityFaceId.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityFaceId.imageset/securityFaceId.svg b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityFaceId.imageset/securityFaceId.svg new file mode 100644 index 00000000..5964f162 --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityFaceId.imageset/securityFaceId.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityTwoStepVerify.imageset/Contents.json b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityTwoStepVerify.imageset/Contents.json new file mode 100644 index 00000000..0400e15e --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityTwoStepVerify.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "securityTwoStepVerify.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityTwoStepVerify.imageset/securityTwoStepVerify.svg b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityTwoStepVerify.imageset/securityTwoStepVerify.svg new file mode 100644 index 00000000..bb4f6945 --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/LoginAndSecurity/securityTwoStepVerify.imageset/securityTwoStepVerify.svg @@ -0,0 +1,3 @@ + + + diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNavigationBarBackIcon.imageset/Contents.json b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNavigationBarBackIcon.imageset/Contents.json new file mode 100644 index 00000000..88fd8a26 --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNavigationBarBackIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "SettingsNavigationBarBackIcon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNavigationBarBackIcon.imageset/SettingsNavigationBarBackIcon.svg b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNavigationBarBackIcon.imageset/SettingsNavigationBarBackIcon.svg new file mode 100644 index 00000000..760f1eab --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNavigationBarBackIcon.imageset/SettingsNavigationBarBackIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNextArrowIcon.imageset/Contents.json b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNextArrowIcon.imageset/Contents.json new file mode 100644 index 00000000..763f6f1b --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNextArrowIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "SettingsNextArrowIcon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNextArrowIcon.imageset/SettingsNextArrowIcon.svg b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNextArrowIcon.imageset/SettingsNextArrowIcon.svg new file mode 100644 index 00000000..ab61a5c8 --- /dev/null +++ b/Permanent/Resources/Assets/Assets.xcassets/Images/SettingsMenu/SettingsNextArrowIcon.imageset/SettingsNextArrowIcon.svg @@ -0,0 +1,3 @@ + + +