From 494a3aba7ceae7e402ebaf20ce75c924234a614b Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Fri, 24 Nov 2023 16:55:00 +0100 Subject: [PATCH 01/15] chore: add faq, open in browser screen --- Core/Core/Configuration/Config/Config.swift | 10 ++++ .../Presentation/Profile/ProfileView.swift | 51 ++++++++++++++----- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 49f1ffdc5..461ea8ecd 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -13,6 +13,7 @@ public protocol ConfigProtocol { var tokenType: TokenType { get } var feedbackEmail: String { get } var appStoreLink: String { get } + var faq: URL? { get } var agreement: AgreementConfig { get } var firebase: FirebaseConfig { get } var features: FeaturesConfig { get } @@ -32,6 +33,7 @@ private enum ConfigKeys: String { case platformName = "PLATFORM_NAME" case organizationCode = "ORGANIZATION_CODE" case appstoreID = "APP_STORE_ID" + case faq = "FAQ_URL" } public class Config { @@ -127,6 +129,14 @@ extension Config: ConfigProtocol { public var appStoreLink: String { "itms-apps://itunes.apple.com/app/id\(appStoreId)?mt=8" } + + public var faq: URL? { + guard let urlString = string(for: ConfigKeys.faq.rawValue), + let url = URL(string: urlString) else { + return nil + } + return url + } } // Mark - For testing and SwiftUI preview diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index f398d735c..bb1265633 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -13,7 +13,7 @@ public struct ProfileView: View { @StateObject private var viewModel: ProfileViewModel @Binding var settingsTapped: Bool - + public init(viewModel: ProfileViewModel, settingsTapped: Binding) { self._viewModel = StateObject(wrappedValue: { viewModel }()) self._settingsTapped = settingsTapped @@ -133,16 +133,18 @@ public struct ProfileView: View { } if let tos = viewModel.config.agreement.tosURL { - Button(action: { - viewModel.trackCookiePolicyClicked() - UIApplication.shared.open(tos) - }, label: { + NavigationLink { + WebBrowser( + url: tos.absoluteString, + pageTitle: ProfileLocalization.terms + ) + } label: { HStack { Text(ProfileLocalization.terms) Spacer() Image(systemName: "chevron.right") } - }) + } .buttonStyle(PlainButtonStyle()) .foregroundColor(.primary) .accessibilityElement(children: .ignore) @@ -153,22 +155,47 @@ public struct ProfileView: View { } if let privacy = viewModel.config.agreement.privacyPolicyURL { - Button(action: { - viewModel.trackPrivacyPolicyClicked() - UIApplication.shared.open(privacy) - }, label: { + NavigationLink { + WebBrowser( + url: privacy.absoluteString, + pageTitle: ProfileLocalization.privacy + ) + } label: { HStack { Text(ProfileLocalization.privacy) Spacer() Image(systemName: "chevron.right") } - }) + } .buttonStyle(PlainButtonStyle()) .foregroundColor(.primary) .accessibilityElement(children: .ignore) .accessibilityLabel(ProfileLocalization.privacy) + } - + + if let faq = viewModel.config.faq { + Rectangle() + .frame(height: 1) + .foregroundColor(Theme.Colors.textSecondary) + NavigationLink { + WebBrowser( + url: faq.absoluteString, + pageTitle: "FAQ" + ) + } label: { + HStack { + Text("FAQ") + Spacer() + Image(systemName: "chevron.right") + } + } + .buttonStyle(PlainButtonStyle()) + .foregroundColor(.primary) + .accessibilityElement(children: .ignore) + .accessibilityLabel(ProfileLocalization.privacy) + } + // MARK: Version Rectangle() .frame(height: 1) From ed701ecafc3f2d5ab2af735c532009447d4f0aec Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 6 Dec 2023 16:03:22 +0100 Subject: [PATCH 02/15] chore: merge develop to branch --- Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index 7f9f32536..4dffd8a9f 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -182,4 +182,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 544edab2f9ecc4ac18973fb8865f1d0613ec8a28 -COCOAPODS: 1.11.3 +COCOAPODS: 1.13.0 From e3b2d29bc63b41f89897ab0a13e85765687b4b4a Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 13 Dec 2023 13:04:51 +0100 Subject: [PATCH 03/15] chore: add new links Cookie Policy, Do Not Sell my Personal Information and refactor --- .../Config/AgreementConfig.swift | 8 +- Profile/Profile.xcodeproj/project.pbxproj | 4 + .../Profile/ProfileSupportInfo.swift | 175 +++++++ .../Presentation/Profile/ProfileView.swift | 483 +++++++----------- Profile/Profile/SwiftGen/Strings.swift | 10 +- Profile/Profile/en.lproj/Localizable.strings | 6 +- Profile/Profile/uk.lproj/Localizable.strings | 3 + 7 files changed, 388 insertions(+), 301 deletions(-) create mode 100644 Profile/Profile/Presentation/Profile/ProfileSupportInfo.swift diff --git a/Core/Core/Configuration/Config/AgreementConfig.swift b/Core/Core/Configuration/Config/AgreementConfig.swift index 46059500e..823bda9ba 100644 --- a/Core/Core/Configuration/Config/AgreementConfig.swift +++ b/Core/Core/Configuration/Config/AgreementConfig.swift @@ -10,15 +10,21 @@ import Foundation private enum AgreementKeys: String { case privacyPolicyURL = "PRIVACY_POLICY_URL" case tosURL = "TOS_URL" + case cookiePolicyURL = "COOKIE_POLICY_URL" + case dataSellContentURL = "DATA_SELL_CONSENT_URL" } public class AgreementConfig: NSObject { public var privacyPolicyURL: URL? public var tosURL: URL? - + public var cookiePolicyURL: URL? + public var dataSellContentURL: URL? + init(dictionary: [String: AnyObject]) { privacyPolicyURL = (dictionary[AgreementKeys.privacyPolicyURL.rawValue] as? String).flatMap(URL.init) tosURL = (dictionary[AgreementKeys.tosURL.rawValue] as? String).flatMap(URL.init) + cookiePolicyURL = (dictionary[AgreementKeys.cookiePolicyURL.rawValue] as? String).flatMap(URL.init) + dataSellContentURL = (dictionary[AgreementKeys.dataSellContentURL.rawValue] as? String).flatMap(URL.init) super.init() } } diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index 28dc8a604..bca6eaddf 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 02F3BFE7292539850051930C /* ProfileRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3BFE6292539850051930C /* ProfileRouter.swift */; }; 0796C8C929B7905300444B05 /* ProfileBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0796C8C829B7905300444B05 /* ProfileBottomSheet.swift */; }; 25B36FF48C1307888A3890DA /* Pods_App_Profile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BEA369C38362C1A91A012F70 /* Pods_App_Profile.framework */; }; + BAD9CA3F2B29BF5C00DE790A /* ProfileSupportInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfo.swift */; }; E8264C634DD8AD314ECE8905 /* Pods_App_Profile_ProfileTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C85ADF87135E03275A980E07 /* Pods_App_Profile_ProfileTests.framework */; }; /* End PBXBuildFile section */ @@ -94,6 +95,7 @@ 9D125F82E0EAC4B6C0CE280F /* Pods-App-Profile-ProfileTests.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile-ProfileTests.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Profile-ProfileTests/Pods-App-Profile-ProfileTests.releaseprod.xcconfig"; sourceTree = ""; }; A9F98CD65D1F657EB8F9EA59 /* Pods-App-Profile.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Profile/Pods-App-Profile.releasedev.xcconfig"; sourceTree = ""; }; B3F05DC21379BD4FE1AFCCF1 /* Pods-App-Profile.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Profile/Pods-App-Profile.debugprod.xcconfig"; sourceTree = ""; }; + BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSupportInfo.swift; sourceTree = ""; }; BEA369C38362C1A91A012F70 /* Pods_App_Profile.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Profile.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C85ADF87135E03275A980E07 /* Pods_App_Profile_ProfileTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Profile_ProfileTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F52EFE7DC07BE68B9A302DAF /* Pods-App-Profile.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile.debug.xcconfig"; path = "Target Support Files/Pods-App-Profile/Pods-App-Profile.debug.xcconfig"; sourceTree = ""; }; @@ -138,6 +140,7 @@ 02D0FD072AD695E10020D752 /* UserProfile */, 021D924528DC634300ACC565 /* ProfileView.swift */, 021D925128DC918D00ACC565 /* ProfileViewModel.swift */, + BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfo.swift */, ); path = Profile; sourceTree = ""; @@ -563,6 +566,7 @@ 021D924E28DC88BB00ACC565 /* ProfileRepository.swift in Sources */, 0796C8C929B7905300444B05 /* ProfileBottomSheet.swift in Sources */, 021D924C28DC884A00ACC565 /* ProfileEndpoint.swift in Sources */, + BAD9CA3F2B29BF5C00DE790A /* ProfileSupportInfo.swift in Sources */, 020306C82932B13F000949EA /* EditProfileView.swift in Sources */, 0262149229AE57A1008BD75A /* DeleteAccountView.swift in Sources */, 02D0FD092AD698380020D752 /* UserProfileView.swift in Sources */, diff --git a/Profile/Profile/Presentation/Profile/ProfileSupportInfo.swift b/Profile/Profile/Presentation/Profile/ProfileSupportInfo.swift new file mode 100644 index 000000000..0350cf4ef --- /dev/null +++ b/Profile/Profile/Presentation/Profile/ProfileSupportInfo.swift @@ -0,0 +1,175 @@ +// +// ProfileSupportInfo.swift +// Profile +// +// Created by Eugene Yatsenko on 13.12.2023. +// + +import SwiftUI +import Theme +import Core + +struct ProfileSupportInfo: View { + + struct LinkViewModel { + let URL: URL + let title: String + } + + @ObservedObject var viewModel: ProfileViewModel + + var body: some View { + Text(ProfileLocalization.supportInfo) + .padding(.horizontal, 24) + .font(Theme.Fonts.labelLarge) + VStack(alignment: .leading, spacing: 24) { + viewModel.contactSupport().map(supportInfo) + viewModel.config.agreement.tosURL.map(terms) + viewModel.config.agreement.privacyPolicyURL.map(privacy) + viewModel.config.agreement.cookiePolicyURL.map(cookiePolicy) + viewModel.config.agreement.dataSellContentURL.map(dataSellContent) + viewModel.config.faq.map(faq) + version + } + .cardStyle( + bgColor: Theme.Colors.textInputUnfocusedBackground, + strokeColor: .clear + ) + } + + @ViewBuilder + private func supportInfo(support: URL) -> some View { + Button { + viewModel.trackEmailSupportClicked() + UIApplication.shared.open(support) + } label: { + HStack { + Text(ProfileLocalization.contact) + Spacer() + Image(systemName: "chevron.right") + } + } + .foregroundColor(.primary) + .accessibilityElement(children: .ignore) + .accessibilityLabel(ProfileLocalization.supportInfo) + Rectangle() + .frame(height: 1) + .foregroundColor(Theme.Colors.textSecondary) + } + + private func terms(url: URL) -> some View { + navigationLink( + viewModel: .init( + URL: url, + title: ProfileLocalization.terms + ) + ) + } + + private func privacy(url: URL) -> some View { + navigationLink( + viewModel: .init( + URL: url, + title: ProfileLocalization.privacy + ) + ) + } + + private func cookiePolicy(url: URL) -> some View { + navigationLink( + viewModel: .init( + URL: url, + title: ProfileLocalization.cookiePolicy + ) + ) + } + + private func dataSellContent(url: URL) -> some View { + navigationLink( + viewModel: .init( + URL: url, + title: ProfileLocalization.doNotSellInformation + ) + ) + } + + private func faq(url: URL) -> some View { + navigationLink( + viewModel: .init( + URL: url, + title: ProfileLocalization.faq + ) + ) + } + + @ViewBuilder + private func navigationLink(viewModel: LinkViewModel) -> some View { + NavigationLink { + WebBrowser( + url: viewModel.URL.absoluteString, + pageTitle: viewModel.title + ) + } label: { + HStack { + Text(viewModel.title) + .multilineTextAlignment(.leading) + Spacer() + Image(systemName: "chevron.right") + } + } + .foregroundColor(.primary) + .accessibilityElement(children: .ignore) + .accessibilityLabel(viewModel.title) + Rectangle() + .frame(height: 1) + .foregroundColor(Theme.Colors.textSecondary) + } + + @ViewBuilder + private var version: some View { + Button(action: { + viewModel.openAppStore() + }, label: { + HStack { + VStack(alignment: .leading, spacing: 0) { + HStack { + if viewModel.versionState == .updateRequired { + CoreAssets.warningFilled.swiftUIImage + .resizable() + .frame(width: 24, height: 24) + } + Text("\(ProfileLocalization.Settings.version) \(viewModel.currentVersion)") + } + switch viewModel.versionState { + case .actual: + HStack { + CoreAssets.checkmark.swiftUIImage + .renderingMode(.template) + .foregroundColor(.green) + Text(ProfileLocalization.Settings.upToDate) + .font(Theme.Fonts.labelMedium) + .foregroundStyle(Theme.Colors.textSecondary) + } + case .updateNeeded: + Text("\(ProfileLocalization.Settings.tapToUpdate) \(viewModel.latestVersion)") + .font(Theme.Fonts.labelMedium) + .foregroundStyle(Theme.Colors.accentColor) + case .updateRequired: + Text(ProfileLocalization.Settings.tapToInstall) + .font(Theme.Fonts.labelMedium) + .foregroundStyle(Theme.Colors.accentColor) + } + } + Spacer() + if viewModel.versionState != .actual { + Image(systemName: "arrow.up.circle") + .resizable() + .frame(width: 24, height: 24) + .foregroundStyle(Theme.Colors.accentColor) + } + + } + }).disabled(viewModel.versionState == .actual) + } + +} diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index 6e7e93cdd..428c403fe 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -11,7 +11,7 @@ import Kingfisher import Theme public struct ProfileView: View { - + @StateObject private var viewModel: ProfileViewModel @Binding var settingsTapped: Bool @@ -23,307 +23,53 @@ public struct ProfileView: View { public var body: some View { ZStack(alignment: .top) { // MARK: - Page Body - RefreshableScrollViewCompat(action: { - await viewModel.getMyProfile(withProgress: false) - }) { - VStack { - if viewModel.isShowProgress { - ProgressBar(size: 40, lineWidth: 8) - .padding(.top, 200) - .padding(.horizontal) - } else { - UserAvatar(url: viewModel.userModel?.avatarUrl ?? "", image: $viewModel.updatedAvatar) - .padding(.top, 30) - Text(viewModel.userModel?.name ?? "") - .font(Theme.Fonts.headlineSmall) - .padding(.top, 20) - - Text("@\(viewModel.userModel?.username ?? "")") - .font(Theme.Fonts.labelLarge) - .padding(.top, 4) - .foregroundColor(Theme.Colors.textSecondary) - .padding(.bottom, 10) - - // MARK: - Profile Info - if viewModel.userModel?.yearOfBirth != 0 || viewModel.userModel?.shortBiography != "" { - VStack(alignment: .leading, spacing: 14) { - Text(ProfileLocalization.info) - .padding(.horizontal, 24) - .font(Theme.Fonts.labelLarge) - - VStack(alignment: .leading, spacing: 16) { - if viewModel.userModel?.yearOfBirth != 0 { - HStack { - Text(ProfileLocalization.Edit.Fields.yearOfBirth) - .foregroundColor(Theme.Colors.textSecondary) - Text(String(viewModel.userModel?.yearOfBirth ?? 0)) - } - } - if let bio = viewModel.userModel?.shortBiography, bio != "" { - HStack(alignment: .top) { - Text(ProfileLocalization.bio + " ") - .foregroundColor(Theme.Colors.textSecondary) - + Text(bio) - } - } - } - .accessibilityElement(children: .ignore) - .accessibilityLabel( - (viewModel.userModel?.yearOfBirth != 0 ? - ProfileLocalization.Edit.Fields.yearOfBirth + String(viewModel.userModel?.yearOfBirth ?? 0) : - "") + - (viewModel.userModel?.shortBiography != nil ? - ProfileLocalization.bio + (viewModel.userModel?.shortBiography ?? "") : - "") - ) - .cardStyle( - bgColor: Theme.Colors.textInputUnfocusedBackground, - strokeColor: .clear - ) - }.padding(.bottom, 16) + RefreshableScrollViewCompat( + action: { + await viewModel.getMyProfile(withProgress: false) + }, + content: content + ) + .accessibilityAction {} + .frameLimit(sizePortrait: 420) + .padding(.top, 8) + .onChange(of: settingsTapped, perform: { _ in + let userModel = viewModel.userModel ?? UserProfile() + viewModel.trackProfileEditClicked() + viewModel.router.showEditProfile( + userModel: userModel, + avatar: viewModel.updatedAvatar, + profileDidEdit: { updatedProfile, updatedImage in + if let updatedProfile { + self.viewModel.userModel = updatedProfile } - - VStack(alignment: .leading, spacing: 14) { - // MARK: - Settings - Text(ProfileLocalization.settings) - .padding(.horizontal, 24) - .font(Theme.Fonts.labelLarge) - VStack(alignment: .leading, spacing: 27) { - Button(action: { - viewModel.trackProfileVideoSettingsClicked() - viewModel.router.showSettings() - }, label: { - HStack { - Text(ProfileLocalization.settingsVideo) - Spacer() - Image(systemName: "chevron.right") - } - }) - - } - .accessibilityElement(children: .ignore) - .accessibilityLabel(ProfileLocalization.settingsVideo) - .cardStyle( - bgColor: Theme.Colors.textInputUnfocusedBackground, - strokeColor: .clear - ) - - // MARK: - Support info - Text(ProfileLocalization.supportInfo) - .padding(.horizontal, 24) - .font(Theme.Fonts.labelLarge) - VStack(alignment: .leading, spacing: 24) { - if let support = viewModel.contactSupport() { - Button(action: { - viewModel.trackEmailSupportClicked() - UIApplication.shared.open(support) - }, label: { - HStack { - Text(ProfileLocalization.contact) - Spacer() - Image(systemName: "chevron.right") - } - }) - .buttonStyle(PlainButtonStyle()) - .foregroundColor(.primary) - .accessibilityElement(children: .ignore) - .accessibilityLabel(ProfileLocalization.supportInfo) - Rectangle() - .frame(height: 1) - .foregroundColor(Theme.Colors.textSecondary) - } - - if let tos = viewModel.config.agreement.tosURL { - NavigationLink { - WebBrowser( - url: tos.absoluteString, - pageTitle: ProfileLocalization.terms - ) - } label: { - HStack { - Text(ProfileLocalization.terms) - Spacer() - Image(systemName: "chevron.right") - } - } - .buttonStyle(PlainButtonStyle()) - .foregroundColor(.primary) - .accessibilityElement(children: .ignore) - .accessibilityLabel(ProfileLocalization.terms) - Rectangle() - .frame(height: 1) - .foregroundColor(Theme.Colors.textSecondary) - } - - if let privacy = viewModel.config.agreement.privacyPolicyURL { - NavigationLink { - WebBrowser( - url: privacy.absoluteString, - pageTitle: ProfileLocalization.privacy - ) - } label: { - HStack { - Text(ProfileLocalization.privacy) - Spacer() - Image(systemName: "chevron.right") - } - } - .buttonStyle(PlainButtonStyle()) - .foregroundColor(.primary) - .accessibilityElement(children: .ignore) - .accessibilityLabel(ProfileLocalization.privacy) - - } - - if let faq = viewModel.config.faq { - Rectangle() - .frame(height: 1) - .foregroundColor(Theme.Colors.textSecondary) - NavigationLink { - WebBrowser( - url: faq.absoluteString, - pageTitle: "FAQ" - ) - } label: { - HStack { - Text("FAQ") - Spacer() - Image(systemName: "chevron.right") - } - } - .buttonStyle(PlainButtonStyle()) - .foregroundColor(.primary) - .accessibilityElement(children: .ignore) - .accessibilityLabel(ProfileLocalization.privacy) - } - - // MARK: Version - Rectangle() - .frame(height: 1) - .foregroundColor(Theme.Colors.textSecondary) - Button(action: { - viewModel.openAppStore() - }, label: { - HStack { - VStack(alignment: .leading, spacing: 0) { - HStack { - if viewModel.versionState == .updateRequired { - CoreAssets.warningFilled.swiftUIImage - .resizable() - .frame(width: 24, height: 24) - } - Text("\(ProfileLocalization.Settings.version) \(viewModel.currentVersion)") - } - switch viewModel.versionState { - case .actual: - HStack { - CoreAssets.checkmark.swiftUIImage - .renderingMode(.template) - .foregroundColor(.green) - Text(ProfileLocalization.Settings.upToDate) - .font(Theme.Fonts.labelMedium) - .foregroundStyle(Theme.Colors.textSecondary) - } - case .updateNeeded: - Text("\(ProfileLocalization.Settings.tapToUpdate) \(viewModel.latestVersion)") - .font(Theme.Fonts.labelMedium) - .foregroundStyle(Theme.Colors.accentColor) - case .updateRequired: - Text(ProfileLocalization.Settings.tapToInstall) - .font(Theme.Fonts.labelMedium) - .foregroundStyle(Theme.Colors.accentColor) - } - } - Spacer() - if viewModel.versionState != .actual { - Image(systemName: "arrow.up.circle") - .resizable() - .frame(width: 24, height: 24) - .foregroundStyle(Theme.Colors.accentColor) - } - - } - }).disabled(viewModel.versionState == .actual) - - }.cardStyle( - bgColor: Theme.Colors.textInputUnfocusedBackground, - strokeColor: .clear - ) - - // MARK: - Log out - VStack { - Button(action: { - viewModel.router.presentView(transitionStyle: .crossDissolve) { - AlertView( - alertTitle: ProfileLocalization.LogoutAlert.title, - alertMessage: ProfileLocalization.LogoutAlert.text, - positiveAction: CoreLocalization.Alert.accept, - onCloseTapped: { - viewModel.router.dismiss(animated: true) - }, - okTapped: { - viewModel.router.dismiss(animated: true) - Task { - await viewModel.logOut() - } - }, type: .logOut - ) - } - }, label: { - HStack { - Text(ProfileLocalization.logout) - Spacer() - Image(systemName: "rectangle.portrait.and.arrow.right") - } - }) - .accessibilityElement(children: .ignore) - .accessibilityLabel(ProfileLocalization.logout) - } - .foregroundColor(Theme.Colors.alert) - .cardStyle(bgColor: Theme.Colors.textInputUnfocusedBackground, - strokeColor: .clear) - .padding(.top, 24) - .padding(.bottom, 60) + if let updatedImage { + self.viewModel.updatedAvatar = updatedImage } - Spacer() } - } - }.accessibilityAction {} - .frameLimit(sizePortrait: 420) - .padding(.top, 8) - .onChange(of: settingsTapped, perform: { _ in - let userModel = viewModel.userModel ?? UserProfile() - viewModel.trackProfileEditClicked() - viewModel.router.showEditProfile( - userModel: userModel, - avatar: viewModel.updatedAvatar, - profileDidEdit: { updatedProfile, updatedImage in - if let updatedProfile { - self.viewModel.userModel = updatedProfile - } - if let updatedImage { - self.viewModel.updatedAvatar = updatedImage - } - } - ) - }) - .navigationBarHidden(false) - .navigationBarBackButtonHidden(false) - - // MARK: - Offline mode SnackBar - OfflineSnackBarView(connectivity: viewModel.connectivity, - reloadAction: { - await viewModel.getMyProfile(withProgress: false) + ) }) - + .navigationBarHidden(false) + .navigationBarBackButtonHidden(false) + + // MARK: - Offline mode SnackBar + OfflineSnackBarView( + connectivity: viewModel.connectivity, + reloadAction: { + await viewModel.getMyProfile(withProgress: false) + } + ) + // MARK: - Error Alert if viewModel.showError { VStack { Spacer() SnackBarView(message: viewModel.errorMessage) } - .padding(.bottom, viewModel.connectivity.isInternetAvaliable - ? 0 : OfflineSnackBarView.height) + .padding( + .bottom, + viewModel.connectivity.isInternetAvaliable + ? 0 : OfflineSnackBarView.height + ) .transition(.move(edge: .bottom)) .onAppear { doAfter(Theme.Timeout.snackbarMessageLongTimeout) { @@ -342,6 +88,149 @@ public struct ProfileView: View { .ignoresSafeArea() ) } + + private var progressBar: some View { + ProgressBar(size: 40, lineWidth: 8) + .padding(.top, 200) + .padding(.horizontal) + } + + private func content() -> some View { + VStack { + if viewModel.isShowProgress { + ProgressBar(size: 40, lineWidth: 8) + .padding(.top, 200) + .padding(.horizontal) + } else { + UserAvatar(url: viewModel.userModel?.avatarUrl ?? "", image: $viewModel.updatedAvatar) + .padding(.top, 30) + Text(viewModel.userModel?.name ?? "") + .font(Theme.Fonts.headlineSmall) + .padding(.top, 20) + Text("@\(viewModel.userModel?.username ?? "")") + .font(Theme.Fonts.labelLarge) + .padding(.top, 4) + .foregroundColor(Theme.Colors.textSecondary) + .padding(.bottom, 10) + profileInfo + VStack(alignment: .leading, spacing: 14) { + settings + ProfileSupportInfo(viewModel: viewModel) + logOutButton + } + Spacer() + } + } + } + + // MARK: - Profile Info + + @ViewBuilder + private var profileInfo: some View { + if viewModel.userModel?.yearOfBirth != 0 || viewModel.userModel?.shortBiography != "" { + VStack(alignment: .leading, spacing: 14) { + Text(ProfileLocalization.info) + .padding(.horizontal, 24) + .font(Theme.Fonts.labelLarge) + + VStack(alignment: .leading, spacing: 16) { + if viewModel.userModel?.yearOfBirth != 0 { + HStack { + Text(ProfileLocalization.Edit.Fields.yearOfBirth) + .foregroundColor(Theme.Colors.textSecondary) + Text(String(viewModel.userModel?.yearOfBirth ?? 0)) + } + } + if let bio = viewModel.userModel?.shortBiography, bio != "" { + HStack(alignment: .top) { + Text(ProfileLocalization.bio + " ") + .foregroundColor(Theme.Colors.textSecondary) + + Text(bio) + } + } + } + .accessibilityElement(children: .ignore) + .accessibilityLabel( + (viewModel.userModel?.yearOfBirth != 0 ? + ProfileLocalization.Edit.Fields.yearOfBirth + String(viewModel.userModel?.yearOfBirth ?? 0) : + "") + + (viewModel.userModel?.shortBiography != nil ? + ProfileLocalization.bio + (viewModel.userModel?.shortBiography ?? "") : + "") + ) + .cardStyle( + bgColor: Theme.Colors.textInputUnfocusedBackground, + strokeColor: .clear + ) + }.padding(.bottom, 16) + } + } + + // MARK: - Settings + + @ViewBuilder + private var settings: some View { + Text(ProfileLocalization.settings) + .padding(.horizontal, 24) + .font(Theme.Fonts.labelLarge) + VStack(alignment: .leading, spacing: 27) { + Button(action: { + viewModel.trackProfileVideoSettingsClicked() + viewModel.router.showSettings() + }, label: { + HStack { + Text(ProfileLocalization.settingsVideo) + Spacer() + Image(systemName: "chevron.right") + } + }) + + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(ProfileLocalization.settingsVideo) + .cardStyle( + bgColor: Theme.Colors.textInputUnfocusedBackground, + strokeColor: .clear + ) + } + + // MARK: - Log out + + private var logOutButton: some View { + VStack { + Button(action: { + viewModel.router.presentView(transitionStyle: .crossDissolve) { + AlertView( + alertTitle: ProfileLocalization.LogoutAlert.title, + alertMessage: ProfileLocalization.LogoutAlert.text, + positiveAction: CoreLocalization.Alert.accept, + onCloseTapped: { + viewModel.router.dismiss(animated: true) + }, + okTapped: { + viewModel.router.dismiss(animated: true) + Task { + await viewModel.logOut() + } + }, type: .logOut + ) + } + }, label: { + HStack { + Text(ProfileLocalization.logout) + Spacer() + Image(systemName: "rectangle.portrait.and.arrow.right") + } + }) + .accessibilityElement(children: .ignore) + .accessibilityLabel(ProfileLocalization.logout) + } + .foregroundColor(Theme.Colors.alert) + .cardStyle(bgColor: Theme.Colors.textInputUnfocusedBackground, + strokeColor: .clear) + .padding(.top, 24) + .padding(.bottom, 60) + } } #if DEBUG @@ -353,12 +242,12 @@ struct ProfileView_Previews: PreviewProvider { analytics: ProfileAnalyticsMock(), config: ConfigMock(), connectivity: Connectivity()) - + ProfileView(viewModel: vm, settingsTapped: .constant(false)) .preferredColorScheme(.light) .previewDisplayName("DiscoveryView Light") .loadFonts() - + ProfileView(viewModel: vm, settingsTapped: .constant(false)) .preferredColorScheme(.dark) .previewDisplayName("DiscoveryView Dark") @@ -368,10 +257,10 @@ struct ProfileView_Previews: PreviewProvider { #endif struct UserAvatar: View { - + private var url: URL? @Binding private var image: UIImage? - + init(url: String, image: Binding) { if let rightUrl = URL(string: url) { self.url = rightUrl @@ -380,7 +269,7 @@ struct UserAvatar: View { } self._image = image } - + var body: some View { ZStack { Circle() diff --git a/Profile/Profile/SwiftGen/Strings.swift b/Profile/Profile/SwiftGen/Strings.swift index 8ff79c726..19d48126f 100644 --- a/Profile/Profile/SwiftGen/Strings.swift +++ b/Profile/Profile/SwiftGen/Strings.swift @@ -14,8 +14,14 @@ public enum ProfileLocalization { public static let bio = ProfileLocalization.tr("Localizable", "BIO", fallback: "Bio:") /// Contact support public static let contact = ProfileLocalization.tr("Localizable", "CONTACT", fallback: "Contact support") + /// Cookie Policy + public static let cookiePolicy = ProfileLocalization.tr("Localizable", "COOKIE_POLICY", fallback: "Cookie Policy") + /// Do Not Sell my Personal Information + public static let doNotSellInformation = ProfileLocalization.tr("Localizable", "DO_NOT_SELL_INFORMATION", fallback: "Do Not Sell my Personal Information") /// Edit profile public static let editProfile = ProfileLocalization.tr("Localizable", "EDIT_PROFILE", fallback: "Edit profile") + /// FAQ + public static let faq = ProfileLocalization.tr("Localizable", "FAQ", fallback: "FAQ") /// full profile public static let fullProfile = ProfileLocalization.tr("Localizable", "FULL_PROFILE", fallback: "full profile") /// Profile info @@ -24,8 +30,8 @@ public enum ProfileLocalization { public static let limitedProfile = ProfileLocalization.tr("Localizable", "LIMITED_PROFILE", fallback: "limited profile") /// Log out public static let logout = ProfileLocalization.tr("Localizable", "LOGOUT", fallback: "Log out") - /// Privacy and policy - public static let privacy = ProfileLocalization.tr("Localizable", "PRIVACY", fallback: "Privacy and policy") + /// Privacy policy + public static let privacy = ProfileLocalization.tr("Localizable", "PRIVACY", fallback: "Privacy policy") /// Settings public static let settings = ProfileLocalization.tr("Localizable", "SETTINGS", fallback: "Settings") /// Video settings diff --git a/Profile/Profile/en.lproj/Localizable.strings b/Profile/Profile/en.lproj/Localizable.strings index c626255de..2c0b2c664 100644 --- a/Profile/Profile/en.lproj/Localizable.strings +++ b/Profile/Profile/en.lproj/Localizable.strings @@ -16,7 +16,11 @@ "SUPPORT_INFO" = "Support info"; "CONTACT" = "Contact support"; "TERMS" = "Terms of use"; -"PRIVACY" = "Privacy and policy"; +"PRIVACY" = "Privacy policy"; +"COOKIE_POLICY" = "Cookie Policy"; +"DO_NOT_SELL_INFORMATION" = "Do Not Sell my Personal Information"; +"FAQ" = "FAQ"; + "LOGOUT" = "Log out"; "SWITCH_TO" = "Switch to"; "FULL_PROFILE" = "full profile"; diff --git a/Profile/Profile/uk.lproj/Localizable.strings b/Profile/Profile/uk.lproj/Localizable.strings index 8b5df15fd..4b629d0c0 100644 --- a/Profile/Profile/uk.lproj/Localizable.strings +++ b/Profile/Profile/uk.lproj/Localizable.strings @@ -17,6 +17,9 @@ "CONTACT" = "Cлужби підтримки"; "TERMS" = "Умови використання"; "PRIVACY" = "Політика конфіденційності"; +"COOKIE_POLICY" = "Cookie Policy"; +"DO_NOT_SELL_INFORMATION" = "Do Not Sell my Personal Information"; +"FAQ" = "FAQ"; "LOGOUT" = "Вийти"; "SWITCH_TO" = "Переключити на"; "FULL_PROFILE" = "повний профіль"; From 4aa242afa278cb8a50dc7319e8798b45479b9af6 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 13 Dec 2023 13:13:53 +0100 Subject: [PATCH 04/15] chore: add folder subviews to profile --- Profile/Profile.xcodeproj/project.pbxproj | 10 +++++++++- .../Profile/{ => Subviews}/ProfileSupportInfo.swift | 0 2 files changed, 9 insertions(+), 1 deletion(-) rename Profile/Profile/Presentation/Profile/{ => Subviews}/ProfileSupportInfo.swift (100%) diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index bca6eaddf..0e16ab3a3 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -137,10 +137,10 @@ 0203DC3D29AE79F80017BD05 /* Profile */ = { isa = PBXGroup; children = ( + BAD9CA402B29D6CD00DE790A /* Subviews */, 02D0FD072AD695E10020D752 /* UserProfile */, 021D924528DC634300ACC565 /* ProfileView.swift */, 021D925128DC918D00ACC565 /* ProfileViewModel.swift */, - BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfo.swift */, ); path = Profile; sourceTree = ""; @@ -345,6 +345,14 @@ path = ../Pods; sourceTree = ""; }; + BAD9CA402B29D6CD00DE790A /* Subviews */ = { + isa = PBXGroup; + children = ( + BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfo.swift */, + ); + path = Subviews; + sourceTree = ""; + }; C456081FB065DCEDAB8119E4 /* Frameworks */ = { isa = PBXGroup; children = ( diff --git a/Profile/Profile/Presentation/Profile/ProfileSupportInfo.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift similarity index 100% rename from Profile/Profile/Presentation/Profile/ProfileSupportInfo.swift rename to Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift From 267e26995c1f638ddc9ff2809c8d317b0aa67c24 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 13 Dec 2023 15:19:29 +0100 Subject: [PATCH 05/15] chore: add support language to support urls --- .../Config/AgreementConfig.swift | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/Core/Core/Configuration/Config/AgreementConfig.swift b/Core/Core/Configuration/Config/AgreementConfig.swift index 823bda9ba..86762b5aa 100644 --- a/Core/Core/Configuration/Config/AgreementConfig.swift +++ b/Core/Core/Configuration/Config/AgreementConfig.swift @@ -12,6 +12,7 @@ private enum AgreementKeys: String { case tosURL = "TOS_URL" case cookiePolicyURL = "COOKIE_POLICY_URL" case dataSellContentURL = "DATA_SELL_CONSENT_URL" + case supportedLanguages = "SUPPORTED_LANGUAGES" } public class AgreementConfig: NSObject { @@ -19,14 +20,44 @@ public class AgreementConfig: NSObject { public var tosURL: URL? public var cookiePolicyURL: URL? public var dataSellContentURL: URL? + public var supportedLanguages: [String]? init(dictionary: [String: AnyObject]) { - privacyPolicyURL = (dictionary[AgreementKeys.privacyPolicyURL.rawValue] as? String).flatMap(URL.init) - tosURL = (dictionary[AgreementKeys.tosURL.rawValue] as? String).flatMap(URL.init) + supportedLanguages = dictionary[AgreementKeys.supportedLanguages.rawValue] as? [String] cookiePolicyURL = (dictionary[AgreementKeys.cookiePolicyURL.rawValue] as? String).flatMap(URL.init) dataSellContentURL = (dictionary[AgreementKeys.dataSellContentURL.rawValue] as? String).flatMap(URL.init) + super.init() + + if let tosURL = dictionary[AgreementKeys.tosURL.rawValue] as? String { + self.tosURL = URL(string: completePath(url: tosURL)) + } + + if let privacyPolicyURL = dictionary[AgreementKeys.privacyPolicyURL.rawValue] as? String { + self.privacyPolicyURL = URL(string: completePath(url: privacyPolicyURL)) + } } + + private func completePath(url: String) -> String { + let langCode = Locale.current.languageCode ?? "" + if supportedLanguages?.contains(langCode) == false { + return url + } + + let URL = URL(string: url) + let host = URL?.host ?? "" + let components = url.components(separatedBy: host) + + if components.count != 2 { + return url + } + + if let firstComponent = components.first, let lastComponent = components.last { + return "\(firstComponent)\(host)/\(langCode)\(lastComponent)" + } + + return url + } } private let key = "AGREEMENT_URLS" From ce4b6e921984a4bb37da7d7f5131179f405f398a Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 13 Dec 2023 15:27:50 +0100 Subject: [PATCH 06/15] chore: add create button --- .../Profile/Subviews/ProfileSupportInfo.swift | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift index 0350cf4ef..a7a572a3a 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift @@ -12,7 +12,7 @@ import Core struct ProfileSupportInfo: View { struct LinkViewModel { - let URL: URL + let url: URL let title: String } @@ -38,29 +38,21 @@ struct ProfileSupportInfo: View { } @ViewBuilder - private func supportInfo(support: URL) -> some View { - Button { - viewModel.trackEmailSupportClicked() - UIApplication.shared.open(support) - } label: { - HStack { - Text(ProfileLocalization.contact) - Spacer() - Image(systemName: "chevron.right") - } - } - .foregroundColor(.primary) - .accessibilityElement(children: .ignore) - .accessibilityLabel(ProfileLocalization.supportInfo) - Rectangle() - .frame(height: 1) - .foregroundColor(Theme.Colors.textSecondary) + private func supportInfo(url: URL) -> some View { + button( + linkViewModel: .init( + url: url, + title: ProfileLocalization.contact + ), + isEmailSupport: true + ) + } private func terms(url: URL) -> some View { navigationLink( viewModel: .init( - URL: url, + url: url, title: ProfileLocalization.terms ) ) @@ -69,7 +61,7 @@ struct ProfileSupportInfo: View { private func privacy(url: URL) -> some View { navigationLink( viewModel: .init( - URL: url, + url: url, title: ProfileLocalization.privacy ) ) @@ -78,7 +70,7 @@ struct ProfileSupportInfo: View { private func cookiePolicy(url: URL) -> some View { navigationLink( viewModel: .init( - URL: url, + url: url, title: ProfileLocalization.cookiePolicy ) ) @@ -87,16 +79,16 @@ struct ProfileSupportInfo: View { private func dataSellContent(url: URL) -> some View { navigationLink( viewModel: .init( - URL: url, + url: url, title: ProfileLocalization.doNotSellInformation ) ) } private func faq(url: URL) -> some View { - navigationLink( - viewModel: .init( - URL: url, + button( + linkViewModel: .init( + url: url, title: ProfileLocalization.faq ) ) @@ -106,7 +98,7 @@ struct ProfileSupportInfo: View { private func navigationLink(viewModel: LinkViewModel) -> some View { NavigationLink { WebBrowser( - url: viewModel.URL.absoluteString, + url: viewModel.url.absoluteString, pageTitle: viewModel.title ) } label: { @@ -125,6 +117,26 @@ struct ProfileSupportInfo: View { .foregroundColor(Theme.Colors.textSecondary) } + @ViewBuilder + private func button(linkViewModel: LinkViewModel, isEmailSupport: Bool = false) -> some View { + Button { + if isEmailSupport { viewModel.trackEmailSupportClicked() } + UIApplication.shared.open(linkViewModel.url) + } label: { + HStack { + Text(linkViewModel.title) + Spacer() + Image(systemName: "chevron.right") + } + } + .foregroundColor(.primary) + .accessibilityElement(children: .ignore) + .accessibilityLabel(linkViewModel.title) + Rectangle() + .frame(height: 1) + .foregroundColor(Theme.Colors.textSecondary) + } + @ViewBuilder private var version: some View { Button(action: { From 88d1a4ccb543db603742295d870c2060540bbb73 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 13 Dec 2023 15:38:49 +0100 Subject: [PATCH 07/15] chore: add tests --- Core/Core/Configuration/Config/AgreementConfig.swift | 3 ++- Core/Core/Configuration/Config/Config.swift | 5 ++++- Core/CoreTests/Configuration/ConfigTests.swift | 8 +++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Core/Core/Configuration/Config/AgreementConfig.swift b/Core/Core/Configuration/Config/AgreementConfig.swift index 86762b5aa..244397a50 100644 --- a/Core/Core/Configuration/Config/AgreementConfig.swift +++ b/Core/Core/Configuration/Config/AgreementConfig.swift @@ -40,7 +40,8 @@ public class AgreementConfig: NSObject { private func completePath(url: String) -> String { let langCode = Locale.current.languageCode ?? "" - if supportedLanguages?.contains(langCode) == false { + if let supportedLanguages = supportedLanguages, + !supportedLanguages.contains(langCode) { return url } diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 1edd94685..75c6dc7c4 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -161,7 +161,10 @@ public class ConfigMock: Config { "WHATS_NEW_ENABLED": false, "AGREEMENT_URLS": [ "PRIVACY_POLICY_URL": "https://www.example.com/privacy", - "TOS_URL": "https://www.example.com/tos" + "TOS_URL": "https://www.example.com/tos", + "DATA_SELL_CONSENT_URL": "https://www.example.com/sell", + "COOKIE_POLICY_URL": "https://www.example.com/cookie", + "SUPPORTED_LANGUAGES": ["es"] ], "GOOGLE": [ "ENABLED": true, diff --git a/Core/CoreTests/Configuration/ConfigTests.swift b/Core/CoreTests/Configuration/ConfigTests.swift index 694ee6734..aa33a9205 100644 --- a/Core/CoreTests/Configuration/ConfigTests.swift +++ b/Core/CoreTests/Configuration/ConfigTests.swift @@ -18,7 +18,10 @@ class ConfigTests: XCTestCase { "WHATS_NEW_ENABLED": true, "AGREEMENT_URLS": [ "PRIVACY_POLICY_URL": "https://www.example.com/privacy", - "TOS_URL": "https://www.example.com/tos" + "TOS_URL": "https://www.example.com/tos", + "DATA_SELL_CONSENT_URL": "https://www.example.com/sell", + "COOKIE_POLICY_URL": "https://www.example.com/cookie", + "SUPPORTED_LANGUAGES": ["es"] ], "FIREBASE": [ "ENABLED": true, @@ -73,6 +76,9 @@ class ConfigTests: XCTestCase { XCTAssertEqual(config.agreement.privacyPolicyURL, URL(string: "https://www.example.com/privacy")) XCTAssertEqual(config.agreement.tosURL, URL(string: "https://www.example.com/tos")) + XCTAssertEqual(config.agreement.cookiePolicyURL, URL(string: "https://www.example.com/cookie")) + XCTAssertEqual(config.agreement.dataSellContentURL, URL(string: "https://www.example.com/sell")) + XCTAssertEqual(config.agreement.supportedLanguages, ["es"]) } func testFirebaseConfigInitialization() { From 713bd704e231f81beb8dfd40110102e1191f15b4 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 13 Dec 2023 16:13:08 +0100 Subject: [PATCH 08/15] chore: fix language local --- Core/Core/Configuration/Config/AgreementConfig.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Core/Configuration/Config/AgreementConfig.swift b/Core/Core/Configuration/Config/AgreementConfig.swift index 244397a50..046cec01f 100644 --- a/Core/Core/Configuration/Config/AgreementConfig.swift +++ b/Core/Core/Configuration/Config/AgreementConfig.swift @@ -39,7 +39,7 @@ public class AgreementConfig: NSObject { } private func completePath(url: String) -> String { - let langCode = Locale.current.languageCode ?? "" + let langCode = String(Locale.preferredLanguages.first?.prefix(2) ?? "") if let supportedLanguages = supportedLanguages, !supportedLanguages.contains(langCode) { return url From aa72677fefd84c03a58a1f0409f9a9f5fa84ba59 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 14 Dec 2023 12:22:23 +0100 Subject: [PATCH 09/15] chore: changes PR feedback --- Core/Core.xcodeproj/project.pbxproj | 8 +++- .../Config/AgreementConfig.swift | 38 +++++++++++-------- .../Configuration/AgreementConfigTests.swift | 31 +++++++++++++++ .../CoreTests/Configuration/ConfigTests.swift | 10 ----- .../Profile/Subviews/ProfileSupportInfo.swift | 1 - 5 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 Core/CoreTests/Configuration/AgreementConfigTests.swift diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 80bc8fd5d..5e7252c90 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -118,7 +118,6 @@ 0770DE5F28D0B22C006D8A5D /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE5E28D0B22C006D8A5D /* Strings.swift */; }; 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; - BAAD62C62AFCF00B000E6103 /* CustomDisclosureGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */; }; BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA30427D2B20B299009B64B7 /* SocialAuthError.swift */; }; BA76135C2B21BC7300B599B7 /* SocialAuthResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */; }; BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */; }; @@ -128,6 +127,8 @@ BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */; }; BA8FA66E2AD59E7D00EA029A /* FacebookAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */; }; BA8FA6702AD59EA300EA029A /* MicrosoftAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */; }; + BAAD62C62AFCF00B000E6103 /* CustomDisclosureGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */; }; + BAD9CA422B2B140100DE790A /* AgreementConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA412B2B140100DE790A /* AgreementConfigTests.swift */; }; BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */; }; BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */; }; BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */; }; @@ -289,7 +290,6 @@ 3B74C6685E416657F3C5F5A8 /* Pods-App-Core.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.releaseprod.xcconfig"; sourceTree = ""; }; 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; - BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisclosureGroup.swift; sourceTree = ""; }; BA30427D2B20B299009B64B7 /* SocialAuthError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthError.swift; sourceTree = ""; }; BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthResponse.swift; sourceTree = ""; }; BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLog.swift; sourceTree = ""; }; @@ -298,6 +298,8 @@ BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthProvider.swift; sourceTree = ""; }; BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookAuthProvider.swift; sourceTree = ""; }; BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftAuthProvider.swift; sourceTree = ""; }; + BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisclosureGroup.swift; sourceTree = ""; }; + BAD9CA412B2B140100DE790A /* AgreementConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreementConfigTests.swift; sourceTree = ""; }; BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtension.swift; sourceTree = ""; }; BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookConfig.swift; sourceTree = ""; }; BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftConfig.swift; sourceTree = ""; }; @@ -718,6 +720,7 @@ isa = PBXGroup; children = ( E09179FC2B0F204D002AB695 /* ConfigTests.swift */, + BAD9CA412B2B140100DE790A /* AgreementConfigTests.swift */, ); path = Configuration; sourceTree = ""; @@ -902,6 +905,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BAD9CA422B2B140100DE790A /* AgreementConfigTests.swift in Sources */, E09179FD2B0F204E002AB695 /* ConfigTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Core/Core/Configuration/Config/AgreementConfig.swift b/Core/Core/Configuration/Config/AgreementConfig.swift index 046cec01f..b790f2cca 100644 --- a/Core/Core/Configuration/Config/AgreementConfig.swift +++ b/Core/Core/Configuration/Config/AgreementConfig.swift @@ -39,26 +39,32 @@ public class AgreementConfig: NSObject { } private func completePath(url: String) -> String { - let langCode = String(Locale.preferredLanguages.first?.prefix(2) ?? "") - if let supportedLanguages = supportedLanguages, - !supportedLanguages.contains(langCode) { - return url - } + let langCode: String + if #available(iOS 16, *) { + langCode = Locale.current.language.languageCode?.identifier ?? "" + } else { + langCode = Locale.current.languageCode ?? "" + } + + if let supportedLanguages = supportedLanguages, + !supportedLanguages.contains(langCode) { + return url + } - let URL = URL(string: url) - let host = URL?.host ?? "" - let components = url.components(separatedBy: host) + let URL = URL(string: url) + let host = URL?.host ?? "" + let components = url.components(separatedBy: host) - if components.count != 2 { - return url - } + if components.count != 2 { + return url + } - if let firstComponent = components.first, let lastComponent = components.last { - return "\(firstComponent)\(host)/\(langCode)\(lastComponent)" - } + if let firstComponent = components.first, let lastComponent = components.last { + return "\(firstComponent)\(host)/\(langCode)\(lastComponent)" + } - return url - } + return url + } } private let key = "AGREEMENT_URLS" diff --git a/Core/CoreTests/Configuration/AgreementConfigTests.swift b/Core/CoreTests/Configuration/AgreementConfigTests.swift new file mode 100644 index 000000000..70d0d0a97 --- /dev/null +++ b/Core/CoreTests/Configuration/AgreementConfigTests.swift @@ -0,0 +1,31 @@ +// +// AgreementConfigTests.swift +// CoreTests +// +// Created by Eugene Yatsenko on 14.12.2023. +// + +import XCTest +@testable import Core + +class AgreementConfigTests: XCTestCase { + private lazy var properties: [String: Any] = [ + "AGREEMENT_URLS": [ + "PRIVACY_POLICY_URL": "https://www.example.com/privacy", + "TOS_URL": "https://www.example.com/tos", + "DATA_SELL_CONSENT_URL": "https://www.example.com/sell", + "COOKIE_POLICY_URL": "https://www.example.com/cookie", + "SUPPORTED_LANGUAGES": ["es"] + ] + ] + + func testAgreementConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertEqual(config.agreement.privacyPolicyURL, URL(string: "https://www.example.com/privacy")) + XCTAssertEqual(config.agreement.tosURL, URL(string: "https://www.example.com/tos")) + XCTAssertEqual(config.agreement.cookiePolicyURL, URL(string: "https://www.example.com/cookie")) + XCTAssertEqual(config.agreement.dataSellContentURL, URL(string: "https://www.example.com/sell")) + XCTAssertEqual(config.agreement.supportedLanguages, ["es"]) + } +} diff --git a/Core/CoreTests/Configuration/ConfigTests.swift b/Core/CoreTests/Configuration/ConfigTests.swift index aa33a9205..32638e484 100644 --- a/Core/CoreTests/Configuration/ConfigTests.swift +++ b/Core/CoreTests/Configuration/ConfigTests.swift @@ -71,16 +71,6 @@ class ConfigTests: XCTestCase { XCTAssertTrue(config.features.whatNewEnabled) } - func testAgreementConfigInitialization() { - let config = Config(properties: properties) - - XCTAssertEqual(config.agreement.privacyPolicyURL, URL(string: "https://www.example.com/privacy")) - XCTAssertEqual(config.agreement.tosURL, URL(string: "https://www.example.com/tos")) - XCTAssertEqual(config.agreement.cookiePolicyURL, URL(string: "https://www.example.com/cookie")) - XCTAssertEqual(config.agreement.dataSellContentURL, URL(string: "https://www.example.com/sell")) - XCTAssertEqual(config.agreement.supportedLanguages, ["es"]) - } - func testFirebaseConfigInitialization() { let config = Config(properties: properties) diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift index a7a572a3a..62bf0cb3a 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift @@ -37,7 +37,6 @@ struct ProfileSupportInfo: View { ) } - @ViewBuilder private func supportInfo(url: URL) -> some View { button( linkViewModel: .init( From 3e0946e7e7e13b4fe4812f5353b658e12b9c853a Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 14 Dec 2023 12:27:50 +0100 Subject: [PATCH 10/15] chore: change strings --- Profile/Profile/en.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Profile/Profile/en.lproj/Localizable.strings b/Profile/Profile/en.lproj/Localizable.strings index 2c0b2c664..17f980848 100644 --- a/Profile/Profile/en.lproj/Localizable.strings +++ b/Profile/Profile/en.lproj/Localizable.strings @@ -17,8 +17,8 @@ "CONTACT" = "Contact support"; "TERMS" = "Terms of use"; "PRIVACY" = "Privacy policy"; -"COOKIE_POLICY" = "Cookie Policy"; -"DO_NOT_SELL_INFORMATION" = "Do Not Sell my Personal Information"; +"COOKIE_POLICY" = "Cookie policy"; +"DO_NOT_SELL_INFORMATION" = "Do not sell my personal information"; "FAQ" = "FAQ"; "LOGOUT" = "Log out"; From 0f3e68fd1724884f624b47a8079efcf928ee48f1 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 14 Dec 2023 12:31:21 +0100 Subject: [PATCH 11/15] chore: change strings --- Profile/Profile/SwiftGen/Strings.swift | 8 ++++---- Profile/Profile/uk.lproj/Localizable.strings | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Profile/Profile/SwiftGen/Strings.swift b/Profile/Profile/SwiftGen/Strings.swift index 19d48126f..6cf17b659 100644 --- a/Profile/Profile/SwiftGen/Strings.swift +++ b/Profile/Profile/SwiftGen/Strings.swift @@ -14,10 +14,10 @@ public enum ProfileLocalization { public static let bio = ProfileLocalization.tr("Localizable", "BIO", fallback: "Bio:") /// Contact support public static let contact = ProfileLocalization.tr("Localizable", "CONTACT", fallback: "Contact support") - /// Cookie Policy - public static let cookiePolicy = ProfileLocalization.tr("Localizable", "COOKIE_POLICY", fallback: "Cookie Policy") - /// Do Not Sell my Personal Information - public static let doNotSellInformation = ProfileLocalization.tr("Localizable", "DO_NOT_SELL_INFORMATION", fallback: "Do Not Sell my Personal Information") + /// Cookie policy + public static let cookiePolicy = ProfileLocalization.tr("Localizable", "COOKIE_POLICY", fallback: "Cookie policy") + /// Do not sell my personal information + public static let doNotSellInformation = ProfileLocalization.tr("Localizable", "DO_NOT_SELL_INFORMATION", fallback: "Do not sell my personal information") /// Edit profile public static let editProfile = ProfileLocalization.tr("Localizable", "EDIT_PROFILE", fallback: "Edit profile") /// FAQ diff --git a/Profile/Profile/uk.lproj/Localizable.strings b/Profile/Profile/uk.lproj/Localizable.strings index 4b629d0c0..fc4e142ef 100644 --- a/Profile/Profile/uk.lproj/Localizable.strings +++ b/Profile/Profile/uk.lproj/Localizable.strings @@ -17,8 +17,8 @@ "CONTACT" = "Cлужби підтримки"; "TERMS" = "Умови використання"; "PRIVACY" = "Політика конфіденційності"; -"COOKIE_POLICY" = "Cookie Policy"; -"DO_NOT_SELL_INFORMATION" = "Do Not Sell my Personal Information"; +"COOKIE_POLICY" = "Cookie policy"; +"DO_NOT_SELL_INFORMATION" = "Do not sell my personal information"; "FAQ" = "FAQ"; "LOGOUT" = "Вийти"; "SWITCH_TO" = "Переключити на"; From ae22f8f1dc9333f8f1b391e44530878da8334a8a Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 14 Dec 2023 12:56:21 +0100 Subject: [PATCH 12/15] chore: changes from PR feedback --- .../Configuration/AgreementConfigTests.swift | 27 ++++++++++++------- Profile/Profile.xcodeproj/project.pbxproj | 8 +++--- .../Presentation/Profile/ProfileView.swift | 2 +- ...nfo.swift => ProfileSupportInfoView.swift} | 12 +++++++-- Profile/Profile/SwiftGen/Strings.swift | 4 +++ Profile/Profile/en.lproj/Localizable.strings | 2 ++ Profile/Profile/uk.lproj/Localizable.strings | 2 ++ 7 files changed, 40 insertions(+), 17 deletions(-) rename Profile/Profile/Presentation/Profile/Subviews/{ProfileSupportInfo.swift => ProfileSupportInfoView.swift} (93%) diff --git a/Core/CoreTests/Configuration/AgreementConfigTests.swift b/Core/CoreTests/Configuration/AgreementConfigTests.swift index 70d0d0a97..9a30e84fb 100644 --- a/Core/CoreTests/Configuration/AgreementConfigTests.swift +++ b/Core/CoreTests/Configuration/AgreementConfigTests.swift @@ -9,23 +9,30 @@ import XCTest @testable import Core class AgreementConfigTests: XCTestCase { + + private let privacy = "https://www.example.com/privacy" + private let tos = "https://www.example.com/tos" + private let dataSellContent = "https://www.example.com/sell" + private let cookie = "https://www.example.com/cookie" + private let supportedLanguages = ["es"] + private lazy var properties: [String: Any] = [ "AGREEMENT_URLS": [ - "PRIVACY_POLICY_URL": "https://www.example.com/privacy", - "TOS_URL": "https://www.example.com/tos", - "DATA_SELL_CONSENT_URL": "https://www.example.com/sell", - "COOKIE_POLICY_URL": "https://www.example.com/cookie", - "SUPPORTED_LANGUAGES": ["es"] + "PRIVACY_POLICY_URL": privacy, + "TOS_URL": tos, + "DATA_SELL_CONSENT_URL": dataSellContent, + "COOKIE_POLICY_URL": cookie, + "SUPPORTED_LANGUAGES": supportedLanguages ] ] func testAgreementConfigInitialization() { let config = Config(properties: properties) - XCTAssertEqual(config.agreement.privacyPolicyURL, URL(string: "https://www.example.com/privacy")) - XCTAssertEqual(config.agreement.tosURL, URL(string: "https://www.example.com/tos")) - XCTAssertEqual(config.agreement.cookiePolicyURL, URL(string: "https://www.example.com/cookie")) - XCTAssertEqual(config.agreement.dataSellContentURL, URL(string: "https://www.example.com/sell")) - XCTAssertEqual(config.agreement.supportedLanguages, ["es"]) + XCTAssertEqual(config.agreement.privacyPolicyURL, URL(string: privacy)) + XCTAssertEqual(config.agreement.tosURL, URL(string: tos)) + XCTAssertEqual(config.agreement.cookiePolicyURL, URL(string: cookie)) + XCTAssertEqual(config.agreement.dataSellContentURL, URL(string: dataSellContent)) + XCTAssertEqual(config.agreement.supportedLanguages, supportedLanguages) } } diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index 0e16ab3a3..f4ebc7e78 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -36,7 +36,7 @@ 02F3BFE7292539850051930C /* ProfileRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3BFE6292539850051930C /* ProfileRouter.swift */; }; 0796C8C929B7905300444B05 /* ProfileBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0796C8C829B7905300444B05 /* ProfileBottomSheet.swift */; }; 25B36FF48C1307888A3890DA /* Pods_App_Profile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BEA369C38362C1A91A012F70 /* Pods_App_Profile.framework */; }; - BAD9CA3F2B29BF5C00DE790A /* ProfileSupportInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfo.swift */; }; + BAD9CA3F2B29BF5C00DE790A /* ProfileSupportInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfoView.swift */; }; E8264C634DD8AD314ECE8905 /* Pods_App_Profile_ProfileTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C85ADF87135E03275A980E07 /* Pods_App_Profile_ProfileTests.framework */; }; /* End PBXBuildFile section */ @@ -95,7 +95,7 @@ 9D125F82E0EAC4B6C0CE280F /* Pods-App-Profile-ProfileTests.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile-ProfileTests.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Profile-ProfileTests/Pods-App-Profile-ProfileTests.releaseprod.xcconfig"; sourceTree = ""; }; A9F98CD65D1F657EB8F9EA59 /* Pods-App-Profile.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Profile/Pods-App-Profile.releasedev.xcconfig"; sourceTree = ""; }; B3F05DC21379BD4FE1AFCCF1 /* Pods-App-Profile.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Profile/Pods-App-Profile.debugprod.xcconfig"; sourceTree = ""; }; - BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSupportInfo.swift; sourceTree = ""; }; + BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSupportInfoView.swift; sourceTree = ""; }; BEA369C38362C1A91A012F70 /* Pods_App_Profile.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Profile.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C85ADF87135E03275A980E07 /* Pods_App_Profile_ProfileTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Profile_ProfileTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F52EFE7DC07BE68B9A302DAF /* Pods-App-Profile.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Profile.debug.xcconfig"; path = "Target Support Files/Pods-App-Profile/Pods-App-Profile.debug.xcconfig"; sourceTree = ""; }; @@ -348,7 +348,7 @@ BAD9CA402B29D6CD00DE790A /* Subviews */ = { isa = PBXGroup; children = ( - BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfo.swift */, + BAD9CA3E2B29BF5C00DE790A /* ProfileSupportInfoView.swift */, ); path = Subviews; sourceTree = ""; @@ -574,7 +574,7 @@ 021D924E28DC88BB00ACC565 /* ProfileRepository.swift in Sources */, 0796C8C929B7905300444B05 /* ProfileBottomSheet.swift in Sources */, 021D924C28DC884A00ACC565 /* ProfileEndpoint.swift in Sources */, - BAD9CA3F2B29BF5C00DE790A /* ProfileSupportInfo.swift in Sources */, + BAD9CA3F2B29BF5C00DE790A /* ProfileSupportInfoView.swift in Sources */, 020306C82932B13F000949EA /* EditProfileView.swift in Sources */, 0262149229AE57A1008BD75A /* DeleteAccountView.swift in Sources */, 02D0FD092AD698380020D752 /* UserProfileView.swift in Sources */, diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index 428c403fe..0450da9c8 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -115,7 +115,7 @@ public struct ProfileView: View { profileInfo VStack(alignment: .leading, spacing: 14) { settings - ProfileSupportInfo(viewModel: viewModel) + ProfileSupportInfoView(viewModel: viewModel) logOutButton } Spacer() diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift similarity index 93% rename from Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift rename to Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift index 62bf0cb3a..7c6954bee 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfo.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift @@ -9,7 +9,7 @@ import SwiftUI import Theme import Core -struct ProfileSupportInfo: View { +struct ProfileSupportInfoView: View { struct LinkViewModel { let url: URL @@ -119,7 +119,15 @@ struct ProfileSupportInfo: View { @ViewBuilder private func button(linkViewModel: LinkViewModel, isEmailSupport: Bool = false) -> some View { Button { - if isEmailSupport { viewModel.trackEmailSupportClicked() } + guard UIApplication.shared.canOpenURL(linkViewModel.url) else { + viewModel.errorMessage = isEmailSupport ? + ProfileLocalization.Error.cannotSendEmail : + CoreLocalization.Error.unknownError + return + } + if isEmailSupport { + viewModel.trackEmailSupportClicked() + } UIApplication.shared.open(linkViewModel.url) } label: { HStack { diff --git a/Profile/Profile/SwiftGen/Strings.swift b/Profile/Profile/SwiftGen/Strings.swift index 6cf17b659..52019109a 100644 --- a/Profile/Profile/SwiftGen/Strings.swift +++ b/Profile/Profile/SwiftGen/Strings.swift @@ -103,6 +103,10 @@ public enum ProfileLocalization { public static let yearOfBirth = ProfileLocalization.tr("Localizable", "EDIT.FIELDS.YEAR_OF_BIRTH", fallback: "Year of birth") } } + public enum Error { + /// Cannot send email. It seems your email client is not set up. + public static let cannotSendEmail = ProfileLocalization.tr("Localizable", "ERROR.CANNOT_SEND_EMAIL", fallback: "Cannot send email. It seems your email client is not set up.") + } public enum LogoutAlert { /// Are you sure you want to log out? public static let text = ProfileLocalization.tr("Localizable", "LOGOUT_ALERT.TEXT", fallback: "Are you sure you want to log out?") diff --git a/Profile/Profile/en.lproj/Localizable.strings b/Profile/Profile/en.lproj/Localizable.strings index 17f980848..80feaed87 100644 --- a/Profile/Profile/en.lproj/Localizable.strings +++ b/Profile/Profile/en.lproj/Localizable.strings @@ -77,3 +77,5 @@ "SETTINGS.UP_TO_DATE" = "Up-to-date"; "SETTINGS.TAP_TO_UPDATE" = "Tap to update to version"; "SETTINGS.TAP_TO_INSTALL" = "Tap to install required app update"; + +"ERROR.CANNOT_SEND_EMAIL" = "Cannot send email. It seems your email client is not set up."; diff --git a/Profile/Profile/uk.lproj/Localizable.strings b/Profile/Profile/uk.lproj/Localizable.strings index fc4e142ef..b3f91c924 100644 --- a/Profile/Profile/uk.lproj/Localizable.strings +++ b/Profile/Profile/uk.lproj/Localizable.strings @@ -76,3 +76,5 @@ "SETTINGS.UP_TO_DATE" = "Оновлено"; "SETTINGS.TAP_TO_UPDATE" = "Клацніть, щоб оновити до версії"; "SETTINGS.TAP_TO_INSTALL" = "Клацніть, щоб встановити обов'язкове оновлення програми"; + +"ERROR.CANNOT_SEND_EMAIL" = "Cannot send email. It seems your email client is not set up."; From 5895a08c61e11c2991a4be38901b3d83aa8da5cd Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 14 Dec 2023 13:22:19 +0100 Subject: [PATCH 13/15] chore: add progress to web browser --- Core/Core/View/Base/WebBrowser.swift | 59 ++++++++++++------- .../Subviews/ProfileSupportInfoView.swift | 3 +- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/Core/Core/View/Base/WebBrowser.swift b/Core/Core/View/Base/WebBrowser.swift index ef04c50c5..a24827073 100644 --- a/Core/Core/View/Base/WebBrowser.swift +++ b/Core/Core/View/Base/WebBrowser.swift @@ -13,40 +13,55 @@ public struct WebBrowser: View { var url: String var pageTitle: String + var progressDisabled: Bool @State private var isShowProgress: Bool = true @Environment(\.presentationMode) var presentationMode - public init(url: String, pageTitle: String) { + public init(url: String, pageTitle: String, progressDisabled: Bool = true) { self.url = url self.pageTitle = pageTitle + self.progressDisabled = progressDisabled } public var body: some View { - ZStack(alignment: .top) { - Theme.Colors.background.ignoresSafeArea() - // MARK: - Page name - VStack(alignment: .center) { - NavigationBar(title: pageTitle, - leftButtonAction: { presentationMode.wrappedValue.dismiss() }) - - // MARK: - Page Body - VStack { - ZStack(alignment: .top) { -// NavigationView { - WebView( - viewModel: .init(url: url, baseURL: ""), - isLoading: $isShowProgress, - refreshCookies: {} - ) - -// } - }.navigationBarTitle(Text("")) // Needed for hide navBar on ios 14, 15 - .navigationBarHidden(true) - .ignoresSafeArea() + GeometryReader { proxy in + ZStack(alignment: .center) { + Theme.Colors.background.ignoresSafeArea() + // MARK: - Page name + webView(proxy: proxy) + if isShowProgress, !progressDisabled { + HStack(alignment: .center) { + ProgressBar( + size: 40, + lineWidth: 8 + ) + .padding(20) + } + .frame(maxWidth: .infinity) } } + .navigationBarTitle(Text("")) + .navigationBarHidden(true) + .ignoresSafeArea() } } + + private func webView(proxy: GeometryProxy) -> some View { + VStack(alignment: .center) { + NavigationBar( + title: pageTitle, + leftButtonAction: { presentationMode.wrappedValue.dismiss() } + ) + // MARK: - Page Body + WebView( + viewModel: .init(url: url, baseURL: ""), + isLoading: $isShowProgress, + refreshCookies: {} + ) + } + .padding(.top, proxy.safeAreaInsets.top) + .padding(.bottom, proxy.safeAreaInsets.bottom) + } } struct WebBrowser_Previews: PreviewProvider { diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift index 7c6954bee..efbd20aba 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift @@ -98,7 +98,8 @@ struct ProfileSupportInfoView: View { NavigationLink { WebBrowser( url: viewModel.url.absoluteString, - pageTitle: viewModel.title + pageTitle: viewModel.title, + progressDisabled: false ) } label: { HStack { From 7196b2b75e0ebf0414ca8b2fdd87c61c19a64aab Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 14 Dec 2023 13:58:22 +0100 Subject: [PATCH 14/15] chore: show always progress when web page loading --- Core/Core/View/Base/WebBrowser.swift | 11 ++++------- .../Profile/Subviews/ProfileSupportInfoView.swift | 3 +-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Core/Core/View/Base/WebBrowser.swift b/Core/Core/View/Base/WebBrowser.swift index a24827073..5972c7df1 100644 --- a/Core/Core/View/Base/WebBrowser.swift +++ b/Core/Core/View/Base/WebBrowser.swift @@ -10,26 +10,24 @@ import WebKit import Theme public struct WebBrowser: View { - + var url: String var pageTitle: String - var progressDisabled: Bool + @State private var isShowProgress: Bool = true @Environment(\.presentationMode) var presentationMode - public init(url: String, pageTitle: String, progressDisabled: Bool = true) { + public init(url: String, pageTitle: String) { self.url = url self.pageTitle = pageTitle - self.progressDisabled = progressDisabled } public var body: some View { GeometryReader { proxy in ZStack(alignment: .center) { Theme.Colors.background.ignoresSafeArea() - // MARK: - Page name webView(proxy: proxy) - if isShowProgress, !progressDisabled { + if isShowProgress { HStack(alignment: .center) { ProgressBar( size: 40, @@ -52,7 +50,6 @@ public struct WebBrowser: View { title: pageTitle, leftButtonAction: { presentationMode.wrappedValue.dismiss() } ) - // MARK: - Page Body WebView( viewModel: .init(url: url, baseURL: ""), isLoading: $isShowProgress, diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift index efbd20aba..7c6954bee 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift @@ -98,8 +98,7 @@ struct ProfileSupportInfoView: View { NavigationLink { WebBrowser( url: viewModel.url.absoluteString, - pageTitle: viewModel.title, - progressDisabled: false + pageTitle: viewModel.title ) } label: { HStack { From fec2fc2187edb1229ef4d3e2e90a11ecfcccf6ed Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 14 Dec 2023 14:09:43 +0100 Subject: [PATCH 15/15] chore: add show progress param to web browser --- Core/Core/View/Base/WebBrowser.swift | 16 +++++++++------- .../Subviews/ProfileSupportInfoView.swift | 3 ++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Core/Core/View/Base/WebBrowser.swift b/Core/Core/View/Base/WebBrowser.swift index 5972c7df1..dafd884fc 100644 --- a/Core/Core/View/Base/WebBrowser.swift +++ b/Core/Core/View/Base/WebBrowser.swift @@ -11,15 +11,17 @@ import Theme public struct WebBrowser: View { - var url: String - var pageTitle: String - - @State private var isShowProgress: Bool = true + @State private var isLoading: Bool = true @Environment(\.presentationMode) var presentationMode + + private var url: String + private var pageTitle: String + private var showProgress: Bool - public init(url: String, pageTitle: String) { + public init(url: String, pageTitle: String, showProgress: Bool = false) { self.url = url self.pageTitle = pageTitle + self.showProgress = showProgress } public var body: some View { @@ -27,7 +29,7 @@ public struct WebBrowser: View { ZStack(alignment: .center) { Theme.Colors.background.ignoresSafeArea() webView(proxy: proxy) - if isShowProgress { + if isLoading, showProgress { HStack(alignment: .center) { ProgressBar( size: 40, @@ -52,7 +54,7 @@ public struct WebBrowser: View { ) WebView( viewModel: .init(url: url, baseURL: ""), - isLoading: $isShowProgress, + isLoading: $isLoading, refreshCookies: {} ) } diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift index 7c6954bee..39485ab72 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift @@ -98,7 +98,8 @@ struct ProfileSupportInfoView: View { NavigationLink { WebBrowser( url: viewModel.url.absoluteString, - pageTitle: viewModel.title + pageTitle: viewModel.title, + showProgress: true ) } label: { HStack {