From 98113eca4785f3111d91c4a38abe4735219cd357 Mon Sep 17 00:00:00 2001 From: Mustafa Merza <mustafa.merza95@gmail.com> Date: Sun, 21 Jul 2024 11:31:45 +0300 Subject: [PATCH] - Added settings screen. - Added language setting to change app's language. - Added app theme setting to change app's appearance. --- MiniScanner.xcodeproj/project.pbxproj | 28 ++++ .../Extensions/String+StringKeys.swift | 23 +++- .../Data/Model/SupportedColorScheme.swift | 16 ++- .../Common/Data/Model/SupportedLanguage.swift | 60 ++++++--- .../Repositories/SettingsRepository.swift | 8 ++ .../Settings/Presentation/SettingsView.swift | 94 +++++++++++++ .../SettingsViewCoordinator.swift | 27 ++++ .../Presentation/SettingsViewModel.swift | 98 ++++++++++++++ .../Localization/Localizable.xcstrings | 124 ++++++++++++++++++ 9 files changed, 456 insertions(+), 22 deletions(-) create mode 100644 MiniScanner/Features/Settings/Presentation/SettingsView.swift create mode 100644 MiniScanner/Features/Settings/Presentation/SettingsViewCoordinator.swift create mode 100644 MiniScanner/Features/Settings/Presentation/SettingsViewModel.swift diff --git a/MiniScanner.xcodeproj/project.pbxproj b/MiniScanner.xcodeproj/project.pbxproj index d976490..46ebe13 100644 --- a/MiniScanner.xcodeproj/project.pbxproj +++ b/MiniScanner.xcodeproj/project.pbxproj @@ -200,6 +200,9 @@ 67807FA02C4934BD00D1F168 /* View+LayoutChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67807F9F2C4934BD00D1F168 /* View+LayoutChanges.swift */; }; 678BD70D2C4CF18300833DA5 /* CustomMenuPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678BD70C2C4CF18300833DA5 /* CustomMenuPickerItem.swift */; }; 678BD70F2C4CF1A000833DA5 /* CustomMenuPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678BD70E2C4CF1A000833DA5 /* CustomMenuPicker.swift */; }; + 678BD7152C4CF1EB00833DA5 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678BD7102C4CF1EB00833DA5 /* SettingsView.swift */; }; + 678BD7162C4CF1EB00833DA5 /* SettingsViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678BD7112C4CF1EB00833DA5 /* SettingsViewCoordinator.swift */; }; + 678BD7172C4CF1EB00833DA5 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678BD7122C4CF1EB00833DA5 /* SettingsViewModel.swift */; }; B827E5196CC419E773B843E1 /* Pods_MiniScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9A37DC9F9A8E3AF632DFB98 /* Pods_MiniScanner.framework */; }; EC0CF1FE254D8BBF00888722 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0CF1FD254D8BBF00888722 /* AppDelegate.swift */; }; EC0CF200254D8BBF00888722 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0CF1FF254D8BBF00888722 /* SceneDelegate.swift */; }; @@ -450,6 +453,9 @@ 67807F9F2C4934BD00D1F168 /* View+LayoutChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+LayoutChanges.swift"; sourceTree = "<group>"; }; 678BD70C2C4CF18300833DA5 /* CustomMenuPickerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomMenuPickerItem.swift; sourceTree = "<group>"; }; 678BD70E2C4CF1A000833DA5 /* CustomMenuPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomMenuPicker.swift; sourceTree = "<group>"; }; + 678BD7102C4CF1EB00833DA5 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; + 678BD7112C4CF1EB00833DA5 /* SettingsViewCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewCoordinator.swift; sourceTree = "<group>"; }; + 678BD7122C4CF1EB00833DA5 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; }; E8AF4FB39674DF589D719DCF /* Pods-MiniScanner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MiniScanner.release.xcconfig"; path = "Target Support Files/Pods-MiniScanner/Pods-MiniScanner.release.xcconfig"; sourceTree = "<group>"; }; E9A37DC9F9A8E3AF632DFB98 /* Pods_MiniScanner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MiniScanner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC0CF1FA254D8BBF00888722 /* MiniScanner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MiniScanner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1020,6 +1026,7 @@ 672C46AF2C47E8D700497EF0 /* Features */ = { isa = PBXGroup; children = ( + 678BD7142C4CF1EB00833DA5 /* Settings */, 672C46AE2C47E8D700497EF0 /* Common */, ); path = Features; @@ -1136,6 +1143,24 @@ path = View; sourceTree = "<group>"; }; + 678BD7132C4CF1EB00833DA5 /* Presentation */ = { + isa = PBXGroup; + children = ( + 678BD7102C4CF1EB00833DA5 /* SettingsView.swift */, + 678BD7112C4CF1EB00833DA5 /* SettingsViewCoordinator.swift */, + 678BD7122C4CF1EB00833DA5 /* SettingsViewModel.swift */, + ); + path = Presentation; + sourceTree = "<group>"; + }; + 678BD7142C4CF1EB00833DA5 /* Settings */ = { + isa = PBXGroup; + children = ( + 678BD7132C4CF1EB00833DA5 /* Presentation */, + ); + path = Settings; + sourceTree = "<group>"; + }; 98E49D3F46C8E62718825860 /* Pods */ = { isa = PBXGroup; children = ( @@ -1541,6 +1566,7 @@ 53014FA82C11A8E80071CE39 /* CIRectangleDetector.swift in Sources */, EC8A9AD5254DB76000F9AF99 /* BaseNavigationViewController.swift in Sources */, 535983EB2C142B16003EB6ED /* UILabel+Extensions.swift in Sources */, + 678BD7172C4CF1EB00833DA5 /* SettingsViewModel.swift in Sources */, EC8A9B10254DC6DD00F9AF99 /* PDFManager.swift in Sources */, 5399964B2C26E86700671340 /* FlashButton.m in Sources */, 5399968C2C27130000671340 /* ConstraintInsetTarget.swift in Sources */, @@ -1583,6 +1609,7 @@ 53014FA32C11A8E80071CE39 /* ZoomGestureController.swift in Sources */, 5399964C2C26E86700671340 /* CDCameraView.m in Sources */, 5399969B2C27130000671340 /* ConstraintView+Extensions.swift in Sources */, + 678BD7152C4CF1EB00833DA5 /* SettingsView.swift in Sources */, 53E3A2092C19D17100C9B95E /* PreviewImageViewController.swift in Sources */, 53E7D3362C1B00880025A1D3 /* FSPageViewTransformer.swift in Sources */, EC70252A254E066400BE1958 /* SettingsViewController+Safari.swift in Sources */, @@ -1631,6 +1658,7 @@ 53E7D3382C1B00880025A1D3 /* FSPagerViewLayoutAttributes.swift in Sources */, 53014F8E2C11A8E80071CE39 /* ImageScannerController.swift in Sources */, 539996A42C27130000671340 /* ConstraintMakerRelatable.swift in Sources */, + 678BD7162C4CF1EB00833DA5 /* SettingsViewCoordinator.swift in Sources */, 539996942C27130000671340 /* Typealiases.swift in Sources */, 5399964D2C26E86700671340 /* CDImageRectangleDetector.m in Sources */, 5399964E2C26E86700671340 /* CDZoomView.m in Sources */, diff --git a/MiniScanner/Extensions/String+StringKeys.swift b/MiniScanner/Extensions/String+StringKeys.swift index 9c56420..2ee2fb1 100644 --- a/MiniScanner/Extensions/String+StringKeys.swift +++ b/MiniScanner/Extensions/String+StringKeys.swift @@ -53,11 +53,22 @@ extension String { static let yes = "yes" static let no = "no" - static let fileManager = "file_manager" - static let folders = "folders" - static let folderFiles = "folder_files" - static let noFilesToShow = "no_files_to_show" + static let fileManager = "file_manager" + static let folders = "folders" + static let folderFiles = "folder_files" + static let noFilesToShow = "no_files_to_show" static let folderName = "folder_name" - static let preview = "preview" - static let done = "done" + static let preview = "preview" + static let done = "done" + + static let settings = "settings" + + static let language = "language" + static let appTheme = "app_theme" + + static let appearence = "appearence" + static let deviceLanguage = "device_language" + static let deviceTheme = "device_theme" + static let darkMode = "dark_mode" + static let lightMode = "light_mode" } diff --git a/MiniScanner/Features/Common/Data/Model/SupportedColorScheme.swift b/MiniScanner/Features/Common/Data/Model/SupportedColorScheme.swift index 25773e7..39381e3 100644 --- a/MiniScanner/Features/Common/Data/Model/SupportedColorScheme.swift +++ b/MiniScanner/Features/Common/Data/Model/SupportedColorScheme.swift @@ -8,11 +8,25 @@ import Foundation -enum SupportedColorScheme: String, CaseIterable { +enum SupportedColorScheme: String, CaseIterable, CustomMenuPickerItem { case device case light case dark + + var id: Self { self } + + var displayedName: String { + switch self { + case .device: + .deviceTheme.localized + case .light: + .lightMode.localized + case .dark: + .darkMode.localized + } + } + } extension SupportedColorScheme { diff --git a/MiniScanner/Features/Common/Data/Model/SupportedLanguage.swift b/MiniScanner/Features/Common/Data/Model/SupportedLanguage.swift index 3883c3b..a8e9bc0 100644 --- a/MiniScanner/Features/Common/Data/Model/SupportedLanguage.swift +++ b/MiniScanner/Features/Common/Data/Model/SupportedLanguage.swift @@ -8,8 +8,9 @@ import Foundation import LanguageManager_iOS +import SwiftUI -enum SupportedLanguage: CaseIterable { +enum SupportedLanguage: CaseIterable, CustomMenuPickerItem { case device case english @@ -18,11 +19,11 @@ enum SupportedLanguage: CaseIterable { var flag: String { switch self { case .device: - return SupportedLanguage.currentLanguage.flag + SupportedLanguage.currentLanguage.flag case .english: - return "🇺🇸" + "🇺🇸" case .arabic: - return "🇰🇼" + "🇰🇼" } } @@ -31,12 +32,41 @@ enum SupportedLanguage: CaseIterable { } var isRTL: Bool { - LanguageManager.shared.isRightToLeft + switch self { + case .device: + LanguageManager.shared.isRightToLeft + case .english: + false + case .arabic: + true + } } var layoutDirection: LayoutDirection { isRTL ? .rightToLeft : .leftToRight } + + var semanticContentAttribute: UISemanticContentAttribute { + isRTL ? .forceRightToLeft : .forceLeftToRight + } + + var id: Self { self } + + var displayedName: String { + switch self { + case .device: + .deviceLanguage.localized + case .english: + displayNameForLanguage(.english) + case .arabic: + displayNameForLanguage(.arabic) + } + } + + func displayNameForLanguage(_ language: SupportedLanguage) -> String { + let locale = NSLocale(localeIdentifier: SupportedLanguage.currentLanguage.codeName) + return locale.displayName(forKey: .identifier, value: language.codeName) ?? "" + } } extension SupportedLanguage { @@ -48,11 +78,11 @@ extension SupportedLanguage { var codeName: String { switch self { case .device: - return Languages.deviceLanguage.rawValue + Languages.deviceLanguage.rawValue case .english: - return Languages.en.rawValue + Languages.en.rawValue case .arabic: - return Languages.ar.rawValue + Languages.ar.rawValue } } } @@ -62,25 +92,25 @@ extension SupportedLanguage { static func from(language: Languages) -> Self { switch language { case .ar: - return .arabic + .arabic case .en: - return .english + .english case .deviceLanguage: - return .device + .device default: - return .device + .device } } var language: Languages { switch self { case .device: - return LanguageManager.shared.deviceLanguage ?? .en + LanguageManager.shared.deviceLanguage ?? .en case .english: - return .en + .en case .arabic: - return .ar + .ar } } } diff --git a/MiniScanner/Features/Common/Data/Repositories/SettingsRepository.swift b/MiniScanner/Features/Common/Data/Repositories/SettingsRepository.swift index 908de5a..199927e 100644 --- a/MiniScanner/Features/Common/Data/Repositories/SettingsRepository.swift +++ b/MiniScanner/Features/Common/Data/Repositories/SettingsRepository.swift @@ -101,6 +101,9 @@ extension SettingsRepository { } private func handleUISettings() { + + handleScrollViewAppearace() + handleNavigationBarAppearace() handleToolBarAppearace() @@ -108,6 +111,11 @@ extension SettingsRepository { handleButtonAppearace() } + private func handleScrollViewAppearace() { + + UIScrollView.appearance().bounces = false + } + private func handleNavigationBarAppearace() { let navigationBarAppearace = UINavigationBar.appearance() diff --git a/MiniScanner/Features/Settings/Presentation/SettingsView.swift b/MiniScanner/Features/Settings/Presentation/SettingsView.swift new file mode 100644 index 0000000..3c4c4e8 --- /dev/null +++ b/MiniScanner/Features/Settings/Presentation/SettingsView.swift @@ -0,0 +1,94 @@ +// +// SettingsView.swift +// MiniScanner +// +// Created by Mustafa Merza on 7/18/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import SwiftUI + +struct SettingsView: View { + + @StateObject var viewModel: SettingsViewModel2 + + init(coordinator: SettingsViewCoordinating) { + self._viewModel = StateObject(wrappedValue: SettingsViewModel2(coordinator: coordinator)) + } + + var body: some View { + MainView(viewModel: viewModel) { + VStack { + + if viewModel.refreshView { + settingsForm + } + else { + settingsForm + } + } + .environment(\.layoutDirection, viewModel.layoutDirection) + } + } + + private var settingsForm: some View { + + Form { + + Section { + + language + + colorScheme + + } header: { + Text(String.appearence.localized) + } + } + } +} + +extension SettingsView { + + private var language: some View { + CustomMenuPicker(selectedItem: $viewModel.selectedLangauge, + items: viewModel.languages, + label: { languageLabel }) + } + + private var languageLabel: some View { + HStack { + + Text(String.language.localized) + + Spacer() + + Text(viewModel.selectedLangauge.displayedName) + + Text(viewModel.selectedLangauge.flag) + } + .foregroundStyle(Color.mainText) + } + + private var colorScheme: some View { + CustomMenuPicker(selectedItem: $viewModel.selectedColorScheme, + items: viewModel.colorShemes, + label: { colorSchemeLabel }) + } + + private var colorSchemeLabel: some View { + HStack { + + Text(String.appTheme.localized) + + Spacer() + + Text(viewModel.selectedColorScheme.displayedName) + } + .foregroundStyle(Color.mainText) + } +} + +#Preview { + SettingsView(coordinator: SettingsViewCoordinator(navigationController: UINavigationController())) +} diff --git a/MiniScanner/Features/Settings/Presentation/SettingsViewCoordinator.swift b/MiniScanner/Features/Settings/Presentation/SettingsViewCoordinator.swift new file mode 100644 index 0000000..8f1578a --- /dev/null +++ b/MiniScanner/Features/Settings/Presentation/SettingsViewCoordinator.swift @@ -0,0 +1,27 @@ +// +// SettingsViewCoordinator.swift +// MiniScanner +// +// Created by Mustafa Merza on 7/18/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import Foundation + +protocol SettingsViewCoordinating { + var navigationController: UINavigationController { get set } +} + +class SettingsViewCoordinator: SettingsViewCoordinating, MainCoordinator { + + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + let view = SettingsView(coordinator: self) + coordinateToView(view, title: .settings.localized) + } +} diff --git a/MiniScanner/Features/Settings/Presentation/SettingsViewModel.swift b/MiniScanner/Features/Settings/Presentation/SettingsViewModel.swift new file mode 100644 index 0000000..a3d0f29 --- /dev/null +++ b/MiniScanner/Features/Settings/Presentation/SettingsViewModel.swift @@ -0,0 +1,98 @@ +// +// SettingsViewModel.swift +// MiniScanner +// +// Created by Mustafa Merza on 7/18/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import SwiftUI + +final class SettingsViewModel2: MainViewModel { + + @Published var layoutDirection: LayoutDirection = .leftToRight + + @Published var refreshView: Bool = false + + @Published var languages: [SupportedLanguage] = [] + @Published var colorShemes: [SupportedColorScheme] = [] + + @Published var selectedLangauge: SupportedLanguage = .device { + didSet { + changeLanguage(to: selectedLangauge) + } + } + + @Published var selectedColorScheme: SupportedColorScheme = .device { + didSet { + changeColorShceme(to: selectedColorScheme) + } + } + + @Inject var getLanguagesUseCase: GetLanguagesUseCase + @Inject var getLanguageUseCase: GetLanguageUseCase + @Inject var changeLanguageUseCase: ChangeLanguageUseCase + + @Inject var getColorSchemeUseCase: GetColorSchemeUseCase + @Inject var changeColorSchemeUseCase: ChangeColorSchemeUseCase + + private let coordinator: SettingsViewCoordinating + + init(coordinator: SettingsViewCoordinating) { + self.coordinator = coordinator + } +} + +extension SettingsViewModel2 { + + func onAppear() { + fetchSettings() + } +} + +extension SettingsViewModel2 { + + private func fetchSettings() { + languages = getLanguagesUseCase.execute() + colorShemes = SupportedColorScheme.allCases + + selectedLangauge = getLanguageUseCase.execute() + selectedColorScheme = getColorSchemeUseCase.execute() + } +} + +extension SettingsViewModel2 { + + private func changeLanguage(to language: SupportedLanguage) { + changeLanguageUseCase.execute(to: language) + layoutDirection = language.layoutDirection + + refreshLocalization() + + if language == .english { + refreshView.toggle() + } + } + + private func changeColorShceme(to colorScheme: SupportedColorScheme) { + changeColorSchemeUseCase.execute(to: colorScheme) + } +} + +extension SettingsViewModel2 { + + private func refreshLocalization() { + let navigationController = coordinator.navigationController + + let settings = navigationController.topViewController! + let filesManager = navigationController.viewControllers[0] + + settings.navigationItem.title = .settings.localized + filesManager.navigationItem.title = .fileManager.localized + + let semanticContentAttribute = SupportedLanguage.currentLanguage.semanticContentAttribute + + navigationController.tabBarController?.tabBar.semanticContentAttribute = semanticContentAttribute + navigationController.navigationBar.semanticContentAttribute = semanticContentAttribute + } +} diff --git a/MiniScanner/Supporting Files/Localization/Localizable.xcstrings b/MiniScanner/Supporting Files/Localization/Localizable.xcstrings index 70deb08..768326e 100644 --- a/MiniScanner/Supporting Files/Localization/Localizable.xcstrings +++ b/MiniScanner/Supporting Files/Localization/Localizable.xcstrings @@ -35,6 +35,34 @@ } } }, + "app_theme" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App theme" + } + } + } + }, + "appearence" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مظهر التطبيق" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Appearence" + } + } + } + }, "are_you_sure" : { "extractionState" : "manual", "localizations" : { @@ -103,6 +131,23 @@ } } }, + "dark_mode" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الوضع الليلي" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dark mode" + } + } + } + }, "delete" : { "extractionState" : "manual", "localizations" : { @@ -154,6 +199,34 @@ } } }, + "device_language" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لغة الجهاز" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Device language" + } + } + } + }, + "device_theme" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Device Theme" + } + } + } + }, "discard" : { "extractionState" : "manual", "localizations" : { @@ -335,6 +408,40 @@ } } }, + "language" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اللغة" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Language" + } + } + } + }, + "light_mode" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الوضع النهاري" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Light mode" + } + } + } + }, "more" : { "extractionState" : "manual", "localizations" : { @@ -482,6 +589,23 @@ } } }, + "settings" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الإعدادات" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings" + } + } + } + }, "telegram" : { "extractionState" : "manual", "localizations" : { -- GitLab