diff --git a/Scribe.xcodeproj/project.pbxproj b/Scribe.xcodeproj/project.pbxproj index 0aee5713..5479ceb8 100644 --- a/Scribe.xcodeproj/project.pbxproj +++ b/Scribe.xcodeproj/project.pbxproj @@ -869,6 +869,7 @@ D1E3851B2C977FD200DCE538 /* TranslationData.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = D1E3850F2C977FD100DCE538 /* TranslationData.sqlite */; }; D1F0367227AAE12200CD7921 /* InterfaceVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = D190B2492741B31F00705659 /* InterfaceVariables.swift */; }; D1F0367327AAE1B400CD7921 /* CommandVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = D190B2462741B24F00705659 /* CommandVariables.swift */; }; + E9202DF02F0FAA0C001590FC /* DownloadStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9202DEF2F0FAA0C001590FC /* DownloadStateManager.swift */; }; E93179A42F03AE78002ED334 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = E93179A32F03AE77002ED334 /* Localizable.xcstrings */; }; E937C5CF2E9FF94E00F94F99 /* TranslationData.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = D1E3850F2C977FD100DCE538 /* TranslationData.sqlite */; }; E96111482F04EC6B001E4F95 /* InstallationDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96111472F04EC62001E4F95 /* InstallationDownload.swift */; }; @@ -1216,7 +1217,7 @@ D1D8B23D2AE408C50070B817 /* French.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = French.entitlements; sourceTree = ""; }; D1E3850F2C977FD100DCE538 /* TranslationData.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = TranslationData.sqlite; sourceTree = ""; }; D1FF8ED12C6C282500EF50AC /* English.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = English.entitlements; sourceTree = ""; }; - E96111472F04EC62001E4F95 /* InstallationDownload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationDownload.swift; sourceTree = ""; }; + E9202DEF2F0FAA0C001590FC /* DownloadStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadStateManager.swift; sourceTree = ""; }; E93179A32F03AE77002ED334 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = i18n/i18n/Localizable.xcstrings; sourceTree = ""; }; E96111472F04EC62001E4F95 /* InstallationDownload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationDownload.swift; sourceTree = ""; }; E9CE5EA72F063D870068A930 /* DownloadDataScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadDataScreen.swift; sourceTree = ""; }; @@ -1384,6 +1385,7 @@ isa = PBXGroup; children = ( E96111472F04EC62001E4F95 /* InstallationDownload.swift */, + E9202DEF2F0FAA0C001590FC /* DownloadStateManager.swift */, E9CE5EA72F063D870068A930 /* DownloadDataScreen.swift */, 38BD213522D5907F00C6795D /* InstallationVC.swift */, ); @@ -2564,6 +2566,7 @@ D171946527AF31770038660B /* Conjugate.swift in Sources */, 147797B52A2CFB490044A53E /* SettingsViewController.swift in Sources */, 1406B7872A2DFCDD001DF45B /* AboutTableData.swift in Sources */, + E9202DF02F0FAA0C001590FC /* DownloadStateManager.swift in Sources */, E996498A2E98AC6000200F53 /* IDInterfaceVariables.swift in Sources */, D171940827AECCE50038660B /* PTCommandVariables.swift in Sources */, 3045396D293B9DDC003AE55B /* ToolTipViewDatasource.swift in Sources */, diff --git a/Scribe/Assets.xcassets/Colors/buttonGreen.colorset/Contents.json b/Scribe/Assets.xcassets/Colors/buttonGreen.colorset/Contents.json new file mode 100644 index 00000000..0b07d967 --- /dev/null +++ b/Scribe/Assets.xcassets/Colors/buttonGreen.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0x3D", + "green": "0xC5", + "red": "0x9B" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "0.260", + "blue": "0x45", + "green": "0xA0", + "red": "0x08" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Scribe/Assets.xcassets/Colors/buttonOrange.colorset/Contents.json b/Scribe/Assets.xcassets/Colors/buttonOrange.colorset/Contents.json new file mode 100644 index 00000000..ebc22156 --- /dev/null +++ b/Scribe/Assets.xcassets/Colors/buttonOrange.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0x0D", + "green": "0xAD", + "red": "0xFD" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0x03", + "green": "0x19", + "red": "0x2A" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Scribe/Assets.xcassets/Colors/lightTextDarkGreen.colorset/Contents.json b/Scribe/Assets.xcassets/Colors/lightTextDarkGreen.colorset/Contents.json new file mode 100644 index 00000000..829a5bd3 --- /dev/null +++ b/Scribe/Assets.xcassets/Colors/lightTextDarkGreen.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0x00", + "green": "0x00", + "red": "0x00" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "0x45", + "green": "0xA0", + "red": "0x08" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Scribe/Button/DownloadButton.swift b/Scribe/Button/DownloadButton.swift new file mode 100644 index 00000000..802a5ee0 --- /dev/null +++ b/Scribe/Button/DownloadButton.swift @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * Download Button for making calls for updated data for each language. + */ + +import SwiftUI + +enum ButtonState { + case ready + case downloading + case updated + case update + + var config: ButtonConfig { + switch self { + case .ready: + return ButtonConfig( + text: NSLocalizedString( + "i18n.app._global.download_data", + value: "Download data", + comment: "" + ), + icon: "icloud.and.arrow.down", + backgroundColor: Color("buttonOrange"), + foregroundColor: Color("lightTextDarkCTA") + ) + case .downloading: + return ButtonConfig( + text: NSLocalizedString( + "i18n.app.download.menu_ui.download_data.downloading", + value: "Downloading", + comment: "" + ), + icon: "arrow.clockwise.circle.fill", + backgroundColor: Color("buttonOrange"), + foregroundColor: Color("lightTextDarkCTA") + ) + case .updated: + return ButtonConfig( + text: NSLocalizedString( + "i18n.app.download.menu_ui.download_data.up_to_date", + value: "Up to date", + comment: "" + ), + icon: "checkmark.circle.fill", + backgroundColor: Color("buttonGreen"), + foregroundColor: Color("lightTextDarkGreen") + ) + case .update: + return ButtonConfig( + text: NSLocalizedString( + "i18n.app.download.menu_ui.update_data", + value: "Update data", + comment: "" + ), + icon: "icloud.and.arrow.down", + backgroundColor: Color("buttonOrange"), + foregroundColor: Color("lightTextDarkCTA") + ) + } + } +} + +struct ButtonConfig { + let text: String + let icon: String + let backgroundColor: Color + let foregroundColor: Color +} + +struct DownloadButton: View { + let state: ButtonState + let action: () -> Void + @Environment(\.colorScheme) var colorScheme + + var body: some View { + Button(action: action) { + HStack(spacing: 8) { + Text(state.config.text) + if state == .downloading { + ProgressView() + .tint(state.config.foregroundColor) + .scaleEffect(0.8) + } else { + Image(systemName: state.config.icon) + } + } + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(state.config.foregroundColor) + .frame(width: 120, height: 20) + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(state.config.backgroundColor) + .cornerRadius(6) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke( + colorScheme == .dark ? state.config.foregroundColor : Color.clear, + lineWidth: 1 + ) + ) + } + .animation(.easeInOut(duration: 0.2), value: state) + } +} diff --git a/Scribe/InstallationTab/DownloadDataScreen.swift b/Scribe/InstallationTab/DownloadDataScreen.swift index f8912df0..54717d41 100644 --- a/Scribe/InstallationTab/DownloadDataScreen.swift +++ b/Scribe/InstallationTab/DownloadDataScreen.swift @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later /** - * Download data screen. + * Download data UI for getting new data for keyboards. */ import SwiftUI @@ -31,6 +31,7 @@ struct RadioCircle: View { } struct UpdateDataCardView: View { + var languages: [Section] private let title = NSLocalizedString( "i18n.app.download.menu_ui.update_data", value: "Update data", @@ -56,18 +57,19 @@ struct UpdateDataCardView: View { .foregroundColor(.primary) VStack(alignment: .leading, spacing: 12) { - HStack { - Text(checkText) - .font(.body) - .foregroundColor(.primary) + if !languages.isEmpty { + HStack { + Text(checkText) + .font(.body) + .foregroundColor(.primary) - Spacer() + Spacer() - RadioCircle(isSelected: $isCheckNew) + RadioCircle(isSelected: $isCheckNew) + } + Divider() } - Divider() - Toggle(isOn: $isRegularUpdate) { HStack { Text(regularUpdateText) @@ -78,18 +80,218 @@ struct UpdateDataCardView: View { .padding() .background(Color(.systemBackground)) .cornerRadius(12) + .padding(.horizontal, 16) + } + } +} + +struct LanguageDownloadCard: View { + let language: String + let state: ButtonState + let action: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + Text(language) + .font(.body) + .foregroundColor(.primary) + + Spacer() + + DownloadButton( + state: state, + action: action + ) + } + } + } +} + +struct EmptyStateView: View { + private var noKeyboardText = NSLocalizedString( + "i18n.app.download.menu_ui.no_keyboards_installed", + value: "You currently do not have any Scribe keyboard installed. Please click the Install keyboards button below to install a Scribe keyboard and then come back to download the needed data.", + comment: "" + ) + + private var installText = NSLocalizedString( + "i18n.app.settings.button_install_keyboards", + value: "Install keyboards", + comment: "") + + func openSettingsApp() { + guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { + return + } + UIApplication.shared.open(settingsURL) + } + + var body: some View { + VStack(alignment: .leading, spacing: 24) { + Text(noKeyboardText) + .foregroundColor(.primary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + .background(Color(.systemBackground)) + .cornerRadius(12) + + CTAButton(title: installText, action: {openSettingsApp()}) + } + .padding(.horizontal, 16) + } +} + +struct LanguageListView: View { + var onNavigateToTranslationSource: ((String, String) -> Void)? + var languages: [Section] + + @ObservedObject private var stateManager = DownloadStateManager.shared + + private let title = NSLocalizedString( + "i18n.app.download.menu_ui.download_data.title", + value: "Select data to download", + comment: "" + ) + + private let allLanguagesText = NSLocalizedString( + "i18n.app.download.menu_ui.download_data.all_languages", + value: "All languages", + comment: "" + ) + + @State private var showConfirmDialog = false + @State private var targetLanguage = "" + @State private var selectedLanguageCode = "" + let userDefaults = UserDefaults(suiteName: "group.be.scri.userDefaultsContainer")! + + private func handleButtonClick(targetLang: String, langCode: String) { + targetLanguage = targetLang + selectedLanguageCode = langCode + let currentState = stateManager.downloadStates[langCode] ?? .ready + if currentState == .ready { + showConfirmDialog = true + } else { + stateManager.handleDownloadAction(key: langCode) + } + } + + var body: some View { + ZStack { + VStack(alignment: .leading, spacing: 6) { + Text(title) + .font(.system(size: 19, weight: .semibold)) + .foregroundColor(.primary) + if languages.isEmpty { + EmptyStateView() + } else { + VStack(spacing: 0) { + LanguageDownloadCard( + language: allLanguagesText, + state: stateManager.downloadStates["all"] ?? .ready, + action: { + handleButtonClick(targetLang: allLanguagesText, langCode: "all") + } + ) + + Divider() + .padding(.vertical, 8) + + ForEach(Array(languages.enumerated()), id: \.offset) { index, section in + let langCode: String = { + if case let .specificLang(code) = section.sectionState { + return code + } + return "" + }() + + LanguageDownloadCard( + language: section.sectionTitle, + state: stateManager.downloadStates[langCode] ?? .ready, + action: { + handleButtonClick(targetLang: section.sectionTitle, langCode: langCode) + } + ) + + if index < languages.count - 1 { + Divider() + .padding(.vertical, 8) + } + } + } + .padding() + .background(Color(.systemBackground)) + .cornerRadius(12) + .padding(.horizontal, 16) + } + } + + if showConfirmDialog { + confirmDialogView + } } } + + private var confirmDialogView: some View { + let languageCode = selectedLanguageCode.isEmpty ? "en" : selectedLanguageCode + let selectedSourceLang = userDefaults.string(forKey: languageCode + "TranslateLanguage") ?? "en" + let sourceLanguage = getKeyInDict(givenValue: selectedSourceLang, dict: languagesAbbrDict) + + let localizedSourceLanguage = NSLocalizedString( + "i18n.app._global." + sourceLanguage.lowercased(), + value: sourceLanguage, + comment: "" + ) + + return ConfirmTranslationSource( + infoText: NSLocalizedString( + "i18n.app.download.menu_ui.translation_source_tooltip.download_warning", + value: "The data you will download will allow you to translate from {source_language} to {target_language}. Do you want to change the language you'll translate from?", + comment: "" + ) + .replacingOccurrences(of: "{source_language}", with: localizedSourceLanguage) + .replacingOccurrences(of: "{target_language}", with: targetLanguage), + changeButtonText: NSLocalizedString( + "i18n.app.download.menu_ui.translation_source_tooltip.change_language", + value: "Change language", + comment: "" + ), + confirmButtonText: NSLocalizedString( + "i18n.app.download.menu_ui.translation_source_tooltip.use_source_language", + value: "Use {source_language}", + comment: "" + ) + .replacingOccurrences(of: "{source_language}", with: localizedSourceLanguage), + onDismiss: { + showConfirmDialog = false + }, + onChange: { + showConfirmDialog = false + onNavigateToTranslationSource?(selectedLanguageCode, targetLanguage) + }, + onConfirm: { + showConfirmDialog = false + stateManager.handleDownloadAction(key: selectedLanguageCode) + } + ) + } } struct DownloadDataScreen: View { + var onNavigateToTranslationSource: ((String, String) -> Void)? + @State private var languages = SettingsTableData.getInstalledKeyboardsSections() var body: some View { ScrollView { VStack(spacing: 20) { - UpdateDataCardView() + UpdateDataCardView(languages: languages) + LanguageListView(onNavigateToTranslationSource: onNavigateToTranslationSource, languages: languages) } .padding() .background(Color(UIColor.scribeAppBackground)) } + .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in + // Refresh when returning from Settings + languages = SettingsTableData.getInstalledKeyboardsSections() + } } } diff --git a/Scribe/InstallationTab/DownloadStateManager.swift b/Scribe/InstallationTab/DownloadStateManager.swift new file mode 100644 index 00000000..f29348de --- /dev/null +++ b/Scribe/InstallationTab/DownloadStateManager.swift @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * Manages data download states and actions. + */ + +import Foundation + +private let PLACEBO_SERVER_UPDATED_AT = "2026-01-8" +private let PLACEBO_LOCAL_UPDATED_AT = "2026-01-01" + +class DownloadStateManager: ObservableObject { + static let shared = DownloadStateManager() + + @Published var downloadStates: [String: ButtonState] = [:] + + private init() {} + + /** + * Returns true if server data is newer than local data. + */ + private func isUpdateAvailable(localUpdatedAt: String, serverUpdatedAt: String) -> Bool { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + + guard let localDate = dateFormatter.date(from: localUpdatedAt), + let serverDate = dateFormatter.date(from: serverUpdatedAt) else { + return false + } + + return serverDate > localDate + } + + /** + * Handles the download action based on the current state. + */ + func handleDownloadAction(key: String) { + let currentState = downloadStates[key] ?? .ready + + downloadStates[key] = switch currentState { + case .ready: + .downloading + case .downloading: + .updated + case .updated: + isUpdateAvailable(localUpdatedAt: PLACEBO_LOCAL_UPDATED_AT, + serverUpdatedAt: PLACEBO_SERVER_UPDATED_AT) ? .update : .updated + case .update: + .downloading + } + } +} diff --git a/Scribe/InstallationTab/InstallationVC.swift b/Scribe/InstallationTab/InstallationVC.swift index aaa938cd..5ae4ee6b 100644 --- a/Scribe/InstallationTab/InstallationVC.swift +++ b/Scribe/InstallationTab/InstallationVC.swift @@ -99,7 +99,21 @@ class InstallationVC: UIViewController { showTipCardView() showDownloadButton() showCTAButton() - // addPopupButton() + + NotificationCenter.default.addObserver( + self, + selector: #selector(handleNavigateToDownloadScreen), + name: NSNotification.Name("NavigateToDownloadScreen"), + object: nil + ) + } + + @objc private func handleNavigateToDownloadScreen() { + navigateToDownloadDataScreen() + } + + deinit { + NotificationCenter.default.removeObserver(self) } /// Includes a call to checkDarkModeSetColors to set brand colors and a call to set the UI for the app screen. @@ -382,7 +396,11 @@ extension InstallationVC { } private func navigateToDownloadDataScreen() { - let downloadDataView = DownloadDataScreen() + let downloadDataView = DownloadDataScreen( + onNavigateToTranslationSource: { [weak self] languageCode, languageName in + self?.navigateToTranslationSourceSelection(languageCode: languageCode, languageName: languageName) + } + ) let hostingController = UIHostingController(rootView: downloadDataView) hostingController.view.backgroundColor = scribeAppBackgroundColor @@ -416,103 +434,87 @@ extension InstallationVC { self.navigationController?.pushViewController(hostingController, animated: true) } - private func addPopupButton() { - let popupButton = UIButton(type: .system) - popupButton.setTitle("Open Popup", for: .normal) - popupButton.titleLabel?.font = UIFont.systemFont(ofSize: fontSize) - popupButton.backgroundColor = .systemBlue - popupButton.setTitleColor(.white, for: .normal) - popupButton.layer.cornerRadius = 10 - popupButton.translatesAutoresizingMaskIntoConstraints = false + private func navigateToTranslationSourceSelection(languageCode: String, languageName: String) { + guard let selectionVC = self.storyboard?.instantiateViewController( + identifier: "SelectionViewTemplateViewController" + ) as? SelectionViewTemplateViewController else { + return + } - popupButton.addTarget(self, action: #selector(showPopup), for: .touchUpInside) - view.addSubview(popupButton) + if let hostingController = self.navigationController?.viewControllers.last as? UIHostingController { + hostingController.navigationItem.backButtonTitle = NSLocalizedString( + "i18n.app._global." + languageName.lowercased(), + value: languageName, + comment: "" + ) + } - NSLayoutConstraint.activate([ - popupButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), - popupButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), - popupButton.widthAnchor.constraint(equalToConstant: 150), - popupButton.heightAnchor.constraint(equalToConstant: 44) - ]) - } + var translateData = SettingsTableData.translateLangSettingsData - @objc private func showPopup() { - var sourceLanguage = "English" - var destLanguage = "German" - var infoText = NSLocalizedString("i18n.app.download.menu_ui.translation_source_tooltip.download_warning", - value: "The data you will download will allow you to translate from \(sourceLanguage) to \(destLanguage). Do you want to change the language you'll translate from?", - comment: "") - var changeText = NSLocalizedString("i18n.app.download.menu_ui.translation_source_tooltip.change_language", - value: "Change language", - comment: "") - var confirmText = NSLocalizedString("i18n.app.download.menu_ui.translation_source_tooltip.use_source_language", - value: "Use \(sourceLanguage)", - comment: "") - - let popupView = ConfirmTranslationSource( - infoText: infoText, - changeButtonText: changeText, - confirmButtonText: confirmText, - onDismiss: {self.dismiss(animated: true)}, - onChange: { - self.dismiss(animated: true) { - if let translationLangController = self.storyboard?.instantiateViewController( - identifier: "SelectionViewTemplateViewController" - ) as? SelectionViewTemplateViewController { - - var data = SettingsTableData.translateLangSettingsData - let langCode = "de" - - // Remove the current keyboard language from translation. - let langCodeIndex = SettingsTableData.translateLangSettingsData[0].section.firstIndex(where: { s in - s.sectionState == .specificLang(langCode) - }) ?? -1 - if langCodeIndex >= 0 { - data[0].section.remove(at: langCodeIndex) - } - - let sectionTitle = getKeyInDict(givenValue: langCode, dict: languagesAbbrDict) - - let parentSection = Section( - sectionTitle: sectionTitle, - imageString: nil, - hasToggle: false, - hasNestedNavigation: true, - sectionState: .translateLang, - shortDescription: nil, - externalLink: false - ) - - translationLangController.configureTable( - for: data, - parentSection: parentSection, - langCode: langCode - ) - - translationLangController.edgesForExtendedLayout = .all - - // COPY the navigation bar appearance from Settings tab. - if let settingsNavController = self.tabBarController?.viewControllers?[1] as? UINavigationController { - // Copy all the styling from Settings' nav controller. - self.navigationController?.navigationBar.standardAppearance = settingsNavController.navigationBar.standardAppearance - self.navigationController?.navigationBar.scrollEdgeAppearance = settingsNavController.navigationBar.scrollEdgeAppearance - self.navigationController?.navigationBar.tintColor = settingsNavController.navigationBar.tintColor - self.navigationController?.navigationBar.barTintColor = settingsNavController.navigationBar.barTintColor - } - - self.navigationController?.setNavigationBarHidden(false, animated: false) - self.navigationController?.pushViewController(translationLangController, animated: true) - } - } - }, - onConfirm: {self.dismiss(animated: true)}, + // Remove the current keyboard language from translation options. + if let langCodeIndex = translateData[0].section.firstIndex(where: { s in + s.sectionState == .specificLang(languageCode) + }) { + translateData[0].section.remove(at: langCodeIndex) + } + let parentSection = Section( + sectionTitle: languageName, + imageString: nil, + hasToggle: false, + hasNestedNavigation: true, + sectionState: .translateLang, + shortDescription: nil, + externalLink: false ) - let hostingController = UIHostingController(rootView: popupView) - hostingController.modalPresentationStyle = .overFullScreen - hostingController.modalTransitionStyle = .crossDissolve - hostingController.view.backgroundColor = .clear - present(hostingController, animated: true) + selectionVC.configureTable(for: translateData, parentSection: parentSection, langCode: languageCode) + selectionVC.edgesForExtendedLayout = .all + + // Copy navigation bar appearance from Settings tab. + if let settingsNavController = self.tabBarController?.viewControllers?[1] as? UINavigationController { + self.navigationController?.navigationBar.standardAppearance = settingsNavController.navigationBar.standardAppearance + self.navigationController?.navigationBar.scrollEdgeAppearance = settingsNavController.navigationBar.scrollEdgeAppearance + self.navigationController?.navigationBar.tintColor = settingsNavController.navigationBar.tintColor + self.navigationController?.navigationBar.barTintColor = settingsNavController.navigationBar.barTintColor + } + + self.navigationController?.setNavigationBarHidden(false, animated: false) + self.navigationController?.pushViewController(selectionVC, animated: true) + } +} + +extension InstallationVC { + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let tabBar = self.tabBarController?.tabBar { + // Remove existing gesture recognizers to avoid duplicates. + tabBar.gestureRecognizers?.forEach { tabBar.removeGestureRecognizer($0) } + + // Add tap gesture recognizer. + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTabBarTap(_:))) + tapGesture.cancelsTouchesInView = false + tabBar.addGestureRecognizer(tapGesture) + } + } + + @objc private func handleTabBarTap(_ gesture: UITapGestureRecognizer) { + guard let tabBar = self.tabBarController?.tabBar else { return } + + let location = gesture.location(in: tabBar) + + // Calculate which tab was tapped. + let tabWidth = tabBar.bounds.width / CGFloat(tabBar.items?.count ?? 1) + let tappedIndex = Int(location.x / tabWidth) + + // If Installation tab (index 0) was tapped and we're already on it. + if tappedIndex == 0 && self.tabBarController?.selectedIndex == 0 { + if let navController = self.navigationController, + navController.viewControllers.count > 1 { + navController.popToRootViewController(animated: true) + navController.setNavigationBarHidden(true, animated: true) + } + } } } diff --git a/Scribe/Views/Cells/InfoChildTableViewCell/WrapperCell.swift b/Scribe/Views/Cells/InfoChildTableViewCell/WrapperCell.swift index 8f0b20ce..4cb59354 100644 --- a/Scribe/Views/Cells/InfoChildTableViewCell/WrapperCell.swift +++ b/Scribe/Views/Cells/InfoChildTableViewCell/WrapperCell.swift @@ -40,7 +40,7 @@ class WrapperCell: UITableViewCell { } /// Configure with any cell loaded from XIB. - func configure(withCellNamed nibName: String, section: Section) { + func configure(withCellNamed nibName: String, section: Section, parentSection: Section? = nil) { wrappedCell?.removeFromSuperview() guard let cell = Bundle.main.loadNibNamed( @@ -52,6 +52,7 @@ class WrapperCell: UITableViewCell { } if let infoCell = cell as? InfoChildTableViewCell { + infoCell.parentSection = parentSection infoCell.configureCell(for: section) } else if let aboutCell = cell as? AboutTableViewCell { aboutCell.configureCell(for: section) diff --git a/Scribe/Views/SelectionViewTemplateViewController.swift b/Scribe/Views/SelectionViewTemplateViewController.swift index e3670b62..fa74097d 100644 --- a/Scribe/Views/SelectionViewTemplateViewController.swift +++ b/Scribe/Views/SelectionViewTemplateViewController.swift @@ -21,6 +21,7 @@ final class SelectionViewTemplateViewController: BaseTableViewController { let userDefaults = UserDefaults(suiteName: "group.be.scri.userDefaultsContainer")! private var langCode: String = "de" + @ObservedObject private var stateManager = DownloadStateManager.shared // MARK: Functions @@ -39,7 +40,11 @@ final class SelectionViewTemplateViewController: BaseTableViewController { self.parentSection = parentSection self.langCode = langCode - title = parentSection.sectionTitle + title = NSLocalizedString( + "i18n.app.settings.keyboard.translation.select_source.title", + value: "Translation language", + comment: "" + ) } } @@ -93,10 +98,25 @@ extension SelectionViewTemplateViewController { } private func showPopup(oldLang: String, newLang: String, oldIndexPath: IndexPath?, newIndexPath: IndexPath, tableView: UITableView) { - let oldLangName = getKeyInDict(givenValue: oldLang, dict: languagesAbbrDict) - let newLangName = getKeyInDict(givenValue: newLang, dict: languagesAbbrDict) + let oldSourceLanguage = getKeyInDict(givenValue: oldLang, dict: languagesAbbrDict) + let newSourceLanguage = getKeyInDict(givenValue: newLang, dict: languagesAbbrDict) - let infoText = NSLocalizedString("i18n.app.settings.keyboard.translation.change_source_tooltip.download_warning", value: "You've changed your source translation language. Would you like to download new data so that you can translate from \(newLangName)?", comment: "") + let infoText = NSLocalizedString("i18n.app.settings.keyboard.translation.change_source_tooltip.download_warning", value: "You've changed your source translation language. Would you like to download new data so that you can translate from {source_language}?", comment: "") + + let changeButtonText = NSLocalizedString("i18n.app.settings.keyboard.translation.change_source_tooltip.keep_source_language", value: "Keep {source_language}", comment: "") + + let confirmButtonText = NSLocalizedString("i18n.app._global.download_data", value: "Download data", comment: "") + + let localizedOldSourceLanguage = NSLocalizedString( + "i18n.app._global." + oldSourceLanguage.lowercased(), + value: oldSourceLanguage, + comment: "" + ) + let localizedNewSourceLanguage = NSLocalizedString( + "i18n.app._global." + newSourceLanguage.lowercased(), + value: newSourceLanguage, + comment: "" + ) func onKeep() { // Keep old language - revert and dismiss. @@ -108,19 +128,20 @@ extension SelectionViewTemplateViewController { } func confirmDownload() { - // Download data - save new language. - self.dismiss(animated: true) { - let dictionaryKey = self.langCode + "TranslateLanguage" - self.userDefaults.setValue(newLang, forKey: dictionaryKey) - - self.navigationController?.popViewController(animated: true) - } + self.dismiss(animated: true) { + let dictionaryKey = self.langCode + "TranslateLanguage" + self.userDefaults.setValue(newLang, forKey: dictionaryKey) + + self.stateManager.handleDownloadAction(key: self.langCode) + self.tabBarController?.selectedIndex = 0 + NotificationCenter.default.post(name: NSNotification.Name("NavigateToDownloadScreen"), object: nil) + } } let popupView = ConfirmTranslationSource( - infoText: infoText, - changeButtonText: NSLocalizedString("i18n.app.settings.keyboard.translation.change_source_tooltip.keep_source_language", value: "Keep \(oldLangName)", comment: ""), - confirmButtonText: NSLocalizedString("i18n.app._global.download_data", value: "Download data", comment: ""), + infoText: infoText.replacingOccurrences(of: "{source_language}", with: localizedNewSourceLanguage).replacingOccurrences(of: "{source_language}", with: localizedNewSourceLanguage), + changeButtonText: changeButtonText.replacingOccurrences(of: "{source_language}", with: localizedOldSourceLanguage), + confirmButtonText: confirmButtonText, onDismiss: { onKeep() }, onChange: { onKeep()}, onConfirm: { confirmDownload() } diff --git a/Scribe/Views/TableViewTemplateViewController.swift b/Scribe/Views/TableViewTemplateViewController.swift index 7c24a9f9..50352c9a 100644 --- a/Scribe/Views/TableViewTemplateViewController.swift +++ b/Scribe/Views/TableViewTemplateViewController.swift @@ -90,7 +90,7 @@ extension TableViewTemplateViewController { let section = dataSet[indexPath.section] let setting = section.section[indexPath.row] - cell.configure(withCellNamed: "InfoChildTableViewCell", section: setting) + cell.configure(withCellNamed: "InfoChildTableViewCell", section: setting, parentSection: self.parentSection) let isFirstRow = indexPath.row == 0 let isLastRow = indexPath.row == section.section.count - 1