// // LockdownFirewallWidget.swift // LockdownFirewallWidget // // Created by Oleg Dreyman on 25.09.2020. // Copyright © 2020 Confirmed Inc. All rights reserved. // import WidgetKit import SwiftUI struct FirewallProvider: TimelineProvider { func placeholder(in context: Context) -> FirewallEntry { FirewallEntry(date: Date(), size: context.displaySize, isFirewallEnabled: false, dayMetricsString: "--") } func getSnapshot(in context: Context, completion: @escaping (FirewallEntry) -> ()) { let entry = FirewallEntry(date: Date(), size: context.displaySize, isFirewallEnabled: getUserWantsFirewallEnabled(), dayMetricsString: getDayMetricsString(commas: true)) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { var entries: [FirewallEntry] = [] let currentDate = Date() let entry = FirewallEntry(date: Date(), size: context.displaySize, isFirewallEnabled: getUserWantsFirewallEnabled(), dayMetricsString: getDayMetricsString(commas: true)) entries.append(entry) let timeline = Timeline( entries: entries, policy: .after(Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)!) ) completion(timeline) } } struct FirewallEntry: TimelineEntry { let date: Date let size: CGSize let isFirewallEnabled: Bool let dayMetricsString: String var buttonColor: Color { if isFirewallEnabled { return .confirmedBlue } else { return Color(.systemGray) } } } struct LockdownFirewallWidgetEntryView : View { var entry: FirewallProvider.Entry var body: some View { VStack(spacing: 0) { LoadingCircle( tunnelState: TunnelState( color: entry.buttonColor, circleColor: entry.buttonColor ), side: entry.size.height, link: "lockdown://" ) .padding(EdgeInsets(top: 12, leading: 0, bottom: 2, trailing: 0)) if entry.isFirewallEnabled { StatusLabel(text: NSLocalizedString("FIREWALL ON", comment: ""), color: .confirmedBlue) } else { StatusLabel(text: NSLocalizedString("FIREWALL OFF", comment: ""), color: .flatRed) } if entry.size.height < 160 { Spacer().frame(minHeight: 4) } else { Spacer() } Link(destination: URL(string: "lockdown://showMetrics")!, label: { VStack(spacing: 0) { Text(entry.dayMetricsString) .font(.system(size: 21, weight: .semibold)) Text(NSLocalizedString("Blocked Today", comment: "")) .font(.system(size: 12, weight: .medium)) } .padding(.bottom, 12) }) }.frame(width: entry.size.height, height: entry.size.height) } } struct VPNProvider: TimelineProvider { func placeholder(in context: Context) -> VPNEntry { VPNEntry(date: Date(), size: context.displaySize, isVPNEnabled: false, vpnRegion: VPNRegion()) } func getSnapshot(in context: Context, completion: @escaping (VPNEntry) -> ()) { let entry = VPNEntry(date: Date(), size: context.displaySize, isVPNEnabled: LatestKnowledge.isVPNEnabled, vpnRegion: getSavedVPNRegion()) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { var entries: [VPNEntry] = [] let currentDate = Date() let entry = VPNEntry(date: Date(), size: context.displaySize, isVPNEnabled: LatestKnowledge.isVPNEnabled, vpnRegion: getSavedVPNRegion()) entries.append(entry) let timeline = Timeline( entries: entries, policy: .after(Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)!) ) completion(timeline) } } struct VPNEntry: TimelineEntry { let date: Date let size: CGSize let isVPNEnabled: Bool let vpnRegion: VPNRegion var buttonColor: Color { if isVPNEnabled { return .confirmedBlue } else { return Color(.systemGray) } } } struct LockdownVPNWidgetEntryView : View { var entry: VPNProvider.Entry var body: some View { VStack(spacing: 0) { LoadingCircle( tunnelState: TunnelState( color: entry.buttonColor, circleColor: entry.buttonColor ), side: entry.size.height, link: "lockdown://" ) .padding(EdgeInsets(top: 12, leading: 0, bottom: 2, trailing: 0)) if entry.isVPNEnabled { StatusLabel(text: NSLocalizedString("Tunnel On", comment: "").uppercased(), color: .confirmedBlue) } else { StatusLabel(text: NSLocalizedString("Tunnel Off", comment: "").uppercased(), color: .flatRed) } if entry.size.height < 160 { Spacer().frame(minHeight: 4) } else { Spacer() } Link(destination: URL(string: "lockdown://changeVPNregion")!, label: { VStack(spacing: 0) { Text(entry.vpnRegion.regionFlagEmoji) .font(.system(size: 21, weight: .semibold)) Text(entry.vpnRegion.regionDisplayNameShort) .font(.system(size: 12, weight: .medium)) } .padding(.bottom, 12) }) }.frame(width: entry.size.height, height: entry.size.height) } } struct CombinedWidgetView: View { let firewall: FirewallEntry let vpn: VPNEntry var body: some View { HStack(alignment: .top, spacing: 0) { LockdownFirewallWidgetEntryView(entry: firewall) LockdownVPNWidgetEntryView(entry: vpn) } .frame(minWidth: firewall.size.width, minHeight: firewall.size.height) } } @main struct LockdownWidgetBundle: WidgetBundle { @WidgetBundleBuilder var body: some Widget { LockdownFirewallWidget() LockdownVPNWidget() LockdownCombinedWidget() } } struct LockdownFirewallWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration( kind: "LockdownFirewallWidget", provider: FirewallProvider(), content: { entry in ZStack { Color.panelBackground LockdownFirewallWidgetEntryView(entry: entry) } } ) .configurationDisplayName("Firewall") .supportedFamilies([.systemSmall]) } } struct LockdownVPNWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration( kind: "LockdownVPNWidget", provider: VPNProvider(), content: { entry in ZStack { Color.panelBackground LockdownVPNWidgetEntryView(entry: entry) } } ) .configurationDisplayName("Secure Tunnel") .supportedFamilies([.systemSmall]) } } struct LockdownCombinedWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration( kind: "LockdownCombinedWidget", provider: CombinedProvider(main: FirewallProvider(), supplemental: VPNProvider()), content: { entry in ZStack { Color.panelBackground CombinedWidgetView(firewall: entry.main, vpn: entry.supplemental) } } ) .configurationDisplayName("Firewall + Tunnel") .supportedFamilies([.systemMedium]) } }