├── .DS_Store ├── .github └── workflows │ └── ios.yml ├── HealthcheckIntents ├── HealthcheckIntents.entitlements ├── Info.plist └── IntentHandler.swift ├── HealthcheckWidget ├── .DS_Store ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── WidgetBackground.colorset │ │ └── Contents.json ├── HealthcheckWidget.intentdefinition ├── HealthcheckWidget.swift ├── HealthcheckWidgetBundle.swift ├── HealthcheckWidgetLiveActivity.swift ├── Info.plist └── WidgetSizes │ ├── AccessoryInlineView.swift │ ├── LargeSizeView.swift │ ├── MediumSizeView.swift │ └── SmallSizeView.swift ├── HealthcheckWidgetExtension.entitlements ├── IPv64.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ └── sebastianrank.xcuserdatad │ │ ├── IDEFindNavigatorScopes.plist │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ ├── HealthcheckWidgetExtension.xcscheme │ │ └── IPv64.xcscheme └── xcuserdata │ └── sebastianrank.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── IPv64 ├── .DS_Store ├── Assets.xcassets │ ├── .DS_Store │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AccountSelectionBG.colorset │ │ └── Contents.json │ ├── AppIcon 1.appiconset │ │ ├── 1024.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── appstore.png │ ├── Contents.json │ ├── Integration Icons │ │ ├── Contents.json │ │ ├── discord_icon.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-discord.svg │ │ ├── gotify_icon.imageset │ │ │ ├── Contents.json │ │ │ └── gotify-logo-notification.svg │ │ ├── matrix_icon.imageset │ │ │ ├── Contents.json │ │ │ └── Matrix_icon.svg │ │ ├── ntfy_icon.imageset │ │ │ ├── Contents.json │ │ │ └── ntfy-svgrepo-com.svg │ │ ├── pushover_icon.imageset │ │ │ ├── Contents.json │ │ │ └── pushover-logo.svg │ │ ├── telegram_icon.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-telegram.svg │ │ └── webhook_icon.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-webhook.svg │ ├── SectionBG.colorset │ │ └── Contents.json │ ├── accountSinceColor.colorset │ │ └── Contents.json │ ├── circleBG.colorset │ │ └── Contents.json │ ├── ip64_color.colorset │ │ └── Contents.json │ ├── ipv64_logo.imageset │ │ ├── Contents.json │ │ └── ipv64_logo.png │ ├── ipv64_logo_new.imageset │ │ ├── Contents.json │ │ └── ipv64_logo.svg │ ├── lightgray.colorset │ │ └── Contents.json │ ├── primaryText.colorset │ │ └── Contents.json │ └── textFieldBG.colorset │ │ └── Contents.json ├── Env │ └── Sounds │ │ ├── tabSelection.wav │ │ └── tabSelection_old.wav ├── GoogleService-Info.plist ├── HelperViews │ ├── CodeScanner.swift │ └── SpinnerView.swift ├── IPv64.entitlements ├── IPv64.xcdatamodeld │ ├── .xccurrentversion │ └── IPv64.xcdatamodel │ │ └── contents ├── IPv64App.swift ├── Info.plist ├── Main │ ├── .DS_Store │ ├── Account │ │ ├── .DS_Store │ │ ├── AccountListView.swift │ │ ├── AccountView.swift │ │ ├── HelpView.swift │ │ ├── IPView.swift │ │ ├── Items │ │ │ └── LogItemView.swift │ │ ├── LogView.swift │ │ └── ProfilView.swift │ ├── Blocklist │ │ └── BlocklistView.swift │ ├── Domains │ │ ├── .DS_Store │ │ ├── ContentView.swift │ │ ├── DetailDomainView.swift │ │ └── Sheets │ │ │ ├── NewDomainDNSView.swift │ │ │ └── NewDomainView.swift │ ├── Healthcheck │ │ ├── .DS_Store │ │ ├── DetailHealthcheckView.swift │ │ ├── HealthcheckView.swift │ │ ├── Items │ │ │ ├── HealthcheckStatsItem.swift │ │ │ └── HealthcheckStatsView.swift │ │ └── Sheets │ │ │ ├── EditHealthcheckView.swift │ │ │ ├── IntegrationSelection.swift │ │ │ └── NewHealthcheckView.swift │ ├── Integrations │ │ └── IntegrationView.swift │ ├── LockView.swift │ ├── LoginView.swift │ ├── Sheets │ │ └── ErrorSheetView.swift │ ├── TabbView.swift │ ├── Tabs.swift │ └── WhatsNew │ │ ├── WhatsNewItemView.swift │ │ └── WhatsNewView.swift ├── Models&Helpers │ ├── Biometrics.swift │ ├── HapticManager.swift │ ├── Models.swift │ ├── NetworkServices.swift │ ├── SetupPrefs.swift │ ├── SoundEffectManager.swift │ └── UserPrefs.swift └── Preview Content │ ├── .DS_Store │ └── Preview Assets.xcassets │ └── Contents.json ├── NotificationService ├── Info.plist ├── NotificationService.entitlements └── NotificationService.swift ├── PrivacyInfo.xcprivacy └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/ios.yml: -------------------------------------------------------------------------------- 1 | name: Apple CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | name: Build and Test default scheme using any available iPhone simulator 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Set Default Scheme 18 | run: | 19 | scheme_list=$(xcodebuild -list -json | tr -d "\n") 20 | default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]") 21 | echo $default | cat >default 22 | echo Using default scheme: $default 23 | - name: Build 24 | env: 25 | scheme: ${{ 'default' }} 26 | platform: ${{ 'iOS Simulator' }} 27 | run: | 28 | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) 29 | device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"` 30 | if [ $scheme = default ]; then scheme=$(cat default); fi 31 | if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi 32 | file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` 33 | xcodebuild build-for-testing -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" 34 | -------------------------------------------------------------------------------- /HealthcheckIntents/HealthcheckIntents.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.ipv64.net 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /HealthcheckIntents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | IntentsRestrictedWhileLocked 10 | 11 | IntentsRestrictedWhileProtectedDataUnavailable 12 | 13 | IntentsSupported 14 | 15 | ConfigurationFourIntent 16 | ConfigurationIntent 17 | 18 | 19 | NSExtensionPointIdentifier 20 | com.apple.intents-service 21 | NSExtensionPrincipalClass 22 | $(PRODUCT_MODULE_NAME).IntentHandler 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /HealthcheckIntents/IntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntentHandler.swift 3 | // HealthcheckIntents 4 | // 5 | // Created by Sebastian Rank on 27.01.23. 6 | // 7 | 8 | import Intents 9 | import SwiftUI 10 | 11 | class IntentHandler: INExtension, ConfigurationIntentHandling { 12 | 13 | func resolveHealthcheckSymbol1(for intent: ConfigurationIntent) async -> HealthcheckSymbolResolutionResult { 14 | return .success(with: HealthcheckSymbol(identifier: "", display: "")) 15 | } 16 | 17 | 18 | func provideHealthcheckSymbol1OptionsCollection(for intent: ConfigurationIntent) async throws -> INObjectCollection { 19 | var symbols:[HealthcheckSymbol] = [] 20 | 21 | let api = NetworkServices() 22 | let hcd = DummyData.HealthcheckListCustom(customCount: 2) 23 | let hcr = await api.GetHealthchecks() ?? HealthCheckResult(domain: hcd) 24 | 25 | let shrinkedEventList = hcr.domain.sorted { $0.name > $1.name } 26 | shrinkedEventList.forEach { health in 27 | let healthStatus = HealthcheckSymbol(identifier: health.healthtoken, display: health.name) 28 | healthStatus.events = [] 29 | health.events.forEach { e in 30 | let event = EventSymbol(identifier: UUID().uuidString, display: e.status!.formatted()) 31 | healthStatus.events?.append(event) 32 | } 33 | 34 | symbols.append(healthStatus) 35 | } 36 | // Create a collection with the array of characters. 37 | let collection = INObjectCollection(items: symbols) 38 | print("Collection") 39 | // Call the completion handler, passing the collection. 40 | return collection 41 | } 42 | 43 | func resolveHealthcheckSymbol2(for intent: ConfigurationIntent) async -> HealthcheckSymbolResolutionResult { 44 | return .success(with: HealthcheckSymbol(identifier: "", display: "")) 45 | } 46 | 47 | 48 | func provideHealthcheckSymbol2OptionsCollection(for intent: ConfigurationIntent) async throws -> INObjectCollection { 49 | var symbols:[HealthcheckSymbol] = [] 50 | 51 | let api = NetworkServices() 52 | let hcd = DummyData.HealthcheckListCustom(customCount: 2) 53 | let hcr = await api.GetHealthchecks() ?? HealthCheckResult(domain: hcd) 54 | 55 | let shrinkedEventList = hcr.domain.sorted { $0.name > $1.name } 56 | shrinkedEventList.forEach { health in 57 | let healthStatus = HealthcheckSymbol(identifier: health.healthtoken, display: health.name) 58 | healthStatus.events = [] 59 | health.events.forEach { e in 60 | let event = EventSymbol(identifier: UUID().uuidString, display: e.status!.formatted()) 61 | healthStatus.events?.append(event) 62 | } 63 | 64 | symbols.append(healthStatus) 65 | } 66 | // Create a collection with the array of characters. 67 | let collection = INObjectCollection(items: symbols) 68 | print("Collection") 69 | // Call the completion handler, passing the collection. 70 | return collection 71 | } 72 | 73 | } 74 | 75 | class IntentFourHandler: INExtension, ConfigurationFourIntentHandling { 76 | 77 | func resolveHealthcheckSymbol11(for intent: ConfigurationFourIntent) async -> HealthcheckSymbolResolutionResult { 78 | return .success(with: HealthcheckSymbol(identifier: "", display: "")) 79 | } 80 | 81 | func provideHealthcheckSymbol11OptionsCollection(for intent: ConfigurationFourIntent) async throws -> INObjectCollection { 82 | var symbols:[HealthcheckSymbol] = [] 83 | 84 | let api = NetworkServices() 85 | let hcd = DummyData.HealthcheckListCustom(customCount: 4) 86 | let hcr = await api.GetHealthchecks() ?? HealthCheckResult(domain: hcd) 87 | 88 | let shrinkedEventList = hcr.domain.sorted { $0.name > $1.name } 89 | shrinkedEventList.forEach { health in 90 | let healthStatus = HealthcheckSymbol(identifier: health.healthtoken, display: health.name) 91 | healthStatus.events = [] 92 | health.events.forEach { e in 93 | let event = EventSymbol(identifier: UUID().uuidString, display: e.status!.formatted()) 94 | healthStatus.events?.append(event) 95 | } 96 | 97 | symbols.append(healthStatus) 98 | } 99 | // Create a collection with the array of characters. 100 | let collection = INObjectCollection(items: symbols) 101 | print("Collection") 102 | // Call the completion handler, passing the collection. 103 | return collection 104 | } 105 | 106 | func resolveHealthcheckSymbol12(for intent: ConfigurationFourIntent) async -> HealthcheckSymbolResolutionResult { 107 | return .success(with: HealthcheckSymbol(identifier: "", display: "")) 108 | } 109 | 110 | 111 | func provideHealthcheckSymbol12OptionsCollection(for intent: ConfigurationFourIntent) async throws -> INObjectCollection { 112 | var symbols:[HealthcheckSymbol] = [] 113 | 114 | let api = NetworkServices() 115 | let hcd = DummyData.HealthcheckListCustom(customCount: 4) 116 | let hcr = await api.GetHealthchecks() ?? HealthCheckResult(domain: hcd) 117 | 118 | let shrinkedEventList = hcr.domain.sorted { $0.name > $1.name } 119 | shrinkedEventList.forEach { health in 120 | let healthStatus = HealthcheckSymbol(identifier: health.healthtoken, display: health.name) 121 | healthStatus.events = [] 122 | health.events.forEach { e in 123 | let event = EventSymbol(identifier: UUID().uuidString, display: e.status!.formatted()) 124 | healthStatus.events?.append(event) 125 | } 126 | 127 | symbols.append(healthStatus) 128 | } 129 | // Create a collection with the array of characters. 130 | let collection = INObjectCollection(items: symbols) 131 | print("Collection") 132 | // Call the completion handler, passing the collection. 133 | return collection 134 | } 135 | 136 | func resolveHealthcheckSymbol21(for intent: ConfigurationFourIntent) async -> HealthcheckSymbolResolutionResult { 137 | return .success(with: HealthcheckSymbol(identifier: "", display: "")) 138 | } 139 | 140 | 141 | func provideHealthcheckSymbol21OptionsCollection(for intent: ConfigurationFourIntent) async throws -> INObjectCollection { 142 | var symbols:[HealthcheckSymbol] = [] 143 | 144 | let api = NetworkServices() 145 | let hcd = DummyData.HealthcheckListCustom(customCount: 4) 146 | let hcr = await api.GetHealthchecks() ?? HealthCheckResult(domain: hcd) 147 | 148 | let shrinkedEventList = hcr.domain.sorted { $0.name > $1.name } 149 | shrinkedEventList.forEach { health in 150 | let healthStatus = HealthcheckSymbol(identifier: health.healthtoken, display: health.name) 151 | healthStatus.events = [] 152 | health.events.forEach { e in 153 | let event = EventSymbol(identifier: UUID().uuidString, display: e.status!.formatted()) 154 | healthStatus.events?.append(event) 155 | } 156 | 157 | symbols.append(healthStatus) 158 | } 159 | // Create a collection with the array of characters. 160 | let collection = INObjectCollection(items: symbols) 161 | print("Collection") 162 | // Call the completion handler, passing the collection. 163 | return collection 164 | } 165 | 166 | func resolveHealthcheckSymbol22(for intent: ConfigurationFourIntent) async -> HealthcheckSymbolResolutionResult { 167 | return .success(with: HealthcheckSymbol(identifier: "", display: "")) 168 | } 169 | 170 | 171 | func provideHealthcheckSymbol22OptionsCollection(for intent: ConfigurationFourIntent) async throws -> INObjectCollection { 172 | var symbols:[HealthcheckSymbol] = [] 173 | 174 | let api = NetworkServices() 175 | let hcd = DummyData.HealthcheckListCustom(customCount: 4) 176 | let hcr = await api.GetHealthchecks() ?? HealthCheckResult(domain: hcd) 177 | 178 | let shrinkedEventList = hcr.domain.sorted { $0.name > $1.name } 179 | shrinkedEventList.forEach { health in 180 | let healthStatus = HealthcheckSymbol(identifier: health.healthtoken, display: health.name) 181 | healthStatus.events = [] 182 | health.events.forEach { e in 183 | let event = EventSymbol(identifier: UUID().uuidString, display: e.status!.formatted()) 184 | healthStatus.events?.append(event) 185 | } 186 | 187 | symbols.append(healthStatus) 188 | } 189 | // Create a collection with the array of characters. 190 | let collection = INObjectCollection(items: symbols) 191 | print("Collection") 192 | // Call the completion handler, passing the collection. 193 | return collection 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /HealthcheckWidget/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/HealthcheckWidget/.DS_Store -------------------------------------------------------------------------------- /HealthcheckWidget/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HealthcheckWidget/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HealthcheckWidget/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HealthcheckWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HealthcheckWidget/HealthcheckWidgetBundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthcheckWidgetBundle.swift 3 | // HealthcheckWidget 4 | // 5 | // Created by Sebastian Rank on 20.01.23. 6 | // 7 | 8 | import WidgetKit 9 | import SwiftUI 10 | 11 | @main 12 | struct HealthcheckWidgetBundle: WidgetBundle { 13 | var body: some Widget { 14 | HealthcheckWidget() 15 | // HealthcheckWidgetFour() 16 | HealthcheckWidgetStatic() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /HealthcheckWidget/HealthcheckWidgetLiveActivity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthcheckWidgetLiveActivity.swift 3 | // HealthcheckWidget 4 | // 5 | // Created by Sebastian Rank on 20.01.23. 6 | // 7 | 8 | import ActivityKit 9 | import WidgetKit 10 | import SwiftUI 11 | 12 | struct HealthcheckWidgetAttributes: ActivityAttributes { 13 | public struct ContentState: Codable, Hashable { 14 | // Dynamic stateful properties about your activity go here! 15 | var value: Int 16 | } 17 | 18 | // Fixed non-changing properties about your activity go here! 19 | var name: String 20 | } 21 | 22 | @available(iOSApplicationExtension 16.1, *) 23 | struct HealthcheckWidgetLiveActivity: Widget { 24 | var body: some WidgetConfiguration { 25 | ActivityConfiguration(for: HealthcheckWidgetAttributes.self) { context in 26 | // Lock screen/banner UI goes here 27 | VStack { 28 | Text("Hello") 29 | } 30 | .activityBackgroundTint(Color.cyan) 31 | .activitySystemActionForegroundColor(Color.black) 32 | 33 | } dynamicIsland: { context in 34 | DynamicIsland { 35 | // Expanded UI goes here. Compose the expanded UI through 36 | // various regions, like leading/trailing/center/bottom 37 | DynamicIslandExpandedRegion(.leading) { 38 | Text("Leading") 39 | } 40 | DynamicIslandExpandedRegion(.trailing) { 41 | Text("Trailing") 42 | } 43 | DynamicIslandExpandedRegion(.bottom) { 44 | Text("Bottom") 45 | // more content 46 | } 47 | } compactLeading: { 48 | Text("L") 49 | } compactTrailing: { 50 | Text("T") 51 | } minimal: { 52 | Text("Min") 53 | } 54 | .widgetURL(URL(string: "http://www.apple.com")) 55 | .keylineTint(Color.red) 56 | } 57 | } 58 | } 59 | 60 | @available(iOSApplicationExtension 16.2, *) 61 | struct HealthcheckWidgetLiveActivity_Previews: PreviewProvider { 62 | static let attributes = HealthcheckWidgetAttributes(name: "Me") 63 | static let contentState = HealthcheckWidgetAttributes.ContentState(value: 3) 64 | 65 | static var previews: some View { 66 | attributes 67 | .previewContext(contentState, viewKind: .dynamicIsland(.compact)) 68 | .previewDisplayName("Island Compact") 69 | attributes 70 | .previewContext(contentState, viewKind: .dynamicIsland(.expanded)) 71 | .previewDisplayName("Island Expanded") 72 | attributes 73 | .previewContext(contentState, viewKind: .dynamicIsland(.minimal)) 74 | .previewDisplayName("Minimal") 75 | attributes 76 | .previewContext(contentState, viewKind: .content) 77 | .previewDisplayName("Notification") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /HealthcheckWidget/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.widgetkit-extension 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /HealthcheckWidget/WidgetSizes/AccessoryInlineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmallSizeView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 20.01.23. 6 | // 7 | 8 | import Foundation 9 | import WidgetKit 10 | import SwiftUI 11 | 12 | 13 | @available(iOSApplicationExtension 16.0, *) 14 | struct AccessoryRectangleView : View { 15 | @Environment(\.widgetFamily) var widgetFamily 16 | 17 | var entry: Provider.Entry 18 | 19 | var body: some View { 20 | ZStack { 21 | VStack { 22 | if (entry.configuration.healthcheckSymbol1 != nil) { 23 | LazyVStack(alignment: .leading, spacing: 0) { 24 | Text(entry.configuration.healthcheckSymbol1!.displayString) 25 | .font(.system(.callout, design: .rounded)) 26 | .lineLimit(1) 27 | Spacer() 28 | HStack(spacing: 4) { 29 | let lastXPills = GetLastXMonitorPills(count: 12, events: entry.configuration.healthcheckSymbol1!.events!) 30 | ForEach(lastXPills, id:\.self) { color in 31 | RoundedRectangle(cornerRadius: 5) 32 | .fill(color) 33 | .frame(width: 7, height: 25) 34 | } 35 | } 36 | .padding(.trailing, 5) 37 | } 38 | .frame(maxWidth: .infinity, alignment: .leading) 39 | .id(UUID()) 40 | .padding(.bottom, 5) 41 | } else { 42 | Text("Keine Healthchecks konfiguriert!") 43 | .font(.system(.callout, design: .rounded)) 44 | } 45 | } 46 | .padding(0) 47 | } 48 | .privacySensitive() 49 | .frame(maxHeight: .infinity) 50 | } 51 | 52 | fileprivate func GetLastXMonitorPills(count: Int, events: [EventSymbol]) -> [Color] { 53 | var colorArr: [Color] = [] 54 | let lastEvents = events.prefix(count) 55 | print("lastEvents") 56 | lastEvents.reversed().forEach { event in 57 | colorArr.append(SetDotColor(statusId: Int(event.displayString) ?? 0)) 58 | } 59 | return colorArr 60 | } 61 | 62 | fileprivate func SetDotColor(statusId: Int) -> Color { 63 | if (statusId == StatusTypes.active.statusId) { 64 | return .white 65 | } 66 | if (statusId == StatusTypes.warning.statusId) { 67 | return .white.opacity(0.6) 68 | } 69 | if (statusId == StatusTypes.alarm.statusId) { 70 | return .white.opacity(0.3) 71 | } 72 | 73 | return .white.opacity(0.1) 74 | } 75 | } 76 | 77 | @available(iOSApplicationExtension 16.0, *) 78 | struct AccessoryRectanglePreviews: PreviewProvider { 79 | static var previews: some View { 80 | let hc = DummyData.HealthcheckListCustom(customCount: 1) 81 | AccessoryRectangleView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), healthcheck: hc)) 82 | .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) 83 | .preferredColorScheme(.light) 84 | .previewDisplayName("Light Mode") 85 | AccessoryRectangleView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), healthcheck: hc)) 86 | .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) 87 | .preferredColorScheme(.dark) 88 | .previewDisplayName("Dark Mode") 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /HealthcheckWidget/WidgetSizes/LargeSizeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LargeSizeView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 20.01.23. 6 | // 7 | 8 | import Foundation 9 | import WidgetKit 10 | import SwiftUI 11 | 12 | struct LargeSizeView : View { 13 | @Environment(\.widgetFamily) var widgetFamily 14 | 15 | var entry: ProviderStatic.Entry 16 | 17 | var body: some View { 18 | ZStack { 19 | HStack { 20 | let firstColumn = GetColumn(start: 0, end: 4) 21 | let secondColumn = GetColumn(start: 5, end: 9) 22 | VStack { 23 | ForEach(firstColumn, id: \.healthtoken) { it in 24 | LazyVStack(alignment: .leading, spacing: 0) { 25 | Text(it.name) 26 | .font(.system(.title3, design: .rounded)) 27 | .lineLimit(1) 28 | Spacer() 29 | HStack(spacing: 4) { 30 | let lastXPills = GetLastXMonitorPills(count: 12, domain: it).reversed() 31 | ForEach(lastXPills, id:\.self) { color in 32 | RoundedRectangle(cornerRadius: 5).fill(color) 33 | .frame(width: 7, height: 25) 34 | } 35 | } 36 | .padding(.trailing, 5) 37 | } 38 | .frame(maxWidth: .infinity, alignment: .leading) 39 | .id(UUID()) 40 | .padding(.bottom, 5) 41 | } 42 | if (firstColumn.count < 5) { 43 | Spacer() 44 | } 45 | } 46 | .padding(.top, firstColumn.count < 5 ? 4 : 0) 47 | VStack { 48 | ForEach(secondColumn, id: \.healthtoken) { it in 49 | LazyVStack(alignment: .leading, spacing: 0) { 50 | Text(it.name) 51 | .font(.system(.title3, design: .rounded)) 52 | .lineLimit(1) 53 | Spacer() 54 | HStack(spacing: 4) { 55 | let lastXPills = GetLastXMonitorPills(count: 12, domain: it).reversed() 56 | ForEach(lastXPills, id:\.self) { color in 57 | RoundedRectangle(cornerRadius: 5).fill(color) 58 | .frame(width: 7, height: 25) 59 | } 60 | } 61 | .padding(.trailing, 5) 62 | } 63 | .frame(maxWidth: .infinity, alignment: .leading) 64 | .id(UUID()) 65 | .padding(.bottom, 5) 66 | } 67 | if (secondColumn.count < 5) { 68 | Spacer() 69 | } 70 | } 71 | .padding(.top, secondColumn.count < 5 ? 4 : 0) 72 | } 73 | .padding() 74 | } 75 | .frame(maxHeight: .infinity) 76 | .widgetBackground(backgroundView: Color("circleBG")) 77 | } 78 | 79 | fileprivate func GetLastXMonitorPills(count: Int, domain: HealthCheck) -> [Color] { 80 | 81 | let lastEvents = domain.events.prefix(count) 82 | var colorArr: [Color] = [] 83 | 84 | lastEvents.forEach { event in 85 | colorArr.append(SetDotColor(statusId: event.status!)) 86 | } 87 | 88 | return colorArr 89 | } 90 | 91 | fileprivate func SetDotColor(statusId: Int) -> Color { 92 | if (statusId == StatusTypes.active.statusId) { 93 | return StatusTypes.active.color! 94 | } 95 | if (statusId == StatusTypes.warning.statusId) { 96 | return StatusTypes.warning.color! 97 | } 98 | if (statusId == StatusTypes.alarm.statusId) { 99 | return StatusTypes.alarm.color! 100 | } 101 | if (statusId == StatusTypes.pause.statusId) { 102 | return StatusTypes.pause.color! 103 | } 104 | 105 | return .gray 106 | } 107 | 108 | fileprivate func GetColumn(start: Int, end: Int) -> ArraySlice { 109 | if (entry.healthcheck.count < start) { 110 | return [] 111 | } else if (entry.healthcheck.count > end) { 112 | return entry.healthcheck[start...end] 113 | } else if (entry.healthcheck.count == 1) { 114 | return entry.healthcheck[start...start] 115 | } else if (entry.healthcheck.count == 0) { 116 | return [] 117 | } else if (entry.healthcheck.count < end) { 118 | return entry.healthcheck[start...entry.healthcheck.count-1] 119 | } else { 120 | return entry.healthcheck[start...end-1] 121 | } 122 | } 123 | } 124 | 125 | 126 | struct LargeSizeViewPreviews: PreviewProvider { 127 | static var previews: some View { 128 | let hc = DummyData.HealthcheckListCustom(customCount: 9) 129 | LargeSizeView(entry: SimpleEntryStatic(date: Date(), healthcheck: hc)) 130 | .previewContext(WidgetPreviewContext(family: .systemLarge)) 131 | .preferredColorScheme(.light) 132 | .previewDisplayName("Light Mode") 133 | LargeSizeView(entry: SimpleEntryStatic(date: Date(), healthcheck: hc)) 134 | .previewContext(WidgetPreviewContext(family: .systemLarge)) 135 | .preferredColorScheme(.dark) 136 | .previewDisplayName("Dark Mode") 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /HealthcheckWidget/WidgetSizes/MediumSizeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MediumSizeView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 20.01.23. 6 | // 7 | 8 | import Foundation 9 | import WidgetKit 10 | import SwiftUI 11 | 12 | struct MediumSizeView : View { 13 | @Environment(\.widgetFamily) var widgetFamily 14 | 15 | var entry: ProviderStatic.Entry 16 | 17 | /* 18 | var body: some View { 19 | ZStack { 20 | if (entry.configuration.healthcheckSymbol11 == nil || entry.configuration.healthcheckSymbol12 == nil || entry.configuration.healthcheckSymbol21 == nil || entry.configuration.healthcheckSymbol22 == nil) { 21 | VStack { 22 | Text("Keine Healthchecks konfiguriert!") 23 | .font(.system(.callout, design: .rounded)) 24 | } 25 | .padding() 26 | } else { 27 | /*HStack { 28 | let firstColumn = GetColumn(start: 0, end: 1) 29 | let secondColumn = GetColumn(start: 2, end: 3) 30 | VStack { 31 | ForEach(firstColumn, id: \.healthtoken) { it in 32 | LazyVStack(alignment: .leading, spacing: 0) { 33 | Text(it.name) 34 | .font(.system(.title3, design: .rounded)) 35 | .lineLimit(1) 36 | Spacer() 37 | HStack(spacing: 4) { 38 | let lastXPills = GetLastXMonitorPills(count: 12, domain: it).reversed() 39 | ForEach(lastXPills, id:\.self) { color in 40 | RoundedRectangle(cornerRadius: 5).fill(color) 41 | .frame(width: 7, height: 25) 42 | } 43 | } 44 | .padding(.trailing, 5) 45 | } 46 | .frame(maxWidth: .infinity, alignment: .leading) 47 | .id(UUID()) 48 | .padding(.bottom, 5) 49 | } 50 | if (firstColumn.count == 1) { 51 | Spacer() 52 | } 53 | } 54 | .padding(.top, firstColumn.count == 1 ? 3 : 0) 55 | VStack { 56 | ForEach(secondColumn, id: \.healthtoken) { it in 57 | LazyVStack(alignment: .leading, spacing: 0) { 58 | Text(it.name) 59 | .font(.system(.title3, design: .rounded)) 60 | .lineLimit(1) 61 | Spacer() 62 | HStack(spacing: 4) { 63 | let lastXPills = GetLastXMonitorPills(count: 12, domain: it).reversed() 64 | ForEach(lastXPills, id:\.self) { color in 65 | RoundedRectangle(cornerRadius: 5).fill(color) 66 | .frame(width: 7, height: 25) 67 | } 68 | } 69 | .padding(.trailing, 5) 70 | } 71 | .frame(maxWidth: .infinity, alignment: .leading) 72 | .id(UUID()) 73 | .padding(.bottom, 5) 74 | } 75 | if (secondColumn.count == 1) { 76 | Spacer() 77 | } 78 | } 79 | .padding(.top, secondColumn.count == 1 ? 0 : 0) 80 | } 81 | .padding()*/ 82 | } 83 | } 84 | .frame(maxHeight: .infinity) 85 | .widgetBackground(backgroundView: Color("circleBG")) 86 | } 87 | */ 88 | 89 | var body: some View { 90 | ZStack { 91 | HStack { 92 | let firstColumn = GetColumn(start: 0, end: 1) 93 | let secondColumn = GetColumn(start: 2, end: 3) 94 | VStack { 95 | ForEach(firstColumn, id: \.healthtoken) { it in 96 | LazyVStack(alignment: .leading, spacing: 0) { 97 | Text(it.name) 98 | .font(.system(.title3, design: .rounded)) 99 | .lineLimit(1) 100 | Spacer() 101 | HStack(spacing: 4) { 102 | let lastXPills = GetLastXMonitorPills(count: 12, domain: it).reversed() 103 | ForEach(lastXPills, id:\.self) { color in 104 | RoundedRectangle(cornerRadius: 5).fill(color) 105 | .frame(width: 7, height: 25) 106 | } 107 | } 108 | .padding(.trailing, 5) 109 | } 110 | .frame(maxWidth: .infinity, alignment: .leading) 111 | .id(UUID()) 112 | .padding(.bottom, 5) 113 | } 114 | if (firstColumn.count == 1) { 115 | Spacer() 116 | } 117 | } 118 | .padding(.top, firstColumn.count == 1 ? 3 : 0) 119 | VStack { 120 | ForEach(secondColumn, id: \.healthtoken) { it in 121 | LazyVStack(alignment: .leading, spacing: 0) { 122 | Text(it.name) 123 | .font(.system(.title3, design: .rounded)) 124 | .lineLimit(1) 125 | Spacer() 126 | HStack(spacing: 4) { 127 | let lastXPills = GetLastXMonitorPills(count: 12, domain: it).reversed() 128 | ForEach(lastXPills, id:\.self) { color in 129 | RoundedRectangle(cornerRadius: 5).fill(color) 130 | .frame(width: 7, height: 25) 131 | } 132 | } 133 | .padding(.trailing, 5) 134 | } 135 | .frame(maxWidth: .infinity, alignment: .leading) 136 | .id(UUID()) 137 | .padding(.bottom, 5) 138 | } 139 | if (secondColumn.count == 1) { 140 | Spacer() 141 | } 142 | } 143 | .padding(.top, secondColumn.count == 1 ? 0 : 0) 144 | } 145 | .padding() 146 | } 147 | .frame(maxHeight: .infinity) 148 | .widgetBackground(backgroundView: Color("circleBG")) 149 | } 150 | 151 | fileprivate func GetLastXMonitorPills(count: Int, domain: HealthCheck) -> [Color] { 152 | 153 | let lastEvents = domain.events.prefix(count) 154 | var colorArr: [Color] = [] 155 | 156 | lastEvents.forEach { event in 157 | colorArr.append(SetDotColor(statusId: event.status!)) 158 | } 159 | 160 | return colorArr 161 | } 162 | 163 | fileprivate func SetDotColor(statusId: Int) -> Color { 164 | if (statusId == StatusTypes.active.statusId) { 165 | return StatusTypes.active.color! 166 | } 167 | if (statusId == StatusTypes.warning.statusId) { 168 | return StatusTypes.warning.color! 169 | } 170 | if (statusId == StatusTypes.alarm.statusId) { 171 | return StatusTypes.alarm.color! 172 | } 173 | if (statusId == StatusTypes.pause.statusId) { 174 | return StatusTypes.pause.color! 175 | } 176 | 177 | return .gray 178 | } 179 | 180 | fileprivate func GetColumn(start: Int, end: Int) -> ArraySlice { 181 | if (entry.healthcheck.count < start) { 182 | return [] 183 | } else if (entry.healthcheck.count > end) { 184 | return entry.healthcheck[start...end] 185 | } else if (entry.healthcheck.count == 1) { 186 | return entry.healthcheck[start...start] 187 | } else if (entry.healthcheck.count == 0) { 188 | return [] 189 | } else { 190 | return entry.healthcheck[start...end-1] 191 | } 192 | } 193 | } 194 | 195 | struct MediumSizeViewPreviews: PreviewProvider { 196 | static var previews: some View { 197 | let hc = DummyData.HealthcheckListCustom(customCount: 2) 198 | MediumSizeView(entry: SimpleEntryStatic(date: Date(), healthcheck: hc)) 199 | .previewContext(WidgetPreviewContext(family: .systemMedium)) 200 | .preferredColorScheme(.light) 201 | .previewDisplayName("Light Mode") 202 | MediumSizeView(entry: SimpleEntryStatic(date: Date(), healthcheck: hc)) 203 | .previewContext(WidgetPreviewContext(family: .systemMedium)) 204 | .preferredColorScheme(.dark) 205 | .previewDisplayName("Dark Mode") 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /HealthcheckWidget/WidgetSizes/SmallSizeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmallSizeView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 20.01.23. 6 | // 7 | 8 | import Foundation 9 | import WidgetKit 10 | import SwiftUI 11 | 12 | struct SmallSizeView : View { 13 | @Environment(\.widgetFamily) var widgetFamily 14 | 15 | var entry: Provider.Entry 16 | 17 | var body: some View { 18 | ZStack { 19 | VStack { 20 | if (entry.configuration.healthcheckSymbol1 != nil || entry.configuration.healthcheckSymbol2 != nil) { 21 | if (entry.configuration.healthcheckSymbol1 != nil) { 22 | LazyVStack(alignment: .leading, spacing: 0) { 23 | Text(entry.configuration.healthcheckSymbol1!.displayString) 24 | .font(.system(.title3, design: .rounded)) 25 | .lineLimit(1) 26 | Spacer() 27 | HStack(spacing: 4) { 28 | let lastXPills = GetLastXMonitorPills(count: 12, events: entry.configuration.healthcheckSymbol1!.events!) 29 | ForEach(lastXPills, id:\.self) { color in 30 | RoundedRectangle(cornerRadius: 5).fill(color) 31 | .frame(width: 7, height: 25) 32 | } 33 | } 34 | .padding(.trailing, 5) 35 | } 36 | .frame(maxWidth: .infinity, alignment: .leading) 37 | .id(UUID()) 38 | .padding(.bottom, 5) 39 | } 40 | if (entry.configuration.healthcheckSymbol2 != nil) { 41 | LazyVStack(alignment: .leading, spacing: 0) { 42 | Text(entry.configuration.healthcheckSymbol2!.displayString) 43 | .font(.system(.title3, design: .rounded)) 44 | .lineLimit(1) 45 | Spacer() 46 | HStack(spacing: 4) { 47 | let lastXPills = GetLastXMonitorPills(count: 12, events: entry.configuration.healthcheckSymbol2!.events!) 48 | ForEach(lastXPills, id:\.self) { color in 49 | RoundedRectangle(cornerRadius: 5).fill(color) 50 | .frame(width: 7, height: 25) 51 | } 52 | } 53 | .padding(.trailing, 5) 54 | } 55 | .frame(maxWidth: .infinity, alignment: .leading) 56 | .id(UUID()) 57 | .padding(.bottom, 5) 58 | } 59 | } else { 60 | Text("Keine Healthchecks konfiguriert!") 61 | .font(.system(.callout, design: .rounded)) 62 | } 63 | } 64 | .padding() 65 | } 66 | .frame(maxHeight: .infinity) 67 | .widgetBackground(backgroundView: Color("circleBG")) 68 | } 69 | 70 | fileprivate func GetLastXMonitorPills(count: Int, events: [EventSymbol]) -> [Color] { 71 | var colorArr: [Color] = [] 72 | let lastEvents = events.prefix(count) 73 | print("lastEvents") 74 | lastEvents.reversed().forEach { event in 75 | colorArr.append(SetDotColor(statusId: Int(event.displayString) ?? 0)) 76 | } 77 | return colorArr 78 | } 79 | 80 | fileprivate func SetDotColor(statusId: Int) -> Color { 81 | if (statusId == StatusTypes.active.statusId) { 82 | return StatusTypes.active.color! 83 | } 84 | if (statusId == StatusTypes.warning.statusId) { 85 | return StatusTypes.warning.color! 86 | } 87 | if (statusId == StatusTypes.alarm.statusId) { 88 | return StatusTypes.alarm.color! 89 | } 90 | if (statusId == StatusTypes.pause.statusId) { 91 | return StatusTypes.pause.color! 92 | } 93 | 94 | return .gray 95 | } 96 | } 97 | 98 | struct SmallSizeViewPreviews: PreviewProvider { 99 | static var previews: some View { 100 | let hc = DummyData.HealthcheckListCustom(customCount: 2) 101 | SmallSizeView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), healthcheck: hc)) 102 | .previewContext(WidgetPreviewContext(family: .systemSmall)) 103 | .preferredColorScheme(.light) 104 | .previewDisplayName("Light Mode") 105 | SmallSizeView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), healthcheck: hc)) 106 | .previewContext(WidgetPreviewContext(family: .systemSmall)) 107 | .preferredColorScheme(.dark) 108 | .previewDisplayName("Dark Mode") 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /HealthcheckWidgetExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.ipv64.net 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /IPv64.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /IPv64.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /IPv64.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "a0d41731ecb092e76a955293f45cb6cc0ed875a448ecf713132d85607ba7596e", 3 | "pins" : [ 4 | { 5 | "identity" : "abseil-cpp-binary", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/google/abseil-cpp-binary.git", 8 | "state" : { 9 | "revision" : "748c7837511d0e6a507737353af268484e1745e2", 10 | "version" : "1.2024011601.1" 11 | } 12 | }, 13 | { 14 | "identity" : "app-check", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/google/app-check.git", 17 | "state" : { 18 | "revision" : "7d2688de038d5484866d835acb47b379722d610e", 19 | "version" : "10.19.0" 20 | } 21 | }, 22 | { 23 | "identity" : "firebase-ios-sdk", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/firebase/firebase-ios-sdk", 26 | "state" : { 27 | "branch" : "main", 28 | "revision" : "c9d013b624a3415192d6644896ecd1856cbbc9e2" 29 | } 30 | }, 31 | { 32 | "identity" : "googleappmeasurement", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/google/GoogleAppMeasurement.git", 35 | "state" : { 36 | "revision" : "51ba746a9d51a4bd0774b68499b0c73ef6e8570d", 37 | "version" : "10.24.0" 38 | } 39 | }, 40 | { 41 | "identity" : "googledatatransport", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/google/GoogleDataTransport.git", 44 | "state" : { 45 | "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565", 46 | "version" : "9.4.0" 47 | } 48 | }, 49 | { 50 | "identity" : "googleutilities", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/google/GoogleUtilities.git", 53 | "state" : { 54 | "revision" : "26c898aed8bed13b8a63057ee26500abbbcb8d55", 55 | "version" : "7.13.1" 56 | } 57 | }, 58 | { 59 | "identity" : "grpc-binary", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/google/grpc-binary.git", 62 | "state" : { 63 | "revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359", 64 | "version" : "1.62.2" 65 | } 66 | }, 67 | { 68 | "identity" : "gtm-session-fetcher", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/google/gtm-session-fetcher.git", 71 | "state" : { 72 | "revision" : "96d7cc73a71ce950723aa3c50ce4fb275ae180b8", 73 | "version" : "3.1.0" 74 | } 75 | }, 76 | { 77 | "identity" : "interop-ios-for-google-sdks", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/google/interop-ios-for-google-sdks.git", 80 | "state" : { 81 | "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", 82 | "version" : "100.0.0" 83 | } 84 | }, 85 | { 86 | "identity" : "leveldb", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/firebase/leveldb.git", 89 | "state" : { 90 | "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", 91 | "version" : "1.22.2" 92 | } 93 | }, 94 | { 95 | "identity" : "nanopb", 96 | "kind" : "remoteSourceControl", 97 | "location" : "https://github.com/firebase/nanopb.git", 98 | "state" : { 99 | "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", 100 | "version" : "2.30910.0" 101 | } 102 | }, 103 | { 104 | "identity" : "promises", 105 | "kind" : "remoteSourceControl", 106 | "location" : "https://github.com/google/promises.git", 107 | "state" : { 108 | "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", 109 | "version" : "2.4.0" 110 | } 111 | }, 112 | { 113 | "identity" : "swift-protobuf", 114 | "kind" : "remoteSourceControl", 115 | "location" : "https://github.com/apple/swift-protobuf.git", 116 | "state" : { 117 | "revision" : "ab3a58b7209a17d781c0d1dbb3e1ff3da306bae8", 118 | "version" : "1.20.3" 119 | } 120 | }, 121 | { 122 | "identity" : "swiftui-introspect", 123 | "kind" : "remoteSourceControl", 124 | "location" : "https://github.com/siteline/SwiftUI-Introspect.git", 125 | "state" : { 126 | "revision" : "121c146fe591b1320238d054ae35c81ffa45f45a", 127 | "version" : "0.12.0" 128 | } 129 | }, 130 | { 131 | "identity" : "toast-swift", 132 | "kind" : "remoteSourceControl", 133 | "location" : "https://github.com/BastiaanJansen/toast-swift", 134 | "state" : { 135 | "revision" : "bc52aa61d6a372da553d1d59bdd4e56037ae2f14", 136 | "version" : "1.5.0" 137 | } 138 | } 139 | ], 140 | "version" : 3 141 | } 142 | -------------------------------------------------------------------------------- /IPv64.xcodeproj/project.xcworkspace/xcuserdata/sebastianrank.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /IPv64.xcodeproj/project.xcworkspace/xcuserdata/sebastianrank.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64.xcodeproj/project.xcworkspace/xcuserdata/sebastianrank.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /IPv64.xcodeproj/xcshareddata/xcschemes/HealthcheckWidgetExtension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 58 | 60 | 66 | 67 | 68 | 69 | 73 | 74 | 78 | 79 | 83 | 84 | 85 | 88 | 89 | 90 | 98 | 100 | 106 | 107 | 108 | 109 | 111 | 112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /IPv64.xcodeproj/xcshareddata/xcschemes/IPv64.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /IPv64.xcodeproj/xcuserdata/sebastianrank.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 36 | 37 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /IPv64.xcodeproj/xcuserdata/sebastianrank.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | HealthcheckIntents.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | HealthcheckWidgetExtension.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | IPv64.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 0 21 | 22 | NotificationService.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 3 26 | 27 | Promises (Playground) 1.xcscheme 28 | 29 | isShown 30 | 31 | orderHint 32 | 5 33 | 34 | Promises (Playground) 2.xcscheme 35 | 36 | isShown 37 | 38 | orderHint 39 | 6 40 | 41 | Promises (Playground) 3.xcscheme 42 | 43 | isShown 44 | 45 | orderHint 46 | 7 47 | 48 | Promises (Playground) 4.xcscheme 49 | 50 | isShown 51 | 52 | orderHint 53 | 8 54 | 55 | Promises (Playground) 5.xcscheme 56 | 57 | isShown 58 | 59 | orderHint 60 | 9 61 | 62 | Promises (Playground).xcscheme 63 | 64 | isShown 65 | 66 | orderHint 67 | 4 68 | 69 | 70 | SuppressBuildableAutocreation 71 | 72 | 9AACEB87291851E1009B506D 73 | 74 | primary 75 | 76 | 77 | 9ABB37CB297B26A600FC9B74 78 | 79 | primary 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /IPv64/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/.DS_Store -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.000", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/AccountSelectionBG.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.900", 9 | "green" : "0.975", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.004", 27 | "green" : "0.116", 28 | "red" : "0.153" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/AppIcon 1.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Assets.xcassets/AppIcon 1.appiconset/1024.png -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/AppIcon 1.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "appstore.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/AppIcon.appiconset/appstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Assets.xcassets/AppIcon.appiconset/appstore.png -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/discord_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-discord.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/discord_icon.imageset/icons8-discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/gotify_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "gotify-logo-notification.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/matrix_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Matrix_icon.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/matrix_icon.imageset/Matrix_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Matrix (protocol) logo 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/ntfy_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ntfy-svgrepo-com.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/ntfy_icon.imageset/ntfy-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/pushover_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pushover-logo.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/pushover_icon.imageset/pushover-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/telegram_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-telegram.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/telegram_icon.imageset/icons8-telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/webhook_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-webhook.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/Integration Icons/webhook_icon.imageset/icons8-webhook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/SectionBG.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "1.000", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.161", 27 | "green" : "0.153", 28 | "red" : "0.153" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/accountSinceColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.644", 9 | "green" : "0.644", 10 | "red" : "0.644" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.353", 27 | "green" : "0.353", 28 | "red" : "0.353" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/circleBG.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.957", 9 | "green" : "0.957", 10 | "red" : "0.957" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "extended-gray", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "white" : "0.133" 27 | } 28 | }, 29 | "idiom" : "universal" 30 | } 31 | ], 32 | "info" : { 33 | "author" : "xcode", 34 | "version" : 1 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/ip64_color.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.027", 9 | "green" : "0.757", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/ipv64_logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ipv64_logo.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/ipv64_logo.imageset/ipv64_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Assets.xcassets/ipv64_logo.imageset/ipv64_logo.png -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/ipv64_logo_new.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ipv64_logo.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/ipv64_logo_new.imageset/ipv64_logo.svg: -------------------------------------------------------------------------------- 1 | svg -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/lightgray.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "gray-gamma-22", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "white" : "0.960" 9 | } 10 | }, 11 | "idiom" : "universal" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/primaryText.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.000", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /IPv64/Assets.xcassets/textFieldBG.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "extended-gray", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "white" : "0.920" 9 | } 10 | }, 11 | "idiom" : "universal" 12 | }, 13 | { 14 | "appearances" : [ 15 | { 16 | "appearance" : "luminosity", 17 | "value" : "dark" 18 | } 19 | ], 20 | "color" : { 21 | "color-space" : "extended-gray", 22 | "components" : { 23 | "alpha" : "1.000", 24 | "white" : "0.133" 25 | } 26 | }, 27 | "idiom" : "universal" 28 | } 29 | ], 30 | "info" : { 31 | "author" : "xcode", 32 | "version" : 1 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /IPv64/Env/Sounds/tabSelection.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Env/Sounds/tabSelection.wav -------------------------------------------------------------------------------- /IPv64/Env/Sounds/tabSelection_old.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Env/Sounds/tabSelection_old.wav -------------------------------------------------------------------------------- /IPv64/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 245002644962-rrkudf4qu75umfho26b7au5b9i0jl6il.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.245002644962-rrkudf4qu75umfho26b7au5b9i0jl6il 9 | API_KEY 10 | AIzaSyBSnFeP9RGUWYK6_oSWclwz-5Wi4Sk1JJg 11 | GCM_SENDER_ID 12 | 245002644962 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | de.rpicloud.IPv64 17 | PROJECT_ID 18 | ipv64-b6e12 19 | STORAGE_BUCKET 20 | ipv64-b6e12.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:245002644962:ios:7d77b9a29458c99f451dbb 33 | 34 | -------------------------------------------------------------------------------- /IPv64/HelperViews/SpinnerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpinnerView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 06.11.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Spinner: UIViewRepresentable { 11 | let isAnimating: Bool 12 | let style: UIActivityIndicatorView.Style 13 | let color: UIColor 14 | 15 | func makeUIView(context: UIViewRepresentableContext) -> UIActivityIndicatorView { 16 | let spinner = UIActivityIndicatorView(style: style) 17 | spinner.hidesWhenStopped = true 18 | spinner.color = color 19 | return spinner 20 | } 21 | 22 | func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext) { 23 | isAnimating ? uiView.startAnimating() : uiView.stopAnimating() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IPv64/IPv64.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.security.application-groups 8 | 9 | group.ipv64.net 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /IPv64/IPv64.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | IPv64.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /IPv64/IPv64.xcdatamodeld/IPv64.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /IPv64/IPv64App.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IPv64App.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 06.11.22. 6 | // 7 | 8 | import SwiftUI 9 | import Firebase 10 | import UserNotifications 11 | import FirebaseMessaging 12 | 13 | 14 | class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate { 15 | let gcmMessageIDKey = "gcm.message_id" 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | FirebaseApp.configure() 19 | // [START set_messaging_delegate] 20 | Messaging.messaging().delegate = self 21 | // [END set_messaging_delegate] 22 | // Register for remote notifications. This shows a permission dialog on first run, to 23 | // show the dialog at a more appropriate time move this registration accordingly. 24 | // [START register_for_notifications] 25 | if #available(iOS 10.0, *) { 26 | // For iOS 10 display notification (sent via APNS) 27 | UNUserNotificationCenter.current().delegate = self 28 | let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] 29 | UNUserNotificationCenter.current().requestAuthorization( 30 | options: authOptions, 31 | completionHandler: {_, _ in }) 32 | } else { 33 | let settings: UIUserNotificationSettings = 34 | UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) 35 | application.registerUserNotificationSettings(settings) 36 | } 37 | 38 | application.registerForRemoteNotifications() 39 | 40 | // [END register_for_notifications] 41 | return true 42 | } 43 | 44 | // MARK: UISceneSession Lifecycle 45 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 46 | // Called when a new scene session is being created. 47 | // Use this method to select a configuration to create the new scene with. 48 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 49 | } 50 | 51 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 52 | // Called when the user discards a scene session. 53 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 54 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 55 | } 56 | 57 | // [START receive_message] 58 | func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { 59 | // If you are receiving a notification message while your app is in the background, 60 | // this callback will not be fired till the user taps on the notification launching the application. 61 | // TODO: Handle data of notification 62 | // With swizzling disabled you must let Messaging know about the message, for Analytics 63 | // Messaging.messaging().appDidReceiveMessage(userInfo) 64 | // Print message ID. 65 | if let messageID = userInfo[gcmMessageIDKey] { 66 | print("Message ID: \(messageID)") 67 | } 68 | 69 | // Print full message. 70 | print(userInfo) 71 | //print(userInfo["gcm.notification.fcm_options"]) 72 | } 73 | 74 | func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], 75 | fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { 76 | // If you are receiving a notification message while your app is in the background, 77 | // this callback will not be fired till the user taps on the notification launching the application. 78 | // TODO: Handle data of notification 79 | // With swizzling disabled you must let Messaging know about the message, for Analytics 80 | // Messaging.messaging().appDidReceiveMessage(userInfo) 81 | // Print message ID. 82 | if let messageID = userInfo[gcmMessageIDKey] { 83 | print("Message ID: \(messageID)") 84 | } 85 | 86 | // Print full message. 87 | print(userInfo) 88 | 89 | completionHandler(UIBackgroundFetchResult.newData) 90 | } 91 | // [END receive_message] 92 | func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 93 | print("Unable to register for remote notifications: \(error.localizedDescription)") 94 | } 95 | 96 | // This function is added here only for debugging purposes, and can be removed if swizzling is enabled. 97 | // If swizzling is disabled then this function must be implemented so that the APNs token can be paired to 98 | // the FCM registration token. 99 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 100 | print("APNs token retrieved: \(deviceToken)") 101 | 102 | // With swizzling disabled you must set the APNs token here. 103 | Messaging.messaging().apnsToken = deviceToken 104 | } 105 | } 106 | 107 | 108 | @main 109 | struct IPv64App: App { 110 | @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate 111 | @AppStorage("BIOMETRIC_ENABLED") var isBiometricEnabled: Bool = false 112 | @State var isBio = false 113 | 114 | init() { 115 | var titleFont = UIFont.preferredFont(forTextStyle: .largeTitle) /// the default large title font 116 | titleFont = UIFont( 117 | descriptor: 118 | titleFont.fontDescriptor 119 | .withDesign(.rounded)? /// make rounded 120 | .withSymbolicTraits(.traitBold) /// make bold 121 | ?? 122 | titleFont.fontDescriptor, /// return the normal title if customization failed 123 | size: titleFont.pointSize 124 | ) 125 | 126 | var smallTitleFont = UIFont.preferredFont(forTextStyle: .body) /// the default large title font 127 | smallTitleFont = UIFont( 128 | descriptor: 129 | smallTitleFont.fontDescriptor 130 | .withDesign(.rounded)? /// make rounded 131 | .withSymbolicTraits(.traitBold) /// make bold 132 | ?? 133 | smallTitleFont.fontDescriptor, /// return the normal title if customization failed 134 | size: smallTitleFont.pointSize 135 | ) 136 | 137 | /// set the rounded font 138 | UINavigationBar.appearance().largeTitleTextAttributes = [.font: titleFont] 139 | UINavigationBar.appearance().titleTextAttributes = [.font : smallTitleFont] 140 | 141 | var lastBuildNumber = SetupPrefs.readPreference(mKey: "LASTBUILDNUMBER", mDefaultValue: "0") as! String 142 | var token = SetupPrefs.readPreference(mKey: "APIKEY", mDefaultValue: "") as! String 143 | let lastBuildNumberStandart = SetupPrefs.readPreferenceStandard(mKey: "LASTBUILDNUMBER", mDefaultValue: "0") as! String 144 | let tokenStandart = SetupPrefs.readPreferenceStandard(mKey: "APIKEY", mDefaultValue: "") as! String 145 | 146 | if (token.isEmpty) { 147 | token = tokenStandart 148 | SetupPrefs.setPreference(mKey: "APIKEY", mValue: token) 149 | } 150 | if (lastBuildNumber.isEmpty || lastBuildNumber == "0") { 151 | lastBuildNumber = lastBuildNumberStandart 152 | SetupPrefs.setPreference(mKey: "LASTBUILDNUMBER", mValue: lastBuildNumber) 153 | } 154 | } 155 | 156 | var body: some Scene { 157 | WindowGroup { 158 | let apikey = SetupPrefs.readPreference(mKey: "APIKEY", mDefaultValue: "") as! String 159 | 160 | if (apikey.count == 0) { 161 | LoginView() 162 | } else { 163 | if isBiometricEnabled { 164 | LockView() 165 | } else { 166 | TabbView(showDomains: .constant(true)) 167 | } 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /IPv64/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleURLTypes 6 | 7 | 8 | CFBundleTypeRole 9 | Editor 10 | CFBundleURLName 11 | openWidget 12 | CFBundleURLSchemes 13 | 14 | ipv64 15 | 16 | 17 | 18 | NSUserActivityTypes 19 | 20 | ConfigurationFourIntent 21 | ConfigurationIntent 22 | 23 | UIBackgroundModes 24 | 25 | fetch 26 | remote-notification 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /IPv64/Main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Main/.DS_Store -------------------------------------------------------------------------------- /IPv64/Main/Account/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Main/Account/.DS_Store -------------------------------------------------------------------------------- /IPv64/Main/Account/AccountListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccountListView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 28.09.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AccountListView: View { 11 | @AppStorage("AccountList") var accountListJson: String = "" 12 | @AppStorage("DomainResult") var listOfDomainsString: String = "" 13 | @AppStorage("IntegrationList") var integrationListS: String = "" 14 | @AppStorage("HealthcheckList") var healthCheckList: String = "" 15 | 16 | @Environment(\.presentationMode) var presentationMode 17 | 18 | @Binding var isBottomSheetVisible: Bool 19 | @Binding var accountList: [Account] 20 | @Binding var newAccountB: Bool 21 | @State var activeSheet: ActiveSheet? = nil 22 | @State var errorTyp: ErrorTyp? = .none 23 | @State private var selectedAccount: Account? = nil 24 | @State private var deleteAccount: Bool = false 25 | 26 | fileprivate func deleteAccountDialog() { 27 | errorTyp = ErrorTypes.deleteAccount 28 | activeSheet = .error 29 | print(errorTyp) 30 | } 31 | 32 | var body: some View { 33 | VStack { 34 | Form { 35 | Section("Deine Accounts") { 36 | ForEach(accountList, id: \.ApiKey) { acc in 37 | let dateDate = dateDBFormatter.date(from: acc.Since ?? "0001-01-01 00:00:00") 38 | let dateString = itemFormatter.string(from: dateDate ?? Date()) 39 | Button(action: { 40 | let beforeAccInt = accountList.firstIndex { $0.Active == true } 41 | if (beforeAccInt! > -1) { 42 | accountList[beforeAccInt!].Active = false 43 | } 44 | let currentAccInd = accountList.firstIndex { $0.ApiKey == acc.ApiKey } 45 | if (currentAccInd! > -1) { 46 | accountList[currentAccInd!].Active = true 47 | SetupPrefs.setPreference(mKey: "APIKEY", mValue: accountList[currentAccInd!].ApiKey) 48 | } 49 | do { 50 | let jsonEncoder = JSONEncoder() 51 | let jsonData = try jsonEncoder.encode(accountList) 52 | let json = String(data: jsonData, encoding: String.Encoding.utf8) 53 | accountListJson = json! 54 | } catch let error { 55 | print(error) 56 | } 57 | listOfDomainsString = "" 58 | integrationListS = "" 59 | healthCheckList = "" 60 | presentationMode.wrappedValue.dismiss() 61 | }) { 62 | HStack { 63 | Image(systemName: acc.Active! ? "checkmark.circle.fill" : "circle") 64 | .resizable() 65 | .scaledToFit() 66 | .foregroundStyle(Color("ip64_color")) 67 | .frame(width: 22, height: 22) 68 | .padding(.trailing) 69 | VStack(alignment: .leading) { 70 | Text(acc.AccountName!) 71 | .foregroundStyle(Color("primaryText")) 72 | Text(dateString) 73 | .font(.system(.subheadline, design: .rounded)) 74 | .foregroundColor(Color("accountSinceColor")) 75 | } 76 | 77 | } 78 | } 79 | .listRowBackground(acc.Active! ? Color("AccountSelectionBG") : Color("SectionBG")) 80 | .swipeActions(edge: .trailing) { 81 | if (!acc.Active!) { 82 | Button(role: .destructive, action: { 83 | print("delete") 84 | selectedAccount = acc 85 | deleteAccountDialog() 86 | }) { 87 | Label("Löschen", systemImage: "trash") 88 | } 89 | .tint(.red) 90 | } 91 | } 92 | } 93 | } 94 | Button(action: { 95 | withAnimation { 96 | newAccountB = true 97 | isBottomSheetVisible = false 98 | // presentationMode.wrappedValue.dismiss() 99 | } 100 | }) { 101 | HStack { 102 | Image(systemName: "plus.circle") 103 | .resizable() 104 | .scaledToFit() 105 | .foregroundStyle(Color("ip64_color")) 106 | .frame(width: 32, height: 32) 107 | .padding(.trailing) 108 | VStack(alignment: .leading) { 109 | Text("Neuen Account hinzufügen") 110 | .foregroundStyle(Color("primaryText")) 111 | } 112 | 113 | } 114 | .padding(.vertical, 10) 115 | } 116 | } 117 | .scrollIndicators(.hidden) 118 | .sheet(item: $activeSheet) { item in 119 | showActiveSheet(item: item) 120 | } 121 | } 122 | } 123 | 124 | @ViewBuilder 125 | func showActiveSheet(item: ActiveSheet?) -> some View { 126 | switch item { 127 | case .error: 128 | ErrorSheetView(errorTyp: $errorTyp, deleteThisDomain: $deleteAccount) 129 | .interactiveDismissDisabled(errorTyp?.status == 202 ? false : true) 130 | .onDisappear { 131 | do { 132 | let jsonDecoder = JSONDecoder() 133 | let jsonData = accountListJson.data(using: .utf8) 134 | var accountList = try jsonDecoder.decode([Account].self, from: jsonData!) 135 | 136 | if (deleteAccount) { 137 | print(selectedAccount) 138 | let indSel = accountList.firstIndex { $0.ApiKey == selectedAccount?.ApiKey } 139 | print(indSel) 140 | if indSel! > -1 { 141 | accountList.remove(at: indSel!) 142 | } 143 | self.accountList = accountList 144 | 145 | let jsonEncoder = JSONEncoder() 146 | let jsonData = try jsonEncoder.encode(accountList) 147 | let json = String(data: jsonData, encoding: String.Encoding.utf8) 148 | accountListJson = json! 149 | } 150 | } catch let error { 151 | print(error) 152 | } 153 | } 154 | default: 155 | EmptyView() 156 | } 157 | } 158 | 159 | private let itemFormatter: DateFormatter = { 160 | let formatter = DateFormatter() 161 | formatter.dateStyle = .medium 162 | formatter.timeStyle = .none 163 | formatter.locale = Locale(identifier: "de_DE") 164 | return formatter 165 | }() 166 | 167 | private let dateDBFormatter: DateFormatter = { 168 | let formatter = DateFormatter() 169 | formatter.locale = Locale(identifier: "de_DE") 170 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 171 | return formatter 172 | }() 173 | } 174 | 175 | #Preview { 176 | AccountListView(isBottomSheetVisible: .constant(false), accountList: .constant([]), newAccountB: .constant(false)) 177 | } 178 | -------------------------------------------------------------------------------- /IPv64/Main/Account/HelpView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelpView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 07.11.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HelpView: View { 11 | @Environment(\.openURL) var openURL 12 | 13 | @ObservedObject var api: NetworkServices = NetworkServices() 14 | 15 | var body: some View { 16 | VStack { 17 | Form() { 18 | Section("Hilfe") { 19 | HStack(alignment: .center) { 20 | Image(systemName: "circle.fill") 21 | .resizable() 22 | .scaledToFill() 23 | .foregroundColor(.red) 24 | .frame(width: 8, height: 8) 25 | Text("A-Record stimmt nicht überein!") 26 | } 27 | .frame(alignment: .center) 28 | HStack(alignment: .center) { 29 | Image(systemName: "circle.fill") 30 | .resizable() 31 | .scaledToFill() 32 | .foregroundColor(.green) 33 | .frame(width: 8, height: 8) 34 | Text("A-Record stimmt überein!") 35 | } 36 | .frame(alignment: .center) 37 | } 38 | Section("Was ist IPv64.net?") { 39 | Text("**IPv64** ist natürlich kein neues Internet-Protokoll (64), sondern einfach eine deduplizierte Kurzform von IPv6 und IPv4. Auf der Seite von IPv64 findest du einen **Dynamischen DNS** Dienst (DynDNS) und viele weitere nützliche Tools für dein tägliches Interneterlebnis. \n\nMit dem **dynamischen DNS Dienst** von IPv64 kannst du dir kostenfreie Subdomains registrieren und nutzen. Das Update der Domain übernimmt voll automatisch dein eigener Router oder alternative Hardware / Software. \n\nÜber den Youtube Kanal RaspberryPi Cloud wirst du ganz sicher noch viel mehr über die Welt der IT kennenlernen dürfen.") 40 | Button(action: { 41 | openURL(URL(string: "https://www.youtube.com/c/RaspberryPiCloud")!) 42 | }) { 43 | HStack { 44 | Image(systemName: "play.rectangle.fill") 45 | .symbolRenderingMode(.hierarchical) 46 | .foregroundColor(.red) 47 | Text("YouTube") 48 | .foregroundColor(.red) 49 | } 50 | } 51 | } 52 | Section("Kontakt") { 53 | Text("Ein Produkt der Prox IT UG (haftungsbeschränkt). \n\n**Angaben gemäß § 5 TMG**\nProx IT UG (haftungsbeschränkt)\nAm Eisenstein 10\n45470 Mülheim an der Ruhr\n\n**Vertreten durch**\nDennis Schröder (Geschäftsführer)\n\nRegistergericht: Amtsgericht Duisburg\nRegisternummer: HRB 35106") 54 | Button(action: { 55 | openURL(URL(string: "mailto:info@ipv64.net")!) 56 | }) { 57 | HStack { 58 | Image(systemName: "envelope.fill") 59 | .symbolRenderingMode(.hierarchical) 60 | .foregroundColor(Color("primaryText")) 61 | Text("info@ipv64.net") 62 | .tint(Color("primaryText")) 63 | } 64 | } 65 | .foregroundColor(Color("primaryText")) 66 | } 67 | Section("Über diese App ") { 68 | Text("Diese App ist mithilfe der Community von RaspberryPi Cloud entstanden. \nAlle Rechte vorbehalten, bei Dennis Schröder.") 69 | HStack { 70 | Text("Version") 71 | Spacer() 72 | Text(Bundle.main.versionNumber) 73 | .foregroundColor(.gray) 74 | } 75 | } 76 | } 77 | .navigationTitle("Über") 78 | } 79 | } 80 | } 81 | 82 | struct HelpView_Previews: PreviewProvider { 83 | static var previews: some View { 84 | HelpView() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /IPv64/Main/Account/IPView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IPView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 25.11.22. 6 | // 7 | 8 | import SwiftUI 9 | import Toast 10 | 11 | struct IPView: View { 12 | 13 | @ObservedObject var api: NetworkServices = NetworkServices() 14 | @State var myIP: MyIP = MyIP(ip: "") 15 | @State var myIPV6: MyIP = MyIP(ip: "") 16 | 17 | var body: some View { 18 | 19 | Form { 20 | Section("IPv4") { 21 | if (myIP.ip?.count == 0) { 22 | Spinner(isAnimating: true, style: .medium, color: .white) 23 | } else { 24 | Text(myIP.ip!) 25 | .lineLimit(1) 26 | .minimumScaleFactor(0.01) 27 | .swipeActions(edge: .trailing) { 28 | Button(role: .none, action: { 29 | let toast = Toast.default( 30 | image: GetUIImage(imageName: "doc.on.doc", color: UIColor.systemBlue, hierarichal: true), 31 | title: "IPv4 kopiert!", config: .init( 32 | direction: .top, 33 | autoHide: true, 34 | enablePanToClose: false, 35 | displayTime: 4, 36 | enteringAnimation: .fade(alphaValue: 0.5), 37 | exitingAnimation: .slide(x: 0, y: -100)) 38 | ) 39 | toast.show(haptic: .success) 40 | UIPasteboard.general.string = myIP.ip! ?? "0.0.0.0" 41 | }) { 42 | Label("IPv4 kopieren", systemImage: "doc.on.doc") 43 | } 44 | .tint(.blue) 45 | } 46 | } 47 | } 48 | Section("IPv6") { 49 | if (myIPV6.ip?.count == 0) { 50 | Spinner(isAnimating: true, style: .medium, color: .white) 51 | } else { 52 | Text(myIPV6.ip!) 53 | .lineLimit(1) 54 | .minimumScaleFactor(0.01) 55 | .swipeActions(edge: .trailing) { 56 | Button(role: .none, action: { 57 | let toast = Toast.default( 58 | image: GetUIImage(imageName: "doc.on.doc", color: UIColor.systemBlue, hierarichal: true), 59 | title: "IPv6 kopiert!", config: .init( 60 | direction: .top, 61 | autoHide: true, 62 | enablePanToClose: false, 63 | displayTime: 4, 64 | enteringAnimation: .fade(alphaValue: 0.5), 65 | exitingAnimation: .slide(x: 0, y: -100)) 66 | ) 67 | toast.show(haptic: .success) 68 | UIPasteboard.general.string = myIPV6.ip! ?? "0.0.0.0" 69 | }) { 70 | Label("IPv6 kopieren", systemImage: "doc.on.doc") 71 | } 72 | .tint(.blue) 73 | } 74 | } 75 | } 76 | } 77 | .onAppear { 78 | Task { 79 | myIP = await api.GetMyIP() ?? MyIP(ip: "0.0.0.0") 80 | myIPV6 = await api.GetMyIPV6() ?? MyIP(ip: "0.0.0.0") 81 | } 82 | } 83 | .navigationTitle("Meine IP") 84 | } 85 | } 86 | 87 | struct IPView_Previews: PreviewProvider { 88 | static var previews: some View { 89 | IPView() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /IPv64/Main/Account/Items/LogItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogItemView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 25.11.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct LogItemView: View { 11 | 12 | @State var log: MyLogs 13 | 14 | var body: some View { 15 | let dateDate = dateDBFormatter.date(from: log.time!) 16 | let dateString = itemFormatter.string(from: dateDate ?? Date()) 17 | Button(action: { 18 | print(log) 19 | withAnimation { 20 | log.expandLog.toggle() 21 | } 22 | }) { 23 | VStack { 24 | HStack { 25 | Text(log.subdomain!) 26 | .font(.system(.caption, design: .rounded)) 27 | Spacer() 28 | Text(dateString) 29 | .font(.system(.caption, design: .rounded)) 30 | } 31 | .padding(.bottom, 2) 32 | VStack(alignment: .leading, spacing: 0) { 33 | Text(log.header!) 34 | .font(.system(.headline, design: .rounded)) 35 | .padding(.bottom, 2) 36 | .frame(maxWidth: .infinity, alignment: .leading) 37 | if (log.expandLog) { 38 | Text(log.content!) 39 | .multilineTextAlignment(.leading) 40 | .frame(maxWidth: .infinity, alignment: .leading) 41 | } else { 42 | Text(log.content!) 43 | .lineLimit(1) 44 | .multilineTextAlignment(.leading) 45 | .frame(maxWidth: .infinity, alignment: .leading) 46 | } 47 | } 48 | } 49 | .foregroundStyle(Color("primaryText")) 50 | } 51 | } 52 | 53 | private let itemFormatter: DateFormatter = { 54 | let formatter = DateFormatter() 55 | formatter.dateStyle = .medium 56 | formatter.timeStyle = .medium 57 | formatter.locale = Locale(identifier: "de_DE") 58 | return formatter 59 | }() 60 | 61 | private let dateDBFormatter: DateFormatter = { 62 | let formatter = DateFormatter() 63 | formatter.locale = Locale(identifier: "de_DE") 64 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 65 | return formatter 66 | }() 67 | } 68 | 69 | #Preview { 70 | LogItemView(log: MyLogs(subdomain: "mesr.ipv64.net", time: "2022-11-25 15:12:16", header: "Record hinzugefügt", content: "DNS Record (A) für mesr.ipv64.net hinzugefügt. jhasiufjajsdnjcn ijsidncdnsciun ")) 71 | } 72 | -------------------------------------------------------------------------------- /IPv64/Main/Account/LogView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 25.11.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct LogView: View { 11 | 12 | @ObservedObject var api: NetworkServices = NetworkServices() 13 | @State var myLogs: [MyLogs] = [] 14 | @State var activeSheet: ActiveSheet? = nil 15 | @State var errorTyp: ErrorTyp? = .none 16 | @State private var showAllLogs = false 17 | @State private var btnTxt = "" 18 | @State private var myCustomLogs: [MyLogs] = [] 19 | 20 | var body: some View { 21 | ZStack { 22 | VStack { 23 | Form { 24 | Section("Letzten \(myCustomLogs.count) Logs") { 25 | ForEach(myCustomLogs, id: \.id) { log in 26 | LogItemView(log: log) 27 | } 28 | } 29 | 30 | Button(action: { 31 | withAnimation { 32 | showAllLogs.toggle() 33 | editLogs() 34 | } 35 | }) { 36 | Text(btnTxt) 37 | .font(.system(.callout, design: .rounded)) 38 | .textCase(.uppercase) 39 | .foregroundColor(Color("ip64_color")) 40 | } 41 | } 42 | } 43 | .sheet(item: $activeSheet) { item in 44 | showActiveSheet(item: item) 45 | } 46 | 47 | if api.isLoading { 48 | VStack() { 49 | Spinner(isAnimating: true, style: .large, color: .white) 50 | } 51 | .frame(maxWidth: .infinity, maxHeight: .infinity) 52 | .background(Color.black.opacity(0.3).ignoresSafeArea()) 53 | } 54 | } 55 | .onAppear { 56 | GetLogs() 57 | } 58 | .navigationTitle("Logs") 59 | } 60 | 61 | fileprivate func editLogs() { 62 | if (showAllLogs) { 63 | btnTxt = "Zeige 10 Logs an" 64 | myCustomLogs = myLogs 65 | } else { 66 | btnTxt = "Zeige \(myLogs.count) Logs an" 67 | myCustomLogs = Array(myLogs.prefix(10)) 68 | } 69 | } 70 | 71 | fileprivate func GetLogs() { 72 | Task { 73 | myLogs = await api.GetLogs()?.logs ?? Logs(logs: []).logs! 74 | editLogs() 75 | } 76 | } 77 | 78 | @ViewBuilder 79 | func showActiveSheet(item: ActiveSheet?) -> some View { 80 | switch item { 81 | case .error: 82 | ErrorSheetView(errorTyp: $errorTyp, deleteThisDomain: .constant(false)) 83 | .interactiveDismissDisabled(errorTyp?.status == 202 ? false : true) 84 | .onDisappear { 85 | if (errorTyp?.status == 401) { 86 | SetupPrefs.setPreference(mKey: "APIKEY", mValue: "") 87 | } else { 88 | GetLogs() 89 | } 90 | } 91 | default: 92 | EmptyView() 93 | } 94 | } 95 | } 96 | 97 | #Preview { 98 | LogView() 99 | } 100 | -------------------------------------------------------------------------------- /IPv64/Main/Blocklist/BlocklistView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlocklistView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 08.08.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BlocklistView: View { 11 | 12 | @Binding var popToRootTab: Tab 13 | 14 | @ObservedObject var api: NetworkServices = NetworkServices() 15 | 16 | @State var activeSheet: ActiveSheet? = nil 17 | @State var errorTyp: ErrorTyp? = .none 18 | 19 | @State private var blockerNodeList: BlockerNodeResults = .empty 20 | @State private var selectedBlockerNodeId = "" 21 | @State private var badIp = "" 22 | @State private var badPort = "" 23 | @State private var badInfo = "" 24 | @State private var badCategory = 0 25 | @State private var showLoginView = false 26 | 27 | var body: some View { 28 | NavigationView { 29 | ZStack { 30 | VStack { 31 | Form { 32 | Section("IP - Adresse Reporten") { 33 | Picker(selection: $selectedBlockerNodeId, label: Text("Blocker Node*") 34 | .font(.system(.callout)) 35 | .padding(.horizontal, 5)) { 36 | ForEach(blockerNodeList.blockers, id: \.blocker_id) { b in 37 | let name = b.name! 38 | let blocker_id = b.blocker_id! 39 | Text(name) 40 | .tag(blocker_id) 41 | .id(blocker_id) 42 | } 43 | } 44 | TextField("IP Adresse*", text: $badIp) 45 | TextField("Port", text: $badPort) 46 | Picker(selection: $badCategory, label: Text("Kategorie*") 47 | .font(.system(.callout)) 48 | .padding(.horizontal, 5)) { 49 | ForEach(0 ..< badNodeCategory.count) { 50 | Text(badNodeCategory[$0].text!) 51 | .tag(badNodeCategory[$0].id! - 1) 52 | } 53 | } 54 | TextField("Information", text: $badInfo) 55 | } 56 | 57 | Text("* benötigte Informationen") 58 | .bold() 59 | } 60 | } 61 | .tint(Color("ip64_color")) 62 | .navigationTitle(Tab.blocklist.labelName) 63 | .toolbar { 64 | ToolbarItem { 65 | Button(action: { 66 | if (badIp.count > 0) { 67 | let poisonedIp = PoisonedIP(blocker_id: selectedBlockerNodeId, report_ip: badIp, port: badPort, category: "\(badCategory+1)", info: badInfo) 68 | Task { 69 | let res = await api.PostPoisonedIP(poisonedIp: poisonedIp) 70 | print(res) 71 | if (res?.info == "success") { 72 | activeSheet = .error 73 | errorTyp = ErrorTypes.poisonedIpSuccesfully 74 | if (!blockerNodeList.blockers.isEmpty) { 75 | selectedBlockerNodeId = blockerNodeList.blockers[0].blocker_id! 76 | } 77 | badIp = "" 78 | badPort = "" 79 | badCategory = 0 80 | badInfo = "" 81 | } else if (res?.info == "error") { 82 | activeSheet = .error 83 | errorTyp = ErrorTypes.poisonedIpError 84 | } else if (res?.info == "Updateintervall overcommited") { 85 | activeSheet = .error 86 | errorTyp = ErrorTypes.tooManyRequests 87 | } 88 | } 89 | } 90 | }) { 91 | Image(systemName: "paperplane") 92 | .symbolRenderingMode(.hierarchical) 93 | .foregroundColor(Color("primaryText")) 94 | } 95 | .foregroundColor(.black) 96 | } 97 | } 98 | // if api.isLoading { 99 | // VStack() { 100 | // Spinner(isAnimating: true, style: .large, color: .white) 101 | // } 102 | // .frame(maxWidth: .infinity, maxHeight: .infinity) 103 | // .background(Color.black.opacity(0.3).ignoresSafeArea()) 104 | // } 105 | } 106 | .onAppear { 107 | loadBlockerNodes() 108 | } 109 | .fullScreenCover(isPresented: $showLoginView) { 110 | LoginView() 111 | } 112 | .sheet(item: $activeSheet) { item in 113 | showActiveSheet(item: item) 114 | } 115 | } 116 | } 117 | 118 | fileprivate func loadBlockerNodes() { 119 | Task { 120 | let response = await api.GetBlockerNodes() 121 | let status = response?.status 122 | if (status == nil) { 123 | throw NetworkError.NoNetworkConnection 124 | } 125 | if (status!.contains("429") && response?.blockers == nil) { 126 | activeSheet = .error 127 | errorTyp = ErrorTypes.tooManyRequests 128 | } else if (status!.contains("401")) { 129 | activeSheet = .error 130 | errorTyp = ErrorTypes.unauthorized 131 | } else { 132 | activeSheet = nil 133 | errorTyp = nil 134 | blockerNodeList = response! 135 | if (!blockerNodeList.blockers.isEmpty) { 136 | selectedBlockerNodeId = blockerNodeList.blockers[0].blocker_id! 137 | } 138 | print(selectedBlockerNodeId) 139 | print(response) 140 | } 141 | } 142 | } 143 | 144 | @ViewBuilder 145 | func showActiveSheet(item: ActiveSheet?) -> some View { 146 | switch item { 147 | case .error: 148 | ErrorSheetView(errorTyp: $errorTyp, deleteThisDomain: .constant(false)) 149 | .interactiveDismissDisabled(errorTyp?.status == 202 ? false : true) 150 | .onDisappear { 151 | if (errorTyp?.status == 401) { 152 | SetupPrefs.setPreference(mKey: "APIKEY", mValue: "") 153 | withAnimation { 154 | showLoginView.toggle() 155 | } 156 | } else { 157 | loadBlockerNodes() 158 | } 159 | } 160 | default: 161 | EmptyView() 162 | } 163 | } 164 | } 165 | 166 | #Preview { 167 | BlocklistView(popToRootTab: .constant(.other)) 168 | } 169 | -------------------------------------------------------------------------------- /IPv64/Main/Domains/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Main/Domains/.DS_Store -------------------------------------------------------------------------------- /IPv64/Main/Domains/Sheets/NewDomainDNSView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewDomainDNSView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 08.11.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NewDomainDNSView: View { 11 | 12 | @Environment(\.presentationMode) var presentationMode 13 | @ObservedObject var api: NetworkServices = NetworkServices() 14 | @State var activeSheet: ActiveSheet? = nil 15 | @State var errorTyp: ErrorTyp? = nil 16 | 17 | @Binding var isSaved: Bool 18 | @State var domainName: String 19 | 20 | @State var praefix: String = "" 21 | @State var content: String = "" 22 | @State var selectedTyp: Int = 0 23 | 24 | var typeList = [ 25 | "A", 26 | "AAAA", 27 | "TXT", 28 | "MX", 29 | "NS", 30 | "SRV", 31 | "CNAME", 32 | "TLSA", 33 | "CAA" 34 | ] 35 | 36 | var body: some View { 37 | NavigationView { 38 | ZStack { 39 | VStack { 40 | Form { 41 | Section("Benötigte Informationen") { 42 | TextField("Präfix", text: $praefix) 43 | Picker(selection: $selectedTyp, label: Text("Typ") 44 | .font(.system(.callout)) 45 | .padding(.horizontal, 5)) { 46 | ForEach(0 ..< typeList.count) { 47 | Text(self.typeList[$0]) 48 | .tag(self.typeList[$0]) 49 | } 50 | } 51 | TextField("Wert", text: $content) 52 | } 53 | } 54 | } 55 | .navigationTitle("Neuer DNS-Record") 56 | .toolbar { 57 | ToolbarItem { 58 | Button(action: { 59 | if (content.count > 0) { 60 | let _praefix = praefix.trimmingCharacters(in: .whitespaces) 61 | let _content = content.trimmingCharacters(in: .whitespaces) 62 | Task { 63 | let res = await api.PostDNSRecord(domain: domainName, praefix: _praefix, typ: typeList[selectedTyp], content: _content) 64 | if (res?.info == "success") { 65 | activeSheet = .error 66 | errorTyp = ErrorTypes.dnsRecordSuccesfullyCreated 67 | } else if (res?.info == "error") { 68 | activeSheet = .error 69 | errorTyp = ErrorTypes.domainNotAvailable 70 | } else if (res?.info == "Updateintervall overcommited") { 71 | activeSheet = .error 72 | errorTyp = ErrorTypes.tooManyRequests 73 | } 74 | } 75 | } 76 | }) { 77 | Image(systemName: "paperplane") 78 | .symbolRenderingMode(.hierarchical) 79 | .foregroundColor(Color("primaryText")) 80 | } 81 | .foregroundColor(.black) 82 | } 83 | } 84 | if api.isLoading { 85 | VStack() { 86 | Spinner(isAnimating: true, style: .large, color: .white) 87 | } 88 | .frame(maxWidth: .infinity, maxHeight: .infinity) 89 | .background(Color.black.opacity(0.3).ignoresSafeArea()) 90 | } 91 | } 92 | } 93 | .sheet(item: $activeSheet) { item in 94 | showActiveSheet(item: item) 95 | } 96 | } 97 | 98 | @ViewBuilder 99 | func showActiveSheet(item: ActiveSheet?) -> some View { 100 | switch item { 101 | case .error: 102 | ErrorSheetView(errorTyp: $errorTyp, deleteThisDomain: .constant(false)) 103 | .onDisappear { 104 | if (self.errorTyp!.status == 201) { 105 | withAnimation { 106 | isSaved = true 107 | presentationMode.wrappedValue.dismiss() 108 | } 109 | } 110 | } 111 | default: 112 | EmptyView() 113 | } 114 | } 115 | } 116 | 117 | struct NewDomainDNSView_Previews: PreviewProvider { 118 | static var previews: some View { 119 | NewDomainDNSView(isSaved: .constant(false), domainName: "seb.nas64.de") 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /IPv64/Main/Domains/Sheets/NewDomainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewDomainView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 06.11.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NewDomainView: View { 11 | 12 | @Environment(\.presentationMode) var presentationMode 13 | @Binding var newItem: Bool 14 | @ObservedObject var api: NetworkServices = NetworkServices() 15 | @State var activeSheet: ActiveSheet? = nil 16 | @State var errorTyp: ErrorTyp? = nil 17 | @State var showSheet = false 18 | 19 | @State var domain: String = "" 20 | @State private var selectedDomain = 0 21 | @State var domainError: String = "" 22 | 23 | var body: some View { 24 | NavigationView { 25 | ZStack { 26 | VStack { 27 | Form { 28 | Section("Benötigte Informationen") { 29 | TextField("zB.: Heimserver01", text: $domain) 30 | Picker(selection: $selectedDomain, label: Text("Domain") 31 | .font(.system(.callout)) 32 | .padding(.horizontal, 5)) { 33 | ForEach(0 ..< dynDomainList.count) { 34 | Text(dynDomainList[$0]) 35 | .tag(dynDomainList[$0]) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | .navigationTitle("Neue Domain") 42 | .toolbar { 43 | ToolbarItem { 44 | Button(action: { 45 | if (domain.count > 0) { 46 | var domainReg = domain.trimmingCharacters(in: .whitespaces) + "." + dynDomainList[selectedDomain] 47 | //loadDomains() 48 | Task { 49 | let res = await api.PostDomain(domain: domainReg) 50 | if (res?.info == "success") { 51 | activeSheet = .error 52 | errorTyp = ErrorTypes.domainCreatedSuccesfully 53 | newItem = true 54 | } else if (res?.info == "error") { 55 | activeSheet = .error 56 | errorTyp = ErrorTypes.domainNotAvailable 57 | } else if (res?.info == "Updateintervall overcommited") { 58 | activeSheet = .error 59 | errorTyp = ErrorTypes.tooManyRequests 60 | } 61 | } 62 | } 63 | }) { 64 | Image(systemName: "paperplane") 65 | .symbolRenderingMode(.hierarchical) 66 | .foregroundColor(Color("primaryText")) 67 | } 68 | .foregroundColor(.black) 69 | } 70 | } 71 | if api.isLoading { 72 | VStack() { 73 | Spinner(isAnimating: true, style: .large, color: .white) 74 | } 75 | .frame(maxWidth: .infinity, maxHeight: .infinity) 76 | .background(Color.black.opacity(0.3).ignoresSafeArea()) 77 | } 78 | } 79 | .sheet(item: $activeSheet) { item in 80 | showActiveSheet(item: item) 81 | } 82 | } 83 | } 84 | 85 | @ViewBuilder 86 | func showActiveSheet(item: ActiveSheet?) -> some View { 87 | switch item { 88 | case .add: 89 | EmptyView() 90 | case .detail: 91 | EmptyView() 92 | case .error: 93 | ErrorSheetView(errorTyp: $errorTyp, deleteThisDomain: .constant(false)) 94 | .interactiveDismissDisabled(true) 95 | .onDisappear { 96 | if (self.errorTyp!.status == 201) { 97 | withAnimation { 98 | presentationMode.wrappedValue.dismiss() 99 | } 100 | } 101 | } 102 | default: 103 | EmptyView() 104 | } 105 | } 106 | } 107 | 108 | struct NewDomainView_Previews: PreviewProvider { 109 | static var previews: some View { 110 | NewDomainView(newItem: .constant(false)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /IPv64/Main/Healthcheck/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Main/Healthcheck/.DS_Store -------------------------------------------------------------------------------- /IPv64/Main/Healthcheck/Items/HealthcheckStatsItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthcheckStatsItem.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 16.01.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HealthcheckStatsItem: View { 11 | 12 | @State var statusType: StatusTyp 13 | @Binding var count: Int 14 | 15 | var body: some View { 16 | VStack { 17 | VStack { 18 | HStack(alignment: .top) { 19 | VStack(alignment: .leading, spacing: 8) { 20 | Text(statusType.name!) 21 | .bold() 22 | .lineLimit(1) 23 | .font(.system(.title2, design: .rounded)) 24 | Text("\(count)") 25 | .font(.system(.title, design: .rounded)) 26 | } 27 | Spacer() 28 | Image(systemName: statusType.icon!) 29 | .resizable() 30 | .scaledToFit() 31 | .frame(width: 25, height: 25) 32 | .offset(y: statusType.statusId == 4 ? -5 : 0) 33 | } 34 | .foregroundColor(.white) 35 | .padding() 36 | } 37 | .frame(maxWidth: .infinity, maxHeight: .infinity) 38 | .background(RoundedRectangle(cornerRadius: 16).fill(statusType.color!)) 39 | } 40 | } 41 | } 42 | 43 | struct HealthcheckStatsItem_Previews: PreviewProvider { 44 | static var previews: some View { 45 | HealthcheckStatsItem(statusType: StatusTypes.pause, count: .constant(20)) 46 | .preferredColorScheme(.light) 47 | .previewDisplayName("Light Mode") 48 | HealthcheckStatsItem(statusType: StatusTypes.pause, count: .constant(20)) 49 | .preferredColorScheme(.dark) 50 | .previewDisplayName("Dark Mode") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /IPv64/Main/Healthcheck/Items/HealthcheckStatsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthcheckStatsView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 16.01.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HealthcheckStatsView: View { 11 | 12 | @Binding var activeCount: Int 13 | @Binding var warningCount: Int 14 | @Binding var alarmCount: Int 15 | @Binding var pausedCount: Int 16 | 17 | @State var columnsGrid: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2) 18 | 19 | var body: some View { 20 | VStack { 21 | LazyVGrid(columns: columnsGrid, spacing: 10) { 22 | HealthcheckStatsItem(statusType: StatusTypes.active, count: $activeCount) 23 | HealthcheckStatsItem(statusType: StatusTypes.warning, count: $warningCount) 24 | HealthcheckStatsItem(statusType: StatusTypes.alarm, count: $alarmCount) 25 | HealthcheckStatsItem(statusType: StatusTypes.pause, count: $pausedCount) 26 | } 27 | .padding() 28 | } 29 | } 30 | } 31 | 32 | struct HealthcheckStatsView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | HealthcheckStatsView(activeCount: .constant(2), warningCount: .constant(0), alarmCount: .constant(3), pausedCount: .constant(1)) 35 | .preferredColorScheme(.light) 36 | .previewDisplayName("Light Mode") 37 | HealthcheckStatsView(activeCount: .constant(2), warningCount: .constant(0), alarmCount: .constant(3), pausedCount: .constant(1)) 38 | .preferredColorScheme(.dark) 39 | .previewDisplayName("Dark Mode") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /IPv64/Main/Healthcheck/Sheets/EditHealthcheckView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditHealthcheckView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 21.01.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct EditHealthcheckView: View { 11 | 12 | @Environment(\.presentationMode) var presentationMode 13 | @AppStorage("IntegrationList") var integrationListS: String = "" 14 | 15 | @ObservedObject var api: NetworkServices = NetworkServices() 16 | @State var activeSheet: ActiveSheet? = nil 17 | @State var errorTyp: ErrorTyp? = nil 18 | @State var showSheet = false 19 | @State var healthcheck: HealthCheck 20 | @Binding var updatedItem: Bool 21 | @State var alarmCount = 0.0 22 | @State var graceCount = 0.0 23 | @State var notifyDown = false 24 | @State var notifyUp = false 25 | @State var integrationIds = "" 26 | 27 | @State var integrationList: [Integration] = [] 28 | 29 | var AlarmUnitList: [AlarmUnit] = [ 30 | AlarmUnitTypes.minutes, 31 | AlarmUnitTypes.stunden, 32 | AlarmUnitTypes.tage 33 | ] 34 | 35 | fileprivate func GetUnit(unit: Int) -> String { 36 | if (unit == AlarmUnitTypes.minutes.id) { 37 | return AlarmUnitTypes.minutes.text! 38 | } else if (unit == AlarmUnitTypes.stunden.id) { 39 | return AlarmUnitTypes.stunden.text! 40 | } else { 41 | return AlarmUnitTypes.tage.text! 42 | } 43 | } 44 | 45 | var body: some View { 46 | ZStack { 47 | NavigationView { 48 | VStack { 49 | Form { 50 | Section("Name") { 51 | TextField("Healthcheck #002", text: $healthcheck.name) 52 | } 53 | Section("Zeitraum") { 54 | Text("\(Int(alarmCount)) \(GetUnit(unit: healthcheck.alarm_unit))") 55 | Slider(value: $alarmCount, in: 1...60, step: 1.0) 56 | Picker(selection: $healthcheck.alarm_unit, label: Text("Zeitraum")) { 57 | ForEach(0 ..< AlarmUnitList.count) { 58 | Text(AlarmUnitList[$0].text!) 59 | .tag(AlarmUnitList[$0].id!) 60 | } 61 | } 62 | } 63 | Section("Karenzzeit") { 64 | Text("\(Int(graceCount)) \(GetUnit(unit: healthcheck.grace_unit))") 65 | Slider(value: $graceCount, in: 1...60, step: 1.0) 66 | Picker(selection: $healthcheck.grace_unit, label: Text("Zeitraum")) { 67 | ForEach(0 ..< AlarmUnitList.count) { 68 | Text(AlarmUnitList[$0].text!) 69 | .tag(AlarmUnitList[$0].id!) 70 | } 71 | } 72 | } 73 | Section("Benachrichtigung") { 74 | /*let intList = integrationList 75 | Picker(selection: $integrationId, label: Text("Benachrichtungsmethode")) { 76 | ForEach(0 ..< intList.count) { 77 | Text(intList[$0].integration_name!) 78 | .tag(intList[$0].integration_id) 79 | } 80 | }*/ 81 | Button(action: { 82 | withAnimation { 83 | activeSheet = .integrationselection 84 | } 85 | }) { 86 | Text("Benachrichtigungsmethoden") 87 | } 88 | Toggle(isOn: $notifyDown) { 89 | Text("Benachrichtung bei DOWN") 90 | }.tint(.red) 91 | Toggle(isOn: $notifyUp) { 92 | Text("Benachrichtung bei UP") 93 | }.tint(.green) 94 | } 95 | } 96 | } 97 | .navigationTitle("Bearbeiten") 98 | .sheet(item: $activeSheet) { item in 99 | showActiveSheet(item: item) 100 | } 101 | .toolbar { 102 | ToolbarItem { 103 | Button(action: { 104 | if (healthcheck.name.count > 0) { 105 | Task { 106 | healthcheck.alarm_count = Int(alarmCount) 107 | healthcheck.grace_count = Int(graceCount) 108 | healthcheck.alarm_down = notifyDown ? 1 : 0 109 | healthcheck.alarm_up = notifyUp ? 1 : 0 110 | let res = await api.PostEditHealthcheck(healthcheck: healthcheck, integrationId: integrationIds) 111 | withAnimation { 112 | if (res?.info == "success") { 113 | activeSheet = .error 114 | errorTyp = ErrorTypes.healthcheckUpdatedSuccesfully 115 | updatedItem = true 116 | } else if (res?.info == "error") { 117 | activeSheet = .error 118 | } else if (res?.info == "Updateintervall overcommited") { 119 | activeSheet = .error 120 | errorTyp = ErrorTypes.tooManyRequests 121 | } 122 | } 123 | } 124 | } 125 | }) { 126 | Image(systemName: "paperplane") 127 | .symbolRenderingMode(.hierarchical) 128 | .foregroundColor(Color("primaryText")) 129 | } 130 | .foregroundColor(.black) 131 | } 132 | } 133 | } 134 | .onAppear { 135 | alarmCount = Double(healthcheck.alarm_count) 136 | graceCount = Double(healthcheck.grace_count) 137 | notifyUp = healthcheck.alarm_up == 1 138 | notifyDown = healthcheck.alarm_down == 1 139 | integrationIds = healthcheck.integration_id.replacingOccurrences(of: ",", with: "_") 140 | do { 141 | let jsonDecoder = JSONDecoder() 142 | let jsonData = integrationListS.data(using: .utf8) 143 | integrationList = try jsonDecoder.decode([Integration].self, from: jsonData!) 144 | } catch { 145 | print("ERROR \(error.localizedDescription)") 146 | } 147 | } 148 | .tint(Color("ip64_color")) 149 | if api.isLoading { 150 | VStack() { 151 | Spinner(isAnimating: true, style: .large, color: .white) 152 | } 153 | .frame(maxWidth: .infinity, maxHeight: .infinity) 154 | .background(Color.black.opacity(0.3).ignoresSafeArea()) 155 | } 156 | } 157 | } 158 | 159 | 160 | @ViewBuilder 161 | func showActiveSheet(item: ActiveSheet?) -> some View { 162 | switch item { 163 | case .error: 164 | ErrorSheetView(errorTyp: $errorTyp, deleteThisDomain: .constant(false)) 165 | .interactiveDismissDisabled(true) 166 | .onDisappear { 167 | if (self.errorTyp!.status == 201) { 168 | withAnimation { 169 | presentationMode.wrappedValue.dismiss() 170 | } 171 | } 172 | } 173 | case .integrationselection: 174 | IntegrationSelection(integrationIds: $integrationIds) 175 | default: 176 | EmptyView() 177 | } 178 | } 179 | } 180 | 181 | struct EditHealthcheckView_Previews: PreviewProvider { 182 | static var previews: some View { 183 | EditHealthcheckView(healthcheck: DummyData.Healthcheck, updatedItem: .constant(false)) 184 | .preferredColorScheme(.light) 185 | .previewDisplayName("Light Mode") 186 | EditHealthcheckView(healthcheck: DummyData.Healthcheck, updatedItem: .constant(false)) 187 | .preferredColorScheme(.dark) 188 | .previewDisplayName("Dark Mode") 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /IPv64/Main/Healthcheck/Sheets/IntegrationSelection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntegrationSelection.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 07.03.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct IntegrationSelection: View { 11 | @Environment(\.presentationMode) var presentationMode 12 | @AppStorage("IntegrationList") var integrationListS: String = "" 13 | @Binding var integrationIds: String 14 | @State var integrationList: [Integration] = [] 15 | @State private var selection = Set() 16 | @State var editMode: EditMode = .active 17 | 18 | var body: some View { 19 | NavigationView { 20 | List(integrationList, id: \.integration_id, selection: $selection) { i in 21 | Text(i.integration_name!.replacingOccurrences(of: """, with: "\"")) 22 | } 23 | .navigationTitle("Benachrichten auf") 24 | .environment(\.editMode, self.$editMode) 25 | .onAppear { 26 | var splitSel = integrationIds.split(separator: "_") 27 | splitSel.forEach { i in 28 | if (Int(i)! != 0) { 29 | selection.insert(Int(i)!) 30 | } 31 | } 32 | do { 33 | let jsonDecoder = JSONDecoder() 34 | let jsonData = integrationListS.data(using: .utf8) 35 | integrationList = try jsonDecoder.decode([Integration].self, from: jsonData!).sorted { $0.integration_name!.lowercased() < $1.integration_name!.lowercased() 36 | } 37 | } catch { 38 | print("ERROR \(error.localizedDescription)") 39 | } 40 | } 41 | .toolbar { 42 | Button(action: { 43 | withAnimation { 44 | integrationIds = selection.compactMap { $0.description } 45 | .joined(separator: "_") 46 | if (integrationIds.count == 0) { 47 | integrationIds = "0" 48 | } 49 | print(integrationIds) 50 | presentationMode.wrappedValue.dismiss() 51 | } 52 | }) { 53 | Text("Speichern") 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | struct IntegrationSelection_Previews: PreviewProvider { 61 | static var previews: some View { 62 | IntegrationSelection(integrationIds: .constant("")) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /IPv64/Main/Healthcheck/Sheets/NewHealthcheckView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewHealthcheckView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 16.01.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NewHealthcheckView: View { 11 | 12 | @Environment(\.presentationMode) var presentationMode 13 | @Binding var newItem: Bool 14 | @ObservedObject var api: NetworkServices = NetworkServices() 15 | @State var activeSheet: ActiveSheet? = nil 16 | @State var errorTyp: ErrorTyp? = nil 17 | @State var showSheet = false 18 | @State var healthName = "" 19 | @State var healthAlarmCount = 30.0 20 | @State var healthAlarmUnit = 1 21 | 22 | var AlarmUnitList: [AlarmUnit] = [ 23 | AlarmUnitTypes.minutes, 24 | AlarmUnitTypes.stunden, 25 | AlarmUnitTypes.tage 26 | ] 27 | 28 | fileprivate func GetUnit() -> String { 29 | if (healthAlarmUnit == AlarmUnitTypes.minutes.id) { 30 | return AlarmUnitTypes.minutes.text! 31 | } else if (healthAlarmUnit == AlarmUnitTypes.stunden.id) { 32 | return AlarmUnitTypes.stunden.text! 33 | } else { 34 | return AlarmUnitTypes.tage.text! 35 | } 36 | } 37 | 38 | var body: some View { 39 | ZStack { 40 | NavigationView { 41 | VStack { 42 | Form { 43 | Section("Benötigte Informationen") { 44 | TextField("Healthcheck #002", text: $healthName) 45 | Text("\(Int(healthAlarmCount)) \(GetUnit())") 46 | Slider(value: $healthAlarmCount, in: 1...60, step: 1.0) 47 | Picker(selection: $healthAlarmUnit, label: Text("Zeitraum") 48 | .font(.system(.callout)) 49 | .padding(.horizontal, 5)) { 50 | ForEach(0 ..< AlarmUnitList.count) { 51 | Text(AlarmUnitList[$0].text!) 52 | .tag(AlarmUnitList[$0].id!) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | .navigationTitle("Neuer Healthcheck") 59 | .sheet(item: $activeSheet) { item in 60 | showActiveSheet(item: item) 61 | } 62 | .toolbar { 63 | ToolbarItem { 64 | Button(action: { 65 | if (healthName.count > 0) { 66 | Task { 67 | let res = await api.PostHealth(add_healthcheck: healthName, alarm_count: Int(healthAlarmCount), alarm_unit: healthAlarmUnit) 68 | if (res?.info == "success") { 69 | activeSheet = .error 70 | errorTyp = ErrorTypes.healthcheckCreatedSuccesfully 71 | newItem = true 72 | } else if (res?.info == "error") { 73 | activeSheet = .error 74 | } else if (res?.info == "Updateintervall overcommited") { 75 | activeSheet = .error 76 | errorTyp = ErrorTypes.tooManyRequests 77 | } 78 | } 79 | } 80 | }) { 81 | Image(systemName: "paperplane") 82 | .symbolRenderingMode(.hierarchical) 83 | .foregroundColor(Color("primaryText")) 84 | } 85 | .foregroundColor(.black) 86 | } 87 | } 88 | if api.isLoading { 89 | VStack() { 90 | Spinner(isAnimating: true, style: .large, color: .white) 91 | } 92 | .frame(maxWidth: .infinity, maxHeight: .infinity) 93 | .background(Color.black.opacity(0.3).ignoresSafeArea()) 94 | } 95 | } 96 | } 97 | } 98 | 99 | 100 | @ViewBuilder 101 | func showActiveSheet(item: ActiveSheet?) -> some View { 102 | switch item { 103 | case .add: 104 | EmptyView() 105 | case .detail: 106 | EmptyView() 107 | case .error: 108 | ErrorSheetView(errorTyp: $errorTyp, deleteThisDomain: .constant(false)) 109 | .interactiveDismissDisabled(true) 110 | .onDisappear { 111 | if (self.errorTyp!.status == 201) { 112 | withAnimation { 113 | presentationMode.wrappedValue.dismiss() 114 | } 115 | } 116 | } 117 | default: 118 | EmptyView() 119 | } 120 | } 121 | } 122 | 123 | struct NewHealthcheckView_Previews: PreviewProvider { 124 | static var previews: some View { 125 | NewHealthcheckView(newItem: .constant(false)) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /IPv64/Main/LockView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LockView.swift 3 | // Tokey 4 | // 5 | // Created by Sebastian Rank on 05.04.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct LockView: View { 11 | 12 | @State var showDomains = false 13 | @State private var frameWidth: CGFloat = .infinity 14 | @ObservedObject private var bio = Biometrics() 15 | 16 | var body: some View { 17 | ZStack { 18 | if !showDomains { 19 | VStack(alignment: .center) { 20 | Image(systemName: "lock.shield") 21 | .resizable() 22 | .scaledToFit() 23 | .symbolRenderingMode(.hierarchical) 24 | .foregroundColor(Color("ip64_color")) 25 | .frame(width: 75, height: 75, alignment: .center) 26 | .padding(.bottom, 25) 27 | Text("Bitte verwende FaceID/TouchID um IPv64.net zu entsperren") 28 | .font(.system(.callout, design: .rounded)) 29 | .foregroundColor(Color("primaryText")) 30 | .padding(.bottom) 31 | .multilineTextAlignment(.center) 32 | .frame(maxWidth: .infinity, alignment: .center) 33 | HStack { 34 | Button(action: { 35 | withAnimation { 36 | bio.disableFields = true 37 | bio.tryToAuthenticate() 38 | } 39 | }) { 40 | ZStack { 41 | if bio.disableFields { 42 | Spinner(isAnimating: true, style: .large, color: UIColor.white) 43 | } else { 44 | HStack { 45 | Image(systemName: Biometrics.GetBiometricSymbol()) 46 | .resizable() 47 | .symbolRenderingMode(.hierarchical) 48 | .scaledToFit() 49 | .frame(width: 24, height: 24, alignment: .center) 50 | .padding(.trailing, 10) 51 | Text("Entsperren") 52 | .font(.system(.callout, design: .rounded)) 53 | .fontWeight(.bold) 54 | .textCase(.uppercase) 55 | } 56 | .frame(minWidth: 0, maxWidth: .infinity) 57 | .padding(16) 58 | .foregroundColor(Color.white) 59 | .background(Color("ip64_color")).clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) 60 | } 61 | } 62 | .frame(maxWidth: .infinity, maxHeight: 55) 63 | .background(Color("ip64_color")).clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) 64 | } 65 | .buttonStyle(PlainButtonStyle()) 66 | .disabled(bio.disableFields) 67 | .padding(EdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0)) 68 | } 69 | } 70 | .padding(16) 71 | .onAppear { 72 | frameWidth = Functions.getOrientationWidth() 73 | bio.reset() 74 | bio.tryToAuthenticate() 75 | } 76 | .onChange(of: bio.isAuthenticated) { value in 77 | withAnimation { 78 | if bio.isAuthenticated { 79 | showDomains = true 80 | let generator = UINotificationFeedbackGenerator() 81 | generator.notificationOccurred(.success) 82 | } 83 | } 84 | } 85 | .frame(maxWidth: frameWidth, maxHeight: .infinity) 86 | } else { 87 | TabbView(showDomains: $showDomains) 88 | } 89 | } 90 | .onRotate { _ in 91 | frameWidth = Functions.getOrientationWidth() 92 | } 93 | .frame(maxWidth: .infinity, maxHeight: .infinity) 94 | } 95 | } 96 | 97 | struct LockView_Previews: PreviewProvider { 98 | static var previews: some View { 99 | LockView() 100 | .preferredColorScheme(.light) 101 | LockView() 102 | .preferredColorScheme(.dark) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /IPv64/Main/Sheets/ErrorSheetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorSheetView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 07.11.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ErrorSheetView: View { 11 | @Environment(\.presentationMode) var presentationMode 12 | 13 | @Binding var errorTyp: ErrorTyp? 14 | @Binding var deleteThisDomain: Bool 15 | 16 | @State var timeRemaining = 10 17 | let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() 18 | 19 | var body: some View { 20 | NavigationView { 21 | VStack { 22 | Spacer() 23 | let icon = errorTyp!.icon! 24 | 25 | if (icon.contains(";")) { 26 | let splittetIcons = icon.split(separator: ";") 27 | ZStack { 28 | ZStack { 29 | Circle() 30 | .fill(Color("circleBG")) 31 | .frame(height: 220) 32 | Circle() 33 | .fill(Color("circleBG")) 34 | .frame(height: 75) 35 | .offset(x: 75, y: 55) 36 | } 37 | Image(systemName: String(splittetIcons[0])) 38 | .resizable() 39 | .scaledToFit() 40 | .symbolRenderingMode(.hierarchical) 41 | .foregroundColor(errorTyp!.iconColor!) 42 | .frame(height: 115) 43 | .padding(25) 44 | Image(systemName: String(splittetIcons[1])) 45 | .resizable() 46 | .scaledToFit() 47 | .symbolRenderingMode(.hierarchical) 48 | .foregroundColor(errorTyp!.iconColor!) 49 | .frame(height: 45) 50 | .padding() 51 | .offset(x: 70, y: 50) 52 | 53 | } 54 | } else { 55 | ZStack { 56 | ZStack { 57 | Circle() 58 | .fill(Color("circleBG")) 59 | .frame(height: 220) 60 | } 61 | Image(systemName: icon) 62 | .resizable() 63 | .scaledToFit() 64 | .symbolRenderingMode(.hierarchical) 65 | .foregroundColor(errorTyp!.iconColor!) 66 | .frame(height: 100) 67 | .padding(25) 68 | 69 | } 70 | } 71 | Spacer() 72 | VStack { 73 | Text(errorTyp!.errorTitle!) 74 | .multilineTextAlignment(.center) 75 | .font(.system(.title3, design: .rounded).bold()) 76 | Text(errorTyp!.errorDescription!) 77 | .font(.system(.callout, design: .rounded)) 78 | .multilineTextAlignment(.center) 79 | .padding() 80 | } 81 | 82 | Spacer() 83 | 84 | if (errorTyp!.status! == 429) { 85 | Button(action: { 86 | withAnimation { 87 | presentationMode.wrappedValue.dismiss() 88 | } 89 | }) { 90 | Text(timeRemaining > 0 ? "Schließen (\(timeRemaining))" : "Schließen") 91 | .font(.system(.callout, design: .rounded)) 92 | .fontWeight(.bold) 93 | .textCase(.uppercase) 94 | .frame(minWidth: 0, maxWidth: .infinity) 95 | .padding(16) 96 | .foregroundColor(Color.white) 97 | .background(Color("ip64_color")).clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) 98 | } 99 | .disabled(timeRemaining > 0) 100 | .padding() 101 | } else if (errorTyp!.status! == 202) { 102 | Button(action: { 103 | withAnimation { 104 | deleteThisDomain = true 105 | presentationMode.wrappedValue.dismiss() 106 | } 107 | }) { 108 | Text("Wirklich löschen?") 109 | .font(.system(.callout, design: .rounded)) 110 | .fontWeight(.bold) 111 | .textCase(.uppercase) 112 | .frame(minWidth: 0, maxWidth: .infinity) 113 | .padding(16) 114 | .foregroundColor(Color.white) 115 | .background(.red).clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) 116 | } 117 | .padding([.horizontal, .top]) 118 | 119 | Button(action: { 120 | withAnimation { 121 | presentationMode.wrappedValue.dismiss() 122 | } 123 | }) { 124 | Text("Abbrechen") 125 | .font(.system(.callout, design: .rounded)) 126 | .fontWeight(.bold) 127 | .textCase(.uppercase) 128 | .frame(minWidth: 0, maxWidth: .infinity) 129 | .padding(16) 130 | .foregroundColor(Color("AccentColor")) 131 | .background(.gray.opacity(0.2)).clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) 132 | } 133 | .padding([.horizontal, .bottom]) 134 | } else { 135 | Button(action: { 136 | withAnimation { 137 | deleteThisDomain = false 138 | presentationMode.wrappedValue.dismiss() 139 | } 140 | }) { 141 | Text("Schließen") 142 | .font(.system(.callout, design: .rounded)) 143 | .fontWeight(.bold) 144 | .textCase(.uppercase) 145 | .frame(minWidth: 0, maxWidth: .infinity) 146 | .padding(16) 147 | .foregroundColor(Color.white) 148 | .background(errorTyp!.iconColor!).clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) 149 | } 150 | .padding() 151 | } 152 | } 153 | .navigationTitle(errorTyp!.navigationTitle!) 154 | .navigationBarTitleDisplayMode(.inline) 155 | .onReceive(timer) { _ in 156 | if timeRemaining > 0 { 157 | timeRemaining -= 1 158 | } else { 159 | timer.upstream.connect().cancel() 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | struct ErrorSheetView_Previews: PreviewProvider { 167 | static var previews: some View { 168 | ErrorSheetView(errorTyp: .constant(ErrorTypes.unauthorized), deleteThisDomain: .constant(false)) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /IPv64/Main/TabbView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabView.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 25.11.22. 6 | // 7 | 8 | import SwiftUI 9 | import FirebaseMessaging 10 | 11 | struct TabbView: View { 12 | 13 | @AppStorage("BIOMETRIC_ENABLED") var isBiometricEnabled: Bool = false 14 | @AppStorage("AccountInfos") var accountInfosJson: String = "" 15 | @AppStorage("AccountList") var accountListJson: String = "" 16 | @AppStorage("ENABLE_NAV_SOUND") var enableNavSound: Bool = true 17 | @AppStorage("DomainResult") var listOfDomainsString: String = "" 18 | @AppStorage("current_Tab") var selectedTab: Tab = .domains 19 | 20 | @Binding var showDomains: Bool 21 | 22 | @ObservedObject var api: NetworkServices = NetworkServices() 23 | 24 | @State var activeSheet: ActiveSheet? = nil 25 | @State var accountInfos = AccountInfo() 26 | 27 | @State private var popToRootTab: Tab = .other 28 | @State private var showPlaceholder = false 29 | @State private var showWhatsNew = false 30 | @State private var accountList: [Account] = [] 31 | 32 | private var availableTabs: [Tab] { 33 | Tab.tabList() 34 | } 35 | 36 | var body: some View { 37 | tabBarView 38 | } 39 | 40 | @ViewBuilder 41 | private func showActiveSheet(item: ActiveSheet?) -> some View { 42 | switch item { 43 | case .whatsnew: 44 | WhatsNewView(activeSheet: $activeSheet, isPresented: $showWhatsNew) 45 | default: 46 | EmptyView() 47 | } 48 | } 49 | 50 | private func sendFCMToken() { 51 | Messaging.messaging().isAutoInitEnabled = true 52 | Messaging.messaging().token { token, error in 53 | if let error = error { 54 | print("Error fetching FCM registration token: \(error)") 55 | } else if let token = token { 56 | print("FCM registration token: \(token)") 57 | print(UIDevice().type.rawValue) 58 | let os = ProcessInfo().operatingSystemVersion 59 | let sdtoken = SetupPrefs.readPreference(mKey: "DEVICETOKEN", mDefaultValue: "") as! String 60 | if (sdtoken != token) { 61 | Task { 62 | let api = NetworkServices() 63 | let result = await api.PostAddIntegration(integrationType: "mobil", dtoken: token, dName: UIDevice().type.rawValue) 64 | SetupPrefs.setPreference(mKey: "DEVICETOKEN", mValue: token) 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | 72 | 73 | private var tabBarView: some View { 74 | TabView(selection: .init(get: { 75 | selectedTab 76 | }, set: { newTab in 77 | Task { 78 | if newTab == selectedTab { 79 | /// Stupid hack to trigger onChange binding in tab views. 80 | popToRootTab = .other 81 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 82 | popToRootTab = selectedTab 83 | } 84 | } 85 | 86 | selectedTab = newTab 87 | if (enableNavSound) { 88 | await HapticManager.shared.fireHaptic(of: .tabSelection) 89 | await SoundEffectManager.shared.playSound(of: .tabSelection) 90 | } 91 | } 92 | 93 | })) { 94 | ForEach(availableTabs) { tab in 95 | tab.makeContentView(popToRootTab: $popToRootTab) 96 | .redacted(reason: showPlaceholder ? .placeholder : .init()) 97 | .tabItem { 98 | tab.label 99 | .labelStyle(TitleAndIconLabelStyle()) 100 | } 101 | .tag(tab) 102 | } 103 | } 104 | .tint(Color("ip64_color")) 105 | .sheet(item: $activeSheet) { item in 106 | showActiveSheet(item: item) 107 | } 108 | .onAppear { 109 | let tabBarAppearance = UITabBarAppearance() 110 | tabBarAppearance.configureWithDefaultBackground() 111 | UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance 112 | 113 | UIApplication.shared.applicationIconBadgeNumber = 0 114 | SetupPrefs.setPreference(mKey: "BADGE_COUNT", mValue: 0) 115 | 116 | if (accountListJson.isEmpty) { 117 | Task { 118 | await createAccountList() 119 | } 120 | } 121 | 122 | var lastBuildNumber = SetupPrefs.readPreference(mKey: "LASTBUILDNUMBER", mDefaultValue: "0") as! String 123 | var token = SetupPrefs.readPreference(mKey: "APIKEY", mDefaultValue: "") as! String 124 | if Int(lastBuildNumber) != Int(Bundle.main.buildNumber) && !token.isEmpty { 125 | withAnimation { 126 | showWhatsNew = true 127 | activeSheet = .whatsnew 128 | } 129 | } 130 | 131 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { 132 | sendFCMToken() 133 | } 134 | } 135 | .onOpenURL { url in 136 | print("URL") 137 | guard url.scheme == "ipv64", url.host == "tab", let tabId = Int(url.pathComponents[1]) 138 | else { 139 | print("issue") 140 | return 141 | } 142 | 143 | var tab: Tab = .other 144 | 145 | if (tabId == 1) { 146 | tab = .domains 147 | } else if (tabId == 2) { 148 | tab = .healthchecks 149 | } else if (tabId == 3) { 150 | tab = .integrations 151 | } else if (tabId == 4) { 152 | tab = .profile 153 | } 154 | 155 | selectedTab = tab 156 | } 157 | .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in 158 | print("Moving to the background! didBecomeActiveNotification") 159 | withAnimation { 160 | showPlaceholder = false 161 | } 162 | } 163 | .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in 164 | print("Moving to the background! willResignActiveNotification") 165 | withAnimation { 166 | if (isBiometricEnabled) { 167 | showPlaceholder = true 168 | } 169 | } 170 | } 171 | .onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { _ in 172 | print("Moving to the background! willTerminateNotification") 173 | } 174 | .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in 175 | print("Moving to the background! willEnterForegroundNotification") 176 | withAnimation { 177 | showPlaceholder = false 178 | showDomains = false 179 | 180 | UIApplication.shared.applicationIconBadgeNumber = 0 181 | SetupPrefs.setPreference(mKey: "BADGE_COUNT", mValue: 0) 182 | } 183 | } 184 | .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in 185 | withAnimation { 186 | // if UIDevice.current.userInterfaceIdiom == .pad { 187 | /*DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 188 | _ = Functions().getGridCount(width: geo!.size.width, gridItem: $columnsGrid) 189 | }*/ 190 | // } 191 | } 192 | } 193 | } 194 | 195 | fileprivate func createAccountList() async { 196 | loadAccountInfos() 197 | if (!accountInfosJson.isEmpty) { 198 | do { 199 | let apikey = SetupPrefs.readPreference(mKey: "APIKEY", mDefaultValue: "") as! String 200 | let sdtoken = SetupPrefs.readPreference(mKey: "DEVICETOKEN", mDefaultValue: "") as! String 201 | 202 | let account = Account(ApiKey: apikey, AccountName: accountInfos.email, DeviceToken: sdtoken, Since: accountInfos.reg_date, Active: true) 203 | 204 | accountList.append(account) 205 | 206 | let jsonEncoder = JSONEncoder() 207 | let jsonData = try jsonEncoder.encode(accountList) 208 | let json = String(data: jsonData, encoding: String.Encoding.utf8) 209 | accountListJson = json! 210 | } catch let error { 211 | print(error) 212 | } 213 | } else { 214 | Task { 215 | accountInfos = await api.GetAccountStatus() ?? AccountInfo() 216 | print(accountInfos) 217 | let jsonEncoder = JSONEncoder() 218 | let jsonData = try jsonEncoder.encode(accountInfos) 219 | let json = String(data: jsonData, encoding: String.Encoding.utf8) 220 | accountInfosJson = json! 221 | await createAccountList() 222 | } 223 | } 224 | } 225 | 226 | fileprivate func loadAccountInfos() { 227 | do { 228 | let jsonDecoder = JSONDecoder() 229 | let jsonData = accountInfosJson.data(using: .utf8) 230 | accountInfos = try jsonDecoder.decode(AccountInfo.self, from: jsonData!) 231 | } catch let error { 232 | print(error) 233 | } 234 | } 235 | } 236 | 237 | #Preview { 238 | TabbView(showDomains: .constant(true)) 239 | } 240 | -------------------------------------------------------------------------------- /IPv64/Main/Tabs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tabs.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 08.08.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum Tab: Int, Identifiable, Hashable { 11 | case domains, healthchecks, integrations, profile, blocklist, other 12 | 13 | var id: Int { 14 | rawValue 15 | } 16 | 17 | static func tabList() -> [Tab] { 18 | // if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { 19 | // return [.timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings] 20 | // } else { 21 | // return [.timeline, .notifications, .explore, .messages, .profile] 22 | // } 23 | return [.domains, .healthchecks, .integrations, .blocklist, .profile] 24 | } 25 | 26 | @ViewBuilder 27 | func makeContentView(popToRootTab: Binding) -> some View { 28 | switch self { 29 | case .domains: 30 | ContentView(popToRootTab: popToRootTab) 31 | case .healthchecks: 32 | HealthcheckView(popToRootTab: popToRootTab) 33 | case .integrations: 34 | IntegrationView(popToRootTab: popToRootTab) 35 | case .profile: 36 | ProfilView(popToRootTab: popToRootTab) 37 | case .blocklist: 38 | BlocklistView(popToRootTab: popToRootTab) 39 | case .other: 40 | EmptyView() 41 | } 42 | } 43 | 44 | @ViewBuilder 45 | var label: some View { 46 | switch self { 47 | case .domains: 48 | Label(labelName, systemImage: iconName) 49 | case .healthchecks: 50 | Label(labelName, systemImage: iconName) 51 | case .integrations: 52 | Label(labelName, systemImage: iconName) 53 | case .profile: 54 | Label(labelName, systemImage: iconName) 55 | case .blocklist: 56 | Label(labelName, systemImage: iconName) 57 | case .other: 58 | EmptyView() 59 | } 60 | } 61 | 62 | var iconName: String { 63 | switch self { 64 | case .domains: 65 | return "network" 66 | case .healthchecks: 67 | return "bolt.heart" 68 | case .integrations: 69 | return "bell.and.waveform" 70 | case .profile: 71 | return "person.circle" 72 | case .blocklist: 73 | return "shield.lefthalf.filled" 74 | case .other: 75 | return "" 76 | } 77 | } 78 | 79 | var labelName: String { 80 | switch self { 81 | case .domains: 82 | return "Domains" 83 | case .healthchecks: 84 | return "Health" 85 | case .integrations: 86 | return "Mitteilungen" 87 | case .profile: 88 | return "Account" 89 | case .blocklist: 90 | return "Blocklist" 91 | case .other: 92 | return "" 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /IPv64/Main/WhatsNew/WhatsNewItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhatsNewItemView.swift 3 | // Treibholz 4 | // 5 | // Created by Sebastian Rank on 16.02.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WhatsNewItemView: View { 11 | 12 | @State var whatsNewItem: WhatsNewObj 13 | 14 | var body: some View { 15 | VStack(alignment: .leading) { 16 | HStack(alignment: .top) { 17 | Image(systemName: whatsNewItem.imageName) 18 | .resizable() 19 | .scaledToFit() 20 | .frame(width: 30, height: 30) 21 | .symbolRenderingMode(.hierarchical) 22 | .padding(.horizontal) 23 | .foregroundColor(Color("ip64_color")) 24 | VStack(alignment: .leading, spacing: 5) { 25 | Text(whatsNewItem.title) 26 | .font(.system(.headline, design: .rounded)) 27 | .fontWeight(.bold) 28 | Text(whatsNewItem.subtitle) 29 | .font(.system(.footnote, design: .rounded)) 30 | .foregroundColor(Color("primaryText")) 31 | } 32 | .padding(.trailing) 33 | .frame(maxWidth: .infinity, alignment: .leading) 34 | } 35 | .padding(.horizontal) 36 | .padding(.bottom) 37 | .frame(maxWidth: .infinity) 38 | } 39 | } 40 | } 41 | 42 | struct WhatsNewItemView_Previews: PreviewProvider { 43 | static var previews: some View { 44 | WhatsNewItemView(whatsNewItem: WhatsNewObj(imageName: "sparkles", title: "Was ist Neu?", subtitle: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren")) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /IPv64/Main/WhatsNew/WhatsNewView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhatsNewView.swift 3 | // Treibholz 4 | // 5 | // Created by Sebastian Rank on 16.02.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WhatsNewView: View { 11 | @Binding var activeSheet: ActiveSheet? 12 | @Binding var isPresented: Bool 13 | var isFromSetting = false 14 | 15 | var whatsNewList = [ 16 | WhatsNewObj(imageName: "ant.circle", title: "Fehlerbehebung", subtitle: "Es wurden in diesem Update ein Paar Fehler behoben, die zur Steigerung der Performance und der Nutzbarkeit dienen"), 17 | WhatsNewObj(imageName: "person.circle", title: "Multi-User Support", subtitle: "Ihr könnt jetzt mehr als 1 Account in der App hinterlegen um Private & Firmenaccount über ein Gerät zuverwalten!"), 18 | WhatsNewObj(imageName: "switch.2", title: "Toggle hinzugefügt", subtitle: "Es wurde ein neuer Toggle Switch hinzugefügt der den Tabsound in der Navigationleiste steuert!") 19 | ] 20 | 21 | var body: some View { 22 | VStack { 23 | VStack { 24 | Text("Was ist neu in") 25 | .font(.system(.largeTitle, design: .rounded)) 26 | .fontWeight(.bold) 27 | .frame(maxWidth: .infinity, alignment: .center) 28 | Text("v\(Bundle.main.versionNumber)?") 29 | .font(.system(.largeTitle, design: .rounded)) 30 | .fontWeight(.bold) 31 | .frame(maxWidth: .infinity, alignment: .center) 32 | } 33 | .padding(.vertical, 27.5) 34 | 35 | Spacer() 36 | 37 | ScrollView { 38 | ForEach(whatsNewList, id: \.id) { item in 39 | WhatsNewItemView(whatsNewItem: item) 40 | } 41 | } 42 | 43 | Button(action: { 44 | withAnimation { 45 | let buildNumber = Bundle.main.buildNumber 46 | SetupPrefs.setPreference(mKey: "LASTBUILDNUMBER", mValue: buildNumber) 47 | activeSheet = nil 48 | } 49 | }) { 50 | Text(isFromSetting ? "Schließen" : "Fortfahren").font(.headline) 51 | .foregroundColor(.white) 52 | .padding() 53 | .frame(maxWidth: .infinity, minHeight: 50, idealHeight: 50, maxHeight: 50, alignment: .center) 54 | .background(Color("ip64_color")) 55 | .cornerRadius(16.0) 56 | } 57 | .padding(.horizontal, 27.5) 58 | .padding(.bottom, UIDevice.isIPad ? 27.5 : 8) 59 | }.interactiveDismissDisabled(!isFromSetting) 60 | } 61 | } 62 | 63 | struct WhatsNewView_Previews: PreviewProvider { 64 | static var previews: some View { 65 | WhatsNewView(activeSheet: .constant(nil), isPresented: .constant(false)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /IPv64/Models&Helpers/Biometrics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Biometrics.swift 3 | // 4 | // 5 | // Created by Sebastian Rank on 05.04.22. 6 | // 7 | 8 | import Foundation 9 | import LocalAuthentication 10 | 11 | class Biometrics: ObservableObject { 12 | 13 | @Published var isAuthenticated = false 14 | @Published var disableFields = true 15 | @Published private(set) var error: Error? 16 | 17 | static func GetBiometricSymbol() -> String { 18 | if (LAContext().biometryType == .faceID) { 19 | return "faceid" 20 | } else if (LAContext().biometryType == .touchID) { 21 | return "touchid" 22 | } else { 23 | return "lock.circle" 24 | } 25 | } 26 | 27 | static func GetBiometricText() -> String { 28 | if (LAContext().biometryType == .faceID) { 29 | return "FaceID" 30 | } else if (LAContext().biometryType == .touchID) { 31 | return "TouchID" 32 | } else { 33 | return "PIN" 34 | } 35 | } 36 | 37 | func reset() { 38 | isAuthenticated = false 39 | disableFields = true 40 | } 41 | 42 | func tryToAuthenticate(isIntro: Bool = false) { 43 | let context = LAContext() 44 | var error: NSError? 45 | let reason = "Bitte authentifiziere dich, um mit der App fortzufahren." 46 | 47 | guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else { 48 | print("No Biometric Sensor Has Been Detected. This device does not support FaceID/TouchID.") 49 | self.disableFields = false 50 | tryToAuthenticatePIN() 51 | return 52 | } 53 | 54 | context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply: { (success, error) -> Void in 55 | DispatchQueue.main.async { 56 | self.error = error 57 | if success { 58 | print("FaceID/TouchID. You are a device owner!") 59 | self.isAuthenticated = true 60 | } else { 61 | print(error!) 62 | // Check if there is an error 63 | if let errorObj = error as? LAError { 64 | print("Error took place. \(errorObj.localizedDescription)") 65 | if errorObj.code == .userCancel { 66 | self.isAuthenticated = false 67 | self.disableFields = false 68 | } 69 | if errorObj.code == .userFallback { 70 | self.tryToAuthenticatePIN() 71 | } 72 | } 73 | } 74 | } 75 | }) 76 | } 77 | 78 | func tryToAuthenticatePIN() { 79 | let context = LAContext() 80 | var error: NSError? 81 | let reason = "Bitte authentifiziere dich, um mit der App fortzufahren." 82 | 83 | guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else { 84 | print("No Biometric Sensor Has Been Detected. This device does not support FaceID/TouchID.") 85 | self.disableFields = false 86 | return 87 | } 88 | 89 | context.evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: reason, reply: { (success, error) -> Void in 90 | DispatchQueue.main.async { 91 | self.error = error 92 | if success { 93 | print("FaceID/TouchID. You are a device owner!") 94 | self.isAuthenticated = true 95 | } else { 96 | print(error!) 97 | // Check if there is an error 98 | if let errorObj = error as? LAError { 99 | print("Error took place. \(errorObj.localizedDescription)") 100 | if errorObj.code == .userCancel { 101 | self.isAuthenticated = false 102 | self.disableFields = false 103 | } 104 | if errorObj.code == .userFallback { 105 | self.isAuthenticated = false 106 | self.disableFields = false 107 | } 108 | } 109 | } 110 | } 111 | }) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /IPv64/Models&Helpers/HapticManager.swift: -------------------------------------------------------------------------------- 1 | import CoreHaptics 2 | import UIKit 3 | 4 | public class HapticManager { 5 | public static let shared: HapticManager = .init() 6 | 7 | public enum HapticType { 8 | case buttonPress 9 | case dataRefresh(intensity: CGFloat) 10 | case notification(_ type: UINotificationFeedbackGenerator.FeedbackType) 11 | case tabSelection 12 | case timeline 13 | } 14 | 15 | private let selectionGenerator = UISelectionFeedbackGenerator() 16 | private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy) 17 | private let notificationGenerator = UINotificationFeedbackGenerator() 18 | 19 | private let userPrefs = UserPrefs.shared 20 | 21 | private init() { 22 | selectionGenerator.prepare() 23 | impactGenerator.prepare() 24 | } 25 | 26 | @MainActor 27 | public func fireHaptic(of type: HapticType) { 28 | guard supportsHaptics else { return } 29 | 30 | switch type { 31 | case .buttonPress: 32 | if userPrefs.hapticButtonPressEnabled { 33 | impactGenerator.impactOccurred() 34 | } 35 | case let .dataRefresh(intensity): 36 | if userPrefs.hapticTimelineEnabled { 37 | impactGenerator.impactOccurred(intensity: intensity) 38 | } 39 | case let .notification(type): 40 | if userPrefs.hapticButtonPressEnabled { 41 | notificationGenerator.notificationOccurred(type) 42 | } 43 | case .tabSelection: 44 | if userPrefs.hapticTabSelectionEnabled { 45 | selectionGenerator.selectionChanged() 46 | } 47 | case .timeline: 48 | if userPrefs.hapticTimelineEnabled { 49 | selectionGenerator.selectionChanged() 50 | } 51 | } 52 | } 53 | 54 | public var supportsHaptics: Bool { 55 | CHHapticEngine.capabilitiesForHardware().supportsHaptics 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /IPv64/Models&Helpers/SetupPrefs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetupPrefs.swift 3 | // IPv64 4 | // 5 | // Created by Sebastian Rank on 06.11.22. 6 | // 7 | 8 | import Foundation 9 | 10 | class SetupPrefs { 11 | 12 | static let preferences = UserDefaults(suiteName:"group.ipv64.net") 13 | static let preferencesStandard = UserDefaults.standard 14 | 15 | static func setPreference(mKey: String, mValue: Any) { 16 | preferences!.set(mValue, forKey: mKey) 17 | preferences!.synchronize() 18 | } 19 | 20 | static func readPreference(mKey: String, mDefaultValue: Any) -> Any { 21 | if preferences!.object(forKey: mKey) == nil { 22 | return mDefaultValue; 23 | } else { 24 | return preferences!.object(forKey: mKey) as Any 25 | } 26 | } 27 | 28 | static func readPreferenceStandard(mKey: String, mDefaultValue: Any) -> Any { 29 | if preferencesStandard.object(forKey: mKey) == nil { 30 | return mDefaultValue; 31 | } else { 32 | return preferencesStandard.object(forKey: mKey) as Any 33 | } 34 | } 35 | 36 | static func getArray(mKey: String, mType: String) -> Any { 37 | if mType.contains("string") { 38 | return preferences!.stringArray(forKey: mKey) ?? [String]() 39 | } else if mType.contains("int") { 40 | return preferences!.array(forKey: mKey) as? [Int] ?? [Int]() 41 | } else if mType.contains("date") { 42 | return preferences!.array(forKey: mKey) as? [Bool] ?? [Bool]() 43 | } else if mType.contains("bool") { 44 | return preferences!.array(forKey: mKey) as? [Date] ?? [Date]() 45 | } else { 46 | return [] 47 | } 48 | } 49 | 50 | static func deletePreference(mKey: String) { 51 | preferences!.removeObject(forKey: mKey) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /IPv64/Models&Helpers/SoundEffectManager.swift: -------------------------------------------------------------------------------- 1 | import AVKit 2 | import CoreHaptics 3 | import UIKit 4 | 5 | public class SoundEffectManager { 6 | public static let shared: SoundEffectManager = .init() 7 | 8 | public enum SoundEffect: String { 9 | case pull, refresh 10 | case tootSent 11 | case tabSelection 12 | case bookmark, boost, favorite, share 13 | } 14 | 15 | private let userPrefs = UserPrefs.shared 16 | 17 | private var currentPlayer: AVAudioPlayer? 18 | 19 | private init() {} 20 | 21 | @MainActor 22 | public func playSound(of type: SoundEffect) { 23 | guard userPrefs.soundEffectEnabled else { return } 24 | if let url = Bundle.main.url(forResource: type.rawValue, withExtension: "wav") { 25 | try? AVAudioSession.sharedInstance().setCategory(.ambient) 26 | try? AVAudioSession.sharedInstance().setActive(true) 27 | currentPlayer = try? .init(contentsOf: url) 28 | currentPlayer?.prepareToPlay() 29 | currentPlayer?.play() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /IPv64/Models&Helpers/UserPrefs.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import Foundation 3 | import Network 4 | import SwiftUI 5 | 6 | @MainActor 7 | public class UserPrefs: ObservableObject { 8 | 9 | // public static let groupContainer = "group.sr.angelninde" 10 | // public static let sharedDefault = UserDefaults(suiteName: groupContainer) 11 | public static let shared = UserPrefs() 12 | 13 | @AppStorage("haptic_tab") public var hapticTabSelectionEnabled = true 14 | @AppStorage("haptic_timeline") public var hapticTimelineEnabled = true 15 | @AppStorage("haptic_button_press") public var hapticButtonPressEnabled = true 16 | @AppStorage("sound_effect_enabled") public var soundEffectEnabled = true 17 | 18 | } 19 | -------------------------------------------------------------------------------- /IPv64/Preview Content/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidseb25/IPv64-iOS/b468343fd3da6b270d8c417fae1319172889e682/IPv64/Preview Content/.DS_Store -------------------------------------------------------------------------------- /IPv64/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /NotificationService/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.usernotifications.service 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).NotificationService 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /NotificationService/NotificationService.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.ipv64.net 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /NotificationService/NotificationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationService.swift 3 | // NotificationService 4 | // 5 | // Created by Sebastian Rank on 02.02.23. 6 | // 7 | 8 | import UserNotifications 9 | 10 | class NotificationService: UNNotificationServiceExtension { 11 | 12 | var contentHandler: ((UNNotificationContent) -> Void)? 13 | var bestAttemptContent: UNMutableNotificationContent? 14 | 15 | override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { 16 | self.contentHandler = contentHandler 17 | bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) 18 | 19 | if let bestAttemptContent = bestAttemptContent { 20 | // Modify the notification content here... 21 | bestAttemptContent.title = "\(bestAttemptContent.title)" 22 | var badgeCount = SetupPrefs.readPreference(mKey: "BADGE_COUNT", mDefaultValue: 0) as! Int 23 | badgeCount = badgeCount + 1 24 | bestAttemptContent.badge = (badgeCount) as NSNumber 25 | SetupPrefs.setPreference(mKey: "BADGE_COUNT", mValue: badgeCount) 26 | var isImageAvailable = true 27 | guard let body = bestAttemptContent.userInfo["fcm_options"] as? Dictionary, let imageUrl = body["image"] as? String 28 | else { 29 | isImageAvailable = false 30 | return 31 | //fatalError("Image Link not found") 32 | } 33 | 34 | if (isImageAvailable) { 35 | print(imageUrl) 36 | downloadImageFrom(url: imageUrl) { (attachment) in 37 | if let attachment = attachment { 38 | bestAttemptContent.attachments = [attachment] 39 | //bestAttemptContent.categoryIdentifier = "myNotificationCategory" // Comment it for now 40 | contentHandler(bestAttemptContent) 41 | } 42 | } 43 | } else { 44 | contentHandler(bestAttemptContent) 45 | } 46 | } 47 | } 48 | 49 | override func serviceExtensionTimeWillExpire() { 50 | // Called just before the extension will be terminated by the system. 51 | // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. 52 | if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { 53 | contentHandler(bestAttemptContent) 54 | } 55 | } 56 | 57 | private func downloadImageFrom(url: String, handler: @escaping (UNNotificationAttachment?) -> Void) { 58 | let task = URLSession.shared.downloadTask(with: URL(string: url)!) { (downloadedUrl, response, error) in 59 | guard let downloadedUrl = downloadedUrl else { handler(nil) ; return } // RETURNING NIL IF IMAGE NOT FOUND 60 | var urlPath = URL(fileURLWithPath: NSTemporaryDirectory()) 61 | let uniqueUrlEnding = ProcessInfo.processInfo.globallyUniqueString + ".jpg" 62 | urlPath = urlPath.appendingPathComponent(uniqueUrlEnding) 63 | try? FileManager.default.moveItem(at: downloadedUrl, to: urlPath) 64 | do { 65 | let attachment = try UNNotificationAttachment(identifier: "picture", url: urlPath, options: nil) 66 | handler(attachment) // RETURNING ATTACHEMENT HAVING THE IMAGE URL SUCCESSFULLY 67 | } catch { 68 | print("attachment error") 69 | handler(nil) 70 | } 71 | } 72 | task.resume() 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Action Build](https://github.com/androidseb25/IPv64-iOS/actions/workflows/ios.yml/badge.svg?branch=androidseb25-patch-1) 2 | ![Stars](https://img.shields.io/github/stars/androidseb25/IPv64-iOS?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAEsWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgdGlmZjpJbWFnZUxlbmd0aD0iNDgiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNDgiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjcyLzEiCiAgIHRpZmY6WVJlc29sdXRpb249IjcyLzEiCiAgIGV4aWY6UGl4ZWxYRGltZW5zaW9uPSI0OCIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjQ4IgogICBleGlmOkNvbG9yU3BhY2U9IjEiCiAgIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiCiAgIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjMtMDQtMTlUMTc6NDE6MDIrMDI6MDAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMDQtMTlUMTc6NDE6MDIrMDI6MDAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJwcm9kdWNlZCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWZmaW5pdHkgUGhvdG8gMiAyLjAuNCIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMy0wNC0xOVQxNzo0MTowMiswMjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+0oDKYwAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHfK4NRGMc/hiamKS5cuFgaV5tmanGjTEItaaYMN9u7X2o/3t53S8utcqsocePXBX8Bt8q1UkRKbrkmbliv591WW7Ln9Jznc77nPE/nPAcsobSS0Vs8kMnmteC037EUXnZYX2mhGzuDWCOKrk7MzwdoaF8PNJnxzm3WanzuX+uIxXUFmtqExxVVywvPCAfW86rJu8I9SioSEz4XdmlyQeF7U49W+M3kZIV/TNZCwUmwdAk7knUcrWMlpWWE5eU4M+mCUr2P+RJbPLu4ILFfvA+dINP4cTDLFJP4GGZMZh9uvAzJigb5nnL+HDnJVWRWKaKxRpIUeVyiFqR6XGJC9LiMNEWz/3/7qidGvJXqNj+0vhjGxwBYd6C0bRjfx4ZROoHmZ7jK1vJzRzD6Kfp2TXMegn0TLq5rWnQPLreg90mNaJGy1CxuSSTg/Qw6w9B9C+0rlZ5V9zl9hNCGfNUN7B/AoJy3r/4CMN5nzbh35xIAAAAJcEhZcwAACxMAAAsTAQCanBgAAAGYSURBVGiB7VjtjcMgDH2cbhx2KJ2syWSlO2Qf+sd3iji+7NhJT+JJVSKCsY39sCkwMTExMTFhhLRhSRvC1XaIQMYn+i1Wer6sFgZw270/rJSYOEBpE7KxxUKXVQTMdjyH016Qdv9Z+Xx3HlFTn0UEWrsftJVZRCA1FXpdnaoRGDnztcmsuhtpwxMDaaIZBbUIlI7OxtxFSy9rJ7IU2b/fwCNopOerMAbOSVV1gIx9MA3TRqTnWnPquyF8tfHI9MfShBYHXo1vZ6NqS5MDRLbT2oIKVufrpO+S+GInmsYDg6dQp7+xQtd4gHGMnuzEcNPHroij1VaI6DzuHAF2JSYFkSs3gJVrPCBsJUjRKpGtYCjfSzjSC8UDsmprWV7qOQhSwSMOXF3gABy4D/RuXmxDhHcEUQQ+6d+2T+GA+JIjdYCT/1GoYwiWEVidh2PUjFt/yl+IiNMhcLEojdzwJERmR6BD4GpFdf63z6m2IhIeSFIoFMZ+0qVrwM4RlVbkKAeGDc9BMnk0YmmuOtKGoFkLtNebmJj4R3gDsdt6T8W+vnEAAAAASUVORK5CYII=) 3 | ![Last Commit](https://img.shields.io/github/last-commit/androidseb25/IPv64-iOS?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAEsWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgdGlmZjpJbWFnZUxlbmd0aD0iNDgiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNDgiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjcyLzEiCiAgIHRpZmY6WVJlc29sdXRpb249IjcyLzEiCiAgIGV4aWY6UGl4ZWxYRGltZW5zaW9uPSI0OCIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjQ4IgogICBleGlmOkNvbG9yU3BhY2U9IjEiCiAgIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiCiAgIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjMtMDQtMTlUMTc6NDM6NTYrMDI6MDAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMDQtMTlUMTc6NDM6NTYrMDI6MDAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJwcm9kdWNlZCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWZmaW5pdHkgUGhvdG8gMiAyLjAuNCIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMy0wNC0xOVQxNzo0Mzo1NiswMjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+k15mlAAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHfK4NRGMc/hiamKS5cuFgaV5tmanGjTEItaaYMN9u7X2o/3t53S8utcqsocePXBX8Bt8q1UkRKbrkmbliv591WW7Ln9Jznc77nPE/nPAcsobSS0Vs8kMnmteC037EUXnZYX2mhGzuDWCOKrk7MzwdoaF8PNJnxzm3WanzuX+uIxXUFmtqExxVVywvPCAfW86rJu8I9SioSEz4XdmlyQeF7U49W+M3kZIV/TNZCwUmwdAk7knUcrWMlpWWE5eU4M+mCUr2P+RJbPLu4ILFfvA+dINP4cTDLFJP4GGZMZh9uvAzJigb5nnL+HDnJVWRWKaKxRpIUeVyiFqR6XGJC9LiMNEWz/3/7qidGvJXqNj+0vhjGxwBYd6C0bRjfx4ZROoHmZ7jK1vJzRzD6Kfp2TXMegn0TLq5rWnQPLreg90mNaJGy1CxuSSTg/Qw6w9B9C+0rlZ5V9zl9hNCGfNUN7B/AoJy3r/4CMN5nzbh35xIAAAAJcEhZcwAACxMAAAsTAQCanBgAAAHUSURBVGiB7VlJksMgDBRT86+Ql5m8DOdlmkOILSssEhZJpoq+hLJB3S1WE4CJiYmJT8JZBkNEDwALeeTT75p+7wAAzrlgyVsVhIgREYuEpI4WoRbXysAmzFD4+4xQFvY8FMTEZMzjYzhRsz61K5m2N5EzUBAQqWBh7FwSwlADGfFq4RkObiSaiE/Budgh2RpmItPFY7oasibOcxTER8xM1Eb7ar2KCVE7rYEcXoiYmKDgpO2aQ+mn8X4V8nphvSbYLt3s5d9GsGvK3qVS7eacW4X6pLgCwDP7C1QSWTUA8MZzy5FzxX3r8bW6rSH0SazPQm0YfbOBGykvpUrfbECEaUCI2iqWBVvZvJqRbChB3Ri2IzRqNqVMjA09Avob7zG6D2ksAfrDHR5Pn14dYI/TZUJqYPgcSBshXRK90IQn5bua+HQXvsZT9QQbAaGH8DAJe4WzmOIzvwm31TxgMZvHbEkdKZl5LxCBvvJe1EtSsnNjUc9nk30SkG9I54OWuWw/KUngOCTwkcP+o54RcBNmBMPFEyJTE5i/X7W72BISdhnJZH1c5oXkmJ6/LJFI7pEKCXifeIEJLSI3/F+MmAi3/osppOIFjqfJlZRH3CNNTExMfAh/ovJ1tC/1YnkAAAAASUVORK5CYII=) 4 | 5 | # IPv64.net iOS App 6 | 7 | Das ist die dazugehörige App zum DynDNS - Service von Dennis Schröder für Apple Geräte. 8 | 9 | https://ipv64.net 10 | 11 | 12 | ## Documentation 13 | 14 | 1. Was benötigst du zum Nutzen der App? 15 | Nur ein iOS Gerät mit einer Version von iOS15/iPadOS15 oder höher und die App TestFlight ([AppStore](https://apps.apple.com/de/app/testflight/id899247664)) 16 | 17 | 2. Du musst der [Einladung folgen](https://testflight.apple.com/join/DdBc50Kl) und die App IPv64.net installieren. 18 | 19 | 3. Desweiteren brauchst du deinen API-Key von deinem Konto, damit du dich anmelden kannst. Diesen scannst du einfach auf der Webseite. 20 | 21 | 4. Fertig! Du hast es geschafft viel Spaß bei mit der App :D 22 | 23 | ## Feedback 24 | 25 | Hast du Anregungen oder Feedback zur App melde dich bei mir im Discord oder schreibe ein Issue. 26 | 27 | ## License 28 | 29 | Diese App ist mithilfe der Community von RaspberryPi Cloud entstanden. 30 | Alle Rechte vorbehalten, bei Dennis Schröder. 31 | 32 | [Datenschutz IPv64.net](https://ipv64.net/datenschutz.php) 33 | 34 | Weitere Information findest du unter [ipv64.net]("https://ipv64.net") 35 | --------------------------------------------------------------------------------