diff --git a/README.md b/README.md index 087190c..aae22eb 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ | 欢迎引导流程 | 数学公式大全 | QR码扫描工具 | | 网络状态检测 | 单位换算 | 电池监控 | | Firebase崩溃监控 (Firebase Crash Monitoring) | 运行时长计算 | 关于页面 (About Page) | -| Git信息嵌入 (Git Info Embedding) | | | +| Git信息嵌入 (Git Info Embedding) | 崩溃测试功能 | | @@ -76,6 +76,15 @@ - **🌐 双语支持 (Bilingual Support)** - 生成的PDF支持中英文双语,适应不同语言环境 (Generated PDFs support bilingual Chinese/English for different language environments) - **💾 本地存储 (Local Storage)** - 题库PDF自动保存至应用文档目录,方便随时访问 (Problem bank PDFs automatically saved to app document directory for easy access) +### 📋 新增设置页面 (New Settings Page) +- **🎨 深色模式切换 (Dark Mode Toggle)** - 支持应用内切换深色模式和浅色模式 (Supports switching between dark and light mode within the app) +- **🔊 TTS语音开关 (TTS Toggle)** - 全局控制题目和乘法表的自动朗读功能 (Globally control the automatic reading function of questions and multiplication tables) +- **🌐 系统偏好 (System Preference)** - 支持跟随系统设置 (Supports following system settings) +- **ℹ️ 关于应用 (About App)** - 查看应用版本、构建号、Git提交信息 (View app version, build number, Git commit information) +- **ℹ️ 关于我 (About Me)** - 查看开发者信息和GitHub仓库链接 (View developer information and GitHub repository link) +- **💻 系统信息 (System Information)** - 实时查看设备信息、性能数据和系统状态 (Real-time view of device info, performance data and system status) +- **🛠️ 崩溃测试 (Crash Test)** - 提供崩溃测试功能,便于验证错误监控系统 (Provides crash testing functionality for verifying error monitoring system) + ### 🎯 智能解题方法 (Intelligent Solution Methods) - **加法方法 (Addition Method)** - 凑十法 (Making Ten Method) @@ -266,6 +275,12 @@ - **🎯 导航入口 (Navigation Entry)** - 从设置页面可直接访问QR码扫描工具 (Directly accessible from the settings page) +- **🔧 技术改进 (Technical Improvements)** - 修复摄像头初始化失败问题,优化后台处理和线程安全 + - 改进了AVCaptureSession配置,添加了canAddInput/canAddOutput的验证检查 + - 优化了后台任务处理:使用beginConfiguration/commitConfiguration确保线程安全 + - 添加了详细的错误日志和异常处理机制 + (Improved AVCaptureSession configuration, optimized background processing and thread safety) + ### 📐 小学数学公式大全 (Elementary Math Formula Guide) - **📚 全面公式库 (Comprehensive Formula Library)** - 涵盖几何图形、单位换算、数量关系、运算定律等小学数学核心公式 (Covers core elementary math formulas including geometric shapes, unit conversions, quantity relations, arithmetic laws, etc.) - **📐 几何公式 (Geometry Formulas)** - 包含平面图形(长方形、正方形、三角形等)和立体图形(长方体、正方体、圆柱等)的周长、面积、体积公式 (Includes perimeter, area, and volume formulas for plane figures like rectangle, square, triangle and solid figures like cuboid, cube, cylinder) @@ -307,8 +322,9 @@ - **测试功能 (Testing Feature)** - 在设置页面提供崩溃测试功能,便于验证错误监控系统 (Provides crash testing functionality in settings for verifying error monitoring system) ### ℹ️ 关于应用页面 (About App Page) -- **版本信息 (Version Information)** - 在设置中新增“关于应用”页面,显示应用版本、构建号。 (Adds an "About App" page in Settings to display app version and build number.) +- **版本信息 (Version Information)** - 在设置中新增"关于应用"页面,显示应用版本、构建号。 (Adds an "About App" page in Settings to display app version and build number.) - **自动Git信息 (Automatic Git Info)** - 通过构建脚本自动嵌入最新的Git提交哈希和信息。 (Automatically embeds the latest Git commit hash and message via a build script.) +- **致谢列表 (Acknowledgments)** - 包含致谢列表,感谢对项目有贡献的个人和工具。 (Includes an acknowledgments list, thanking individuals and tools that contributed to the project.) - **国际化 (Internationalized)** - 页面内容完全支持中英文。 (The page content is fully localized in Chinese and English.) ### 🌐 多语言支持 (Language Settings) @@ -339,8 +355,10 @@ - **深色模式切换 (Dark Mode Toggle)** - 支持应用内切换深色模式和浅色模式 (Supports switching between dark and light mode within the app) - **TTS语音开关 (TTS Toggle)** - 全局控制题目和乘法表的自动朗读功能 (Globally control the automatic reading function of questions and multiplication tables) - **系统偏好 (System Preference)** - 支持跟随系统设置 (Supports following system settings) +- **关于应用 (About App)** - 查看应用版本、构建号、Git提交信息和致谢列表 (View app version, build number, Git commit information and acknowledgments list) - **关于我 (About Me)** - 查看开发者信息和GitHub仓库链接 (View developer information and GitHub repository link) - **系统信息 (System Information)** - 实时查看设备信息、性能数据和系统状态 (Real-time view of device info, performance data and system status) +- **崩溃测试 (Crash Test)** - 提供崩溃测试功能,便于验证错误监控系统 (Provides crash testing functionality for verifying error monitoring system) ### 🎨 UI界面优化 (UI Improvements) - **简洁选择器 (Cleaner Picker)** - 隐藏难度选择器标签,创建更清洁的界面 (Difficulty picker labels are now hidden to create a cleaner interface) @@ -729,7 +747,8 @@ Arithmetic/ │ ├── SystemInfoManager.swift # 系统信息管理器(含电池、网络、屏幕信息) │ ├── ProgressViewUtils.swift # 进度视图工具 │ ├── MathBankPDFGenerator.swift # PDF题库生成器 -│ └── ImageCacheManager.swift # 图片缓存管理器 +│ ├── ImageCacheManager.swift # 图片缓存管理器 +│ └── QRCodeHelper.swift # QR码扫描工具辅助类 ├── 📁 ViewModels/ # 视图模型 │ └── GameViewModel.swift # 游戏逻辑控制器 ├── 📁 Views/ # 视图层 @@ -748,11 +767,12 @@ Arithmetic/ │ ├── SystemInfoView.swift # 系统信息视图 │ ├── MathBankView.swift # 数学题库生成视图 │ ├── QrCodeToolView.swift # QR码工具视图 +│ ├── AboutAppView.swift # 关于应用视图 │ └── CachedAsyncImageView.swift # 图片缓存视图 ├── 📁 scripts/ # 构建和工具脚本 │ ├── check_localizations.sh # 本地化检查并嵌入Git信息 (Checks localization and embeds Git info) │ ├── upload_dsyms.sh # dSYM上传脚本 -│ └── upload-symbols # dSYM上传工具 +│ └── embed_git_info.sh # Git信息嵌入脚本 └── 📁 Tests/ # 测试文件 └── UtilsTests.swift # 工具类测试 ``` diff --git a/Views/SettingsView.swift b/Views/SettingsView.swift index f61877d..20f6cca 100644 --- a/Views/SettingsView.swift +++ b/Views/SettingsView.swift @@ -125,15 +125,18 @@ struct SettingsView: View { struct AboutAppView: View { @Environment(\.presentationMode) var presentationMode - + @State private var animateHero = false + @State private var animateCards = false + @State private var copiedHashState = false + var appVersion: String { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "N/A" } - + var buildNumber: String { Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "N/A" } - + var gitCommitHash: String { gitInfo.hash } @@ -141,7 +144,7 @@ struct AboutAppView: View { var gitCommitMessage: String { gitInfo.message } - + private var gitInfo: (hash: String, message: String) { if let appVersionURL = Bundle.main.url(forResource: "appversion", withExtension: "txt"), let content = try? String(contentsOf: appVersionURL, encoding: .utf8) { @@ -157,66 +160,386 @@ struct AboutAppView: View { var body: some View { NavigationView { - Form { - Section { - HStack { - Spacer() - VStack(spacing: 10) { - Image("logo") - .resizable() - .frame(width: 80, height: 80) - .cornerRadius(16) - .shadow(radius: 3) - Text("Arithmetic") - .font(.title) - .fontWeight(.bold) - Text("Version \(appVersion) (\(buildNumber))") - .font(.caption) - .foregroundColor(.secondary) + ZStack { + Color(.systemBackground).ignoresSafeArea() + + ScrollView { + VStack(spacing: 28) { + // Hero Section + HeroHeader( + appVersion: appVersion, + buildNumber: buildNumber, + isAnimating: animateHero + ) + .padding(.top, 20) + + VStack(spacing: 20) { + // Git Details Section + VStack(alignment: .leading, spacing: 16) { + AboutSectionHeader( + title: "about.app.section.git_details".localized, + icon: "square.and.pencil" + ) + + CopyableInfoRow( + label: "about.app.label.hash".localized, + value: gitCommitHash, + isCopied: $copiedHashState + ) + .opacity(animateCards ? 1 : 0) + .offset(y: animateCards ? 0 : 10) + + InfoCard( + label: "about.app.label.message".localized, + value: gitCommitMessage, + icon: "quote.bubble", + iconColor: .blue + ) + .opacity(animateCards ? 1 : 0) + .offset(y: animateCards ? 0 : 10) + } + .padding(.horizontal, 16) + + // About App Description Section + VStack(alignment: .leading, spacing: 16) { + AboutSectionHeader( + title: "about.app.section.about_app".localized, + icon: "info.circle.fill" + ) + + DescriptionCard( + text: "about.app.description".localized + ) + .opacity(animateCards ? 1 : 0) + .offset(y: animateCards ? 0 : 10) + } + .padding(.horizontal, 16) + + // Acknowledgements Section + VStack(alignment: .leading, spacing: 16) { + AboutSectionHeader( + title: "about.app.section.acknowledgements".localized, + icon: "heart.fill" + ) + + VStack(spacing: 12) { + AccreditationLink( + title: "Firebase", + icon: "flame.fill", + color: .orange, + url: URL(string: "https://firebase.google.com")! + ) + .opacity(animateCards ? 1 : 0) + .offset(y: animateCards ? 0 : 10) + + AccreditationLink( + title: "SwiftUI", + icon: "swift", + color: .blue, + url: URL(string: "https://developer.apple.com/xcode/swiftui/")! + ) + .opacity(animateCards ? 1 : 0) + .offset(y: animateCards ? 0 : 10) + } + } + .padding(.horizontal, 16) + + Spacer(minLength: 40) } - Spacer() + .padding(.vertical, 20) } - .padding(.vertical) - } - - Section(header: Text("about.app.section.git_details".localized)) { - InfoRow(label: "about.app.label.hash".localized, value: gitCommitHash) - InfoRow(label: "about.app.label.message".localized, value: gitCommitMessage) - } - - Section(header: Text("about.app.section.about_app".localized)) { - Text("about.app.description".localized) - .font(.body) - .padding(.vertical, 5) - } - - Section(header: Text("about.app.section.acknowledgements".localized)) { - Link("Firebase", destination: URL(string: "https://firebase.google.com")!) - Link("SwiftUI", destination: URL(string: "https://developer.apple.com/xcode/swiftui/")!) } } .navigationTitle("about.arithmetic.title".localized) + .navigationBarTitleDisplayMode(.large) .navigationBarItems(trailing: Button("Done") { presentationMode.wrappedValue.dismiss() }) + .onAppear { + withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) { + animateHero = true + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + withAnimation(.easeInOut(duration: 0.6)) { + animateCards = true + } + } + } + } + } +} + +// MARK: - Hero Header Component +private struct HeroHeader: View { + let appVersion: String + let buildNumber: String + let isAnimating: Bool + + var body: some View { + VStack(spacing: 16) { + Image("logo") + .resizable() + .frame(width: 100, height: 100) + .cornerRadius(24) + .shadow(color: Color.black.opacity(0.1), radius: 12, x: 0, y: 6) + .scaleEffect(isAnimating ? 1 : 0.8) + .opacity(isAnimating ? 1 : 0) + + VStack(spacing: 8) { + Text("Arithmetic") + .font(.adaptiveTitle()) + .fontWeight(.bold) + .foregroundColor(.primary) + + VStack(spacing: 4) { + Text("Version \(appVersion)") + .font(.adaptiveHeadline()) + .foregroundColor(.primary) + + Text("Build \(buildNumber)") + .font(.adaptiveCaption()) + .foregroundColor(.secondary) + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background(Color.gray.opacity(0.08)) + .cornerRadius(.adaptiveCornerRadius) + } + } + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + } +} + +// MARK: - About Section Header Component +private struct AboutSectionHeader: View { + let title: String + let icon: String + + var body: some View { + HStack(spacing: 12) { + Image(systemName: icon) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.blue) + .frame(width: 28, height: 28) + .background(Color.blue.opacity(0.1)) + .cornerRadius(.adaptiveCornerRadius) + + Text(title) + .font(.adaptiveHeadline()) + .fontWeight(.semibold) + .foregroundColor(.primary) + + Spacer() + } + } +} + +// MARK: - Copyable Info Row Component +private struct CopyableInfoRow: View { + let label: String + let value: String + @Binding var isCopied: Bool + @State private var showToast = false + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 8) { + Text(label) + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.medium) + + Spacer() + + Button(action: copyToClipboard) { + HStack(spacing: 6) { + Image(systemName: isCopied ? "checkmark" : "doc.on.doc") + .font(.system(size: 12, weight: .semibold)) + Text(isCopied ? "Copied" : "Copy") + .font(.caption2) + .fontWeight(.medium) + } + .foregroundColor(.white) + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(isCopied ? Color.green : Color.blue) + .cornerRadius(6) + } + .buttonStyle(PlainButtonStyle()) + } + + Text(value) + .font(.system(.body, design: .monospaced)) + .lineLimit(2) + .truncationMode(.middle) + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color.gray.opacity(0.08)) + .cornerRadius(.adaptiveCornerRadius) + .overlay( + RoundedRectangle(cornerRadius: .adaptiveCornerRadius) + .stroke(Color.gray.opacity(0.2), lineWidth: 1) + ) + } + .padding(14) + .background(Color(.systemBackground)) + .cornerRadius(.adaptiveCornerRadius * 1.5) + .overlay( + RoundedRectangle(cornerRadius: .adaptiveCornerRadius * 1.5) + .stroke(Color.gray.opacity(0.15), lineWidth: 1) + ) + .shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4) + } + + private func copyToClipboard() { + UIPasteboard.general.string = value + isCopied = true + + let impactFeedback = UIImpactFeedbackGenerator(style: .medium) + impactFeedback.impactOccurred() + + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + isCopied = false } } } -struct InfoRow: View { +// MARK: - Info Card Component +private struct InfoCard: View { let label: String let value: String + let icon: String + let iconColor: Color var body: some View { - VStack(alignment: .leading, spacing: 4) { - Text(label) - .font(.caption) - .foregroundColor(.secondary) + VStack(alignment: .leading, spacing: 10) { + HStack(spacing: 8) { + Image(systemName: icon) + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(iconColor) + + Text(label) + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.medium) + + Spacer() + } + Text(value) - .font(.body) - .lineLimit(3) + .font(.adaptiveBody()) + .lineSpacing(1.2) + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color(.systemBackground)) + .cornerRadius(.adaptiveCornerRadius) + .overlay( + RoundedRectangle(cornerRadius: .adaptiveCornerRadius) + .stroke(iconColor.opacity(0.2), lineWidth: 1) + ) + } + .padding(14) + .background(Color(.systemBackground)) + .cornerRadius(.adaptiveCornerRadius * 1.5) + .overlay( + RoundedRectangle(cornerRadius: .adaptiveCornerRadius * 1.5) + .stroke(Color.gray.opacity(0.15), lineWidth: 1) + ) + .shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4) + } +} + +// MARK: - Description Card Component +private struct DescriptionCard: View { + let text: String + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 8) { + Image(systemName: "lightbulb.fill") + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(.yellow) + + Text("Description") + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.medium) + + Spacer() + } + + Text(text) + .font(.adaptiveBody()) + .lineSpacing(1.5) + .foregroundColor(.primary) } - .padding(.vertical, 4) + .padding(16) + .background(Color.yellow.opacity(0.08)) + .cornerRadius(.adaptiveCornerRadius * 1.5) + .overlay( + RoundedRectangle(cornerRadius: .adaptiveCornerRadius * 1.5) + .stroke(Color.yellow.opacity(0.3), lineWidth: 1) + ) + .shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4) + } +} + +// MARK: - Accreditation Link Component +private struct AccreditationLink: View { + let title: String + let icon: String + let color: Color + let url: URL + + @State private var isPressed = false + + var body: some View { + Link(destination: url) { + HStack(spacing: 12) { + Image(systemName: icon) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .frame(width: 36, height: 36) + .background(color) + .cornerRadius(10) + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.adaptiveHeadline()) + .fontWeight(.semibold) + .foregroundColor(.primary) + + Text("Open official website") + .font(.caption) + .foregroundColor(.secondary) + } + + Spacer() + + Image(systemName: "arrow.up.right") + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(.secondary) + .padding(8) + .background(Color.gray.opacity(0.1)) + .cornerRadius(8) + } + .padding(14) + .background(Color(.systemBackground)) + .cornerRadius(.adaptiveCornerRadius * 1.5) + .overlay( + RoundedRectangle(cornerRadius: .adaptiveCornerRadius * 1.5) + .stroke(Color.gray.opacity(0.15), lineWidth: 1) + ) + .shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4) + } + .buttonStyle(PlainButtonStyle()) + .scaleEffect(isPressed ? 0.98 : 1.0) + .onLongPressGesture(minimumDuration: 0.1, perform: {}, onPressingChanged: { pressing in + withAnimation(.easeInOut(duration: 0.15)) { + isPressed = pressing + } + }) } }