Skip to content

Commit b88c7be

Browse files
committed
feat(widget): improve widget design and add theme support
Widget improvements: - Small widget: Show only 5h limit with percentage and reset time - Medium widget: Show up to 3 rings (5h, 7d, Sonnet) with reset times - Add text labels in ring center (5h, 7d, S, $) - Add Dark/Light theme support with adaptive background - Match ring colors with main app's color scheme - Remove unused Opus limit case - Add file-based data sharing via ~/.Usage4Claude/SharedUsageData.json
1 parent 4148cd4 commit b88c7be

File tree

9 files changed

+150
-89
lines changed

9 files changed

+150
-89
lines changed

Usage4Claude.xcodeproj/project.pbxproj

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838

3939
/* Begin PBXFileReference section */
4040
8873E4CF2E9F61C700ACFF5C /* Usage4Claude.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Usage4Claude.app; sourceTree = BUILT_PRODUCTS_DIR; };
41-
WIDGETPROD /* Usage4ClaudeWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Usage4ClaudeWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
4241
WIDGET000 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
4342
WIDGET003 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
43+
WIDGETPROD /* Usage4ClaudeWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Usage4ClaudeWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
4444
/* End PBXFileReference section */
4545

4646
/* Begin PBXFileSystemSynchronizedRootGroup section */
@@ -161,7 +161,7 @@
161161
attributes = {
162162
BuildIndependentTargetsInParallel = 1;
163163
LastSwiftUpdateCheck = 2600;
164-
LastUpgradeCheck = 2600;
164+
LastUpgradeCheck = 2620;
165165
TargetAttributes = {
166166
8873E4CE2E9F61C700ACFF5C = {
167167
CreatedOnToolsVersion = 26.0.1;
@@ -244,6 +244,7 @@
244244
buildSettings = {
245245
ALWAYS_SEARCH_USER_PATHS = NO;
246246
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
247+
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
247248
CLANG_ANALYZER_NONNULL = YES;
248249
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
249250
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@@ -273,6 +274,7 @@
273274
CLANG_WARN_UNREACHABLE_CODE = YES;
274275
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
275276
COPY_PHASE_STRIP = NO;
277+
DEAD_CODE_STRIPPING = YES;
276278
DEBUG_INFORMATION_FORMAT = dwarf;
277279
ENABLE_STRICT_OBJC_MSGSEND = YES;
278280
ENABLE_TESTABILITY = YES;
@@ -298,6 +300,7 @@
298300
MTL_FAST_MATH = YES;
299301
ONLY_ACTIVE_ARCH = YES;
300302
SDKROOT = macosx;
303+
STRING_CATALOG_GENERATE_SYMBOLS = YES;
301304
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
302305
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
303306
};
@@ -308,6 +311,7 @@
308311
buildSettings = {
309312
ALWAYS_SEARCH_USER_PATHS = NO;
310313
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
314+
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
311315
CLANG_ANALYZER_NONNULL = YES;
312316
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
313317
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@@ -337,6 +341,7 @@
337341
CLANG_WARN_UNREACHABLE_CODE = YES;
338342
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
339343
COPY_PHASE_STRIP = NO;
344+
DEAD_CODE_STRIPPING = YES;
340345
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
341346
ENABLE_NS_ASSERTIONS = NO;
342347
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -355,23 +360,26 @@
355360
MTL_ENABLE_DEBUG_INFO = NO;
356361
MTL_FAST_MATH = YES;
357362
SDKROOT = macosx;
363+
STRING_CATALOG_GENERATE_SYMBOLS = YES;
358364
SWIFT_COMPILATION_MODE = wholemodule;
359365
};
360366
name = Release;
361367
};
362368
8873E4DB2E9F61C800ACFF5C /* Debug */ = {
363369
isa = XCBuildConfiguration;
364370
buildSettings = {
365-
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
366371
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
367372
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
368373
CLANG_ENABLE_MODULES = YES;
369374
CODE_SIGN_ENTITLEMENTS = Usage4Claude/Usage4Claude.entitlements;
375+
CODE_SIGN_IDENTITY = "-";
376+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
370377
CODE_SIGN_STYLE = Automatic;
371378
COMBINE_HIDPI_IMAGES = YES;
372379
CURRENT_PROJECT_VERSION = 1;
380+
DEAD_CODE_STRIPPING = YES;
373381
DEVELOPMENT_TEAM = "";
374-
ENABLE_APP_SANDBOX = YES;
382+
ENABLE_APP_SANDBOX = NO;
375383
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
376384
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
377385
ENABLE_PREVIEWS = YES;
@@ -412,16 +420,18 @@
412420
8873E4DC2E9F61C800ACFF5C /* Release */ = {
413421
isa = XCBuildConfiguration;
414422
buildSettings = {
415-
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
416423
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
417424
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
418425
CLANG_ENABLE_MODULES = YES;
419426
CODE_SIGN_ENTITLEMENTS = Usage4Claude/Usage4Claude.entitlements;
427+
CODE_SIGN_IDENTITY = "-";
428+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
420429
CODE_SIGN_STYLE = Automatic;
421430
COMBINE_HIDPI_IMAGES = YES;
422431
CURRENT_PROJECT_VERSION = 1;
432+
DEAD_CODE_STRIPPING = YES;
423433
DEVELOPMENT_TEAM = "";
424-
ENABLE_APP_SANDBOX = YES;
434+
ENABLE_APP_SANDBOX = NO;
425435
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
426436
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
427437
ENABLE_PREVIEWS = YES;
@@ -465,10 +475,13 @@
465475
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
466476
CLANG_ENABLE_MODULES = YES;
467477
CODE_SIGN_ENTITLEMENTS = Usage4ClaudeWidget/Usage4ClaudeWidget.entitlements;
478+
CODE_SIGN_IDENTITY = "-";
479+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
468480
CODE_SIGN_STYLE = Automatic;
469481
CURRENT_PROJECT_VERSION = 1;
482+
DEAD_CODE_STRIPPING = YES;
470483
DEVELOPMENT_TEAM = "";
471-
ENABLE_APP_SANDBOX = YES;
484+
ENABLE_APP_SANDBOX = NO;
472485
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
473486
GENERATE_INFOPLIST_FILE = YES;
474487
INFOPLIST_FILE = "";
@@ -502,10 +515,13 @@
502515
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
503516
CLANG_ENABLE_MODULES = YES;
504517
CODE_SIGN_ENTITLEMENTS = Usage4ClaudeWidget/Usage4ClaudeWidget.entitlements;
518+
CODE_SIGN_IDENTITY = "-";
519+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
505520
CODE_SIGN_STYLE = Automatic;
506521
CURRENT_PROJECT_VERSION = 1;
522+
DEAD_CODE_STRIPPING = YES;
507523
DEVELOPMENT_TEAM = "";
508-
ENABLE_APP_SANDBOX = YES;
524+
ENABLE_APP_SANDBOX = NO;
509525
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
510526
GENERATE_INFOPLIST_FILE = YES;
511527
INFOPLIST_FILE = "";

Usage4Claude.xcodeproj/xcshareddata/xcschemes/Usage4Claude-Debug.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "2600"
3+
LastUpgradeVersion = "2620"
44
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"

Usage4Claude.xcodeproj/xcshareddata/xcschemes/Usage4Claude-Release.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "2600"
3+
LastUpgradeVersion = "2620"
44
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"

Usage4Claude.xcodeproj/xcshareddata/xcschemes/Usage4Claude.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "2600"
3+
LastUpgradeVersion = "2620"
44
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"

Usage4Claude/Shared/SharedUsageData.swift

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,23 +197,36 @@ extension SharedUsageData {
197197
}
198198
}
199199

200-
// MARK: - UserDefaults Storage
200+
// MARK: - File-based Storage (Local Development without App Groups)
201201

202202
extension SharedUsageData {
203-
private static let storageKey = "SharedUsageData"
203+
private static let fileName = "SharedUsageData.json"
204204

205-
/// Save to App Group UserDefaults
205+
/// Get shared storage URL accessible by both app and widget
206+
/// Uses a file in the user's Application Support directory
207+
private static var storageURL: URL? {
208+
// Use a shared location in user's home directory
209+
let homeDir = FileManager.default.homeDirectoryForCurrentUser
210+
let sharedDir = homeDir.appendingPathComponent(".Usage4Claude")
211+
212+
// Create directory if needed
213+
try? FileManager.default.createDirectory(at: sharedDir, withIntermediateDirectories: true)
214+
215+
return sharedDir.appendingPathComponent(fileName)
216+
}
217+
218+
/// Save to shared file
206219
func save() {
207-
guard let defaults = UserDefaults(suiteName: appGroupIdentifier) else { return }
220+
guard let url = Self.storageURL else { return }
208221
if let encoded = try? JSONEncoder().encode(self) {
209-
defaults.set(encoded, forKey: Self.storageKey)
222+
try? encoded.write(to: url)
210223
}
211224
}
212225

213-
/// Load from App Group UserDefaults
226+
/// Load from shared file
214227
static func load() -> SharedUsageData? {
215-
guard let defaults = UserDefaults(suiteName: appGroupIdentifier),
216-
let data = defaults.data(forKey: storageKey),
228+
guard let url = storageURL,
229+
let data = try? Data(contentsOf: url),
217230
let decoded = try? JSONDecoder().decode(SharedUsageData.self, from: data) else {
218231
return nil
219232
}

Usage4Claude/Usage4Claude.entitlements

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,6 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5-
<key>com.apple.security.app-sandbox</key>
6-
<true/>
7-
<key>com.apple.security.application-groups</key>
8-
<array>
9-
<string>group.xyz.fi5h.Usage4Claude</string>
10-
</array>
11-
<key>com.apple.security.network.client</key>
12-
<true/>
135
<key>com.apple.security.files.user-selected.read-write</key>
146
<true/>
157
</dict>

Usage4ClaudeWidget/SharedUsageData.swift

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,23 +142,36 @@ struct SharedUsageData: Codable {
142142
}
143143
}
144144

145-
// MARK: - UserDefaults Storage
145+
// MARK: - File-based Storage (Local Development without App Groups)
146146

147147
extension SharedUsageData {
148-
private static let storageKey = "SharedUsageData"
148+
private static let fileName = "SharedUsageData.json"
149149

150-
/// Save to App Group UserDefaults
150+
/// Get shared storage URL accessible by both app and widget
151+
/// Uses a file in the user's home directory
152+
private static var storageURL: URL? {
153+
// Use a shared location in user's home directory
154+
let homeDir = FileManager.default.homeDirectoryForCurrentUser
155+
let sharedDir = homeDir.appendingPathComponent(".Usage4Claude")
156+
157+
// Create directory if needed
158+
try? FileManager.default.createDirectory(at: sharedDir, withIntermediateDirectories: true)
159+
160+
return sharedDir.appendingPathComponent(fileName)
161+
}
162+
163+
/// Save to shared file
151164
func save() {
152-
guard let defaults = UserDefaults(suiteName: appGroupIdentifier) else { return }
165+
guard let url = Self.storageURL else { return }
153166
if let encoded = try? JSONEncoder().encode(self) {
154-
defaults.set(encoded, forKey: Self.storageKey)
167+
try? encoded.write(to: url)
155168
}
156169
}
157170

158-
/// Load from App Group UserDefaults
171+
/// Load from shared file
159172
static func load() -> SharedUsageData? {
160-
guard let defaults = UserDefaults(suiteName: appGroupIdentifier),
161-
let data = defaults.data(forKey: storageKey),
173+
guard let url = storageURL,
174+
let data = try? Data(contentsOf: url),
162175
let decoded = try? JSONDecoder().decode(SharedUsageData.self, from: data) else {
163176
return nil
164177
}
Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
4-
<dict>
5-
<key>com.apple.security.app-sandbox</key>
6-
<true/>
7-
<key>com.apple.security.application-groups</key>
8-
<array>
9-
<string>group.xyz.fi5h.Usage4Claude</string>
10-
</array>
11-
<key>com.apple.security.network.client</key>
12-
<true/>
13-
</dict>
4+
<dict/>
145
</plist>

0 commit comments

Comments
 (0)