Refactor SettingsView to improve layout and navigation structure. Replace NavigationView with a ZStack for better background handling and update UI elements for app permissions and privacy policy. Enhance localization support by adding new strings for app permissions in English, Thai, and Simplified Chinese. Update navigation titles and button styles for consistency across the settings interface.
parent
a3df0ebc25
commit
667d4afb98
@ -0,0 +1,264 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Privacy Policy - MyQrCode</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 40px 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 2.2em;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .date {
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 40px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #2c3e50;
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 30px 0 15px 0;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #555;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 15px 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #555;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: #2c3e50;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-box {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-box h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-box ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-box li {
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #7f8c8d;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 30px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 30px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<div class="date">Last Updated: December 28, 2024</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="section">
|
||||||
|
<div class="highlight">
|
||||||
|
<p><strong>MyQrCode</strong> is committed to protecting your privacy. This Privacy Policy explains
|
||||||
|
how we collect, use, and safeguard your information when you use our mobile application.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>1. Information We Collect</h2>
|
||||||
|
<p>We collect only the minimum information necessary for the app's functionality:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Camera Access:</strong> Required to scan QR codes and barcodes using your device's
|
||||||
|
camera</li>
|
||||||
|
<li><strong>Photo Library Access:</strong> Required to save generated QR codes and barcodes to your
|
||||||
|
photo library</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Important:</strong> We do not collect personal information such as names, email addresses,
|
||||||
|
phone numbers, or any other personally identifiable information.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>2. How We Use Your Information</h2>
|
||||||
|
<p>The information we collect is used solely for app functionality:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Scanning QR codes and barcodes</li>
|
||||||
|
<li>Generating QR codes and barcodes</li>
|
||||||
|
<li>Saving images to your device</li>
|
||||||
|
</ul>
|
||||||
|
<p>We do not use your information for advertising, analytics, or any other commercial purposes.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>3. Information Sharing</h2>
|
||||||
|
<p>We do not share, sell, or transfer your information to third parties. All data processing occurs
|
||||||
|
locally on your device, and we do not transmit any information to external servers.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>4. Data Security</h2>
|
||||||
|
<p>We implement appropriate security measures to protect your information:</p>
|
||||||
|
<ul>
|
||||||
|
<li>All data is stored locally on your device</li>
|
||||||
|
<li>No data is transmitted to external servers</li>
|
||||||
|
<li>We use industry-standard security practices</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>5. Your Rights</h2>
|
||||||
|
<p>You have the right to:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Access, modify, or delete your data at any time</li>
|
||||||
|
<li>Revoke app permissions through your device settings</li>
|
||||||
|
<li>Uninstall the app to remove all related data</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>6. Children's Privacy</h2>
|
||||||
|
<p>Our app does not knowingly collect personal information from children under 13. If you are a parent
|
||||||
|
or guardian and believe your child has provided us with personal information, please contact us
|
||||||
|
immediately.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>7. Changes to This Policy</h2>
|
||||||
|
<p>We may update this Privacy Policy from time to time. We will notify you of any changes by posting the
|
||||||
|
new Privacy Policy in the app and updating the "Last Updated" date.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>8. Contact Us</h2>
|
||||||
|
<h3>Get in Touch</h3>
|
||||||
|
<p>If you have any questions about this Privacy Policy or our data practices, please contact us through:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>• App Store reviews and ratings</li>
|
||||||
|
<li>• Our support channels</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="highlight">
|
||||||
|
<p><strong>By using this app, you agree to the terms of this Privacy Policy.</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>© 2024 MyQrCode. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -0,0 +1,350 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import AVFoundation
|
||||||
|
import Photos
|
||||||
|
|
||||||
|
struct AppPermissionsView: View {
|
||||||
|
@EnvironmentObject private var languageManager: LanguageManager
|
||||||
|
@State private var cameraPermissionStatus: AVAuthorizationStatus = .notDetermined
|
||||||
|
@State private var photoPermissionStatus: PHAuthorizationStatus = .notDetermined
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
ZStack {
|
||||||
|
// 背景渐变
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
Color(.systemBackground),
|
||||||
|
Color(.systemGray6).opacity(0.2)
|
||||||
|
]),
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
VStack(spacing: 24) {
|
||||||
|
// 顶部图标
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.fill(
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
Color.blue.opacity(0.1),
|
||||||
|
Color.blue.opacity(0.05)
|
||||||
|
]),
|
||||||
|
startPoint: .topLeading,
|
||||||
|
endPoint: .bottomTrailing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.frame(width: 80, height: 80)
|
||||||
|
|
||||||
|
Image(systemName: "lock.shield")
|
||||||
|
.font(.system(size: 36, weight: .light))
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("app_permissions".localized)
|
||||||
|
.font(.system(size: 28, weight: .bold, design: .rounded))
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.id(languageManager.refreshTrigger)
|
||||||
|
}
|
||||||
|
.padding(.top, 20)
|
||||||
|
|
||||||
|
// 权限说明卡片
|
||||||
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
.font(.system(size: 20, weight: .medium))
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.frame(width: 32)
|
||||||
|
|
||||||
|
Text("permissions_info".localized)
|
||||||
|
.font(.system(size: 18, weight: .semibold))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("permissions_description".localized)
|
||||||
|
.font(.system(size: 14))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.lineLimit(nil)
|
||||||
|
}
|
||||||
|
.padding(20)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 16)
|
||||||
|
.fill(Color(.systemBackground))
|
||||||
|
.shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 2)
|
||||||
|
)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
|
||||||
|
// 相机权限卡片
|
||||||
|
PermissionCard(
|
||||||
|
icon: "camera.fill",
|
||||||
|
iconColor: .blue,
|
||||||
|
title: "camera_permission".localized,
|
||||||
|
description: "camera_permission_description".localized,
|
||||||
|
status: cameraPermissionStatus.displayText,
|
||||||
|
statusColor: cameraPermissionStatus.statusColor,
|
||||||
|
action: {
|
||||||
|
requestCameraPermission()
|
||||||
|
},
|
||||||
|
actionTitle: cameraPermissionStatus.actionTitle
|
||||||
|
)
|
||||||
|
|
||||||
|
// 相册权限卡片
|
||||||
|
PermissionCard(
|
||||||
|
icon: "photo.fill",
|
||||||
|
iconColor: .green,
|
||||||
|
title: "photo_permission".localized,
|
||||||
|
description: "photo_permission_description".localized,
|
||||||
|
status: photoPermissionStatus.displayText,
|
||||||
|
statusColor: photoPermissionStatus.statusColor,
|
||||||
|
action: {
|
||||||
|
requestPhotoPermission()
|
||||||
|
},
|
||||||
|
actionTitle: photoPermissionStatus.actionTitle
|
||||||
|
)
|
||||||
|
|
||||||
|
// 系统设置卡片
|
||||||
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "gearshape.fill")
|
||||||
|
.font(.system(size: 20, weight: .medium))
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
.frame(width: 32)
|
||||||
|
|
||||||
|
Text("system_settings".localized)
|
||||||
|
.font(.system(size: 18, weight: .semibold))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("system_settings_description".localized)
|
||||||
|
.font(.system(size: 14))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.lineLimit(nil)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
openSystemSettings()
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "arrow.up.right.square")
|
||||||
|
.font(.system(size: 16, weight: .medium))
|
||||||
|
Text("open_system_settings".localized)
|
||||||
|
.font(.system(size: 16, weight: .medium))
|
||||||
|
}
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.fill(Color.blue)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(20)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 16)
|
||||||
|
.fill(Color(.systemBackground))
|
||||||
|
.shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 2)
|
||||||
|
)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
|
||||||
|
Spacer(minLength: 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("App Permissions")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.onAppear {
|
||||||
|
checkPermissions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 权限检查
|
||||||
|
private func checkPermissions() {
|
||||||
|
cameraPermissionStatus = AVCaptureDevice.authorizationStatus(for: .video)
|
||||||
|
photoPermissionStatus = PHPhotoLibrary.authorizationStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 请求相机权限
|
||||||
|
private func requestCameraPermission() {
|
||||||
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.cameraPermissionStatus = granted ? .authorized : .denied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 请求相册权限
|
||||||
|
private func requestPhotoPermission() {
|
||||||
|
PHPhotoLibrary.requestAuthorization { status in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.photoPermissionStatus = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 打开系统设置
|
||||||
|
private func openSystemSettings() {
|
||||||
|
if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
|
||||||
|
UIApplication.shared.open(settingsUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 权限卡片组件
|
||||||
|
struct PermissionCard: View {
|
||||||
|
let icon: String
|
||||||
|
let iconColor: Color
|
||||||
|
let title: String
|
||||||
|
let description: String
|
||||||
|
let status: String
|
||||||
|
let statusColor: Color
|
||||||
|
let action: () -> Void
|
||||||
|
let actionTitle: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: icon)
|
||||||
|
.font(.system(size: 20, weight: .medium))
|
||||||
|
.foregroundColor(iconColor)
|
||||||
|
.frame(width: 32)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(title)
|
||||||
|
.font(.system(size: 18, weight: .semibold))
|
||||||
|
Text(description)
|
||||||
|
.font(.system(size: 14))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text(status)
|
||||||
|
.font(.system(size: 14, weight: .medium))
|
||||||
|
.foregroundColor(statusColor)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: action) {
|
||||||
|
Text(actionTitle)
|
||||||
|
.font(.system(size: 14, weight: .medium))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
|
.fill(statusColor)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(20)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 16)
|
||||||
|
.fill(Color(.systemBackground))
|
||||||
|
.shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 2)
|
||||||
|
)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 权限状态扩展
|
||||||
|
extension AVAuthorizationStatus {
|
||||||
|
var displayText: String {
|
||||||
|
switch self {
|
||||||
|
case .notDetermined:
|
||||||
|
return "not_determined".localized
|
||||||
|
case .restricted:
|
||||||
|
return "restricted".localized
|
||||||
|
case .denied:
|
||||||
|
return "denied".localized
|
||||||
|
case .authorized:
|
||||||
|
return "authorized".localized
|
||||||
|
@unknown default:
|
||||||
|
return "unknown".localized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusColor: Color {
|
||||||
|
switch self {
|
||||||
|
case .notDetermined:
|
||||||
|
return .orange
|
||||||
|
case .restricted, .denied:
|
||||||
|
return .red
|
||||||
|
case .authorized:
|
||||||
|
return .green
|
||||||
|
@unknown default:
|
||||||
|
return .gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .notDetermined:
|
||||||
|
return "request_permission".localized
|
||||||
|
case .restricted, .denied:
|
||||||
|
return "open_settings".localized
|
||||||
|
case .authorized:
|
||||||
|
return "permission_granted".localized
|
||||||
|
@unknown default:
|
||||||
|
return "unknown".localized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PHAuthorizationStatus {
|
||||||
|
var displayText: String {
|
||||||
|
switch self {
|
||||||
|
case .notDetermined:
|
||||||
|
return "not_determined".localized
|
||||||
|
case .restricted:
|
||||||
|
return "restricted".localized
|
||||||
|
case .denied:
|
||||||
|
return "denied".localized
|
||||||
|
case .authorized:
|
||||||
|
return "authorized".localized
|
||||||
|
case .limited:
|
||||||
|
return "limited".localized
|
||||||
|
@unknown default:
|
||||||
|
return "unknown".localized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusColor: Color {
|
||||||
|
switch self {
|
||||||
|
case .notDetermined:
|
||||||
|
return .orange
|
||||||
|
case .restricted, .denied:
|
||||||
|
return .red
|
||||||
|
case .authorized, .limited:
|
||||||
|
return .green
|
||||||
|
@unknown default:
|
||||||
|
return .gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .notDetermined:
|
||||||
|
return "request_permission".localized
|
||||||
|
case .restricted, .denied:
|
||||||
|
return "open_settings".localized
|
||||||
|
case .authorized, .limited:
|
||||||
|
return "permission_granted".localized
|
||||||
|
@unknown default:
|
||||||
|
return "unknown".localized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
AppPermissionsView()
|
||||||
|
.environmentObject(LanguageManager.shared)
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import WebKit
|
||||||
|
|
||||||
|
struct PrivacyPolicyView: View {
|
||||||
|
@EnvironmentObject private var languageManager: LanguageManager
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
ZStack {
|
||||||
|
// 背景渐变
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
Color(.systemBackground),
|
||||||
|
Color(.systemGray6).opacity(0.2)
|
||||||
|
]),
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
// WebView显示HTML隐私政策
|
||||||
|
WebView(url: Bundle.main.url(forResource: "privacy_policy", withExtension: "html")!)
|
||||||
|
.ignoresSafeArea(.container, edges: .bottom)
|
||||||
|
}
|
||||||
|
.navigationTitle("Privacy Policy")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - WebView
|
||||||
|
struct WebView: UIViewRepresentable {
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> WKWebView {
|
||||||
|
let webView = WKWebView()
|
||||||
|
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
|
||||||
|
return webView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: WKWebView, context: Context) {
|
||||||
|
// 不需要更新
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
PrivacyPolicyView()
|
||||||
|
.environmentObject(LanguageManager.shared)
|
||||||
|
}
|
Loading…
Reference in new issue