diff --git a/Flipper/BatteryWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 000000000..eb8789700
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Flipper/BatteryWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..230588010
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,35 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Flipper/BatteryWidget/Assets.xcassets/Charging.imageset/Charging.svg b/Flipper/BatteryWidget/Assets.xcassets/Charging.imageset/Charging.svg
new file mode 100644
index 000000000..53936103b
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/Charging.imageset/Charging.svg
@@ -0,0 +1,3 @@
+
diff --git a/Flipper/BatteryWidget/Assets.xcassets/Charging.imageset/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/Charging.imageset/Contents.json
new file mode 100644
index 000000000..ff96eac89
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/Charging.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "Charging.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Flipper/BatteryWidget/Assets.xcassets/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Flipper/BatteryWidget/Assets.xcassets/Flipper.imageset/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/Flipper.imageset/Contents.json
new file mode 100644
index 000000000..ba3e914d6
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/Flipper.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images": [
+ {
+ "filename": "Flipper.svg",
+ "idiom": "universal",
+ "scale": "1x"
+ },
+ {
+ "idiom": "universal",
+ "scale": "2x"
+ },
+ {
+ "idiom": "universal",
+ "scale": "3x"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
diff --git a/Flipper/BatteryWidget/Assets.xcassets/Flipper.imageset/Flipper.svg b/Flipper/BatteryWidget/Assets.xcassets/Flipper.imageset/Flipper.svg
new file mode 100644
index 000000000..dd4e342d6
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/Flipper.imageset/Flipper.svg
@@ -0,0 +1,3 @@
+
diff --git a/Flipper/BatteryWidget/Assets.xcassets/FlipperCharging.imageset/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/FlipperCharging.imageset/Contents.json
new file mode 100644
index 000000000..376e23e47
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/FlipperCharging.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "FlipperCharging.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Flipper/BatteryWidget/Assets.xcassets/FlipperCharging.imageset/FlipperCharging.svg b/Flipper/BatteryWidget/Assets.xcassets/FlipperCharging.imageset/FlipperCharging.svg
new file mode 100644
index 000000000..f954d9a5a
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/FlipperCharging.imageset/FlipperCharging.svg
@@ -0,0 +1,12 @@
+
diff --git a/Flipper/BatteryWidget/Assets.xcassets/FlipperDisconnected.imageset/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/FlipperDisconnected.imageset/Contents.json
new file mode 100644
index 000000000..5afe21406
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/FlipperDisconnected.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "FlipperDisconnected.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Flipper/BatteryWidget/Assets.xcassets/FlipperDisconnected.imageset/FlipperDisconnected.svg b/Flipper/BatteryWidget/Assets.xcassets/FlipperDisconnected.imageset/FlipperDisconnected.svg
new file mode 100644
index 000000000..2fa85d925
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/FlipperDisconnected.imageset/FlipperDisconnected.svg
@@ -0,0 +1,21 @@
+
diff --git a/Flipper/BatteryWidget/Assets.xcassets/FlipperUnknown.imageset/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/FlipperUnknown.imageset/Contents.json
new file mode 100644
index 000000000..bed0b711c
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/FlipperUnknown.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "FlipperUnknown.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Flipper/BatteryWidget/Assets.xcassets/FlipperUnknown.imageset/FlipperUnknown.svg b/Flipper/BatteryWidget/Assets.xcassets/FlipperUnknown.imageset/FlipperUnknown.svg
new file mode 100644
index 000000000..8947b8257
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/FlipperUnknown.imageset/FlipperUnknown.svg
@@ -0,0 +1,13 @@
+
diff --git a/Flipper/BatteryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/Flipper/BatteryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
new file mode 100644
index 000000000..eb8789700
--- /dev/null
+++ b/Flipper/BatteryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Flipper/BatteryWidget/BatteryWidget.swift b/Flipper/BatteryWidget/BatteryWidget.swift
new file mode 100644
index 000000000..2b0634273
--- /dev/null
+++ b/Flipper/BatteryWidget/BatteryWidget.swift
@@ -0,0 +1,19 @@
+import SwiftUI
+import WidgetKit
+
+struct BatteryWidget: Widget {
+ var body: some WidgetConfiguration {
+ StaticConfiguration(
+ kind: "BatteryWidget",
+ provider: Provider()
+ ) { entry in
+ EntryView(entry: entry)
+ .containerBackground(.fill.tertiary, for: .widget)
+ }
+ .configurationDisplayName("Flipper Battery")
+ .description("Displays battery status of your Flipper device")
+ .supportedFamilies(
+ [.accessoryInline, .accessoryCircular, .accessoryRectangular]
+ )
+ }
+}
diff --git a/Flipper/BatteryWidget/BatteryWidgetExtension.entitlements b/Flipper/BatteryWidget/BatteryWidgetExtension.entitlements
new file mode 100644
index 000000000..98c53f08e
--- /dev/null
+++ b/Flipper/BatteryWidget/BatteryWidgetExtension.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.flipperdevices.main
+
+
+
diff --git a/Flipper/BatteryWidget/Components/BatteryViewCircular.swift b/Flipper/BatteryWidget/Components/BatteryViewCircular.swift
new file mode 100644
index 000000000..18d4dd0c1
--- /dev/null
+++ b/Flipper/BatteryWidget/Components/BatteryViewCircular.swift
@@ -0,0 +1,51 @@
+import SwiftUI
+import WidgetKit
+
+struct BatteryViewCircular: View {
+ let entry: Provider.Entry
+
+ var body: some View {
+ Gauge(value: Double(entry.value), in: 0...100) {
+ Image(entry.image)
+ .resizable()
+ .frame(width: 40, height: 40)
+ }
+ .gaugeStyle(.accessoryCircularCapacity)
+ }
+}
+
+#Preview("Circular Loading", as: .accessoryCircular) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .loading
+ )
+}
+
+#Preview("Circular Disconnected", as: .accessoryCircular) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .disconnected
+ )
+}
+
+#Preview("Circular Default", as: .accessoryCircular) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .connected(65, false)
+ )
+}
+
+#Preview("Circular Charging", as: .accessoryCircular) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .connected(65, true)
+ )
+}
diff --git a/Flipper/BatteryWidget/Components/BatteryViewInline.swift b/Flipper/BatteryWidget/Components/BatteryViewInline.swift
new file mode 100644
index 000000000..9fb31688d
--- /dev/null
+++ b/Flipper/BatteryWidget/Components/BatteryViewInline.swift
@@ -0,0 +1,64 @@
+import WidgetKit
+import SwiftUI
+
+struct BatteryViewInline: View {
+ let entry: Provider.Entry
+
+ var body: some View {
+ switch entry.state {
+ case .loading:
+ Text("Loading Device")
+ .redacted(reason: .placeholder)
+ case .disconnected:
+ Text("Flipper Disconnected")
+ .font(.system(size: 12, weight: .medium))
+ case .connected(let battery, let isCharging):
+ HStack {
+ if isCharging {
+ Image("Charging")
+ .resizable()
+ .frame(width: 24, height: 24)
+ }
+
+ Text("Flipper \(battery)%")
+ .font(.system(size: 12, weight: .medium))
+ }
+ }
+ }
+}
+
+#Preview("Inline Loading", as: .accessoryInline) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .loading
+ )
+}
+
+#Preview("Inline Disconnected", as: .accessoryInline) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .disconnected
+ )
+}
+
+#Preview("Inline Default", as: .accessoryInline) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .connected(65, false)
+ )
+}
+
+#Preview("Inline Charging", as: .accessoryInline) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .connected(65, true)
+ )
+}
diff --git a/Flipper/BatteryWidget/Components/BatteryViewRectangular.swift b/Flipper/BatteryWidget/Components/BatteryViewRectangular.swift
new file mode 100644
index 000000000..16906bae0
--- /dev/null
+++ b/Flipper/BatteryWidget/Components/BatteryViewRectangular.swift
@@ -0,0 +1,74 @@
+import WidgetKit
+import SwiftUI
+
+struct BatteryViewRectangular: View {
+ let entry: Provider.Entry
+
+ private var text: String {
+ return switch entry.state {
+ case .loading: "Loading Device"
+ case .disconnected: "Disconnected"
+ case .connected(let battery, _):
+ "Flipper \(battery)%"
+ }
+ }
+
+ var body: some View {
+ Gauge(value: Double(entry.value), in: 0...100) {
+ HStack {
+ Spacer()
+
+ Image(entry.image)
+ .resizable()
+ .frame(width: 40, height: 40)
+
+ if entry.state == .loading {
+ Text(text)
+ .redacted(reason: .placeholder)
+ } else {
+ Text(text)
+ .font(.system(size: 12, weight: .medium))
+ }
+
+ Spacer()
+ }
+ }
+ .gaugeStyle(.accessoryLinearCapacity)
+ }
+}
+
+#Preview("Rectangular Loading", as: .accessoryRectangular) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .loading
+ )
+}
+
+#Preview("Rectangular Disconnected", as: .accessoryRectangular) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .disconnected
+ )
+}
+
+#Preview("Rectangular Default", as: .accessoryRectangular) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .connected(65, false)
+ )
+}
+
+#Preview("Rectangular Charging", as: .accessoryRectangular) {
+ BatteryWidget()
+} timeline: {
+ Entry(
+ date: .now,
+ state: .connected(65, true)
+ )
+}
diff --git a/Flipper/BatteryWidget/Components/Entry+UI.swift b/Flipper/BatteryWidget/Components/Entry+UI.swift
new file mode 100644
index 000000000..09a43199f
--- /dev/null
+++ b/Flipper/BatteryWidget/Components/Entry+UI.swift
@@ -0,0 +1,32 @@
+import SwiftUI
+import WidgetKit
+
+struct BatteryGaugeView: View {
+ let entry: Provider.Entry
+
+ var body: some View {
+ Gauge(value: Double(entry.value), in: 0...100) {
+ Image(entry.image)
+ .resizable()
+ .frame(width: 40, height: 40)
+ }
+ }
+}
+
+extension Entry {
+ var image: String {
+ return switch self.state {
+ case .loading: "FlipperUnknown"
+ case .disconnected: "FlipperDisconnected"
+ case .connected(_, let isCharging):
+ isCharging ? "FlipperCharging" : "Flipper"
+ }
+ }
+
+ var value: Int {
+ return switch self.state {
+ case .connected(let battery, _): battery
+ default: 0
+ }
+ }
+}
diff --git a/Flipper/BatteryWidget/Entry.swift b/Flipper/BatteryWidget/Entry.swift
new file mode 100644
index 000000000..12103859b
--- /dev/null
+++ b/Flipper/BatteryWidget/Entry.swift
@@ -0,0 +1,12 @@
+import WidgetKit
+
+struct Entry: TimelineEntry {
+ let date: Date
+ let state: State
+
+ enum State: Equatable {
+ case loading
+ case disconnected
+ case connected(Int, Bool)
+ }
+}
diff --git a/Flipper/BatteryWidget/EntryView.swift b/Flipper/BatteryWidget/EntryView.swift
new file mode 100644
index 000000000..16ab6f3e6
--- /dev/null
+++ b/Flipper/BatteryWidget/EntryView.swift
@@ -0,0 +1,16 @@
+import SwiftUI
+
+struct EntryView: View {
+ @Environment(\.widgetFamily) private var widgetFamily
+
+ let entry: Provider.Entry
+
+ var body: some View {
+ switch widgetFamily {
+ case .accessoryCircular: BatteryViewCircular(entry: entry)
+ case .accessoryInline: BatteryViewInline(entry: entry)
+ case .accessoryRectangular: BatteryViewRectangular(entry: entry)
+ default: EmptyView()
+ }
+ }
+}
diff --git a/Flipper/BatteryWidget/Info.plist b/Flipper/BatteryWidget/Info.plist
new file mode 100644
index 000000000..0f118fb75
--- /dev/null
+++ b/Flipper/BatteryWidget/Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/Flipper/BatteryWidget/Main.swift b/Flipper/BatteryWidget/Main.swift
new file mode 100644
index 000000000..0355a5a2e
--- /dev/null
+++ b/Flipper/BatteryWidget/Main.swift
@@ -0,0 +1,9 @@
+import WidgetKit
+import SwiftUI
+
+@main
+struct BatteryWidgetBundle: WidgetBundle {
+ var body: some Widget {
+ BatteryWidget()
+ }
+}
diff --git a/Flipper/BatteryWidget/Provider.swift b/Flipper/BatteryWidget/Provider.swift
new file mode 100644
index 000000000..9c10af1ec
--- /dev/null
+++ b/Flipper/BatteryWidget/Provider.swift
@@ -0,0 +1,55 @@
+import SwiftUI
+import WidgetKit
+
+struct Provider: TimelineProvider {
+
+ func placeholder(in context: Context) -> Entry {
+ .init(
+ date: .now,
+ state: .loading)
+ }
+
+ func getSnapshot(
+ in context: Context,
+ completion: @escaping (Entry) -> Void
+ ) {
+ completion(
+ .init(
+ date: .now,
+ state: getState())
+ )
+ }
+
+ func getTimeline(
+ in context: Context,
+ completion: @escaping (Timeline) -> Void
+ ) {
+ completion(
+ .init(
+ entries: [
+ .init(
+ date: Date().addingTimeInterval(0),
+ state: getState())
+ ],
+ policy: .never
+ )
+ )
+ }
+
+ private func getState() -> Entry.State {
+ let battery = UserDefaults.group.integer(forKey: "battery_level")
+
+ if battery == -1 {
+ return .disconnected
+ }
+
+ let isCharging = UserDefaults.group.bool(forKey: "battery_charging")
+ return .connected(battery, isCharging)
+ }
+}
+
+extension UserDefaults {
+ static var group: UserDefaults {
+ .init(suiteName: "group.com.flipperdevices.main")!
+ }
+}
diff --git a/Flipper/Flipper.xcodeproj/project.pbxproj b/Flipper/Flipper.xcodeproj/project.pbxproj
index 673e5907f..13cb4aa4b 100644
--- a/Flipper/Flipper.xcodeproj/project.pbxproj
+++ b/Flipper/Flipper.xcodeproj/project.pbxproj
@@ -11,6 +11,15 @@
0AD5C05D29D9A04E00E0F97E /* ArchivedItemEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AD5C05B29D9A04E00E0F97E /* ArchivedItemEntity.swift */; };
0AD5C05E29D9A04E00E0F97E /* SendArchivedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AD5C05C29D9A04E00E0F97E /* SendArchivedItem.swift */; };
49CE8F9E25262E2300B9CBE4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49CE8F9D25262E2300B9CBE4 /* LaunchScreen.storyboard */; };
+ 654B00422C86FC6000B72D5B /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B1A481829C113A5000169E9 /* WidgetKit.framework */; };
+ 654B00432C86FC6000B72D5B /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B3E56602C7613A70090F9B8 /* SwiftUI.framework */; };
+ 654B004A2C86FC6100B72D5B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 654B00492C86FC6100B72D5B /* Assets.xcassets */; };
+ 654B004E2C86FC6100B72D5B /* BatteryWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 654B00412C86FC6000B72D5B /* BatteryWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 6578F92C2C8707EC00A320CD /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6578F92B2C8707EC00A320CD /* Main.swift */; };
+ 6578F92E2C87094000A320CD /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6578F92D2C87094000A320CD /* Entry.swift */; };
+ 6578F9302C8709F600A320CD /* EntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6578F92F2C8709F600A320CD /* EntryView.swift */; };
+ 6578F9322C870A0800A320CD /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6578F9312C870A0800A320CD /* Provider.swift */; };
+ 6578F9342C87139C00A320CD /* BatteryWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6578F9332C87139C00A320CD /* BatteryWidget.swift */; };
65CAD4C22BD6946000628789 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65CAD4C12BD6946000628789 /* PrivacyInfo.xcprivacy */; };
65CAD4C42BD6946000628789 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65CAD4C12BD6946000628789 /* PrivacyInfo.xcprivacy */; };
8B064D5D2BD6C8F6008C00B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B064D5C2BD6C8F6008C00B3 /* Assets.xcassets */; };
@@ -71,6 +80,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ 654B004C2C86FC6100B72D5B /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = F0DBFA1524EF2F9600EB2880 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 654B00402C86FC6000B72D5B;
+ remoteInfo = BatteryWidgetExtension;
+ };
8B1A482929C113A6000169E9 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F0DBFA1524EF2F9600EB2880 /* Project object */;
@@ -121,6 +137,7 @@
8BF9CED428E21A4400CD5D31 /* TodayWidget.appex in Embed Foundation Extensions */,
8B1A482B29C113A6000169E9 /* ActivityWidget.appex in Embed Foundation Extensions */,
8B2F29922C1CD73500FEB48E /* LiveWidgetExtension.appex in Embed Foundation Extensions */,
+ 654B004E2C86FC6100B72D5B /* BatteryWidgetExtension.appex in Embed Foundation Extensions */,
8BCFDDEB277A2405002DA4CD /* KeyPreview.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
@@ -134,6 +151,15 @@
0AD5C05C29D9A04E00E0F97E /* SendArchivedItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendArchivedItem.swift; sourceTree = ""; };
44A5B59224F05647009EE7FB /* iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iOS.entitlements; sourceTree = ""; };
49CE8F9D25262E2300B9CBE4 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; };
+ 654B00412C86FC6000B72D5B /* BatteryWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = BatteryWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 654B00492C86FC6100B72D5B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 654B004B2C86FC6100B72D5B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 6578F92A2C87044200A320CD /* BatteryWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BatteryWidgetExtension.entitlements; sourceTree = ""; };
+ 6578F92B2C8707EC00A320CD /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = ""; };
+ 6578F92D2C87094000A320CD /* Entry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entry.swift; sourceTree = ""; };
+ 6578F92F2C8709F600A320CD /* EntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryView.swift; sourceTree = ""; };
+ 6578F9312C870A0800A320CD /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; };
+ 6578F9332C87139C00A320CD /* BatteryWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryWidget.swift; sourceTree = ""; };
65B31A442C400D8500F72D36 /* Backend */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Backend; path = Packages/Backend; sourceTree = ""; };
65CAD4C12BD6946000628789 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
8B064D5C2BD6C8F6008C00B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -204,7 +230,33 @@
F0DBFA2424EF2F9900EB2880 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
/* End PBXFileReference section */
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 6578F93C2C87380400A320CD /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ BatteryViewCircular.swift,
+ BatteryViewInline.swift,
+ BatteryViewRectangular.swift,
+ "Entry+UI.swift",
+ );
+ target = 654B00402C86FC6000B72D5B /* BatteryWidgetExtension */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 6578F9362C87358D00A320CD /* Components */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (6578F93C2C87380400A320CD /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Components; sourceTree = ""; };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
/* Begin PBXFrameworksBuildPhase section */
+ 654B003E2C86FC6000B72D5B /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 654B00432C86FC6000B72D5B /* SwiftUI.framework in Frameworks */,
+ 654B00422C86FC6000B72D5B /* WidgetKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
8B1A481429C113A5000169E9 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -282,6 +334,22 @@
name = Frameworks;
sourceTree = "";
};
+ 654B00442C86FC6000B72D5B /* BatteryWidget */ = {
+ isa = PBXGroup;
+ children = (
+ 6578F9362C87358D00A320CD /* Components */,
+ 6578F9312C870A0800A320CD /* Provider.swift */,
+ 6578F92F2C8709F600A320CD /* EntryView.swift */,
+ 6578F92D2C87094000A320CD /* Entry.swift */,
+ 654B00492C86FC6100B72D5B /* Assets.xcassets */,
+ 654B004B2C86FC6100B72D5B /* Info.plist */,
+ 6578F92A2C87044200A320CD /* BatteryWidgetExtension.entitlements */,
+ 6578F92B2C8707EC00A320CD /* Main.swift */,
+ 6578F9332C87139C00A320CD /* BatteryWidget.swift */,
+ );
+ path = BatteryWidget;
+ sourceTree = "";
+ };
8B1A481C29C113A5000169E9 /* ActivityWidget */ = {
isa = PBXGroup;
children = (
@@ -403,6 +471,7 @@
8B1A481C29C113A5000169E9 /* ActivityWidget */,
0AD5C05A29D9A02300E0F97E /* AppIntents */,
8B2F29862C1CD73400FEB48E /* LiveWidget */,
+ 654B00442C86FC6000B72D5B /* BatteryWidget */,
F0DBFA2224EF2F9900EB2880 /* Products */,
44A5B5B124F05FCE009EE7FB /* Frameworks */,
);
@@ -430,6 +499,7 @@
8BF9CEC828E21A4400CD5D31 /* TodayWidget.appex */,
8B1A481729C113A5000169E9 /* ActivityWidget.appex */,
8B2F29832C1CD73400FEB48E /* LiveWidgetExtension.appex */,
+ 654B00412C86FC6000B72D5B /* BatteryWidgetExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -448,6 +518,23 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 654B00402C86FC6000B72D5B /* BatteryWidgetExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 654B00512C86FC6100B72D5B /* Build configuration list for PBXNativeTarget "BatteryWidgetExtension" */;
+ buildPhases = (
+ 654B003D2C86FC6000B72D5B /* Sources */,
+ 654B003E2C86FC6000B72D5B /* Frameworks */,
+ 654B003F2C86FC6000B72D5B /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = BatteryWidgetExtension;
+ productName = BatteryWidgetExtension;
+ productReference = 654B00412C86FC6000B72D5B /* BatteryWidgetExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
8B1A481629C113A5000169E9 /* ActivityWidget */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8B1A482E29C113A6000169E9 /* Build configuration list for PBXNativeTarget "ActivityWidget" */;
@@ -542,6 +629,7 @@
8BF9CED328E21A4400CD5D31 /* PBXTargetDependency */,
8B1A482A29C113A6000169E9 /* PBXTargetDependency */,
8B2F29912C1CD73500FEB48E /* PBXTargetDependency */,
+ 654B004D2C86FC6100B72D5B /* PBXTargetDependency */,
);
name = "Flipper(iOS)";
packageProductDependencies = (
@@ -563,6 +651,9 @@
LastSwiftUpdateCheck = 1610;
LastUpgradeCheck = 1610;
TargetAttributes = {
+ 654B00402C86FC6000B72D5B = {
+ CreatedOnToolsVersion = 16.0;
+ };
8B1A481629C113A5000169E9 = {
CreatedOnToolsVersion = 14.3;
};
@@ -600,11 +691,20 @@
8BF9CEC728E21A4400CD5D31 /* TodayWidget */,
8B1A481629C113A5000169E9 /* ActivityWidget */,
8B2F29822C1CD73400FEB48E /* LiveWidgetExtension */,
+ 654B00402C86FC6000B72D5B /* BatteryWidgetExtension */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 654B003F2C86FC6000B72D5B /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 654B004A2C86FC6100B72D5B /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
8B1A481529C113A5000169E9 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -701,6 +801,18 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 654B003D2C86FC6000B72D5B /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6578F92E2C87094000A320CD /* Entry.swift in Sources */,
+ 6578F9342C87139C00A320CD /* BatteryWidget.swift in Sources */,
+ 6578F92C2C8707EC00A320CD /* Main.swift in Sources */,
+ 6578F9322C870A0800A320CD /* Provider.swift in Sources */,
+ 6578F9302C8709F600A320CD /* EntryView.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
8B1A481329C113A5000169E9 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -768,6 +880,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 654B004D2C86FC6100B72D5B /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 654B00402C86FC6000B72D5B /* BatteryWidgetExtension */;
+ targetProxy = 654B004C2C86FC6100B72D5B /* PBXContainerItemProxy */;
+ };
8B1A482A29C113A6000169E9 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilter = ios;
@@ -794,6 +911,86 @@
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
+ 654B004F2C86FC6100B72D5B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CODE_SIGN_ENTITLEMENTS = BatteryWidget/BatteryWidgetExtension.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 0;
+ DEVELOPMENT_TEAM = SXH69675TZ;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = BatteryWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = BatteryWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 17.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 14.0;
+ MARKETING_VERSION = 1.7.1;
+ PRODUCT_BUNDLE_IDENTIFIER = com.flipperdevices.main.BatteryWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
+ SUPPORTS_MACCATALYST = NO;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 1;
+ };
+ name = Debug;
+ };
+ 654B00502C86FC6100B72D5B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CODE_SIGN_ENTITLEMENTS = BatteryWidget/BatteryWidgetExtension.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 0;
+ DEVELOPMENT_TEAM = SXH69675TZ;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = BatteryWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = BatteryWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 17.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 14.0;
+ MARKETING_VERSION = 1.7.1;
+ PRODUCT_BUNDLE_IDENTIFIER = com.flipperdevices.main.BatteryWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
+ SUPPORTS_MACCATALYST = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 1;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
8B1A482C29C113A6000169E9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -1257,6 +1454,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 654B00512C86FC6100B72D5B /* Build configuration list for PBXNativeTarget "BatteryWidgetExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 654B004F2C86FC6100B72D5B /* Debug */,
+ 654B00502C86FC6100B72D5B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
8B1A482E29C113A6000169E9 /* Build configuration list for PBXNativeTarget "ActivityWidget" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/Flipper/Flipper.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Flipper/Flipper.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
deleted file mode 100644
index 55895e656..000000000
--- a/Flipper/Flipper.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ /dev/null
@@ -1,140 +0,0 @@
-{
- "pins" : [
- {
- "identity" : "countly-sdk-ios",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/Countly/countly-sdk-ios.git",
- "state" : {
- "revision" : "e12be8153743a17c1389f80f9f7401c9f7f7de61",
- "version" : "24.4.0"
- }
- },
- {
- "identity" : "dcompression",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/swiftstack/dcompression.git",
- "state" : {
- "branch" : "dev",
- "revision" : "9bb4eda095d9e9d962bcf3618993614f417c95c4"
- }
- },
- {
- "identity" : "firebase-ios-sdk",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/tonyfreeman/firebase-ios-sdk",
- "state" : {
- "branch" : "master",
- "revision" : "fccc5e45f145baf6c9ac73f9579180efd9334f8d"
- }
- },
- {
- "identity" : "googledatatransport",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/GoogleDataTransport.git",
- "state" : {
- "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
- "version" : "9.4.0"
- }
- },
- {
- "identity" : "googleutilities",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/GoogleUtilities.git",
- "state" : {
- "revision" : "26c898aed8bed13b8a63057ee26500abbbcb8d55",
- "version" : "7.13.1"
- }
- },
- {
- "identity" : "nanopb",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/firebase/nanopb.git",
- "state" : {
- "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
- "version" : "2.30910.0"
- }
- },
- {
- "identity" : "networkimage",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/gonzalezreal/NetworkImage",
- "state" : {
- "revision" : "7aff8d1b31148d32c5933d75557d42f6323ee3d1",
- "version" : "6.0.0"
- }
- },
- {
- "identity" : "promises",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/promises.git",
- "state" : {
- "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
- "version" : "2.4.0"
- }
- },
- {
- "identity" : "radix",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/swiftstack/radix.git",
- "state" : {
- "branch" : "dev",
- "revision" : "21246051060c7050e707eb9f4b6796d99e7a1c60"
- }
- },
- {
- "identity" : "stream",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/swiftstack/stream.git",
- "state" : {
- "branch" : "dev",
- "revision" : "67deefcfc7af25a914677ce15b9c42981529419a"
- }
- },
- {
- "identity" : "swift-collections",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-collections.git",
- "state" : {
- "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
- "version" : "1.1.0"
- }
- },
- {
- "identity" : "swift-log",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-log.git",
- "state" : {
- "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
- "version" : "1.5.4"
- }
- },
- {
- "identity" : "swift-markdown-ui",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/gonzalezreal/swift-markdown-ui",
- "state" : {
- "revision" : "9a8119b37e09a770367eeb26e05267c75d854053",
- "version" : "2.3.1"
- }
- },
- {
- "identity" : "swift-protobuf",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-protobuf.git",
- "state" : {
- "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
- "version" : "1.26.0"
- }
- },
- {
- "identity" : "swift-syntax",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-syntax.git",
- "state" : {
- "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
- "version" : "509.1.1"
- }
- }
- ],
- "version" : 2
-}
diff --git a/Flipper/Packages/UI/Sources/Main/MainView.swift b/Flipper/Packages/UI/Sources/Main/MainView.swift
index 843ed558f..3436fab3b 100644
--- a/Flipper/Packages/UI/Sources/Main/MainView.swift
+++ b/Flipper/Packages/UI/Sources/Main/MainView.swift
@@ -45,10 +45,23 @@ struct MainView: View {
.onReceive(device.$status) { status in
if status == .disconnected {
UserDefaults.group.set("", forKey: "emulating")
+ UserDefaults.group.set(-1, forKey: "battery_level")
+ UserDefaults.group.set(false, forKey: "battery_charging")
UserDefaults.group.synchronize()
+
emulate.stopEmulate()
WidgetCenter.shared.reloadTimelines(ofKind: "LiveWidget")
+ WidgetCenter.shared.reloadTimelines(ofKind: "BatteryWidget")
}
}
+ .onReceive(device.$flipper) { flipper in
+ let battery = flipper?.battery?.level ?? -1
+ let isCharging = flipper?.battery?.state == .charging
+
+ UserDefaults.group.set(battery, forKey: "battery_level")
+ UserDefaults.group.set(isCharging, forKey: "battery_charging")
+ UserDefaults.group.synchronize()
+ WidgetCenter.shared.reloadTimelines(ofKind: "BatteryWidget")
+ }
}
}