diff --git a/Conjugate/Views/Tabs/Conjugate/ConjugateTab.swift b/Conjugate/Views/Tabs/Conjugate/ConjugateTab.swift index 35061e70..075f9342 100644 --- a/Conjugate/Views/Tabs/Conjugate/ConjugateTab.swift +++ b/Conjugate/Views/Tabs/Conjugate/ConjugateTab.swift @@ -3,39 +3,48 @@ import SwiftUI struct ConjugateTab: View { + @State private var navigateToDownload = false + var body: some View { - AppNavigation { + AppNavigation { ScrollView { - VStack(spacing: 20) { - Image("ScribeLogo") - .resizable() - .scaledToFit() - .frame(width: 200, height: 100) - .padding(.top, 30) - CardView( - title: NSLocalizedString( - "i18n.app.download.menu_option.conjugate_title", - value: "Verb data", - comment: "" - ), - mainText: NSLocalizedString( - "i18n.app.download.menu_option.conjugate_download_data_start", - value: "Download data to start conjugating!", - comment: "" - ), - subtitle: NSLocalizedString( - "i18n.app.download.menu_option.conjugate_description", - value: "Add new data to Scribe Conjugate.", - comment: "" - ) - ) { - // Insert navigation to download screen. - } - } - .padding() - } - .background(Color("scribeAppBackground")) - .navigationBarHidden(true) + VStack(spacing: 20) { + Image("ScribeLogo") + .resizable() + .scaledToFit() + .frame(width: 200, height: 100) + .padding(.top, 30) + + CardView( + title: NSLocalizedString( + "i18n.app.download.menu_option.conjugate_title", + value: "Verb data", + comment: "" + ), + mainText: NSLocalizedString( + "i18n.app.download.menu_option.conjugate_download_data_start", + value: "Download data to start conjugating!", + comment: "" + ), + subtitle: NSLocalizedString( + "i18n.app.download.menu_option.conjugate_description", + value: "Add new data to Scribe Conjugate.", + comment: "" + ) + ) { + navigateToDownload = true } - } + .background( + NavigationLink( + destination: ConjugateDownloadDataScreen(), + isActive: $navigateToDownload + ) { EmptyView() } + ) } + .padding() + } + .background(Color("scribeAppBackground")) + .navigationBarHidden(true) + } + } +} diff --git a/Conjugate/Views/Tabs/Conjugate/DownloadDataScreen.swift b/Conjugate/Views/Tabs/Conjugate/DownloadDataScreen.swift new file mode 100644 index 00000000..f3b8b72d --- /dev/null +++ b/Conjugate/Views/Tabs/Conjugate/DownloadDataScreen.swift @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * Download data UI for the Scribe-Conjugate app. + * + * Unlike the keyboard app which only shows installed keyboards, + * this screen shows all supported languages listed alphabetically. + */ + +import SwiftUI + +// MARK: - Language Data + +/// All languages supported by Scribe-Conjugate with their abbreviations. +private let conjugateLanguagesAbbrDict: [String: String] = [ + "Deutsch": "de", + "English": "en", + "Español": "es", + "Français": "fr", + "Italiano": "it", + "Português": "pt", + "Русская": "ru", + "Svenska": "sv" +] + +/// Returns all languages sorted alphabetically by display name. +private func allLanguagesSorted() -> [(display: String, code: String)] { + conjugateLanguagesAbbrDict + .map { (display: $0.key, code: $0.value) } + .sorted { $0.display.localizedCompare($1.display) == .orderedAscending } +} + +// MARK: - Download Button State + +enum ConjugateButtonState: Equatable { + case ready + case downloading + case updated + case update +} + +// MARK: - Download State Manager + +/// Manages per-language download states for the Conjugate app. +/// Actual download logic will be wired up once the data service +/// is available to this target (tracked in a follow-up issue). +@MainActor +class ConjugateDownloadStateManager: ObservableObject { + static let shared = ConjugateDownloadStateManager() + + @Published var downloadStates: [String: ConjugateButtonState] = [:] + @Published var toastMessage: String? + @Published var showToast: Bool = false + + private let userDefaults = UserDefaults.standard + private let lastUpdateKey = "conjugate_last_update_" + + private init() {} + + func initializeStates(languages: [String]) { + for language in languages { + if downloadStates[language] != nil { continue } + downloadStates[language] = hasLocalData(for: language) ? .updated : .ready + } + } + + func handleDownloadAction(key: String) { + let currentState = downloadStates[key] ?? .ready + let displayName = conjugateLanguagesAbbrDict.first(where: { $0.value == key })?.key ?? key + + if currentState == .downloading { return } + + if currentState == .updated { + showToastMessage( + String( + format: NSLocalizedString( + "i18n.app.download.menu_ui.download_data.up_to_date", + value: "%@ data is already up to date", + comment: "" + ), + displayName + ) + ) + return + } + + // Download functionality will be implemented in a follow-up issue. + // For now, reflect the downloading state as a UI placeholder. + downloadStates[key] = .downloading + } + + private func hasLocalData(for language: String) -> Bool { + userDefaults.string(forKey: "\(lastUpdateKey)\(language)") != nil + } + + func showToastMessage(_ message: String) { + toastMessage = message + showToast = true + Task { + try? await Task.sleep(nanoseconds: 3_000_000_000) + if toastMessage == message { showToast = false } + } + } +} + +// MARK: - Download Button + +private struct ConjugateDownloadButton: View { + let state: ConjugateButtonState + let action: () -> Void + + private var label: String { + switch state { + case .ready: + return NSLocalizedString( + "i18n.app._global.download_data", value: "Download data", comment: "" + ) + case .downloading: + return NSLocalizedString( + "i18n.app.download.menu_ui.download_data.downloading", + value: "Downloading", + comment: "" + ) + case .updated: + return NSLocalizedString( + "i18n.app.download.menu_ui.download_data.up_to_date", + value: "Up to date", + comment: "" + ) + case .update: + return NSLocalizedString( + "i18n.app.download.menu_ui.update_data", value: "Update data", comment: "" + ) + } + } + + private var icon: String { + switch state { + case .ready, .update: return "icloud.and.arrow.down" + case .downloading: return "arrow.clockwise.circle.fill" + case .updated: return "checkmark.circle.fill" + } + } + + private var bgColor: Color { + state == .updated ? Color("buttonGreen") : Color("buttonOrange") + } + + private var fgColor: Color { + state == .updated ? Color("lightTextDarkGreen") : Color("lightTextDarkCTA") + } + + var body: some View { + Button(action: action) { + HStack(spacing: 8) { + Text(label) + if state == .downloading { + ProgressView().tint(fgColor).scaleEffect(0.8) + } else { + Image(systemName: icon) + } + } + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(fgColor) + .frame(width: 120, height: 20) + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(bgColor) + .cornerRadius(6) + } + .animation(.easeInOut(duration: 0.2), value: state) + } +} + +// MARK: - Check Data Spinner + +private enum CheckDataState { case idle, checking, checked } + +private struct CheckDataSpinner: View { + @Binding var state: CheckDataState + @State private var rotation: Double = 0 + @State private var spinnerTimer: Timer? + + var body: some View { + ZStack { + switch state { + case .idle: + Circle() + .stroke(Color.gray, lineWidth: 2) + .frame(width: 28, height: 28) + .contentShape(Circle()) + .onTapGesture { startChecking() } + + case .checking: + ZStack { + Circle() + .stroke(Color.gray.opacity(0.3), lineWidth: 2.5) + .frame(width: 28, height: 28) + Circle() + .trim(from: 0, to: 0.7) + .stroke( + Color("scribeCTA"), + style: StrokeStyle(lineWidth: 2.5, lineCap: .round) + ) + .frame(width: 28, height: 28) + .rotationEffect(.degrees(rotation)) + .onAppear { startRotation() } + .onDisappear { stopRotation() } + Image(systemName: "xmark") + .font(.system(size: 10, weight: .bold)) + .foregroundColor(Color("scribeCTA")) + } + .contentShape(Circle()) + .onTapGesture { cancelChecking() } + + case .checked: + ZStack { + Circle().fill(Color("scribeCTA")).frame(width: 28, height: 28) + Image(systemName: "checkmark") + .font(.system(size: 12, weight: .bold)) + .foregroundColor(.white) + } + .contentShape(Circle()) + .onTapGesture { withAnimation { state = .idle } } + } + } + .animation(.easeInOut(duration: 0.2), value: state == .idle) + } + + private func startChecking() { + withAnimation { state = .checking } + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + guard state == .checking else { return } + stopRotation() + withAnimation { state = .checked } + } + } + + private func cancelChecking() { + stopRotation() + withAnimation { state = .idle } + } + + private func startRotation() { + rotation = 0 + spinnerTimer?.invalidate() + spinnerTimer = Timer.scheduledTimer(withTimeInterval: 0.016, repeats: true) { _ in + rotation += 4 + } + } + + private func stopRotation() { + spinnerTimer?.invalidate() + spinnerTimer = nil + } +} + +// MARK: - Toast + +private struct ConjugateToastView: View { + let message: String + + var body: some View { + HStack(spacing: 12) { + Text(message).font(.subheadline) + Spacer() + } + .foregroundColor(Color("lightTextDarkCTA")) + .padding() + .background(Color(.systemBackground)) + .cornerRadius(10) + .shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 2) + .padding(.horizontal) + } +} + +// MARK: - Update Data Card + +private struct UpdateDataCard: View { + @State private var checkState: CheckDataState = .idle + @State private var isRegularUpdate = false + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + Text( + NSLocalizedString( + "i18n.app.download.menu_ui.update_data", value: "Update data", comment: "" + ) + ) + .font(.system(size: 20, weight: .bold)) + .foregroundColor(.primary) + .padding(.horizontal, 4) + + VStack(alignment: .leading, spacing: 0) { + HStack { + Text( + NSLocalizedString( + "i18n.app.download.menu_ui.update_data.check_new", + value: "Check for new data", + comment: "" + ) + ) + .font(.system(size: 17)) + .foregroundColor(.primary) + Spacer() + CheckDataSpinner(state: $checkState) + } + .padding(.horizontal, 16) + .padding(.vertical, 14) + + Divider() + .padding(.horizontal, 16) + + Toggle(isOn: $isRegularUpdate) { + Text( + NSLocalizedString( + "i18n.app.download.menu_ui.update_data.regular_update", + value: "Regularly update data", + comment: "" + ) + ) + .font(.system(size: 17)) + } + .tint(Color("scribeCTA")) + .padding(.horizontal, 16) + .padding(.vertical, 14) + } + .background(Color(.systemBackground)) + .cornerRadius(12) + } + } +} + +// MARK: - Language List + +private struct LanguageListCard: View { + let languages: [(display: String, code: String)] + @ObservedObject var stateManager: ConjugateDownloadStateManager + + private func handleUpdateAll() { + for lang in languages + where stateManager.downloadStates[lang.code] != .updated + && stateManager.downloadStates[lang.code] != .downloading { + stateManager.handleDownloadAction(key: lang.code) + } + } + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + Text( + NSLocalizedString( + "i18n.app.download.menu_ui.download_data.title", + value: "Select data to download", + comment: "" + ) + ) + .font(.system(size: 20, weight: .bold)) + .foregroundColor(.primary) + .padding(.horizontal, 4) + + VStack(spacing: 0) { + HStack { + Spacer() + Text( + NSLocalizedString( + "i18n.app.download.menu_ui.download_data.update_all", + value: "Update all", + comment: "" + ) + ) + .foregroundColor(Color("linkBlue")) + .font(.system(size: 17, weight: .medium)) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .onTapGesture { handleUpdateAll() } + } + + ForEach(Array(languages.enumerated()), id: \.offset) { index, lang in + HStack { + Text(lang.display) + .font(.system(size: 17)) + .foregroundColor(.primary) + Spacer() + ConjugateDownloadButton( + state: stateManager.downloadStates[lang.code] ?? .ready, + action: { stateManager.handleDownloadAction(key: lang.code) } + ) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + + if index < languages.count - 1 { + Divider() + .padding(.horizontal, 16) + } + } + } + .background(Color(.systemBackground)) + .cornerRadius(12) + } + } +} + +// MARK: - Download Data Screen + +struct ConjugateDownloadDataScreen: View { + private let languages = allLanguagesSorted() + @StateObject private var stateManager = ConjugateDownloadStateManager.shared + + private func initializeStates() { + stateManager.initializeStates(languages: languages.map(\.code)) + } + + var body: some View { + ZStack(alignment: .bottom) { + ScrollView { + VStack(alignment: .leading, spacing: 24) { + UpdateDataCard() + LanguageListCard(languages: languages, stateManager: stateManager) + } + .padding(.horizontal, 20) + .padding(.vertical, 16) + .padding(.bottom, stateManager.showToast ? 60 : 0) + } + .background(Color("scribeAppBackground").ignoresSafeArea()) + + if stateManager.showToast, let message = stateManager.toastMessage { + ConjugateToastView(message: message) + .transition(.move(edge: .bottom).combined(with: .opacity)) + .animation(.spring(), value: stateManager.showToast) + .padding(.bottom, 16) + } + } + .navigationTitle( + NSLocalizedString( + "i18n.app.download.menu_ui.download_data", + value: "Download data", + comment: "" + ) + ) + .navigationBarTitleDisplayMode(.large) + .onAppear { initializeStates() } + } +} diff --git a/Scribe.xcodeproj/project.pbxproj b/Scribe.xcodeproj/project.pbxproj index 6980936d..0079b0e5 100644 --- a/Scribe.xcodeproj/project.pbxproj +++ b/Scribe.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0797C1252F92286A00ACB031 /* DownloadDataScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0797C1242F92286A00ACB031 /* DownloadDataScreen.swift */; }; 140158992A430DD000D14E52 /* ThirdPartyLicense.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140158982A430DD000D14E52 /* ThirdPartyLicense.swift */; }; 1401589B2A45A07200D14E52 /* WikimediaAndScribe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1401589A2A45A07200D14E52 /* WikimediaAndScribe.swift */; }; 140158A22A4EDB2200D14E52 /* TableViewTemplateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140158A12A4EDB2200D14E52 /* TableViewTemplateViewController.swift */; }; @@ -788,13 +789,13 @@ F786BB3B2F1E8F70003F7505 /* Russian.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D1671A60275A1E8700A7C118 /* Russian.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; F7A17EBA2F6A8C180040B09B /* AppNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A17EB62F6A8BFE0040B09B /* AppNavigation.swift */; }; F7A17EBB2F6A8C1C0040B09B /* AboutTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A17EAC2F6A8BFE0040B09B /* AboutTab.swift */; }; + F7A17EBC2F6A8C200040B09B /* ConjugateTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A17EAE2F6A8BFE0040B09B /* ConjugateTab.swift */; }; + F7A17EBD2F6A8C230040B09B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A17EB42F6A8BFE0040B09B /* ContentView.swift */; }; FA001AAB2F9A000000000001 /* AboutTipCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA001AAA2F9A000000000001 /* AboutTipCardView.swift */; }; FA001AAB2F9A000000000002 /* AboutSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA001AAA2F9A000000000002 /* AboutSectionView.swift */; }; FA001AAB2F9A000000000003 /* AboutRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA001AAA2F9A000000000003 /* AboutRowView.swift */; }; FA001AAB2F9A000000000004 /* AboutInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA001AAA2F9A000000000004 /* AboutInfoView.swift */; }; FA001AAB2F9A000000000005 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA001AAA2F9A000000000005 /* ShareSheet.swift */; }; - F7A17EBC2F6A8C200040B09B /* ConjugateTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A17EAE2F6A8BFE0040B09B /* ConjugateTab.swift */; }; - F7A17EBD2F6A8C230040B09B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A17EB42F6A8BFE0040B09B /* ContentView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1015,6 +1016,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0797C1242F92286A00ACB031 /* DownloadDataScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadDataScreen.swift; sourceTree = ""; }; 140158982A430DD000D14E52 /* ThirdPartyLicense.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartyLicense.swift; sourceTree = ""; }; 1401589A2A45A07200D14E52 /* WikimediaAndScribe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WikimediaAndScribe.swift; sourceTree = ""; }; 140158A12A4EDB2200D14E52 /* TableViewTemplateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewTemplateViewController.swift; sourceTree = ""; }; @@ -1170,11 +1172,6 @@ F786BB422F1E8F70003F7505 /* ConjugateApp-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "ConjugateApp-Info.plist"; path = "/Users/gauthammohanraj/Developer/Scribe-iOS/ConjugateApp-Info.plist"; sourceTree = ""; }; F7A17EAB2F6A8BFE0040B09B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContentView.swift; path = Conjugate/ContentView.swift; sourceTree = ""; }; F7A17EAC2F6A8BFE0040B09B /* AboutTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutTab.swift; sourceTree = ""; }; - FA001AAA2F9A000000000001 /* AboutTipCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutTipCardView.swift; sourceTree = ""; }; - FA001AAA2F9A000000000002 /* AboutSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSectionView.swift; sourceTree = ""; }; - FA001AAA2F9A000000000003 /* AboutRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutRowView.swift; sourceTree = ""; }; - FA001AAA2F9A000000000004 /* AboutInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInfoView.swift; sourceTree = ""; }; - FA001AAA2F9A000000000005 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; F7A17EAE2F6A8BFE0040B09B /* ConjugateTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConjugateTab.swift; sourceTree = ""; }; F7A17EB02F6A8BFE0040B09B /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = ""; }; F7A17EB22F6A8BFE0040B09B /* ConjugateTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConjugateTab.swift; sourceTree = ""; }; @@ -1182,6 +1179,11 @@ F7A17EB62F6A8BFE0040B09B /* AppNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppNavigation.swift; path = Conjugate/AppNavigation.swift; sourceTree = ""; }; F7A17EB72F6A8BFE0040B09B /* ConjugateApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ConjugateApp.swift; path = Conjugate/ConjugateApp.swift; sourceTree = ""; }; F7A17EB82F6A8BFF0040B09B /* AppNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigation.swift; sourceTree = ""; }; + FA001AAA2F9A000000000001 /* AboutTipCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutTipCardView.swift; sourceTree = ""; }; + FA001AAA2F9A000000000002 /* AboutSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSectionView.swift; sourceTree = ""; }; + FA001AAA2F9A000000000003 /* AboutRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutRowView.swift; sourceTree = ""; }; + FA001AAA2F9A000000000004 /* AboutInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInfoView.swift; sourceTree = ""; }; + FA001AAA2F9A000000000005 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -2094,6 +2096,7 @@ isa = PBXGroup; children = ( F725CAE42F6A782500A8C950 /* ConjugateTab.swift */, + 0797C1242F92286A00ACB031 /* DownloadDataScreen.swift */, ); path = Conjugate; sourceTree = ""; @@ -3643,27 +3646,7 @@ A4C829A12F8400800015D657 /* ConjugateTab.swift in Sources */, F725CADE2F6A72BC00A8C950 /* ConjugateApp.swift in Sources */, F725CAE72F6A783400A8C950 /* SettingsTab.swift in Sources */, - E91980C82F2F540A00B5852F /* NavigationStructure.swift in Sources */, - F786BB0F2F1E8F70003F7505 /* InterfaceVariables.swift in Sources */, - F786BB232F1E8F70003F7505 /* InterfaceConstants.swift in Sources */, - F786BAF52F1E8F70003F7505 /* KeyboardProvider.swift in Sources */, - F786BAF42F1E8F70003F7505 /* KeyboardBuilder.swift in Sources */, - F786BB1D2F1E8F70003F7505 /* KeyAltChars.swift in Sources */, - F786BAFF2F1E8F70003F7505 /* CommandVariables.swift in Sources */, - F786BB132F1E8F70003F7505 /* ColorVariables.swift in Sources */, - F786BAD72F1E8F70003F7505 /* ScribeColor.swift in Sources */, - F786BAF62F1E8F70003F7505 /* UIColor+ScribeColors.swift in Sources */, - F786BB222F1E8F70003F7505 /* Extensions.swift in Sources */, - F786BAE72F1E8F70003F7505 /* ENInterfaceVariables.swift in Sources */, - F786BAE62F1E8F70003F7505 /* FR-AZERTYInterfaceVariables.swift in Sources */, - F786BAE92F1E8F70003F7505 /* DEInterfaceVariables.swift in Sources */, - F786BADC2F1E8F70003F7505 /* IDInterfaceVariables.swift in Sources */, - F786BB0A2F1E8F70003F7505 /* ITInterfaceVariables.swift in Sources */, - F786BB1E2F1E8F70003F7505 /* NBInterfaceVariables.swift in Sources */, - F786BB0B2F1E8F70003F7505 /* PTInterfaceVariables.swift in Sources */, - F786BB092F1E8F70003F7505 /* RUInterfaceVariables.swift in Sources */, - E97E65172F2CDD730070810A /* ESInterfaceVariables.swift in Sources */, - F786BAF72F1E8F70003F7505 /* SVInterfaceVariables.swift in Sources */, + 0797C1252F92286A00ACB031 /* DownloadDataScreen.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };