Skip to content

Commit cb68c61

Browse files
Prot10claude
andcommitted
feat: add update notification button in toolbar when updates are available
- New UpdateAvailableButton shows in toolbar when update is available - UpdateAvailableSheet modal shows current vs new version with download option - Added localization strings for EN/IT/ES - Version bumped to 1.1.6 (build 8) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f3f3efc commit cb68c61

File tree

6 files changed

+376
-13
lines changed

6 files changed

+376
-13
lines changed

MyMacCleaner.xcodeproj/project.pbxproj

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
objectVersion = 77;
77
objects = {
88

9+
/* Begin PBXBuildFile section */
10+
651BAB022F236CD600A82441 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 65E790862F0C23E200CA264D /* Sparkle */; };
11+
/* End PBXBuildFile section */
12+
913
/* Begin PBXContainerItemProxy section */
1014
650DE2AA2EE843C200C84519 /* PBXContainerItemProxy */ = {
1115
isa = PBXContainerItemProxy;
@@ -65,6 +69,7 @@
6569
isa = PBXFrameworksBuildPhase;
6670
buildActionMask = 2147483647;
6771
files = (
72+
651BAB022F236CD600A82441 /* Sparkle in Frameworks */,
6873
);
6974
runOnlyForDeploymentPostprocessing = 0;
7075
};
@@ -414,7 +419,7 @@
414419
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
415420
CODE_SIGN_STYLE = Manual;
416421
COMBINE_HIDPI_IMAGES = YES;
417-
CURRENT_PROJECT_VERSION = 7;
422+
CURRENT_PROJECT_VERSION = 8;
418423
DEVELOPMENT_TEAM = "";
419424
"DEVELOPMENT_TEAM[sdk=macosx*]" = 7K4SKUHU47;
420425
ENABLE_HARDENED_RUNTIME = YES;
@@ -434,7 +439,7 @@
434439
"$(inherited)",
435440
"@executable_path/../Frameworks",
436441
);
437-
MARKETING_VERSION = 1.1.5;
442+
MARKETING_VERSION = 1.1.6;
438443
PRODUCT_BUNDLE_IDENTIFIER = com.mymaccleaner.MyMacCleaner;
439444
PRODUCT_NAME = "$(TARGET_NAME)";
440445
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -460,7 +465,7 @@
460465
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
461466
CODE_SIGN_STYLE = Manual;
462467
COMBINE_HIDPI_IMAGES = YES;
463-
CURRENT_PROJECT_VERSION = 7;
468+
CURRENT_PROJECT_VERSION = 8;
464469
DEVELOPMENT_TEAM = "";
465470
"DEVELOPMENT_TEAM[sdk=macosx*]" = 7K4SKUHU47;
466471
ENABLE_HARDENED_RUNTIME = YES;
@@ -480,7 +485,7 @@
480485
"$(inherited)",
481486
"@executable_path/../Frameworks",
482487
);
483-
MARKETING_VERSION = 1.1.5;
488+
MARKETING_VERSION = 1.1.6;
484489
PRODUCT_BUNDLE_IDENTIFIER = com.mymaccleaner.MyMacCleaner;
485490
PRODUCT_NAME = "$(TARGET_NAME)";
486491
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -501,10 +506,10 @@
501506
buildSettings = {
502507
BUNDLE_LOADER = "$(TEST_HOST)";
503508
CODE_SIGN_STYLE = Automatic;
504-
CURRENT_PROJECT_VERSION = 7;
509+
CURRENT_PROJECT_VERSION = 8;
505510
GENERATE_INFOPLIST_FILE = YES;
506511
MACOSX_DEPLOYMENT_TARGET = 14.0;
507-
MARKETING_VERSION = 1.1.5;
512+
MARKETING_VERSION = 1.1.6;
508513
PRODUCT_BUNDLE_IDENTIFIER = com.mymaccleaner.MyMacCleanerTests;
509514
PRODUCT_NAME = "$(TARGET_NAME)";
510515
SWIFT_EMIT_LOC_STRINGS = NO;
@@ -518,10 +523,10 @@
518523
buildSettings = {
519524
BUNDLE_LOADER = "$(TEST_HOST)";
520525
CODE_SIGN_STYLE = Automatic;
521-
CURRENT_PROJECT_VERSION = 7;
526+
CURRENT_PROJECT_VERSION = 8;
522527
GENERATE_INFOPLIST_FILE = YES;
523528
MACOSX_DEPLOYMENT_TARGET = 14.0;
524-
MARKETING_VERSION = 1.1.5;
529+
MARKETING_VERSION = 1.1.6;
525530
PRODUCT_BUNDLE_IDENTIFIER = com.mymaccleaner.MyMacCleanerTests;
526531
PRODUCT_NAME = "$(TARGET_NAME)";
527532
SWIFT_EMIT_LOC_STRINGS = NO;
@@ -534,9 +539,9 @@
534539
isa = XCBuildConfiguration;
535540
buildSettings = {
536541
CODE_SIGN_STYLE = Automatic;
537-
CURRENT_PROJECT_VERSION = 7;
542+
CURRENT_PROJECT_VERSION = 8;
538543
GENERATE_INFOPLIST_FILE = YES;
539-
MARKETING_VERSION = 1.1.5;
544+
MARKETING_VERSION = 1.1.6;
540545
PRODUCT_BUNDLE_IDENTIFIER = com.mymaccleaner.MyMacCleanerUITests;
541546
PRODUCT_NAME = "$(TARGET_NAME)";
542547
SWIFT_EMIT_LOC_STRINGS = NO;
@@ -549,9 +554,9 @@
549554
isa = XCBuildConfiguration;
550555
buildSettings = {
551556
CODE_SIGN_STYLE = Automatic;
552-
CURRENT_PROJECT_VERSION = 7;
557+
CURRENT_PROJECT_VERSION = 8;
553558
GENERATE_INFOPLIST_FILE = YES;
554-
MARKETING_VERSION = 1.1.5;
559+
MARKETING_VERSION = 1.1.6;
555560
PRODUCT_BUNDLE_IDENTIFIER = com.mymaccleaner.MyMacCleanerUITests;
556561
PRODUCT_NAME = "$(TARGET_NAME)";
557562
SWIFT_EMIT_LOC_STRINGS = NO;

MyMacCleaner/App/ContentView.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ struct DetailContentView: View {
287287

288288
// CRITICAL: This makes SwiftUI observe language changes
289289
@Environment(LocalizationManager.self) var localization
290+
@Environment(UpdateManager.self) var updateManager
290291

291292
var body: some View {
292293
ZStack {
@@ -305,6 +306,12 @@ struct DetailContentView: View {
305306
ToolbarItem(placement: .automatic) {
306307
Spacer()
307308
}
309+
// Update available button (only shows when update is available)
310+
if updateManager.updateAvailable {
311+
ToolbarItem(placement: .automatic) {
312+
UpdateAvailableButton()
313+
}
314+
}
308315
ToolbarItem(placement: .automatic) {
309316
LanguageSwitcherButton()
310317
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import SwiftUI
2+
3+
// MARK: - Update Available Button
4+
5+
struct UpdateAvailableButton: View {
6+
@Environment(UpdateManager.self) var updateManager
7+
@State private var showingSheet = false
8+
@State private var isHovered = false
9+
10+
var body: some View {
11+
Button {
12+
showingSheet = true
13+
} label: {
14+
HStack(spacing: Theme.Spacing.xxxs) {
15+
Image(systemName: "arrow.down.circle.fill")
16+
.foregroundStyle(.orange)
17+
18+
if let version = updateManager.availableVersion {
19+
Text(version)
20+
.foregroundStyle(.orange)
21+
}
22+
}
23+
}
24+
.help(L("update.tooltip"))
25+
.scaleEffect(isHovered ? 1.05 : 1.0)
26+
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: isHovered)
27+
.onHover { isHovered = $0 }
28+
.sheet(isPresented: $showingSheet) {
29+
UpdateAvailableSheet()
30+
.environment(updateManager)
31+
}
32+
}
33+
}
34+
35+
// MARK: - Update Available Sheet
36+
37+
struct UpdateAvailableSheet: View {
38+
@Environment(UpdateManager.self) var updateManager
39+
@Environment(\.dismiss) var dismiss
40+
@State private var isHovered = false
41+
42+
private var currentVersion: String {
43+
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0.0"
44+
}
45+
46+
var body: some View {
47+
VStack(spacing: Theme.Spacing.xl) {
48+
// Icon
49+
ZStack {
50+
Circle()
51+
.fill(Color.orange.opacity(0.15))
52+
.frame(width: 80, height: 80)
53+
54+
Image(systemName: "arrow.down.circle.fill")
55+
.font(.system(size: 40))
56+
.foregroundStyle(.orange)
57+
}
58+
.padding(.top, Theme.Spacing.lg)
59+
60+
// Title
61+
Text(L("update.sheet.title"))
62+
.font(Theme.Typography.size22Semibold)
63+
64+
// Version info
65+
VStack(spacing: Theme.Spacing.xs) {
66+
HStack(spacing: Theme.Spacing.md) {
67+
VStack(spacing: Theme.Spacing.tiny) {
68+
Text(L("update.sheet.current"))
69+
.font(Theme.Typography.caption)
70+
.foregroundStyle(.secondary)
71+
Text("v\(currentVersion)")
72+
.font(Theme.Typography.size15Semibold)
73+
.foregroundStyle(.secondary)
74+
}
75+
76+
Image(systemName: "arrow.right")
77+
.font(Theme.Typography.size18)
78+
.foregroundStyle(.tertiary)
79+
80+
VStack(spacing: Theme.Spacing.tiny) {
81+
Text(L("update.sheet.new"))
82+
.font(Theme.Typography.caption)
83+
.foregroundStyle(.secondary)
84+
if let newVersion = updateManager.availableVersion {
85+
Text("v\(newVersion)")
86+
.font(Theme.Typography.size15Semibold)
87+
.foregroundStyle(.orange)
88+
}
89+
}
90+
}
91+
.padding(Theme.Spacing.md)
92+
.background(
93+
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
94+
.fill(Color.orange.opacity(0.08))
95+
)
96+
}
97+
98+
// Description
99+
Text(L("update.sheet.description"))
100+
.font(Theme.Typography.body)
101+
.foregroundStyle(.secondary)
102+
.multilineTextAlignment(.center)
103+
.padding(.horizontal, Theme.Spacing.lg)
104+
105+
Spacer()
106+
107+
// Buttons
108+
VStack(spacing: Theme.Spacing.sm) {
109+
Button {
110+
updateManager.checkForUpdates()
111+
dismiss()
112+
} label: {
113+
HStack {
114+
Image(systemName: "arrow.down.to.line")
115+
Text(L("update.sheet.download"))
116+
}
117+
.frame(maxWidth: .infinity)
118+
.padding(.vertical, Theme.Spacing.sm)
119+
}
120+
.buttonStyle(.borderedProminent)
121+
.tint(.orange)
122+
.scaleEffect(isHovered ? 1.02 : 1.0)
123+
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: isHovered)
124+
.onHover { isHovered = $0 }
125+
126+
Button {
127+
dismiss()
128+
} label: {
129+
Text(L("update.sheet.later"))
130+
.frame(maxWidth: .infinity)
131+
.padding(.vertical, Theme.Spacing.sm)
132+
}
133+
.buttonStyle(.plain)
134+
.foregroundStyle(.secondary)
135+
}
136+
.padding(.horizontal, Theme.Spacing.lg)
137+
.padding(.bottom, Theme.Spacing.lg)
138+
}
139+
.frame(width: 340, height: 420)
140+
}
141+
}
142+
143+
// MARK: - Preview
144+
145+
#Preview("Update Available Button") {
146+
HStack {
147+
UpdateAvailableButton()
148+
}
149+
.padding()
150+
.environment(UpdateManager())
151+
}
152+
153+
#Preview("Update Available Sheet") {
154+
UpdateAvailableSheet()
155+
.environment(UpdateManager())
156+
}

0 commit comments

Comments
 (0)