├── .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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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 | 
2 | 
3 | 
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 |
--------------------------------------------------------------------------------