fix panel layout; add more widgets; click to refresh widgets; set any update interval

This commit is contained in:
lihaoyun6
2024-06-09 01:16:16 +08:00
parent 226662cc67
commit 2a237f03e1
11 changed files with 573 additions and 119 deletions

View File

@@ -40,6 +40,7 @@
18B8E5C92B83C9AD00A20D03 /* BatteryWidget2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B8E5C82B83C9AD00A20D03 /* BatteryWidget2.swift */; };
18E6E36E2C0BA6A800A666CE /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 18E6E36D2C0BA6A800A666CE /* Sparkle */; };
18E6E3702C0BA7D200A666CE /* Sparkle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E6E36F2C0BA7D200A666CE /* Sparkle.swift */; };
18EF7E632C141CB70065A9E1 /* BatteryWidget3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7E622C1414A50065A9E1 /* BatteryWidget3.swift */; };
18F615F32B763DD800873AA4 /* BLEBattery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F615F22B763DD800873AA4 /* BLEBattery.swift */; };
18F615F92B76439300873AA4 /* AirBatteryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F615F82B76439300873AA4 /* AirBatteryModel.swift */; };
18F615FB2B7676D200873AA4 /* Supports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F615FA2B7676D200873AA4 /* Supports.swift */; };
@@ -114,6 +115,7 @@
18B8E5C82B83C9AD00A20D03 /* BatteryWidget2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryWidget2.swift; sourceTree = "<group>"; };
18C7CFDE2AAF842E005898EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Base; path = Base.lproj/Credits.rtf; sourceTree = "<group>"; };
18E6E36F2C0BA7D200A666CE /* Sparkle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sparkle.swift; sourceTree = "<group>"; };
18EF7E622C1414A50065A9E1 /* BatteryWidget3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryWidget3.swift; sourceTree = "<group>"; };
18F615F22B763DD800873AA4 /* BLEBattery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEBattery.swift; sourceTree = "<group>"; };
18F615F82B76439300873AA4 /* AirBatteryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirBatteryModel.swift; sourceTree = "<group>"; };
18F615FA2B7676D200873AA4 /* Supports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Supports.swift; sourceTree = "<group>"; };
@@ -233,6 +235,7 @@
180B1C0D2C12017400DDB570 /* AppIntent.swift */,
18892A8F2B81F8320017377E /* BatteryWidget.swift */,
18B8E5C82B83C9AD00A20D03 /* BatteryWidget2.swift */,
18EF7E622C1414A50065A9E1 /* BatteryWidget3.swift */,
18892A952B81F8330017377E /* Info.plist */,
18892A962B81F8330017377E /* widget.entitlements */,
);
@@ -434,6 +437,7 @@
buildActionMask = 2147483647;
files = (
18892AA22B82F0880017377E /* AirBatteryModel.swift in Sources */,
18EF7E632C141CB70065A9E1 /* BatteryWidget3.swift in Sources */,
18892AA32B82F09C0017377E /* InternalBattery.swift in Sources */,
18892A8E2B81F8320017377E /* widgetBundle.swift in Sources */,
18892A902B81F8320017377E /* BatteryWidget.swift in Sources */,
@@ -492,7 +496,7 @@
CODE_SIGN_ENTITLEMENTS = AirBatteryHelper/AirBatteryHelper.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 134;
CURRENT_PROJECT_VERSION = 135;
DEVELOPMENT_ASSET_PATHS = "\"AirBatteryHelper/Preview Content\"";
DEVELOPMENT_TEAM = L4T783637F;
ENABLE_HARDENED_RUNTIME = YES;
@@ -507,7 +511,7 @@
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.4;
MARKETING_VERSION = 1.3.5;
PRODUCT_BUNDLE_IDENTIFIER = com.lihaoyun6.AirBatteryHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -526,7 +530,7 @@
CODE_SIGN_ENTITLEMENTS = AirBatteryHelper/AirBatteryHelper.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 134;
CURRENT_PROJECT_VERSION = 135;
DEVELOPMENT_ASSET_PATHS = "\"AirBatteryHelper/Preview Content\"";
DEVELOPMENT_TEAM = L4T783637F;
ENABLE_HARDENED_RUNTIME = YES;
@@ -541,7 +545,7 @@
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.4;
MARKETING_VERSION = 1.3.5;
PRODUCT_BUNDLE_IDENTIFIER = com.lihaoyun6.AirBatteryHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -678,7 +682,7 @@
CODE_SIGN_ENTITLEMENTS = AirBattery/AirBattery.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 134;
CURRENT_PROJECT_VERSION = 135;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"AirBattery/Preview Content\"";
DEVELOPMENT_TEAM = L4T783637F;
@@ -695,7 +699,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.4;
MARKETING_VERSION = 1.3.5;
PRODUCT_BUNDLE_IDENTIFIER = com.lihaoyun6.AirBattery;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -712,7 +716,7 @@
CODE_SIGN_ENTITLEMENTS = AirBattery/AirBattery.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 134;
CURRENT_PROJECT_VERSION = 135;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"AirBattery/Preview Content\"";
DEVELOPMENT_TEAM = L4T783637F;
@@ -729,7 +733,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.4;
MARKETING_VERSION = 1.3.5;
PRODUCT_BUNDLE_IDENTIFIER = com.lihaoyun6.AirBattery;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -745,7 +749,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = widget/widget.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 134;
CURRENT_PROJECT_VERSION = 135;
DEVELOPMENT_TEAM = L4T783637F;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -761,7 +765,7 @@
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.4;
MARKETING_VERSION = 1.3.5;
PRODUCT_BUNDLE_IDENTIFIER = com.lihaoyun6.AirBattery.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -779,7 +783,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = widget/widget.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 134;
CURRENT_PROJECT_VERSION = 135;
DEVELOPMENT_TEAM = L4T783637F;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -795,7 +799,7 @@
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.3.4;
MARKETING_VERSION = 1.3.5;
PRODUCT_BUNDLE_IDENTIFIER = com.lihaoyun6.AirBattery.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;

View File

@@ -48,6 +48,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
let magicBattery = MagicBattery()
let ideviceBattery = IDeviceBattery()
func application(_ application: NSApplication, open urls: [URL]) {
print(urls)
}
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
// Dock
if showOn == "sbar" {
@@ -73,7 +77,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
switch dockOrientation {
case "bottom":
// Dock
menuX = menuX + 176 > visibleFrame.maxX ? visibleFrame.maxX - 176 : menuX - 176
menuX = menuX + 186 > visibleFrame.maxX ? visibleFrame.maxX - 362 : menuX - 176
if menuX + 186 > visibleFrame.maxX {
menuX = visibleFrame.maxX - 362
} else if menuX - 166 < visibleFrame.minX {
menuX = visibleFrame.minX + 10
} else {
menuX = menuX - 176
}
menuY = max(menuY, visibleFrame.origin.y) + 20
case "right":
// Dock
@@ -124,6 +135,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(handleURLEvent(_:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
//if let window = NSApplication.shared.windows.first { window.close() }
launchAtLogin = NSWorkspace.shared.runningApplications.contains { $0.bundleIdentifier == "com.lihaoyun6.AirBatteryHelper" }
print("⚙️ Launch AirBattery at login = \(launchAtLogin)")
@@ -226,6 +238,21 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
statusMenu.removeAllItems()
statusMenu.addItem(menuItem)
}
@objc func handleURLEvent(_ event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
if let urlString = event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))?.stringValue,
let url = URL(string: urlString) {
if url.scheme == "airbattery"{
switch url.host {
case "reloadwingets" :
print("Reloading all widgets...")
AirBatteryModel.writeData()
WidgetCenter.shared.reloadAllTimelines()
default: print("Unknow command!")
}
}
}
}
@objc func openAboutPanel() {
NSApp.activate(ignoringOtherApps: true)

View File

@@ -2,6 +2,17 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.lihaoyun6.AirBattery</string>
<key>CFBundleURLSchemes</key>
<array>
<string>airbattery</string>
</array>
</dict>
</array>
<key>SUFeedURL</key>
<string>https://raw.githubusercontent.com/lihaoyun6/AirBattery/main/appcast.xml</string>
<key>SUPublicEDKey</key>

View File

@@ -10,7 +10,7 @@ import UserNotifications
let dockTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
let alertTimer = Timer.publish(every: 300, on: .main, in: .common).autoconnect()
let widgetDataTimer = Timer.publish(every: 29, on: .main, in: .common).autoconnect()
let widgetDataTimer = Timer.publish(every: 25, on: .main, in: .common).autoconnect()
let widgetInterval = UserDefaults.standard.integer(forKey: "widgetInterval")
let updateInterval = UserDefaults.standard.double(forKey: "updateInterval")
let widgetViewTimer = Timer.publish(every: TimeInterval(60 * (widgetInterval != 0 ? abs(widgetInterval) : Int(updateInterval))), on: .main, in: .common).autoconnect()

View File

@@ -237,6 +237,7 @@ struct popover: View {
var fromDock: Bool = false
var allDevices: [Device]
let hiddenDevices = AirBatteryModel.getBlackList()
@State private var overReloadButton = false
@State private var overCopyButton = false
@State private var overHideButton = false
@State private var overAlertButton = false

View File

@@ -133,7 +133,7 @@ struct SettingsView: View {
HStack{
Toggle(isOn: $readIDevice) {}.toggleStyle(.switch)
HStack(spacing: 2) {
Text("WiFi / LAN Scanner")
Text("WiFi Scanner (iOS)")
SWInfoButton(showOnHover: false, fillMode: true, animatePopover: true, content: "Scan your iPhone / iPad / Apple Watch / VisionPro and other iDevices under the same router".local, primaryColor: NSColor(named: "my_blue") ?? NSColor.systemGray, preferredEdge: .minY)
.frame(width: 19, height: 19)
}
@@ -141,7 +141,7 @@ struct SettingsView: View {
HStack{
Toggle(isOn: $ideviceOverBLE) {}.toggleStyle(.switch)
HStack(spacing: 2) {
Text("Bluetooth Scanner")
Text("BLE Scanner (iOS)")
SWInfoButton(showOnHover: false, fillMode: true, animatePopover: true, content: "Scan your iPhone and iPad(Cellular) via Bluetooth".local, primaryColor: NSColor(named: "my_blue") ?? NSColor.systemGray, preferredEdge: .minY)
.frame(width: 19, height: 19)
}
@@ -157,22 +157,22 @@ struct SettingsView: View {
}
Spacer()
Form(){
HStack{
Toggle(isOn: $readAirpods) {}.toggleStyle(.switch)
Text("Find AirPods / Beats")
}
HStack{
Toggle(isOn: $readBTDevice) {}.toggleStyle(.switch)
HStack(spacing: 2) {
Text("Non-Apple Devices")
SWInfoButton(showOnHover: false, fillMode: true, animatePopover: true, content: "Only some of non-Apple peripherals are supported!".local, primaryColor: NSColor(named: "my_blue") ?? NSColor.systemGray, preferredEdge: .minY)
Text("Bluetooth Scanner")
SWInfoButton(showOnHover: false, fillMode: true, animatePopover: true, content: "Get the battery usage of some Bluetooth peripherals (mouse, keyboard, headphone, etc.)\n\nPlease Note: Some devices cannot be listed even though macOS knows their battery usage".local, primaryColor: NSColor(named: "my_blue") ?? NSColor.systemGray, preferredEdge: .minY)
.frame(width: 19, height: 19)
}
}
HStack{
Toggle(isOn: $readAirpods) {}.toggleStyle(.switch)
Text("Find AirPods / Beats")
}
HStack{
Toggle(isOn: $readBLEDevice) {}.toggleStyle(.switch)
HStack(spacing: 2) {
Text("Get All BLE Devices")
Text("Non-Apple BLE Devices")
SWInfoButton(showOnHover: false, fillMode: true, animatePopover: true, content: "Try to get the battery usage of any Bluetooth device that AirBattery can find\n\nWARNING: This is a BETA feature and may cause unexpected errors!".local, primaryColor: NSColor(named: "my_yellow") ?? NSColor.systemGray, preferredEdge: .minY)
.frame(width: 19, height: 19)
}
@@ -183,19 +183,29 @@ struct SettingsView: View {
//Divider().frame(width: 440)
HStack {
Spacer()
Picker("Update Interval", selection: $updateInterval) {
Text("Short").tag(1.0)
Text("Medium").tag(2.0)
Text("Long").tag(3.0)
}
.frame(width: 270)
HStack(spacing: 2) {
Picker("Update Interval", selection: $updateInterval) {
Text("Default".local).tag(1.0)
Text("Custom".local).tag(updateInterval == 1.0 ? 2.0 : updateInterval)
}
.pickerStyle(.segmented)
.onChange(of: updateInterval) { _ in
_ = createAlert(title: "Relaunch Required".local, message: "Restart AirBattery to apply this change.".local, button1: "OK".local).runModal()
}
TextField("", value: $updateInterval, formatter: NumberFormatter())
.textFieldStyle(.squareBorder)
.frame(width: 26)
.onChange(of: updateInterval) { newValue in
if newValue > 99.0 { updateInterval = 99.0 }
if newValue < 1.0 { updateInterval = 1.0 }
}
.disabled(updateInterval == 1.0)
Text("min")
.foregroundColor(updateInterval == 1.0 ? .secondary : .primary)
}.fixedSize()
Spacer()
HStack(spacing: 2) {
Text("Merge Threshold".local).padding(.trailing, 5)
Text("Merge Limits".local).padding(.trailing, 5)
TextField("", value: $twsMerge, formatter: NumberFormatter())
.textFieldStyle(.squareBorder)
.frame(width: 26)
@@ -287,15 +297,12 @@ struct SettingsView: View {
Text("Reverse the Device List")
}
HStack(spacing: 3) {
Text("Update Interval")
SWInfoButton(showOnHover: false, fillMode: true, animatePopover: true, content: "System: Determined by macOS\n\nNearbility: Same as Nearbility setting".local, primaryColor: NSColor(named: "my_blue") ?? NSColor.systemGray, preferredEdge: .maxX)
Text("Refresh Interval")
SWInfoButton(showOnHover: false, fillMode: true, animatePopover: true, content: "System Default: Determined by macOS\n\nNearbility: Same as Nearbility setting".local, primaryColor: NSColor(named: "my_blue") ?? NSColor.systemGray, preferredEdge: .maxX)
.frame(width: 19, height: 19)
Picker("", selection: $widgetInterval) {
Text("System").tag(-1)
Text("System Default").tag(-1)
Text("Nearbility").tag(0)
Text("1 "+"min".local).tag(1)
Text("3 "+"min".local).tag(3)
Text("5 "+"min".local).tag(5)
}
.pickerStyle(.segmented)
.onChange(of: widgetInterval) { _ in
@@ -315,14 +322,14 @@ struct SettingsView: View {
}.onChange(of: deviceOnWidget) { _ in _ = AirBatteryModel.singleDeviceName() }
}
HStack {
Spacer()
Button(action: {
AirBatteryModel.writeData()
_ = AirBatteryModel.singleDeviceName()
WidgetCenter.shared.reloadAllTimelines()
}, label: {
Text("Reload All Widgets")
})}
.foregroundColor(.orange)
}).padding(.top, 14)
}
}
.onAppear { devices = AirBatteryModel.getAll(noFilter: true).filter({ $0.hasBattery }).map({ $0.deviceName }) }
.onReceive(dockTimer) { _ in

View File

@@ -34,23 +34,25 @@
"Carousel Mode:" = "轮播模式:";
"On" = "启用";
"Off" = "关闭";
"WiFi / LAN Scanner" = "通过局域网搜索设备";
"Bluetooth Scanner" = "通过蓝牙搜索设备";
"WiFi Scanner (iOS)" = "通过局域网搜索 iOS 设备";
"BLE Scanner (iOS)" = "通过蓝牙搜索 iOS 设备";
"Bluetooth Scanner" = "搜索支持的蓝牙外设";
"Find AirPods / Beats" = "搜索AirPods/Beats耳机";
"Non-Apple Devices" = "搜索非 Apple 设备";
"Guess Power Status" = "推测BLE设备充电状态";
"Get All BLE Devices" = "搜索所有 BLE 设备";
"Non-Apple BLE Devices" = "搜索第三方 BLE 设备";
"Guess Power Status" = "推测 BLE 设备充电状态";
"Get All BLE Devices" = "搜索所有蓝牙 BLE 设备";
"after 20min" = "20分钟后";
"after 40min" = "40分钟后";
"min" = "分钟";
"Remove Offline Device:" = "移除离线设备:";
"Never" = "永不";
"Hidden" = "隐藏";
"AirBattery is not running\nLaunch the app to make the widget work." = "请先启动 AirBattery 主程序以使小组件正常工作.";
"AirBattery is not running\nLaunch the app to make\nthe widget work." = "请先启动 AirBattery 主程序\n以使小组件正常工作.";
"Displays battery usage for your devices from AirBattery." = "显示由 AirBattery 收集到的电池用量信息.";
"More ways to displays battery usage for your devices from AirBattery." = "以更多方式展示由 AirBattery 收集到的电池用量信息";
"Displays the battery usage of a specific device." = "显示某台特定设备的电池用量信息";
"AirBattery is not running\nLaunch the app to make the widget work" = "请先启动 AirBattery 主程序以使小组件正常工作";
"AirBattery is not running\nLaunch the app to make\nthe widget work" = "请先启动 AirBattery 主程序\n以使小组件正常工作";
"Displays battery usage for your devices from AirBattery" = "显示由 AirBattery 收集到的电量信息";
"More ways to displays battery usage for your devices" = "以更多方式展示所有设备的电量信息";
"Displays battery usage for your devices without percentage" = "以纯图形展示所有设备的电量信息";
"Displays the battery usage of a specific device" = "显示某台特定设备的电量信息";
"Batteries" = "电量信息";
"Hide This" = "隐藏此设备";
"Hidden Device..." = "已隐藏的设备...";
@@ -79,8 +81,9 @@
"Fully Charged Threshold:" = "充电完成阈值:";
"Select a Device in Preferences" = "请在设置中选择需要显示的设备";
"Single Device Widget" = "单设备小组件";
"Update Interval" = "更新间隔";
"Merge Threshold" = "双耳合并阈值";
"Update Interval" = "数据更新间隔";
"Refresh Interval" = "内容刷新间隔";
"Merge Limits" = "耳机合并百分比";
"Short" = "较短";
"Medium" = "中等";
"Long" = "较长";
@@ -98,17 +101,19 @@
"Scan your iPhone / iPad / Apple Watch / VisionPro and other iDevices under the same router" = "尝试搜索当前局域网中所有属于您的 iPhone / iPad / Apple Watch / VisionPro 等设备";
"Scan your iPhone and iPad(Cellular) via Bluetooth" = "尝试通过蓝牙搜索周边属于您的 iPhone 或蜂窝版 iPad";
"Guess if the iPhone / iPad or BLE device found by Bluetooth is charging" = "利用电量变化来推测通过蓝牙发现的 iPhone / iPad 或其他 BLE 设备";
"Only some of non-Apple peripherals are supported!" = "仅支持部分第三方蓝牙设备";
"Get the battery usage of some Bluetooth peripherals (mouse, keyboard, headphone, etc.)\n\nPlease Note: Some devices cannot be listed even though macOS knows their battery usage" = "获取某些受支持的蓝牙外设的电量信息 (鼠标、键盘、耳机等)\n\n请注意: 有些设备无法被列出, 即使你可以在 macOS 内置的小组件中看到它";
"Try to get the battery usage of any Bluetooth device that AirBattery can find\n\nWARNING: This is a BETA feature and may cause unexpected errors!" = "尝试搜索周边所有的 BLE 设备, 即使它可能不属于您\n\n请注意: 这是一项测试版功能, 可能会导致未知的错误!";
"Show or hide this Mac's built-in battery in the Dock icon" = "在 Dock 图标中显示此 Mac 的内置电池, 或将其从 Dock 图标上隐藏";
"Cycle through all found devices in the Dock icon" = "在 Dock 图标中循环显示所有已发现的设备";
"If the difference in battery usage between the left and right earbuds is less than this value, AirBattery will show them as one device" = "当同一副耳机左右两侧的电量差距小于此值的时候, AirBattery会将它们合并显示为一个设备";
"System" = "系统默认";
"System: Determined by macOS\n\nNearbility: Same as Nearbility setting" = "系统默认: 由 macOS 决定小组件何时更新\n\nNearbility: 与 Nearbility 更新频率保持一致";
"AirBattery battery usage widget" = "AirBattery 电池用量小组件";
"System Default" = "跟随系统默认";
"System Default: Determined by macOS\n\nNearbility: Same as Nearbility setting" = "跟随系统默认: 由 macOS 决定小组件何时更新\n\nNearbility: 与 Nearbility 更新频率保持一致";
"AirBattery battery usage widget" = "AirBattery 电量小组件";
"Configuration" = "配置项";
"Enter Device Name" = "请输入设备名称";
"Right click to configure" = "右键单击以配置此小组件";
"Device Name Copied" = "设备名复制成功";
"Device name: \"%@\" has been copied to the clipboard." = "设备名 \"%@\" 已成功拷贝至剪贴板";
"Reload All Widgets" = "强制刷新所有小组件";
"Reload All Widgets" = "刷新所有小组件";
"Default" = "默认";
"Custom" = "自定义";

View File

@@ -22,7 +22,7 @@ struct ViewSizeTimelineProviderNew: AppIntentTimelineProvider {
if context.family == .systemSmall || context.family == .systemMedium {
while data.count < 8 { data.append(Device(hasBattery: false, deviceID: "", deviceType: "blank", deviceName: "", batteryLevel: 0, isCharging: 0, lastUpdate: 0.0)) }
} else if context.family == .systemLarge {
if data.count >= 8 { data = Array(data[0..<8]) }
if data.count >= 11 { data = Array(data[0..<11]) }
}
return SimpleEntry(date: Date(), data: data, family: context.family, mainApp: mainApp, configuration: configuration)
}
@@ -36,7 +36,7 @@ struct ViewSizeTimelineProviderNew: AppIntentTimelineProvider {
if context.family == .systemSmall || context.family == .systemMedium {
while data.count < 8 { data.append(Device(hasBattery: false, deviceID: "", deviceType: "blank", deviceName: "", batteryLevel: 0, isCharging: 0, lastUpdate: 0.0)) }
} else if context.family == .systemLarge {
if data.count >= 8 { data = Array(data[0..<8]) }
if data.count >= 11 { data = Array(data[0..<11]) }
}
entry = SimpleEntry(date: Date(), data: data, family: context.family, mainApp: mainApp, configuration: configuration)
let entries: [SimpleEntry] = [entry]
@@ -107,7 +107,7 @@ struct batteryWidgetEntryView : View {
@unknown default:
EmptyView()
}
}
}.widgetURL(URL(string: "airbattery://reloadwingets"))
}
}
@@ -117,7 +117,7 @@ struct LargeWidgetView : View {
var body: some View {
if !entry.mainApp{
Text("AirBattery is not running\nLaunch the app to make the widget work.")
Text("AirBattery is not running\nLaunch the app to make the widget work")
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color.gray)
@@ -151,35 +151,38 @@ struct LargeWidgetView : View {
.padding(.horizontal, 15)
} else {
VStack(alignment:.leading) {
ForEach(entry.data, id: \.self) { item in
VStack{
HStack() {
Image(getDeviceIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
ForEach(entry.data.indices, id: \.self) { index in
if index < 8 {
let item = entry.data[index]
VStack{
HStack() {
Image(getDeviceIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
//.foregroundColor(Color("black_white"))
.frame(width: 20, height: 20, alignment: .center)
Text("\(((Date().timeIntervalSince1970 - item.lastUpdate) / 60) > 10 ? "⚠︎ " : "")\(item.deviceName)")
.font(.system(size: 11))
.frame(height: 31, alignment: .center)
.padding(.horizontal, 7)
Spacer()
if item.batteryLevel <= 10 {
Text("\(item.batteryLevel)%") .font(.system(size: 11))
.foregroundColor(Color("dark_my_red"))
} else {
Text("\(item.batteryLevel)%") .font(.system(size: 11))
.frame(width: 20, height: 20, alignment: .center)
Text("\(((Date().timeIntervalSince1970 - item.lastUpdate) / 60) > 10 ? "⚠︎ " : "")\(item.deviceName)")
.font(.system(size: 11))
.frame(height: 31, alignment: .center)
.padding(.horizontal, 7)
Spacer()
if item.batteryLevel <= 10 {
Text("\(item.batteryLevel)%") .font(.system(size: 11))
.foregroundColor(Color("dark_my_red"))
} else {
Text("\(item.batteryLevel)%") .font(.system(size: 11))
}
/*Image(getBatteryIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20, alignment: .center)
*/
BatteryView(item: item)
.scaleEffect(0.76)
}
/*Image(getBatteryIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20, alignment: .center)
*/
BatteryView(item: item)
.scaleEffect(0.76)
if index != 7 { Divider() }
}
if entry.data.firstIndex(of: item) != 7 { Divider() }
}
}
Spacer()
@@ -198,7 +201,7 @@ struct SmallWidgetView : View {
var body: some View {
if !entry.mainApp {
Text("AirBattery is not running\nLaunch the app to make\nthe widget work.")
Text("AirBattery is not running\nLaunch the app to make\nthe widget work")
.multilineTextAlignment(.center)
.font(.system(size: 11, weight: .medium))
.foregroundColor(Color.gray)
@@ -357,7 +360,7 @@ struct MediumWidgetView : View {
var body: some View {
if !entry.mainApp{
Text("AirBattery is not running\nLaunch the app to make the widget work.")
Text("AirBattery is not running\nLaunch the app to make the widget work")
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color.gray)
@@ -382,26 +385,25 @@ struct MediumWidgetView : View {
VStack(spacing: 17){
ZStack{
Group {
Circle()
.stroke(lineWidth: lineWidth)
.opacity(0.15)
Circle()
.trim(from: 0.0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 0.5)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Circle()
.trim(from: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.001)), to: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.0005)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.shadow(color: .black, radius: lineWidth*0.76, x: 0, y: 0)
.rotationEffect(Angle(degrees: 270.0))
.clipShape( Circle().stroke(lineWidth: lineWidth) )
Circle()
.trim(from: item.batteryLevel > 50 ? 0.25 : 0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 1.0)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Group {
Circle()
.stroke(lineWidth: lineWidth)
.opacity(0.15)
Circle()
.trim(from: 0.0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 0.5)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
Circle()
.trim(from: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.001)), to: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.0005)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.shadow(color: .black, radius: lineWidth*0.76, x: 0, y: 0)
.clipShape( Circle().stroke(lineWidth: lineWidth) )
Circle()
.trim(from: item.batteryLevel > 50 ? 0.25 : 0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 1.0)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
}.rotationEffect(Angle(degrees: 270.0))
Image(getDeviceIcon(item))
.resizable()
@@ -489,7 +491,7 @@ struct batteryWidget: Widget {
.widgetBackground(Color("WidgetBackground"))
}
.configurationDisplayName("Batteries")
.description("Displays battery usage for your devices from AirBattery.")
.description("Displays battery usage for your devices from AirBattery")
.disableContentMarginsIfNeeded()
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}

View File

@@ -8,13 +8,94 @@
import WidgetKit
import SwiftUI
struct LargeWidgetView2: View {
var entry: ViewSizeTimelineProvider.Entry
let lineWidth = 6.0
var body: some View {
if !entry.mainApp{
Text("AirBattery is not running\nLaunch the app to make the widget work")
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color.gray)
} else {
if entry.data.count == 0 {
VStack(alignment:.leading) {
ForEach(0..<11) { index in
VStack{
HStack() {
Image("blank")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20, alignment: .center)
Text(" ")
.font(.system(size: 11))
.frame(height: 20, alignment: .center)
.padding(.horizontal, 7)
Spacer()
Text(" ")
.font(.system(size: 11))
Image("blank")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20, alignment: .center)
}
if index != 10 { Divider().padding(.top, -2) }
}
}
}
.padding(.vertical, 8)
.padding(.horizontal, 15)
} else {
VStack(alignment:.leading) {
ForEach(entry.data.indices, id: \.self) { index in
let item = entry.data[index]
VStack{
HStack() {
Image(getDeviceIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20, alignment: .center)
Text("\(((Date().timeIntervalSince1970 - item.lastUpdate) / 60) > 10 ? "⚠︎ " : "")\(item.deviceName)")
.font(.system(size: 11))
.frame(height: 20, alignment: .center)
.padding(.horizontal, 7)
Spacer()
if item.batteryLevel <= 10 {
Text("\(item.batteryLevel)%") .font(.system(size: 11))
.foregroundColor(Color("dark_my_red"))
} else {
Text("\(item.batteryLevel)%") .font(.system(size: 11))
}
/*Image(getBatteryIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20, alignment: .center)
*/
BatteryView(item: item)
.scaleEffect(0.76)
}
if index != 10 { Divider().padding(.top, -2) }
}
}
Spacer()
}
.offset(y:4)
.padding(.vertical, 8)
.padding(.horizontal, 18)
}
}
}
}
struct doubleRowBatteryWidgetEntryView: View {
var entry: ViewSizeTimelineProvider.Entry
let lineWidth = 6.0
var body: some View {
if !entry.mainApp{
Text("AirBattery is not running\nLaunch the app to make the widget work.")
Text("AirBattery is not running\nLaunch the app to make the widget work")
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color.gray)
@@ -178,7 +259,7 @@ struct singleBatteryWidgetEntryView: View {
var body: some View {
if !entry.mainApp{
Text("AirBattery is not running\nLaunch the app to make\nthe widget work.")
Text("AirBattery is not running\nLaunch the app to make\nthe widget work")
.multilineTextAlignment(.center)
.font(.system(size: 11, weight: .medium))
.foregroundColor(Color.gray)
@@ -283,13 +364,13 @@ struct batteryWidgetEntryView2: View {
case .systemMedium:
doubleRowBatteryWidgetEntryView(entry: entry)
case .systemLarge:
EmptyView()
LargeWidgetView2(entry: entry)
case .systemExtraLarge:
EmptyView()
@unknown default:
EmptyView()
}
}
}.widgetURL(URL(string: "airbattery://reloadwingets"))
}
}
@@ -305,7 +386,7 @@ struct batteryWidget2New: Widget {
.widgetBackground(Color("WidgetBackground"))
}
.configurationDisplayName("Batteries")
.description("Displays the battery usage of a specific device.")
.description("Displays the battery usage of a specific device")
.disableContentMarginsIfNeeded()
.supportedFamilies([.systemSmall])
}
@@ -322,7 +403,7 @@ struct batteryWidget2: Widget {
.widgetBackground(Color("WidgetBackground"))
}
.configurationDisplayName("Batteries")
.description("More ways to displays battery usage for your devices from AirBattery.")
.description("More ways to displays battery usage for your devices")
.disableContentMarginsIfNeeded()
.supportFamily()
}

316
widget/BatteryWidget3.swift Normal file
View File

@@ -0,0 +1,316 @@
//
// BatteryWidget3.swift
// AirBattery
//
// Created by apple on 2024/6/8.
//
import WidgetKit
import SwiftUI
struct batteryWidgetEntryView3 : View {
var entry: ViewSizeTimelineProvider.Entry
var body: some View {
VStack {
switch entry.family {
case .systemSmall:
SmallWidgetView2(entry: entry)
case .systemMedium:
doubleBatteryWidgetEntryView2(entry: entry)
case .systemLarge:
EmptyView()
case .systemExtraLarge:
EmptyView()
@unknown default:
EmptyView()
}
}.widgetURL(URL(string: "airbattery://reloadwingets"))
}
}
struct SmallWidgetView2: View {
var entry: ViewSizeTimelineProvider.Entry
let lineWidth = 6.0
var body: some View {
if !entry.mainApp {
Text("AirBattery is not running\nLaunch the app to make\nthe widget work")
.multilineTextAlignment(.center)
.font(.system(size: 11, weight: .medium))
.foregroundColor(Color.gray)
} else {
if entry.data.count == 0 {
VStack(spacing: 17) {
HStack(spacing: 17) {
ForEach(0..<2) { index in
Circle()
.stroke(lineWidth: lineWidth)
.opacity(0.15)
}
}
HStack(spacing: 17) {
ForEach(0..<2) { index in
Circle()
.stroke(lineWidth: lineWidth)
.opacity(0.15)
}
}
}
}else{
VStack(spacing: 17) {
HStack(spacing: 17){
ForEach(entry.data[0..<2], id: \.self) { item in
ZStack{
Group {
Circle()
.stroke(lineWidth: lineWidth)
.opacity(0.15)
Circle()
.trim(from: 0.0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 0.5)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Circle()
.trim(from: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.001)), to: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.0005)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.shadow(color: .black, radius: lineWidth*0.76, x: 0, y: 0)
.rotationEffect(Angle(degrees: 270.0))
.clipShape( Circle().stroke(lineWidth: lineWidth) )
Circle()
.trim(from: item.batteryLevel > 50 ? 0.25 : 0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 1.0)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Image(getDeviceIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 26, height: 26, alignment: .center)
if item.isCharging != 0 {
Image("batt_bolt_mask")
.resizable()
.scaledToFit()
.frame(width: 12, alignment: .center)
.blendMode(.destinationOut)
.offset(y:-29.5)
Image("batt_bolt")
.resizable()
.scaledToFit()
.frame(width: 10, alignment: .center)
.foregroundColor(item.batteryLevel == 100 ? Color("my_green") : Color.primary)
.offset(y:-29.5)
}
}.frame(width: 58, height: 58, alignment: .center)
}.compositingGroup()
}
}
HStack(spacing: 17){
ForEach(entry.data[2..<4], id: \.self) { item in
ZStack{
Group {
Circle()
.stroke(lineWidth: lineWidth)
.opacity(0.15)
Circle()
.trim(from: 0.0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 0.5)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Circle()
.trim(from: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.001)), to: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.0005)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.shadow(color: .black, radius: lineWidth*0.76, x: 0, y: 0)
.rotationEffect(Angle(degrees: 270.0))
.clipShape( Circle().stroke(lineWidth: lineWidth) )
Circle()
.trim(from: item.batteryLevel > 50 ? 0.25 : 0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 1.0)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Image(getDeviceIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 26, height: 26, alignment: .center)
if item.isCharging != 0 {
Image("batt_bolt_mask")
.resizable()
.scaledToFit()
.frame(width: 12, alignment: .center)
.blendMode(.destinationOut)
.offset(y:-29.5)
Image("batt_bolt")
.resizable()
.scaledToFit()
.frame(width: 10, alignment: .center)
.foregroundColor(item.batteryLevel == 100 ? Color("my_green") : Color.primary)
.offset(y:-29.5)
}
}.frame(width: 58, height: 58, alignment: .center)
}.compositingGroup()
}
}
}
}
}
}
}
struct doubleBatteryWidgetEntryView2: View {
var entry: ViewSizeTimelineProvider.Entry
let lineWidth = 6.0
var body: some View {
if !entry.mainApp{
Text("AirBattery is not running\nLaunch the app to make the widget work")
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color.gray)
} else {
if entry.data.count == 0 {
VStack(spacing: 17){
HStack(spacing: 23) {
ForEach(0..<4) { index in
Circle()
.stroke(lineWidth: lineWidth)
.frame(width: 58, alignment: .center)
}
}
HStack(spacing: 23) {
ForEach(0..<4) { index in
Circle()
.stroke(lineWidth: lineWidth)
.frame(width: 58, alignment: .center)
}
}
}.opacity(0.15)
} else {
VStack(spacing: 17){
HStack(spacing: 23) {
ForEach(entry.data[0..<4], id: \.self) { item in
VStack(spacing: 17){
ZStack{
Group {
Circle()
.stroke(lineWidth: lineWidth)
.opacity(0.15)
Circle()
.trim(from: 0.0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 0.5)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Circle()
.trim(from: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.001)), to: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.0005)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.shadow(color: .black, radius: lineWidth*0.76, x: 0, y: 0)
.rotationEffect(Angle(degrees: 270.0))
.clipShape( Circle().stroke(lineWidth: lineWidth) )
Circle()
.trim(from: item.batteryLevel > 50 ? 0.25 : 0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 1.0)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Image(getDeviceIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 26, height: 26, alignment: .center)
if item.isCharging != 0 {
Image("batt_bolt_mask")
.resizable()
.scaledToFit()
.frame(width: 12, alignment: .center)
.blendMode(.destinationOut)
.offset(y:-29.5)
Image("batt_bolt")
.resizable()
.scaledToFit()
.frame(width: 10, alignment: .center)
.foregroundColor(item.batteryLevel == 100 ? Color("my_green") : Color.primary)
.offset(y:-29.5)
}
}.frame(width: 58, height: 58, alignment: .center)
}.compositingGroup()
}
}
}
HStack(spacing: 23) {
ForEach(entry.data[4..<8], id: \.self) { item in
VStack(spacing: 17){
ZStack{
Group {
Circle()
.stroke(lineWidth: lineWidth)
.opacity(0.15)
Circle()
.trim(from: 0.0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 0.5)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Circle()
.trim(from: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.001)), to: CGFloat(abs((min(Double(item.batteryLevel)/100.0, 1.0))-0.0005)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.shadow(color: .black, radius: lineWidth*0.76, x: 0, y: 0)
.rotationEffect(Angle(degrees: 270.0))
.clipShape( Circle().stroke(lineWidth: lineWidth) )
Circle()
.trim(from: item.batteryLevel > 50 ? 0.25 : 0, to: CGFloat(min(Double(item.batteryLevel)/100.0, 1.0)))
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(getPowerColor(item.batteryLevel)))
.rotationEffect(Angle(degrees: 270.0))
Image(getDeviceIcon(item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 26, height: 26, alignment: .center)
if item.isCharging != 0 {
Image("batt_bolt_mask")
.resizable()
.scaledToFit()
.frame(width: 12, alignment: .center)
.blendMode(.destinationOut)
.offset(y:-29.5)
Image("batt_bolt")
.resizable()
.scaledToFit()
.frame(width: 10, alignment: .center)
.foregroundColor(item.batteryLevel == 100 ? Color("my_green") : Color.primary)
.offset(y:-29.5)
}
}.frame(width: 58, height: 58, alignment: .center)
}.compositingGroup()
}
}
}
}
}
}
}
}
struct batteryWidget3: Widget {
let kind: String = "widget.battery.part4"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: ViewSizeTimelineProvider()) { entry in
batteryWidgetEntryView3(entry: entry)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
.widgetBackground(Color("WidgetBackground"))
}
.configurationDisplayName("Batteries")
.description("Displays battery usage for your devices without percentage")
.disableContentMarginsIfNeeded()
.supportedFamilies([.systemMedium, .systemSmall])
}
}

View File

@@ -29,9 +29,9 @@ extension WidgetConfiguration {
func supportFamily() -> some WidgetConfiguration {
if #available(macOS 14, *) {
return self.supportedFamilies([.systemMedium])
return self.supportedFamilies([.systemLarge, .systemMedium])
} else {
return self.supportedFamilies([.systemMedium, .systemSmall])
return self.supportedFamilies([.systemLarge, .systemMedium, .systemSmall])
}
}
}
@@ -44,9 +44,9 @@ struct widgetBundle: WidgetBundle {
func widgets() -> some Widget {
if #available(macOS 14, *) {
return WidgetBundleBuilder.buildBlock(batteryWidget(), batteryWidget2(), batteryWidget2New())
return WidgetBundleBuilder.buildBlock(batteryWidget(), batteryWidget2New(), batteryWidget2(), batteryWidget3())
} else {
return WidgetBundleBuilder.buildBlock(batteryWidget(), batteryWidget2())
return WidgetBundleBuilder.buildBlock(batteryWidget(), batteryWidget2(), batteryWidget3())
}
}
}