diff --git a/Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift b/Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift index 6e6cf5a..07367af 100644 --- a/Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift +++ b/Apps/AtlasApp/Sources/AtlasApp/AppShellView.swift @@ -1,5 +1,6 @@ import AtlasDesignSystem import AtlasDomain +import AtlasFeaturesAbout import AtlasFeaturesApps import AtlasFeaturesHistory import AtlasFeaturesOverview @@ -133,7 +134,21 @@ struct AppShellView: View { case .overview: OverviewFeatureView( snapshot: model.filteredSnapshot, - isRefreshingHealthSnapshot: model.isHealthSnapshotRefreshing + isRefreshingHealthSnapshot: model.isHealthSnapshotRefreshing, + onStartSmartClean: { + model.navigate(to: .smartClean) + Task { await model.runSmartCleanScan() } + }, + onNavigateToSmartClean: { + model.navigate(to: .smartClean) + }, + onNavigateToHistory: { + model.navigate(to: .history) + }, + onNavigateToPermissions: { + model.navigate(to: .permissions) + Task { await model.inspectPermissions() } + } ) case .smartClean: SmartCleanFeatureView( @@ -209,6 +224,8 @@ struct AppShellView: View { Task { await model.setNotificationsEnabled(isEnabled) } } ) + case .about: + AboutFeatureView() } } @@ -223,18 +240,7 @@ private struct SidebarRouteRow: View { let route: AtlasRoute var body: some View { - Label { - VStack(alignment: .leading, spacing: AtlasSpacing.xxs) { - Text(route.title) - .font(AtlasTypography.rowTitle) - - Text(route.subtitle) - .font(AtlasTypography.captionSmall) - .foregroundStyle(.secondary) - .lineLimit(1) - .truncationMode(.tail) - } - } icon: { + HStack(alignment: .center, spacing: AtlasSpacing.md) { ZStack { RoundedRectangle(cornerRadius: AtlasRadius.sm, style: .continuous) .fill(AtlasColor.brand.opacity(0.1)) @@ -245,6 +251,17 @@ private struct SidebarRouteRow: View { .foregroundStyle(AtlasColor.brand) .accessibilityHidden(true) } + + VStack(alignment: .leading, spacing: AtlasSpacing.xxs) { + Text(route.title) + .font(AtlasTypography.rowTitle) + + Text(route.subtitle) + .font(.caption2) + .foregroundStyle(.tertiary) + .lineLimit(1) + .truncationMode(.tail) + } } .padding(.vertical, AtlasSpacing.sm) .contentShape(Rectangle()) @@ -275,6 +292,8 @@ private extension AtlasRoute { return "5" case .settings: return "6" + case .about: + return "7" } } } diff --git a/Packages/AtlasDomain/Sources/AtlasDomain/AtlasDomain.swift b/Packages/AtlasDomain/Sources/AtlasDomain/AtlasDomain.swift index c1472df..45b846d 100644 --- a/Packages/AtlasDomain/Sources/AtlasDomain/AtlasDomain.swift +++ b/Packages/AtlasDomain/Sources/AtlasDomain/AtlasDomain.swift @@ -7,6 +7,7 @@ public enum AtlasRoute: String, CaseIterable, Codable, Hashable, Identifiable, S case history case permissions case settings + case about public var id: String { rawValue } @@ -24,6 +25,8 @@ public enum AtlasRoute: String, CaseIterable, Codable, Hashable, Identifiable, S return AtlasL10n.string("route.permissions.title") case .settings: return AtlasL10n.string("route.settings.title") + case .about: + return AtlasL10n.string("route.about.title") } } @@ -41,6 +44,8 @@ public enum AtlasRoute: String, CaseIterable, Codable, Hashable, Identifiable, S return AtlasL10n.string("route.permissions.subtitle") case .settings: return AtlasL10n.string("route.settings.subtitle") + case .about: + return AtlasL10n.string("route.about.subtitle") } } @@ -58,6 +63,8 @@ public enum AtlasRoute: String, CaseIterable, Codable, Hashable, Identifiable, S return "lock.shield" case .settings: return "gearshape" + case .about: + return "person.crop.circle" } } } diff --git a/Packages/AtlasDomain/Sources/AtlasDomain/Resources/en.lproj/Localizable.strings b/Packages/AtlasDomain/Sources/AtlasDomain/Resources/en.lproj/Localizable.strings index dfb90ec..3ce5e12 100644 --- a/Packages/AtlasDomain/Sources/AtlasDomain/Resources/en.lproj/Localizable.strings +++ b/Packages/AtlasDomain/Sources/AtlasDomain/Resources/en.lproj/Localizable.strings @@ -14,6 +14,8 @@ "route.permissions.subtitle" = "Explain why access is needed before asking for it."; "route.settings.title" = "Settings"; "route.settings.subtitle" = "Preferences, exclusions, acknowledgement, and notices."; +"route.about.title" = "About"; +"route.about.subtitle" = "Meet the developer and explore more products."; "risk.safe" = "Safe"; "risk.review" = "Review"; @@ -244,6 +246,14 @@ "overview.risk.safe" = "Low-risk cleanup"; "overview.risk.review" = "Review before removing"; "overview.risk.advanced" = "Advanced inspection recommended"; +"overview.actions.viewAll" = "View all %d findings"; +"overview.activity.viewAll" = "View all activity"; +"overview.snapshot.viewAllOptimizations" = "Show all %d suggestions"; +"overview.snapshot.collapseOptimizations" = "Collapse suggestions"; +"overview.action.smartClean" = "Run Smart Clean"; +"overview.action.smartClean.hint" = "Navigate to Smart Clean and start a scan."; +"overview.action.permissions" = "Fix Permissions"; +"overview.action.permissions.hint" = "Navigate to Permissions to check and grant access."; "smartclean.screen.title" = "Smart Clean"; "smartclean.screen.subtitle" = "Turn scan results into a clear cleanup plan before you decide to run anything destructive."; @@ -559,3 +569,33 @@ "settings.acknowledgement.subtitle" = "The in-app attribution text users can read without leaving the product."; "settings.notices.title" = "Third-Party Notices"; "settings.notices.subtitle" = "A concise notice surface for shipped third-party attributions."; + +"confirm.title" = "Confirm"; +"confirm.cancel" = "Cancel"; +"smartclean.confirm.execute.title" = "Run this cleanup plan?"; +"smartclean.confirm.execute.message" = "This will process the reviewed plan. Items marked recoverable can be restored from History."; +"apps.confirm.uninstall.title" = "Uninstall this app?"; +"apps.confirm.uninstall.message" = "This will remove %@ and its leftovers. Recoverable items can be restored from History."; +"emptystate.action.scan" = "Run Smart Clean"; +"emptystate.action.refresh" = "Refresh Apps"; +"emptystate.action.viewHistory" = "View History"; +"emptystate.action.startScan" = "Start Scan"; +"about.screen.title" = "About"; +"about.screen.subtitle" = "The person and vision behind Atlas for Mac."; +"about.author.title" = "Developer"; +"about.author.name" = "Lizi KK"; +"about.author.role" = "Ex-Baidu & Alibaba Tech Lead · Hands On AI Coding"; +"about.author.bio" = "Built Atlas from 0 to 1 solo. Every line is AI-assisted, but passed through human decision-making, taste, and architectural judgment."; +"about.author.quote" = "AI is incredibly powerful hands and feet, but you must be the clear-headed brain."; +"about.product.title" = "Also by the Developer"; +"about.product.name" = "AtomStorm Studio"; +"about.product.detail" = "AI Agent-powered design platform — from idea to presentations, landing pages, and posters in minutes."; +"about.product.visit" = "Visit AtomStorm Studio"; +"about.opensource.title" = "Open Source"; +"about.opensource.name" = "Atlas for Mac (Mole Installer)"; +"about.opensource.detail" = "This project is open source. Star, fork, or contribute on GitHub."; +"about.opensource.visit" = "View on GitHub"; +"about.social.title" = "Follow Me"; +"about.social.detail" = "WeChat Official Account & Xiaohongshu"; + +"smartclean.preview.callout.review.title" = "Some steps in this plan need a closer review"; diff --git a/Packages/AtlasDomain/Sources/AtlasDomain/Resources/zh-Hans.lproj/Localizable.strings b/Packages/AtlasDomain/Sources/AtlasDomain/Resources/zh-Hans.lproj/Localizable.strings index 7b158c5..3ae212a 100644 --- a/Packages/AtlasDomain/Sources/AtlasDomain/Resources/zh-Hans.lproj/Localizable.strings +++ b/Packages/AtlasDomain/Sources/AtlasDomain/Resources/zh-Hans.lproj/Localizable.strings @@ -14,6 +14,8 @@ "route.permissions.subtitle" = "先解释原因,再请求访问。"; "route.settings.title" = "设置"; "route.settings.subtitle" = "偏好、排除项、致谢和通知。"; +"route.about.title" = "关于"; +"route.about.subtitle" = "了解作者,探索更多产品。"; "risk.safe" = "安全"; "risk.review" = "复核"; @@ -244,6 +246,14 @@ "overview.risk.safe" = "低风险清理"; "overview.risk.review" = "建议删除前先复核"; "overview.risk.advanced" = "建议高级检查"; +"overview.actions.viewAll" = "查看全部 %d 项发现"; +"overview.activity.viewAll" = "查看全部活动"; +"overview.snapshot.viewAllOptimizations" = "展开全部 %d 条建议"; +"overview.snapshot.collapseOptimizations" = "收起建议"; +"overview.action.smartClean" = "运行智能清理"; +"overview.action.smartClean.hint" = "前往智能清理并开始扫描。"; +"overview.action.permissions" = "修复权限"; +"overview.action.permissions.hint" = "前往权限页面检查并授权。"; "smartclean.screen.title" = "智能清理"; "smartclean.screen.subtitle" = "先把扫描结果变成清晰的清理计划,再决定是否执行任何具有破坏性的操作。"; @@ -559,3 +569,33 @@ "settings.acknowledgement.subtitle" = "用户无需离开产品,就能查看应用内致谢文本。"; "settings.notices.title" = "第三方说明"; "settings.notices.subtitle" = "用于展示随产品一起分发的第三方说明信息。"; + +"confirm.title" = "确认"; +"confirm.cancel" = "取消"; +"smartclean.confirm.execute.title" = "执行这份清理计划?"; +"smartclean.confirm.execute.message" = "将按复核后的计划执行清理。标记为可恢复的项目可以在历史中找回。"; +"apps.confirm.uninstall.title" = "卸载这个应用?"; +"apps.confirm.uninstall.message" = "将移除 %@ 及其残留文件。可恢复的项目可以在历史中找回。"; +"emptystate.action.scan" = "运行智能清理"; +"emptystate.action.refresh" = "刷新应用"; +"emptystate.action.viewHistory" = "查看历史"; +"emptystate.action.startScan" = "开始扫描"; +"about.screen.title" = "关于"; +"about.screen.subtitle" = "Atlas for Mac 背后的开发者与愿景。"; +"about.author.title" = "关于作者"; +"about.author.name" = "Lizi KK"; +"about.author.role" = "前百度 & 阿里技术负责人 · AI 全栈独立开发"; +"about.author.bio" = "从 0 到 1 独立构建 Atlas。每一行代码都由 AI 辅助,但都经过了人的决策、审美和架构判断。"; +"about.author.quote" = "AI 是极其强大的手和脚,但你必须做那个头脑清醒的大脑。"; +"about.product.title" = "作者的其他产品"; +"about.product.name" = "AtomStorm Studio"; +"about.product.detail" = "AI Agent 驱动的设计创作平台——从想法到演示文稿、落地页和海报,几分钟搞定。"; +"about.product.visit" = "访问 AtomStorm Studio"; +"about.opensource.title" = "开源项目"; +"about.opensource.name" = "Atlas for Mac (Mole Installer)"; +"about.opensource.detail" = "本项目已开源。欢迎在 GitHub 上 Star、Fork 或参与贡献。"; +"about.opensource.visit" = "在 GitHub 上查看"; +"about.social.title" = "关注作者"; +"about.social.detail" = "微信公众号 & 小红书"; + +"smartclean.preview.callout.review.title" = "这份计划中仍有步骤需要复核"; diff --git a/Packages/AtlasFeaturesAbout/Sources/AtlasFeaturesAbout/AboutFeatureView.swift b/Packages/AtlasFeaturesAbout/Sources/AtlasFeaturesAbout/AboutFeatureView.swift new file mode 100644 index 0000000..ca38465 --- /dev/null +++ b/Packages/AtlasFeaturesAbout/Sources/AtlasFeaturesAbout/AboutFeatureView.swift @@ -0,0 +1,100 @@ +import AtlasDesignSystem +import AtlasDomain +import SwiftUI + +public struct AboutFeatureView: View { + public init() {} + + public var body: some View { + AtlasScreen( + title: AtlasL10n.string("about.screen.title"), + subtitle: AtlasL10n.string("about.screen.subtitle") + ) { + AtlasInfoCard( + title: AtlasL10n.string("about.author.title") + ) { + HStack(alignment: .center, spacing: AtlasSpacing.md) { + Image(systemName: "person.crop.circle.fill") + .font(.system(size: 40, weight: .light)) + .foregroundStyle(AtlasColor.brand) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: AtlasSpacing.xxs) { + Text(AtlasL10n.string("about.author.name")) + .font(AtlasTypography.sectionTitle) + + Text(AtlasL10n.string("about.author.role")) + .font(.caption) + .foregroundStyle(.secondary) + } + } + .padding(.bottom, AtlasSpacing.sm) + + Text(AtlasL10n.string("about.author.bio")) + .font(AtlasTypography.body) + .foregroundStyle(.secondary) + } + + AtlasCallout( + title: AtlasL10n.string("about.author.quote"), + detail: AtlasL10n.string("about.author.name"), + tone: .neutral, + systemImage: "quote.opening" + ) + + AtlasInfoCard( + title: AtlasL10n.string("about.product.title") + ) { + AtlasDetailRow( + title: AtlasL10n.string("about.product.name"), + subtitle: AtlasL10n.string("about.product.detail"), + systemImage: "sparkle" + ) + + Link(destination: URL(string: "https://studio.atomstorm.ai")!) { + Text(AtlasL10n.string("about.product.visit")) + .frame(maxWidth: .infinity) + } + .buttonStyle(.atlasPrimary) + .padding(.top, AtlasSpacing.sm) + } + + AtlasInfoCard( + title: AtlasL10n.string("about.opensource.title") + ) { + AtlasDetailRow( + title: AtlasL10n.string("about.opensource.name"), + subtitle: AtlasL10n.string("about.opensource.detail"), + systemImage: "chevron.left.forwardslash.chevron.right" + ) + + Link(destination: URL(string: "https://github.com/CSZHK/mole-installer")!) { + Text(AtlasL10n.string("about.opensource.visit")) + .frame(maxWidth: .infinity) + } + .buttonStyle(.atlasSecondary) + .padding(.top, AtlasSpacing.sm) + } + + AtlasInfoCard( + title: AtlasL10n.string("about.social.title") + ) { + HStack(spacing: AtlasSpacing.md) { + Image(systemName: "ellipsis.bubble") + .font(.title3) + .foregroundStyle(AtlasColor.brand) + .accessibilityHidden(true) + + Text(AtlasL10n.string("about.social.detail")) + .font(AtlasTypography.body) + .foregroundStyle(.secondary) + } + } + } + .accessibilityIdentifier("about.screen") + } +} + +#Preview { + AboutFeatureView() +} diff --git a/Packages/Package.swift b/Packages/Package.swift index ff5e6e8..fa2eb22 100644 --- a/Packages/Package.swift +++ b/Packages/Package.swift @@ -14,6 +14,7 @@ let package = Package( .library(name: "AtlasFeaturesHistory", targets: ["AtlasFeaturesHistory"]), .library(name: "AtlasFeaturesOverview", targets: ["AtlasFeaturesOverview"]), .library(name: "AtlasFeaturesPermissions", targets: ["AtlasFeaturesPermissions"]), + .library(name: "AtlasFeaturesAbout", targets: ["AtlasFeaturesAbout"]), .library(name: "AtlasFeaturesSettings", targets: ["AtlasFeaturesSettings"]), .library(name: "AtlasFeaturesSmartClean", targets: ["AtlasFeaturesSmartClean"]), .library(name: "AtlasFeaturesStorage", targets: ["AtlasFeaturesStorage"]), @@ -23,6 +24,7 @@ let package = Package( targets: [ .target( name: "AtlasDesignSystem", + dependencies: ["AtlasDomain"], path: "AtlasDesignSystem/Sources/AtlasDesignSystem", resources: [.process("Resources")] ), @@ -62,6 +64,11 @@ let package = Package( dependencies: ["AtlasApplication", "AtlasDesignSystem", "AtlasDomain"], path: "AtlasFeaturesSmartClean/Sources/AtlasFeaturesSmartClean" ), + .target( + name: "AtlasFeaturesAbout", + dependencies: ["AtlasDesignSystem", "AtlasDomain"], + path: "AtlasFeaturesAbout/Sources/AtlasFeaturesAbout" + ), .target( name: "AtlasFeaturesApps", dependencies: ["AtlasDesignSystem", "AtlasDomain"],