From 8cbe7696351c70ff346e8f383482bd803ac42d98 Mon Sep 17 00:00:00 2001 From: Prince Yadav <66916296+prince-0408@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:38:09 +0530 Subject: [PATCH 1/3] Convert keyboard app About tab to SwiftUI (#608) --- Scribe.xcodeproj/project.pbxproj | 28 +- Scribe/AboutTab/AboutInfoView.swift | 177 ++++++++++++ Scribe/AboutTab/AboutRowView.swift | 112 ++++++++ Scribe/AboutTab/AboutSectionView.swift | 31 +++ Scribe/AboutTab/AboutTab.swift | 320 ++++++++++++++++++++++ Scribe/AboutTab/AboutTableData.swift | 157 ----------- Scribe/AboutTab/AboutTipCardView.swift | 53 ++++ Scribe/AboutTab/AboutViewController.swift | 314 +-------------------- Scribe/AboutTab/InformationScreenVC.swift | 213 +------------- Scribe/AboutTab/ShareSheet.swift | 48 ++++ Scribe/Base.lproj/AppScreen.storyboard | 13 +- Scribe/TipCard/TipCardView.swift | 28 +- 12 files changed, 798 insertions(+), 696 deletions(-) create mode 100644 Scribe/AboutTab/AboutInfoView.swift create mode 100644 Scribe/AboutTab/AboutRowView.swift create mode 100644 Scribe/AboutTab/AboutSectionView.swift create mode 100644 Scribe/AboutTab/AboutTab.swift delete mode 100644 Scribe/AboutTab/AboutTableData.swift create mode 100644 Scribe/AboutTab/AboutTipCardView.swift create mode 100644 Scribe/AboutTab/ShareSheet.swift diff --git a/Scribe.xcodeproj/project.pbxproj b/Scribe.xcodeproj/project.pbxproj index 6980936d..45a8a7f2 100644 --- a/Scribe.xcodeproj/project.pbxproj +++ b/Scribe.xcodeproj/project.pbxproj @@ -7,10 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 075257CC2F7D481E00E57E2A /* AboutTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257C92F7D481D00E57E2A /* AboutTab.swift */; }; + 075257CD2F7D481E00E57E2A /* AboutRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257C72F7D481D00E57E2A /* AboutRowView.swift */; }; + 075257CE2F7D481E00E57E2A /* AboutInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257C62F7D481D00E57E2A /* AboutInfoView.swift */; }; + 075257CF2F7D481E00E57E2A /* AboutSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257C82F7D481D00E57E2A /* AboutSectionView.swift */; }; + 075257D02F7D481E00E57E2A /* AboutTipCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257CA2F7D481D00E57E2A /* AboutTipCardView.swift */; }; + 075257D12F7D481E00E57E2A /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257CB2F7D481D00E57E2A /* ShareSheet.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 */; }; - 1406B7872A2DFCDD001DF45B /* AboutTableData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1406B7862A2DFCDD001DF45B /* AboutTableData.swift */; }; 1406B78C2A3209CF001DF45B /* AppExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1406B78B2A3209CF001DF45B /* AppExtensions.swift */; }; 146B70BD2A853A3800710BD4 /* SwipeableTabBarController in Frameworks */ = {isa = PBXBuildFile; productRef = 146B70BC2A853A3800710BD4 /* SwipeableTabBarController */; }; 147797B02A2CD3370044A53E /* InfoChildTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147797AE2A2CD3370044A53E /* InfoChildTableViewCell.swift */; }; @@ -1018,7 +1023,12 @@ 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 = ""; }; - 1406B7862A2DFCDD001DF45B /* AboutTableData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutTableData.swift; sourceTree = ""; }; + 075257C62F7D481D00E57E2A /* AboutInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInfoView.swift; sourceTree = ""; }; + 075257C72F7D481D00E57E2A /* AboutRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutRowView.swift; sourceTree = ""; }; + 075257C82F7D481D00E57E2A /* AboutSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSectionView.swift; sourceTree = ""; }; + 075257C92F7D481D00E57E2A /* AboutTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutTab.swift; sourceTree = ""; }; + 075257CA2F7D481D00E57E2A /* AboutTipCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutTipCardView.swift; sourceTree = ""; }; + 075257CB2F7D481D00E57E2A /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; 1406B78B2A3209CF001DF45B /* AppExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppExtensions.swift; sourceTree = ""; }; 144B56F22A568AC200C2F447 /* Scribe.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Scribe.entitlements; sourceTree = ""; }; 147797AE2A2CD3370044A53E /* InfoChildTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoChildTableViewCell.swift; sourceTree = ""; }; @@ -1554,7 +1564,12 @@ 147797A92A2CD2B50044A53E /* AboutTab */ = { isa = PBXGroup; children = ( - 1406B7862A2DFCDD001DF45B /* AboutTableData.swift */, + 075257C62F7D481D00E57E2A /* AboutInfoView.swift */, + 075257C72F7D481D00E57E2A /* AboutRowView.swift */, + 075257C82F7D481D00E57E2A /* AboutSectionView.swift */, + 075257C92F7D481D00E57E2A /* AboutTab.swift */, + 075257CA2F7D481D00E57E2A /* AboutTipCardView.swift */, + 075257CB2F7D481D00E57E2A /* ShareSheet.swift */, 14AC56832A24AED3006B1DDF /* AboutViewController.swift */, 14AC56892A261663006B1DDF /* InformationScreenVC.swift */, ); @@ -2887,7 +2902,6 @@ CE1378C428F5D7AC00E1CBC2 /* ScribeColor.swift in Sources */, 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 */, 3045396D293B9DDC003AE55B /* ToolTipViewDatasource.swift in Sources */, @@ -2897,6 +2911,12 @@ D180EC0328FDFABF0018E29B /* FR-AZERTYInterfaceVariables.swift in Sources */, D1CDED7B2A859FBF00098546 /* ENInterfaceVariables.swift in Sources */, 38BD213622D5907F00C6795D /* InstallationVC.swift in Sources */, + 075257CC2F7D481E00E57E2A /* AboutTab.swift in Sources */, + 075257CD2F7D481E00E57E2A /* AboutRowView.swift in Sources */, + 075257CE2F7D481E00E57E2A /* AboutInfoView.swift in Sources */, + 075257CF2F7D481E00E57E2A /* AboutSectionView.swift in Sources */, + 075257D02F7D481E00E57E2A /* AboutTipCardView.swift in Sources */, + 075257D12F7D481E00E57E2A /* ShareSheet.swift in Sources */, D171942D27AECEB80038660B /* DEInterfaceVariables.swift in Sources */, 147797C02A2D0CDF0044A53E /* SettingsTableData.swift in Sources */, E9F7273F2F45A6E60060B92D /* APIClient.swift in Sources */, diff --git a/Scribe/AboutTab/AboutInfoView.swift b/Scribe/AboutTab/AboutInfoView.swift new file mode 100644 index 00000000..5deb12c0 --- /dev/null +++ b/Scribe/AboutTab/AboutInfoView.swift @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * Detail information screens navigated to from the About tab. + */ + +import SwiftUI + +enum AboutInfoSection { + case wikimedia + case privacyPolicy + case licenses +} + +struct AboutInfoView: View { + let section: AboutInfoSection + + private var title: String { + switch section { + case .wikimedia: + return NSLocalizedString( + "i18n.app.about.community.wikimedia", value: "Wikimedia and Scribe", comment: "") + case .privacyPolicy: + return NSLocalizedString( + "i18n._global.privacy_policy", value: "Privacy policy", comment: "") + case .licenses: + return NSLocalizedString( + "i18n.app.about.legal.third_party", value: "Third-party licenses", comment: "") + } + } + + private var caption: String { + switch section { + case .wikimedia: + return NSLocalizedString( + "i18n.app.about.community.wikimedia.caption", value: "How we work together", + comment: "") + case .privacyPolicy: + return NSLocalizedString( + "i18n.app.about.legal.privacy_policy.caption", value: "Keeping you safe", + comment: "") + case .licenses: + return NSLocalizedString( + "i18n.app.about.legal.third_party.caption", value: "Whose code we used", + comment: "") + } + } + + private var bodyText: String { + switch section { + case .wikimedia: return wikimediaBodyText + case .privacyPolicy: return privacyPolicyBodyText + case .licenses: return thirdPartyLicensesBodyText + } + } + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 0) { + VStack(alignment: .leading, spacing: 16) { + Text(caption) + .font(.title2) + .fontWeight(.bold) + .foregroundColor(.primary) + + Text(bodyText) + .font(.body) + .foregroundColor(.primary) + .tint(Color("linkBlue")) + } + .padding(20) + .background(Color("lightWhiteDarkBlack")) + .cornerRadius(12) + .padding(.horizontal, 20) + .padding(.top, 16) + } + .padding(.bottom, 32) + } + .background(Color("scribeAppBackground").ignoresSafeArea()) + .navigationTitle(title) + .navigationBarTitleDisplayMode(.large) + } +} + +// MARK: - Body text + +private let wikimediaBodyText: String = { + let t1 = NSLocalizedString( + "i18n.app.about.community.wikimedia.text_1", + value: "Scribe would not be possible without countless contributions by Wikimedia contributors to the many projects that they support. Specifically Scribe makes use of data from the Wikidata Lexicographical data community, as well as data from Wikipedia for each language that Scribe supports.", + comment: "") + let t2 = NSLocalizedString( + "i18n.app.about.community.wikimedia.text_2", + value: "Wikidata is a collaboratively edited multilingual knowledge graph hosted by the Wikimedia Foundation. It provides freely available data that anyone can use under a Creative Commons Public Domain license (CC0). Scribe uses language data from Wikidata to provide users with verb conjugations, noun-form annotations, noun plurals, and many other features.", + comment: "") + let t3 = NSLocalizedString( + "i18n.app.about.community.wikimedia.text_3", + value: "Wikipedia is a multilingual free online encyclopedia written and maintained by a community of volunteers through open collaboration and a wiki-based editing system. Scribe uses data from Wikipedia to produce autosuggestions by deriving the most common words in a language as well as the most common words that follow them.", + comment: "") + return [t1, t2, t3].joined(separator: "\n\n") +}() + +private let privacyPolicyBodyText: String = NSLocalizedString( + "i18n.app.about.legal.privacy_policy.text", + value: """ + Please note that the English version of this policy takes precedence over all other versions. + + The Scribe developers (SCRIBE) built the iOS application "Scribe - Language Keyboards" (SERVICE) as an open-source application. This SERVICE is provided by SCRIBE at no cost and is intended for use as is. + + This privacy policy (POLICY) is used to inform the reader of the policies for the access, tracking, collection, retention, use, and disclosure of personal information (USER INFORMATION) and usage data (USER DATA) for all individuals who make use of this SERVICE (USERS). + + USER INFORMATION is specifically defined as any information related to the USERS themselves or the devices they use to access the SERVICE. + + USER DATA is specifically defined as any text that is typed or actions that are done by the USERS while using the SERVICE. + + 1. Policy Statement + + This SERVICE does not access, track, collect, retain, use, or disclose any USER INFORMATION or USER DATA. + + 2. Do Not Track + + USERS contacting SCRIBE to ask that their USER INFORMATION and USER DATA not be tracked will be provided with a copy of this POLICY as well as a link to all source codes as proof that they are not being tracked. + + 3. Third-Party Data + + This SERVICE makes use of third-party data. All data used in the creation of this SERVICE comes from sources that allow its full use in the manner done so by the SERVICE. Specifically, the data for this SERVICE comes from Wikidata, Wikipedia and Unicode. Wikidata states that, "All structured data in the main, property and lexeme namespaces is made available under the Creative Commons CC0 License; text in other namespaces is made available under the Creative Commons Attribution-Share Alike License." The policy detailing Wikidata data usage can be found at https://www.wikidata.org/wiki/Wikidata:Licensing. Wikipedia states that text data, the type of data used by the SERVICE, "… can be used under the terms of the Creative Commons Attribution Share-Alike license". The policy detailing Wikipedia data usage can be found at https://en.wikipedia.org/wiki/Wikipedia:Reusing_Wikipedia_content. Unicode provides permission, "… free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation (the "Data Files") or Unicode software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction…" The policy detailing Unicode data usage can be found at https://www.unicode.org/license.txt. + + 4. Third-Party Source Code + + This SERVICE was based on third-party code. All source code used in the creation of this SERVICE comes from sources that allow its full use in the manner done so by the SERVICE. Specifically, the basis of this project was the project Custom Keyboard by Ethan Sarif-Kattan. Custom Keyboard was released under an MIT license, with this license being available at https://github.com/EthanSK/CustomKeyboard/blob/master/LICENSE. + + 5. Third-Party Services + + This SERVICE makes use of third-party services to manipulate some of the third-party data. Specifically, data has been translated using models from Hugging Face transformers. This service is covered by an Apache License 2.0, which states that it is available for commercial use, modification, distribution, patent use, and private use. The license for the aforementioned service can be found at https://github.com/huggingface/transformers/blob/master/LICENSE. + + 6. Third-Party Links + + This SERVICE contains links to external websites. If USERS click on a third-party link, they will be directed to a website. Note that these external websites are not operated by this SERVICE. Therefore, USERS are strongly advised to review the privacy policy of these websites. This SERVICE has no control over and assumes no responsibility for the content, privacy policies, or practices of any third-party sites or services. + + 7. Third-Party Images + + This SERVICE contains images that are copyrighted by third-parties. Specifically, this app includes a copy of the logos of GitHub, Inc and Wikidata, trademarked by Wikimedia Foundation, Inc. The terms by which the GitHub logo can be used are found on https://github.com/logos, and the terms for the Wikidata logo are found on the following Wikimedia page: https://foundation.wikimedia.org/wiki/Policy:Trademark_policy. This SERVICE uses the copyrighted images in a way that matches these criteria, with the only deviation being a rotation of the GitHub logo that is common in the open-source community to indicate that there is a link to the GitHub website. + + 8. Content Notice + + This SERVICE allows USERS to access linguistic content (CONTENT). Some of this CONTENT could be deemed inappropriate for children and legal minors. Accessing CONTENT using the SERVICE is done in a way that the information is unavailable unless explicitly known. Specifically, USERS "can" translate words, conjugate verbs, and access other grammatical features of CONTENT that may be sexual, violent, or otherwise inappropriate in nature. USERS "cannot" translate words, conjugate verbs, and access other grammatical features of CONTENT that may be sexual, violent, or otherwise inappropriate in nature if they do not already know about the nature of this CONTENT. SCRIBE takes no responsibility for the access of such CONTENT. + + 9. Changes + + This POLICY is subject to change. Updates to this POLICY will replace all prior instances, and if deemed material will further be clearly stated in the next applicable update to the SERVICE. SCRIBE encourages USERS to periodically review this POLICY for the latest information on our privacy practices and to familiarize themselves with any changes. + + 10. Contact + + If you have any questions, concerns, or suggestions about this POLICY, do not hesitate to visit https://github.com/scribe-org or contact SCRIBE at scribe.langauge@gmail.com. The person responsible for such inquiries is Andrew Tavis McAllister. + + 11. Effective Date + + This POLICY is effective as of the 24th of May, 2022. + """, + comment: "" +) + +private let thirdPartyLicensesBodyText: String = { + let intro = NSLocalizedString( + "i18n.app.about.legal.third_party.text", + value: "The Scribe developers (SCRIBE) built the iOS application \"Scribe - Language Keyboards\" (SERVICE) using third party code. All source code used in the creation of this SERVICE comes from sources that allow its full use in the manner done so by the SERVICE. This section lists the source code on which the SERVICE was based as well as the coinciding licenses of each.\n\nThe following is a list of all used source code, the main author or authors of the code, the license under which it was released at time of usage, and a link to the license.", + comment: "") + let entry1 = NSLocalizedString( + "i18n.app.about.legal.third_party.entry_custom_keyboard", + value: "Custom Keyboard\n• Author: EthanSK\n• License: MIT\n• Link: https://github.com/EthanSK/CustomKeyboard/blob/master/LICENSE", + comment: "") + let entry2 = NSLocalizedString( + "i18n.app.about.legal.third_party.entry_simple_keyboard", + value: "Simple Keyboard\n• Author: Simple Mobile Tools\n• License: GPL-3.0\n• Link: https://github.com/SimpleMobileTools/Simple-Keyboard/blob/main/LICENSE", + comment: "") + return "\(intro)\n\n1. \(entry1)\n\n2. \(entry2)" +}() diff --git a/Scribe/AboutTab/AboutRowView.swift b/Scribe/AboutTab/AboutRowView.swift new file mode 100644 index 00000000..a971ee99 --- /dev/null +++ b/Scribe/AboutTab/AboutRowView.swift @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * A single row in the About tab list. + */ + +import SwiftUI + +enum AboutRowTrailing { + case externalLink + case chevron + case reset + case none +} + +struct AboutRowView: View { + let icon: String + let isCustomImage: Bool + let title: String + var trailing: AboutRowTrailing = .none + var invertIconInDarkMode: Bool = false + let action: () -> Void + + @Environment(\.colorScheme) private var colorScheme + + init( + icon: String, + isCustomImage: Bool, + title: String, + hasExternalLink: Bool = false, + hasNestedNavigation: Bool = false, + isReset: Bool = false, + invertIconInDarkMode: Bool = false, + action: @escaping () -> Void + ) { + self.icon = icon + self.isCustomImage = isCustomImage + self.title = title + self.invertIconInDarkMode = invertIconInDarkMode + self.action = action + if hasExternalLink { + self.trailing = .externalLink + } else if hasNestedNavigation { + self.trailing = .chevron + } else if isReset { + self.trailing = .reset + } else { + self.trailing = .none + } + } + + var body: some View { + Button(action: action) { + HStack(spacing: 14) { + iconView + .frame(width: 28, height: 28) + .padding(.leading, 16) + + Text(title) + .foregroundColor(.primary) + .font(.body) + + Spacer() + + trailingView + .padding(.trailing, 16) + } + .frame(minHeight: 52) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + } + + @ViewBuilder + private var iconView: some View { + if isCustomImage { + let image = Image(icon) + .resizable() + .scaledToFit() + if invertIconInDarkMode && colorScheme == .dark { + image.colorInvert() + } else { + image + } + } else { + Image(systemName: icon) + .resizable() + .scaledToFit() + .foregroundColor(.primary) + } + } + + @ViewBuilder + private var trailingView: some View { + switch trailing { + case .externalLink: + Image(systemName: "arrow.up.right.square") + .font(.system(size: 17)) + .foregroundColor(Color(.systemGray3)) + case .chevron: + Image(systemName: "chevron.right") + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(Color(.systemGray3)) + case .reset: + Image(systemName: "arrow.counterclockwise") + .font(.system(size: 17)) + .foregroundColor(Color(.systemGray3)) + case .none: + EmptyView() + } + } +} diff --git a/Scribe/AboutTab/AboutSectionView.swift b/Scribe/AboutTab/AboutSectionView.swift new file mode 100644 index 00000000..ed77e0db --- /dev/null +++ b/Scribe/AboutTab/AboutSectionView.swift @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * A grouped section container used in the About tab. + */ + +import SwiftUI + +struct AboutSectionView: View { + let heading: String + @ViewBuilder let content: () -> Content + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + Text(heading) + .font(.title2) + .fontWeight(.bold) + .foregroundColor(.primary) + .padding(.horizontal, 20) + .padding(.top, 24) + .padding(.bottom, 10) + + VStack(spacing: 0) { + content() + } + .background(Color("lightWhiteDarkBlack")) + .cornerRadius(12) + .padding(.horizontal, 20) + } + } +} diff --git a/Scribe/AboutTab/AboutTab.swift b/Scribe/AboutTab/AboutTab.swift new file mode 100644 index 00000000..b1596f44 --- /dev/null +++ b/Scribe/AboutTab/AboutTab.swift @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * The About tab for the Scribe keyboard app. + */ + +import MessageUI +import StoreKit +import SwiftUI + +struct AboutTab: View { + @State private var showShareSheet = false + @State private var showAppHintsConfirmation = false + @State private var showMailCompose = false + @State private var showMailAlert = false + @State private var tipCardVisible: Bool = { + UserDefaults.standard.object(forKey: "aboutTipCardState") as? Bool ?? true + }() + + private var appVersion: String { + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "—" + } + + var body: some View { + ZStack(alignment: .top) { + Color("scribeAppBackground") + .ignoresSafeArea() + + ScrollView { + VStack(spacing: 0) { + if tipCardVisible { + AboutTipCardView( + infoText: NSLocalizedString( + "i18n.app.about.app_hint_tooltip", + value: "Here's where you can learn more about Scribe and its community.", + comment: "" + ), + isVisible: $tipCardVisible, + onDismiss: { + UserDefaults.standard.set(false, forKey: "aboutTipCardState") + } + ) + .padding(.horizontal, 20) + .padding(.top, 12) + .transition(.opacity.combined(with: .move(edge: .top))) + } + + // MARK: Community + + AboutSectionView( + heading: NSLocalizedString( + "i18n.app.about.community.title", value: "Community", comment: "") + ) { + AboutRowView( + icon: "github", + isCustomImage: true, + title: NSLocalizedString( + "i18n.app.about.community.github", + value: "See the code on GitHub", comment: ""), + hasExternalLink: true + ) { openURL("https://github.com/scribe-org/Scribe-iOS") } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "matrix", + isCustomImage: true, + title: NSLocalizedString( + "i18n.app.about.community.matrix", + value: "Chat with the team on Matrix", comment: ""), + hasExternalLink: true + ) { + openURL( + "https://matrix.to/#/#scribe_community:matrix.org", + encoded: true) + } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "mastodon", + isCustomImage: true, + title: NSLocalizedString( + "i18n.app.about.community.mastodon", + value: "Follow us on Mastodon", comment: ""), + hasExternalLink: true + ) { openURL("https://wikis.world/@scribe") } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "Bluesky", + isCustomImage: true, + title: NSLocalizedString( + "i18n.app.about.community.bluesky", + value: "Follow us on Bluesky", comment: ""), + hasExternalLink: true, + invertIconInDarkMode: true + ) { openURL("https://bsky.app/profile/scribe-org.bsky.social") } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "globe", + isCustomImage: false, + title: NSLocalizedString( + "i18n.app.about.community.visit_website", + value: "Visit the Scribe website", comment: ""), + hasExternalLink: true + ) { openURL("https://scri.be/") } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "square.and.arrow.up", + isCustomImage: false, + title: NSLocalizedString( + "i18n.app.about.community.share_scribe", + value: "Share Scribe", comment: ""), + hasExternalLink: true + ) { showShareSheet = true } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "wikimedia", + isCustomImage: true, + title: NSLocalizedString( + "i18n.app.about.community.wikimedia", + value: "Wikimedia and Scribe", comment: ""), + hasNestedNavigation: true + ) {} + .overlay( + NavigationLink(destination: AboutInfoView(section: .wikimedia)) { + Color.clear + } + ) + } + + // MARK: Feedback and support + + AboutSectionView( + heading: NSLocalizedString( + "i18n.app.about.feedback.title", value: "Feedback and support", + comment: "") + ) { + AboutRowView( + icon: "star.fill", + isCustomImage: false, + title: NSLocalizedString( + "i18n.app.about.feedback.rate_scribe", + value: "Rate Scribe", comment: ""), + hasExternalLink: true + ) { rateApp() } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "ladybug", + isCustomImage: false, + title: NSLocalizedString( + "i18n.app.about.feedback.bug_report", value: "Report a bug", + comment: ""), + hasExternalLink: true + ) { openURL("https://github.com/scribe-org/Scribe-iOS/issues") } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "envelope", + isCustomImage: false, + title: NSLocalizedString( + "i18n.app.about.feedback.send_email", value: "Send us an email", + comment: ""), + hasExternalLink: true + ) { sendEmail() } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "bookmark", + isCustomImage: false, + title: String( + format: NSLocalizedString( + "i18n.app.about.feedback.version", value: "Version %@", + comment: ""), + appVersion + ), + hasExternalLink: true + ) { openURL("https://github.com/scribe-org/Scribe-iOS/releases") } + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "lightbulb.max", + isCustomImage: false, + title: NSLocalizedString( + "i18n.app.about.feedback.reset_app_hints", + value: "Reset app hints", comment: ""), + isReset: true + ) { showAppHintsConfirmation = true } + } + + // MARK: Legal + + AboutSectionView( + heading: NSLocalizedString( + "i18n._global.legal", value: "Legal", comment: "") + ) { + AboutRowView( + icon: "lock.shield", + isCustomImage: false, + title: NSLocalizedString( + "i18n._global.privacy_policy", value: "Privacy policy", + comment: ""), + hasNestedNavigation: true + ) {} + .overlay( + NavigationLink(destination: AboutInfoView(section: .privacyPolicy)) { + Color.clear + } + ) + + Divider().padding(.leading, 54) + + AboutRowView( + icon: "doc.text", + isCustomImage: false, + title: NSLocalizedString( + "i18n.app.about.legal.third_party", + value: "Third-party licenses", comment: ""), + hasNestedNavigation: true + ) {} + .overlay( + NavigationLink(destination: AboutInfoView(section: .licenses)) { + Color.clear + } + ) + } + + Spacer(minLength: 32) + } + .padding(.top, 8) + .animation(.easeInOut(duration: 0.2), value: tipCardVisible) + } + } + .sheet(isPresented: $showShareSheet) { + ShareSheet(items: [scribeShareURL()]) + } + .sheet(isPresented: $showMailCompose) { + MailComposeView(recipients: ["team@scri.be"], subject: "Hey Scribe!") + } + .alert( + NSLocalizedString( + "i18n.app.about.feedback.send_email", value: "Send us an email", comment: ""), + isPresented: $showMailAlert + ) { + Button(NSLocalizedString("i18n._global.cancel", value: "Cancel", comment: ""), role: .cancel) {} + } message: { + Text("Reach out to us at team@scri.be") + } + .alert( + NSLocalizedString( + "i18n.app.about.feedback.reset_app_hints", value: "Reset app hints", comment: ""), + isPresented: $showAppHintsConfirmation + ) { + Button( + NSLocalizedString("i18n._global.cancel", value: "Cancel", comment: ""), + role: .cancel + ) {} + Button(NSLocalizedString("i18n._global.reset", value: "Reset", comment: "")) { + resetAppHints() + } + } message: { + Text( + NSLocalizedString( + "i18n.app.about.feedback.reset_app_hints.message", + value: "This will reset all app hints to their default visible state.", + comment: "" + )) + } + } + + // MARK: - Actions + + private func openURL(_ urlString: String, encoded: Bool = false) { + let target = encoded + ? (urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? urlString) + : urlString + guard let url = URL(string: target) else { return } + UIApplication.shared.open(url) + } + + private func rateApp() { + guard + let scene = UIApplication.shared.connectedScenes + .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene + else { return } + SKStoreReviewController.requestReview(in: scene) + } + + private func sendEmail() { + if MFMailComposeViewController.canSendMail() { + showMailCompose = true + } else { + showMailAlert = true + } + } + + private func resetAppHints() { + UserDefaults.standard.set(true, forKey: "installationTipCardState") + UserDefaults.standard.set(true, forKey: "settingsTipCardState") + UserDefaults.standard.set(true, forKey: "aboutTipCardState") + withAnimation { tipCardVisible = true } + } + + private func scribeShareURL() -> URL { + URL(string: "itms-apps://itunes.apple.com/app/id1596613886")! + } +} diff --git a/Scribe/AboutTab/AboutTableData.swift b/Scribe/AboutTab/AboutTableData.swift deleted file mode 100644 index b99d16cd..00000000 --- a/Scribe/AboutTab/AboutTableData.swift +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -/* - * Controls data displayed in the About tab. - */ - -import Foundation - -enum AboutTableData { - static var aboutTableData = [ - ParentTableCellModel( - headingTitle: NSLocalizedString( - "i18n.app.about.community.title", value: "Community", comment: "" - ), - section: [ - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.community.github", value: "See the code on GitHub", - comment: "" - ), - imageString: "github", - sectionState: .github, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.community.matrix", value: "Chat with the team on Matrix", - comment: "" - ), - imageString: "matrix", - sectionState: .matrix, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.community.mastodon", value: "Follow us on Mastodon", - comment: "" - ), - imageString: "mastodon", - sectionState: .mastodon, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.community.visit_website", value: "Visit the Scribe website", - comment: "" - ), - imageString: "globe", - sectionState: .scribeApps, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.community.share_scribe", value: "Share Scribe", comment: "" - ), - imageString: "square.and.arrow.up", - sectionState: .shareScribe, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.community.wikimedia", - value: "Wikimedia and Scribe", - comment: "" - ), - imageString: "wikimedia", - hasNestedNavigation: true, - sectionState: .wikimedia - ) - ], - hasDynamicData: nil - ), - ParentTableCellModel( - headingTitle: NSLocalizedString( - "i18n.app.about.feedback.title", - value: "Feedback and support", - comment: "" - ), - section: [ - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.feedback.rate_scribe", - value: "Rate Scribe", - comment: "" - ), - imageString: "star.fill", - sectionState: .rateScribe, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.feedback.bug_report", - value: "Report a bug", - comment: "" - ), - imageString: "ladybug", - sectionState: .bugReport, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.feedback.send_email", - value: "Send us an email", - comment: "" - ), - imageString: "envelope", - sectionState: .email, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.feedback.version", - value: "Check version", - comment: "" - ), - imageString: "bookmark", - sectionState: .version, - externalLink: true - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.feedback.reset_app_hints", - value: "Reset app hints", - comment: "" - ), - imageString: "lightbulb.max", - hasNestedNavigation: true, - sectionState: .appHints - ) - ], - hasDynamicData: nil - ), - ParentTableCellModel( - headingTitle: NSLocalizedString("i18n._global.legal", value: "Legal", comment: ""), - section: [ - Section( - sectionTitle: NSLocalizedString( - "i18n._global.privacy_policy", value: "Privacy policy", comment: "" - ), - imageString: "lock.shield", - hasNestedNavigation: true, - sectionState: .privacyPolicy - ), - Section( - sectionTitle: NSLocalizedString( - "i18n.app.about.legal.third_party", value: "Third-party licenses", - comment: "" - ), - imageString: "doc.text", - hasNestedNavigation: true, - sectionState: .licenses - ) - ], - hasDynamicData: nil - ) - ] -} diff --git a/Scribe/AboutTab/AboutTipCardView.swift b/Scribe/AboutTab/AboutTipCardView.swift new file mode 100644 index 00000000..7f983bdb --- /dev/null +++ b/Scribe/AboutTab/AboutTipCardView.swift @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * Tip card shown at the top of the About tab. + */ + +import SwiftUI + +struct AboutTipCardView: View { + let infoText: String + @Binding var isVisible: Bool + var onDismiss: (() -> Void)? + + private let cardCornerRadius: CGFloat = 10 + + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: cardCornerRadius) + .fill(Color("lightWhiteDarkBlack")) + HStack(spacing: 12) { + Image(systemName: "lightbulb.max") + .resizable() + .scaledToFit() + .frame(width: 28, height: 28) + .foregroundColor(Color("scribeCTA")) + .padding(.leading, 12) + + Text(infoText) + .font(.subheadline) + .foregroundColor(.primary) + + Spacer() + + Button { + withAnimation(.easeInOut(duration: 0.2)) { + isVisible = false + } + onDismiss?() + } label: { + Text("OK") + .foregroundColor(.white) + .frame(width: 40, height: 40) + .background(Color("scribeBlue")) + .cornerRadius(cardCornerRadius) + } + .padding(.trailing, 12) + } + } + .frame(maxWidth: .infinity) + .frame(height: 70) + .shadow(color: Color("keyShadow").opacity(0.4), radius: 5, x: 0, y: 2) + } +} diff --git a/Scribe/AboutTab/AboutViewController.swift b/Scribe/AboutTab/AboutViewController.swift index f9067a85..0ef16f59 100644 --- a/Scribe/AboutTab/AboutViewController.swift +++ b/Scribe/AboutTab/AboutViewController.swift @@ -4,318 +4,28 @@ * Functions for the About tab. */ -import MessageUI -import StoreKit import SwiftUI import UIKit -final class AboutViewController: BaseTableViewController { - override var dataSet: [ParentTableCellModel] { - AboutTableData.aboutTableData - } - - private let aboutTipCardState: Bool = { - let userDefault = UserDefaults.standard - return userDefault.object(forKey: "aboutTipCardState") as? Bool ?? true - }() - - private var tipHostingController: UIHostingController! - private var tableViewOffset: CGFloat? - private let cornerRadius: CGFloat = 12 - +final class AboutViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - showTipCardView() - title = NSLocalizedString( - "i18n.app.about.title", - value: "About", - comment: "" - ) - - tableView.register( - WrapperCell.self, - forCellReuseIdentifier: WrapperCell.reuseIdentifier - ) - - tableView.register( - UINib(nibName: "AboutTableViewCell", bundle: nil), - forCellReuseIdentifier: AboutTableViewCell.reuseIdentifier - ) - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - if tableViewOffset == nil, UIDevice.current.userInterfaceIdiom != .pad { - tableViewOffset = tableView.contentOffset.y - } - } -} - -// MARK: UITableViewDataSource - -extension AboutViewController { - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) - -> UITableViewCell { - guard - let cell = tableView.dequeueReusableCell( - withIdentifier: WrapperCell.reuseIdentifier, - for: indexPath - ) as? WrapperCell - else { - fatalError("Failed to dequeue WrapperCell") - } - - let section = dataSet[indexPath.section] - let setting = section.section[indexPath.row] - - cell.configure(withCellNamed: "AboutTableViewCell", section: setting) - - let isFirstRow = indexPath.row == 0 - let isLastRow = indexPath.row == section.section.count - 1 - WrapperCell.applyCornerRadius(to: cell, isFirst: isFirstRow, isLast: isLastRow) - - return cell - } -} - -// MARK: UITableViewDelegate - -extension AboutViewController { - override func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) - -> CGFloat { - let section = dataSet[indexPath.section] - let setting = section.section[indexPath.row] - - let hasDescription = setting.shortDescription != nil - return hasDescription ? 80.0 : 48.0 - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let tableSection = dataSet[indexPath.section] - let section = tableSection.section[indexPath.row] - - switch section.sectionState { - case .github: - openURLString( - urlString: "https://github.com/scribe-org/Scribe-iOS", withEncoding: false - ) - - case .matrix: - openURLString( - urlString: "https://matrix.to/#/#scribe_community:matrix.org", withEncoding: true - ) - - case .mastodon: - openURLString(urlString: "https://wikis.world/@scribe", withEncoding: false) - - case .scribeApps: - openURLString(urlString: "https://scri.be/", withEncoding: false) - - case .wikimedia: - if let viewController = storyboard?.instantiateViewController( - identifier: "InformationScreenVC" - ) as? InformationScreenVC { - navigationController?.pushViewController(viewController, animated: true) - viewController.section = .wikimedia - } - - case .shareScribe: - showShareSheet() - - case .rateScribe: - showRateScribeUI() - - case .bugReport: - openURLString( - urlString: "https://github.com/scribe-org/Scribe-iOS/issues", withEncoding: false - ) - - case .version: - openURLString( - urlString: "https://github.com/scribe-org/Scribe-iOS/releases", withEncoding: false - ) - - case .email: - showEmailUI() - - case .appHints: - let userDefaults = UserDefaults.standard - userDefaults.set(true, forKey: "installationTipCardState") - userDefaults.set(true, forKey: "settingsTipCardState") - userDefaults.set(true, forKey: "aboutTipCardState") - startGlowingEffect(on: tipHostingController.view) + title = NSLocalizedString("i18n.app.about.title", value: "About", comment: "") + navigationController?.tabBarItem.title = NSLocalizedString("i18n.app.about.title", value: "About", comment: "") + navigationController?.navigationBar.prefersLargeTitles = true + navigationItem.largeTitleDisplayMode = .always - case .privacyPolicy: - if let viewController = storyboard?.instantiateViewController( - identifier: "InformationScreenVC" - ) as? InformationScreenVC { - navigationController?.pushViewController(viewController, animated: true) - viewController.section = .privacyPolicy - } - - case .licenses: - if let viewController = storyboard?.instantiateViewController( - identifier: "InformationScreenVC" - ) as? InformationScreenVC { - navigationController?.pushViewController(viewController, animated: true) - viewController.section = .licenses - } - - default: break - } - - if let selectedIndexPath = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: selectedIndexPath, animated: false) - } - } - - private func openURLString(urlString: String, withEncoding: Bool) { - if withEncoding { - let encodedString = urlString.addingPercentEncoding( - withAllowedCharacters: .urlQueryAllowed - ) - guard let encodedURLString = encodedString, let url = URL(string: encodedURLString) - else { - return - } - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } else { - guard let url = URL(string: urlString) else { return } - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } - } - - private func showRateScribeUI() { - guard let scene = UIApplication.shared.foregroundActiveScene else { return } - SKStoreReviewController.requestReview(in: scene) - } - - private func openScribeAppStore(alert _: UIAlertAction) { - openURLString( - urlString: "itms-apps: //itunes.apple.com/app/id1596613886", withEncoding: true - ) - } - - private func showEmailUI() { - if MFMailComposeViewController.canSendMail() { - let mailComposeViewController = MFMailComposeViewController() - mailComposeViewController.mailComposeDelegate = self - mailComposeViewController.setToRecipients(["team@scri.be"]) - mailComposeViewController.setSubject("Hey Scribe!") - - present(mailComposeViewController, animated: true, completion: nil) - } else { - // Show alert mentioning the email address. - let alert = UIAlertController( - title: NSLocalizedString( - "i18n.app.about.feedback.send_email", - value: "Send us an email", - comment: "" - ), - message: "Reach out to us at team@scri.be", preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alert, animated: true) - } - } - - private func showShareSheet() { - let urlString = "itms-apps: //itunes.apple.com/app/id1596613886" - let encodedString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) - guard let encodedURLString = encodedString, let url = URL(string: encodedURLString) - else { - return - } - - let shareSheetVC = UIActivityViewController( - activityItems: [url], applicationActivities: nil - ) - - present(shareSheetVC, animated: true, completion: nil) - } -} - -// MARK: MFMailComposeViewControllerDelegate - -extension AboutViewController: MFMailComposeViewControllerDelegate { - func mailComposeController( - _ controller: MFMailComposeViewController, didFinishWith _: MFMailComposeResult, - error _: Error? - ) { - controller.dismiss(animated: true, completion: nil) - } -} - -// MARK: TipCardView - -extension AboutViewController { - private func showTipCardView() { - let overlayView = AboutTipCardView( - aboutTipCardState: aboutTipCardState - ) - - let hostingController = UIHostingController(rootView: overlayView) - tipHostingController = hostingController - hostingController.view.frame = CGRect( - x: 0, y: 0, width: view.bounds.width, height: -40 - ) - hostingController.view.backgroundColor = .clear + let hostingController = UIHostingController(rootView: AboutTab()) + addChild(hostingController) hostingController.view.translatesAutoresizingMaskIntoConstraints = false - hostingController.view.isUserInteractionEnabled = true - - let navigationView = navigationController?.navigationBar - guard let navigationView else { return } - navigationView.addSubview(hostingController.view) - navigationView.bringSubviewToFront(hostingController.view) - + view.addSubview(hostingController.view) NSLayoutConstraint.activate([ - hostingController.view.topAnchor.constraint( - equalTo: navigationView.topAnchor, constant: 30 - ), - hostingController.view.leadingAnchor.constraint(equalTo: navigationView.leadingAnchor), - hostingController.view.trailingAnchor.constraint( - equalTo: navigationView.trailingAnchor - ) + hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) hostingController.didMove(toParent: self) - startGlowingEffect(on: hostingController.view) - } - - override func scrollViewDidScroll(_ scrollView: UIScrollView) { - guard let hostingController = tipHostingController, - let tableViewOffset - else { return } - - let currentOffset = scrollView.contentOffset.y - - if currentOffset > tableViewOffset { - // Scrolling up. - UIView.animate(withDuration: 0.2) { - hostingController.view.alpha = 0 - } - } else if currentOffset == tableViewOffset { - // Show the view only when scrolled to the top. - UIView.animate(withDuration: 0.1) { - hostingController.view.alpha = 1 - } - } - } - - func startGlowingEffect(on view: UIView, duration: TimeInterval = 1.0) { - view.layer.shadowColor = UIColor.scribeCTA.cgColor - view.layer.shadowRadius = 8 - view.layer.shadowOpacity = 0.0 - view.layer.shadowOffset = CGSize(width: 0, height: 0) - - UIView.animate( - withDuration: duration, - delay: 0, - options: [.curveEaseOut, .autoreverse], - animations: { - view.layer.shadowOpacity = 0.6 - }, completion: nil - ) } } diff --git a/Scribe/AboutTab/InformationScreenVC.swift b/Scribe/AboutTab/InformationScreenVC.swift index 27ba5a99..9616a897 100644 --- a/Scribe/AboutTab/InformationScreenVC.swift +++ b/Scribe/AboutTab/InformationScreenVC.swift @@ -1,217 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* - * Sets up information views in the About tab. + * Retained for storyboard compatibility. + * Navigation to information screens is now handled via SwiftUI NavigationLink in AboutTab. */ import UIKit -class InformationScreenVC: UIViewController { - @IBOutlet var scrollViewPhone: UIScrollView! - @IBOutlet var scrollViewPad: UIScrollView! - - @IBOutlet var scrollContainerViewPhone: UIView! - @IBOutlet var scrollContainerViewPad: UIView! - var scrollContainerView: UIView! - - @IBOutlet var relativeViewPhone: UIView! - @IBOutlet var relativeViewPad: UIView! - var relativeView: UIView! - - @IBOutlet var contentContainerViewPhone: UIView! - @IBOutlet var contentContainerViewPad: UIView! - var contentContainerView: UIView! - - @IBOutlet var headingLabelPhone: UILabel! - @IBOutlet var headingLabelPad: UILabel! - var headingLabel: UILabel! - - @IBOutlet var textViewPhone: UITextView! - @IBOutlet var textViewPad: UITextView! - var textView: UITextView! - - var text: String = "" - var section: SectionState = .privacyPolicy - - func setAppTextView() { - if DeviceType.isPad { - scrollContainerView = scrollContainerViewPad - relativeView = relativeViewPad - contentContainerView = contentContainerViewPad - headingLabel = headingLabelPad - textView = textViewPad - - scrollViewPhone.removeFromSuperview() - scrollContainerViewPhone.removeFromSuperview() - relativeViewPhone.removeFromSuperview() - contentContainerViewPhone.removeFromSuperview() - headingLabelPhone.removeFromSuperview() - textViewPhone.removeFromSuperview() - } else { - scrollContainerView = scrollContainerViewPhone - relativeView = relativeViewPhone - contentContainerView = contentContainerViewPhone - headingLabel = headingLabelPhone - textView = textViewPhone - - scrollViewPad.removeFromSuperview() - scrollContainerViewPad.removeFromSuperview() - relativeViewPad.removeFromSuperview() - contentContainerViewPad.removeFromSuperview() - headingLabelPad.removeFromSuperview() - textViewPad.removeFromSuperview() - } - } - - override func viewDidLoad() { - super.viewDidLoad() - - setupInformationPageUI() - - if section == .privacyPolicy { - setupPrivacyPolicyPage() - } else if section == .licenses { - setupLicensesPage() - } else { - setupWikimediaAndScribePage() - } - } - - /// Needed since Wikimedia and Scribe have an image as an attachment in the text. - /// Thus, it doesn't dynamically switch on theme change like a UIImage would. - /// Therefore, monitor for theme change and manually re-render textView. - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { - if section == .wikimedia { - textView.attributedText = switchAttachmentOnThemeChange( - for: textView.attributedText - ) - } - } - } - - func setupInformationPageUI() { - setAppTextView() - - textView.backgroundColor = .clear - scrollContainerView.backgroundColor = .clear - relativeView.backgroundColor = .clear - - contentContainerView.backgroundColor = lightWhiteDarkBlackColor - applyCornerRadius( - elem: contentContainerView, radius: contentContainerView.frame.width * 0.05 - ) - - contentContainerView.clipsToBounds = true - - textView.isEditable = false - } - - func setupPrivacyPolicyPage() { - navigationItem.title = NSLocalizedString( - "i18n._global.privacy_policy", value: "Privacy policy", comment: "" - ) - headingLabel.attributedText = NSMutableAttributedString( - string: NSLocalizedString( - "i18n.app.about.legal.privacy_policy.caption", value: "Keeping you safe", - comment: "" - ), - attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize * 1.1)] - ) - textView.attributedText = setPrivacyPolicy( - fontSize: fontSize, - text: NSLocalizedString( - "i18n.app.about.legal.privacy_policy.text", - value: """ - Please note that the English version of this policy takes precedence over all other versions. - - The Scribe developers (SCRIBE) built the iOS application "Scribe - Language Keyboards" (SERVICE) as an open-source application. This SERVICE is provided by SCRIBE at no cost and is intended for use as is. - - This privacy policy (POLICY) is used to inform the reader of the policies for the access, tracking, collection, retention, use, and disclosure of personal information (USER INFORMATION) and usage data (USER DATA) for all individuals who make use of this SERVICE (USERS). - - USER INFORMATION is specifically defined as any information related to the USERS themselves or the devices they use to access the SERVICE. - - USER DATA is specifically defined as any text that is typed or actions that are done by the USERS while using the SERVICE. - - 1. Policy Statement - - This SERVICE does not access, track, collect, retain, use, or disclose any USER INFORMATION or USER DATA. - - 2. Do Not Track - - USERS contacting SCRIBE to ask that their USER INFORMATION and USER DATA not be tracked will be provided with a copy of this POLICY as well as a link to all source codes as proof that they are not being tracked. - - 3. Third-Party Data - - This SERVICE makes use of third-party data. All data used in the creation of this SERVICE comes from sources that allow its full use in the manner done so by the SERVICE. Specifically, the data for this SERVICE comes from Wikidata, Wikipedia and Unicode. Wikidata states that, "All structured data in the main, property and lexeme namespaces is made available under the Creative Commons CC0 License; text in other namespaces is made available under the Creative Commons Attribution-Share Alike License." The policy detailing Wikidata data usage can be found at https://www.wikidata.org/wiki/Wikidata:Licensing. Wikipedia states that text data, the type of data used by the SERVICE, "… can be used under the terms of the Creative Commons Attribution Share-Alike license". The policy detailing Wikipedia data usage can be found at https://en.wikipedia.org/wiki/Wikipedia:Reusing_Wikipedia_content. Unicode provides permission, "… free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation (the "Data Files") or Unicode software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction…" The policy detailing Unicode data usage can be found at https://www.unicode.org/license.txt. - - 4. Third-Party Source Code - - This SERVICE was based on third-party code. All source code used in the creation of this SERVICE comes from sources that allow its full use in the manner done so by the SERVICE. Specifically, the basis of this project was the project Custom Keyboard by Ethan Sarif-Kattan. Custom Keyboard was released under an MIT license, with this license being available at https://github.com/EthanSK/CustomKeyboard/blob/master/LICENSE. - - 5. Third-Party Services - - This SERVICE makes use of third-party services to manipulate some of the third-party data. Specifically, data has been translated using models from Hugging Face transformers. This service is covered by an Apache License 2.0, which states that it is available for commercial use, modification, distribution, patent use, and private use. The license for the aforementioned service can be found at https://github.com/huggingface/transformers/blob/master/LICENSE. - - 6. Third-Party Links - - This SERVICE contains links to external websites. If USERS click on a third-party link, they will be directed to a website. Note that these external websites are not operated by this SERVICE. Therefore, USERS are strongly advised to review the privacy policy of these websites. This SERVICE has no control over and assumes no responsibility for the content, privacy policies, or practices of any third-party sites or services. - - 7. Third-Party Images - - This SERVICE contains images that are copyrighted by third-parties. Specifically, this app includes a copy of the logos of GitHub, Inc and Wikidata, trademarked by Wikimedia Foundation, Inc. The terms by which the GitHub logo can be used are found on https://github.com/logos, and the terms for the Wikidata logo are found on the following Wikimedia page: https://foundation.wikimedia.org/wiki/Policy:Trademark_policy. This SERVICE uses the copyrighted images in a way that matches these criteria, with the only deviation being a rotation of the GitHub logo that is common in the open-source community to indicate that there is a link to the GitHub website. - - 8. Content Notice - - This SERVICE allows USERS to access linguistic content (CONTENT). Some of this CONTENT could be deemed inappropriate for children and legal minors. Accessing CONTENT using the SERVICE is done in a way that the information is unavailable unless explicitly known. Specifically, USERS "can" translate words, conjugate verbs, and access other grammatical features of CONTENT that may be sexual, violent, or otherwise inappropriate in nature. USERS "cannot" translate words, conjugate verbs, and access other grammatical features of CONTENT that may be sexual, violent, or otherwise inappropriate in nature if they do not already know about the nature of this CONTENT. SCRIBE takes no responsibility for the access of such CONTENT. - - 9. Changes - - This POLICY is subject to change. Updates to this POLICY will replace all prior instances, and if deemed material will further be clearly stated in the next applicable update to the SERVICE. SCRIBE encourages USERS to periodically review this POLICY for the latest information on our privacy practices and to familiarize themselves with any changes. - - 10. Contact - - If you have any questions, concerns, or suggestions about this POLICY, do not hesitate to visit https://github.com/scribe-org or contact SCRIBE at scribe.langauge@gmail.com. The person responsible for such inquiries is Andrew Tavis McAllister. - - 11. Effective Date - - This POLICY is effective as of the 24th of May, 2022. - """, comment: "" - ) - ) - textView.textColor = keyCharColor - textView.linkTextAttributes = [ - NSAttributedString.Key.foregroundColor: linkBlueColor - ] - } - - func setupLicensesPage() { - navigationItem.title = thirdPartyLicensesTitle - headingLabel.attributedText = NSMutableAttributedString( - string: thirdPartyLicensesCaption, - attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize * 1.1)] - ) - textView.attributedText = setThirdPartyLicenses( - fontSize: fontSize, text: thirdPartyLicensesText - ) - textView.textColor = keyCharColor - textView.linkTextAttributes = [ - NSAttributedString.Key.foregroundColor: linkBlueColor - ] - } - - func setupWikimediaAndScribePage() { - navigationItem.title = wikimediaAndScribeTitle - headingLabel.attributedText = NSMutableAttributedString( - string: wikimediaAndScribeCaption, - attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize * 1.1)] - ) - textView.attributedText = setWikimediaAndScribe( - text: wikimediaAndScribeText, fontSize: fontSize, - imageWidth: contentContainerView.frame.width * 0.6 - ) - textView.textColor = keyCharColor - } -} +class InformationScreenVC: UIViewController {} diff --git a/Scribe/AboutTab/ShareSheet.swift b/Scribe/AboutTab/ShareSheet.swift new file mode 100644 index 00000000..cbe076c4 --- /dev/null +++ b/Scribe/AboutTab/ShareSheet.swift @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * UIKit bridge for presenting the system share sheet in SwiftUI. + */ + +import MessageUI +import SwiftUI +import UIKit + +struct ShareSheet: UIViewControllerRepresentable { + let items: [Any] + + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: items, applicationActivities: nil) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} +} + +// MARK: - MailComposeView + +struct MailComposeView: UIViewControllerRepresentable { + let recipients: [String] + let subject: String + + func makeUIViewController(context: Context) -> MFMailComposeViewController { + let vc = MFMailComposeViewController() + vc.mailComposeDelegate = context.coordinator + vc.setToRecipients(recipients) + vc.setSubject(subject) + return vc + } + + func updateUIViewController(_: MFMailComposeViewController, context _: Context) {} + + func makeCoordinator() -> Coordinator { Coordinator() } + + class Coordinator: NSObject, MFMailComposeViewControllerDelegate { + func mailComposeController( + _ controller: MFMailComposeViewController, + didFinishWith _: MFMailComposeResult, + error _: Error? + ) { + controller.dismiss(animated: true) + } + } +} diff --git a/Scribe/Base.lproj/AppScreen.storyboard b/Scribe/Base.lproj/AppScreen.storyboard index e2030782..a1725a05 100644 --- a/Scribe/Base.lproj/AppScreen.storyboard +++ b/Scribe/Base.lproj/AppScreen.storyboard @@ -14,19 +14,14 @@ - - + + - - - - - - + - + diff --git a/Scribe/TipCard/TipCardView.swift b/Scribe/TipCard/TipCardView.swift index 416e2bc6..08083d50 100644 --- a/Scribe/TipCard/TipCardView.swift +++ b/Scribe/TipCard/TipCardView.swift @@ -98,17 +98,17 @@ struct SettingsTipCardView: View { } } -struct AboutTipCardView: View { - @AppStorage("aboutTipCardState", store: .standard) var aboutTipCardState: Bool = true - - var body: some View { - TipCardView( - infoText: NSLocalizedString( - "i18n.app.about.app_hint_tooltip", - value: "Here's where you can learn more about Scribe and its community.", - comment: "" - ), - tipCardState: $aboutTipCardState - ) - } -} +//struct AboutTipCardView: View { +// @AppStorage("aboutTipCardState", store: .standard) var aboutTipCardState: Bool = true +// +// var body: some View { +// TipCardView( +// infoText: NSLocalizedString( +// "i18n.app.about.app_hint_tooltip", +// value: "Here's where you can learn more about Scribe and its community.", +// comment: "" +// ), +// tipCardState: $aboutTipCardState +// ) +// } +//} From 66b5b2f64d7aeef8fd0579428323ae54f5a1c83d Mon Sep 17 00:00:00 2001 From: Prince Yadav <66916296+prince-0408@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:54:40 +0530 Subject: [PATCH 2/3] fix: add spaces after comment slashes to pass SwiftLint --- Scribe/TipCard/TipCardView.swift | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Scribe/TipCard/TipCardView.swift b/Scribe/TipCard/TipCardView.swift index 08083d50..90b5998b 100644 --- a/Scribe/TipCard/TipCardView.swift +++ b/Scribe/TipCard/TipCardView.swift @@ -98,17 +98,17 @@ struct SettingsTipCardView: View { } } -//struct AboutTipCardView: View { -// @AppStorage("aboutTipCardState", store: .standard) var aboutTipCardState: Bool = true +// struct AboutTipCardView: View { +// @AppStorage("aboutTipCardState", store: .standard) var aboutTipCardState: Bool = true // -// var body: some View { -// TipCardView( -// infoText: NSLocalizedString( -// "i18n.app.about.app_hint_tooltip", -// value: "Here's where you can learn more about Scribe and its community.", -// comment: "" -// ), -// tipCardState: $aboutTipCardState -// ) -// } -//} +// var body: some View { +// TipCardView( +// infoText: NSLocalizedString( +// "i18n.app.about.app_hint_tooltip", +// value: "Here's where you can learn more about Scribe and its community.", +// comment: "" +// ), +// tipCardState: $aboutTipCardState +// ) +// } +// } From 1c82d9bd9863b3c98e7c86591cbacda268326e12 Mon Sep 17 00:00:00 2001 From: Prince Yadav <66916296+prince-0408@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:31:39 +0530 Subject: [PATCH 3/3] feat: migrate AboutTab to SwiftUI with AboutHostingController --- Scribe.xcodeproj/project.pbxproj | 12 +- Scribe/AboutTab/AboutHostingController.swift | 17 ++ Scribe/AboutTab/AboutTab.swift | 2 + Scribe/AboutTab/AboutViewController.swift | 31 --- Scribe/AboutTab/InformationScreenVC.swift | 10 - Scribe/Base.lproj/AppScreen.storyboard | 204 +------------------ 6 files changed, 25 insertions(+), 251 deletions(-) create mode 100644 Scribe/AboutTab/AboutHostingController.swift delete mode 100644 Scribe/AboutTab/AboutViewController.swift delete mode 100644 Scribe/AboutTab/InformationScreenVC.swift diff --git a/Scribe.xcodeproj/project.pbxproj b/Scribe.xcodeproj/project.pbxproj index 45a8a7f2..442d598a 100644 --- a/Scribe.xcodeproj/project.pbxproj +++ b/Scribe.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 075257CF2F7D481E00E57E2A /* AboutSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257C82F7D481D00E57E2A /* AboutSectionView.swift */; }; 075257D02F7D481E00E57E2A /* AboutTipCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257CA2F7D481D00E57E2A /* AboutTipCardView.swift */; }; 075257D12F7D481E00E57E2A /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075257CB2F7D481D00E57E2A /* ShareSheet.swift */; }; + 14AC56842A24AED3006B1DDF /* AboutHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14AC56832A24AED3006B1DDF /* AboutHostingController.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 */; }; @@ -23,8 +24,6 @@ 147797B32A2CD5AB0044A53E /* ParentTableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147797B22A2CD5AB0044A53E /* ParentTableCellModel.swift */; }; 147797B52A2CFB490044A53E /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147797B42A2CFB490044A53E /* SettingsViewController.swift */; }; 147797C02A2D0CDF0044A53E /* SettingsTableData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147797BF2A2D0CDF0044A53E /* SettingsTableData.swift */; }; - 14AC56842A24AED3006B1DDF /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14AC56832A24AED3006B1DDF /* AboutViewController.swift */; }; - 14AC568A2A261663006B1DDF /* InformationScreenVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14AC56892A261663006B1DDF /* InformationScreenVC.swift */; }; 1900C00E2C88BF980017A874 /* TestKeyboardStyling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1900C00D2C88BF980017A874 /* TestKeyboardStyling.swift */; }; 198369CC2C7980BA00C1B583 /* KeyboardProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 198369CB2C7980BA00C1B583 /* KeyboardProvider.swift */; }; 198369CD2C7980BA00C1B583 /* KeyboardProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 198369CB2C7980BA00C1B583 /* KeyboardProvider.swift */; }; @@ -1029,6 +1028,7 @@ 075257C92F7D481D00E57E2A /* AboutTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutTab.swift; sourceTree = ""; }; 075257CA2F7D481D00E57E2A /* AboutTipCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutTipCardView.swift; sourceTree = ""; }; 075257CB2F7D481D00E57E2A /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; + 14AC56832A24AED3006B1DDF /* AboutHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutHostingController.swift; sourceTree = ""; }; 1406B78B2A3209CF001DF45B /* AppExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppExtensions.swift; sourceTree = ""; }; 144B56F22A568AC200C2F447 /* Scribe.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Scribe.entitlements; sourceTree = ""; }; 147797AE2A2CD3370044A53E /* InfoChildTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoChildTableViewCell.swift; sourceTree = ""; }; @@ -1036,8 +1036,6 @@ 147797B22A2CD5AB0044A53E /* ParentTableCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParentTableCellModel.swift; sourceTree = ""; }; 147797B42A2CFB490044A53E /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 147797BF2A2D0CDF0044A53E /* SettingsTableData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableData.swift; sourceTree = ""; }; - 14AC56832A24AED3006B1DDF /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; - 14AC56892A261663006B1DDF /* InformationScreenVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InformationScreenVC.swift; sourceTree = ""; }; 1900C00D2C88BF980017A874 /* TestKeyboardStyling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestKeyboardStyling.swift; sourceTree = ""; }; 198369CB2C7980BA00C1B583 /* KeyboardProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardProvider.swift; sourceTree = ""; }; 19DC85F92C7772FC006E32FD /* KeyboardBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardBuilder.swift; sourceTree = ""; }; @@ -1570,8 +1568,7 @@ 075257C92F7D481D00E57E2A /* AboutTab.swift */, 075257CA2F7D481D00E57E2A /* AboutTipCardView.swift */, 075257CB2F7D481D00E57E2A /* ShareSheet.swift */, - 14AC56832A24AED3006B1DDF /* AboutViewController.swift */, - 14AC56892A261663006B1DDF /* InformationScreenVC.swift */, + 14AC56832A24AED3006B1DDF /* AboutHostingController.swift */, ); path = AboutTab; sourceTree = ""; @@ -2917,11 +2914,11 @@ 075257CF2F7D481E00E57E2A /* AboutSectionView.swift in Sources */, 075257D02F7D481E00E57E2A /* AboutTipCardView.swift in Sources */, 075257D12F7D481E00E57E2A /* ShareSheet.swift in Sources */, + 14AC56842A24AED3006B1DDF /* AboutHostingController.swift in Sources */, D171942D27AECEB80038660B /* DEInterfaceVariables.swift in Sources */, 147797C02A2D0CDF0044A53E /* SettingsTableData.swift in Sources */, E9F7273F2F45A6E60060B92D /* APIClient.swift in Sources */, E98034E42F45B88C006C1CDC /* ToastView.swift in Sources */, - 14AC568A2A261663006B1DDF /* InformationScreenVC.swift in Sources */, 147797B02A2CD3370044A53E /* InfoChildTableViewCell.swift in Sources */, D1A2DCB427AD3EB50057A10D /* AppUISymbols.swift in Sources */, D171945427AF04E50038660B /* KeyboardViewController.swift in Sources */, @@ -2975,7 +2972,6 @@ D1CDED812A85A12400098546 /* NBInterfaceVariables.swift in Sources */, D17693DE28FC8D6B00DF0FBB /* FR-QWERTYInterfaceVariables.swift in Sources */, D1B0719727C63C9100FD7DBD /* KeyAnimation.swift in Sources */, - 14AC56842A24AED3006B1DDF /* AboutViewController.swift in Sources */, D171945527AF05D40038660B /* Extensions.swift in Sources */, EDC364692AE408F20001E456 /* InterfaceConstants.swift in Sources */, ); diff --git a/Scribe/AboutTab/AboutHostingController.swift b/Scribe/AboutTab/AboutHostingController.swift new file mode 100644 index 00000000..bb85c8dc --- /dev/null +++ b/Scribe/AboutTab/AboutHostingController.swift @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import SwiftUI +import UIKit + +/// Hosts the SwiftUI AboutTab inside the UIKit storyboard navigation stack. +final class AboutHostingController: UIHostingController { + required init?(coder: NSCoder) { + super.init(coder: coder, rootView: AnyView(AboutTab())) + } + + override func viewDidLoad() { + super.viewDidLoad() + tabBarItem.title = NSLocalizedString("i18n.app.about.title", value: "About", comment: "") + navigationController?.tabBarItem.title = NSLocalizedString("i18n.app.about.title", value: "About", comment: "") + } +} diff --git a/Scribe/AboutTab/AboutTab.swift b/Scribe/AboutTab/AboutTab.swift index b1596f44..924b073c 100644 --- a/Scribe/AboutTab/AboutTab.swift +++ b/Scribe/AboutTab/AboutTab.swift @@ -244,6 +244,8 @@ struct AboutTab: View { .animation(.easeInOut(duration: 0.2), value: tipCardVisible) } } + .navigationTitle(NSLocalizedString("i18n.app.about.title", value: "About", comment: "")) + .navigationBarTitleDisplayMode(.large) .sheet(isPresented: $showShareSheet) { ShareSheet(items: [scribeShareURL()]) } diff --git a/Scribe/AboutTab/AboutViewController.swift b/Scribe/AboutTab/AboutViewController.swift deleted file mode 100644 index 0ef16f59..00000000 --- a/Scribe/AboutTab/AboutViewController.swift +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -/* - * Functions for the About tab. - */ - -import SwiftUI -import UIKit - -final class AboutViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - - title = NSLocalizedString("i18n.app.about.title", value: "About", comment: "") - navigationController?.tabBarItem.title = NSLocalizedString("i18n.app.about.title", value: "About", comment: "") - navigationController?.navigationBar.prefersLargeTitles = true - navigationItem.largeTitleDisplayMode = .always - - let hostingController = UIHostingController(rootView: AboutTab()) - addChild(hostingController) - hostingController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(hostingController.view) - NSLayoutConstraint.activate([ - hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - hostingController.didMove(toParent: self) - } -} diff --git a/Scribe/AboutTab/InformationScreenVC.swift b/Scribe/AboutTab/InformationScreenVC.swift deleted file mode 100644 index 9616a897..00000000 --- a/Scribe/AboutTab/InformationScreenVC.swift +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -/* - * Retained for storyboard compatibility. - * Navigation to information screens is now handled via SwiftUI NavigationLink in AboutTab. - */ - -import UIKit - -class InformationScreenVC: UIViewController {} diff --git a/Scribe/Base.lproj/AppScreen.storyboard b/Scribe/Base.lproj/AppScreen.storyboard index a1725a05..e04749ff 100644 --- a/Scribe/Base.lproj/AppScreen.storyboard +++ b/Scribe/Base.lproj/AppScreen.storyboard @@ -14,218 +14,18 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -