mirror of
https://github.com/jkcoxson/LocalDevVPN.git
synced 2026-03-02 06:26:16 +01:00
Add Apple TV support and Fix UI
This commit is contained in:
@@ -5,7 +5,7 @@ MARKETING_VERSION = 1.1.1
|
|||||||
CURRENT_PROJECT_VERSION = 1
|
CURRENT_PROJECT_VERSION = 1
|
||||||
|
|
||||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
DEVELOPMENT_TEAM = 95J8WZ4TN8
|
||||||
ORG_IDENTIFIER = com.stossy11
|
ORG_IDENTIFIER = com.stossy11
|
||||||
|
|
||||||
// Codesigning settings defined optionally, see `CodeSigning.xcconfig.example`
|
// Codesigning settings defined optionally, see `CodeSigning.xcconfig.example`
|
||||||
|
|||||||
@@ -417,9 +417,13 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||||
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -440,8 +444,12 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||||
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -455,8 +463,12 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(TUNNEL_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(TUNNEL_BUNDLE_IDENTIFIER)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||||
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -470,8 +482,12 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(TUNNEL_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(TUNNEL_BUNDLE_IDENTIFIER)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||||
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|||||||
Binary file not shown.
@@ -533,7 +533,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.navigationTitle("StosVPN")
|
.navigationTitle("StosVPN")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.tvOSNavigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
@@ -559,6 +559,17 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
@ViewBuilder
|
||||||
|
func tvOSNavigationBarTitleDisplayMode(_ displayMode: NavigationBarItem.TitleDisplayMode) -> some View {
|
||||||
|
#if os(iOS)
|
||||||
|
self.navigationBarTitleDisplayMode(displayMode)
|
||||||
|
#else
|
||||||
|
self
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct StatusIndicatorView: View {
|
struct StatusIndicatorView: View {
|
||||||
@StateObject private var tunnelManager = TunnelManager.shared
|
@StateObject private var tunnelManager = TunnelManager.shared
|
||||||
@@ -776,6 +787,7 @@ struct SettingsView: View {
|
|||||||
@AppStorage("autoConnect") private var autoConnect = false
|
@AppStorage("autoConnect") private var autoConnect = false
|
||||||
@AppStorage("shownTunnelAlert") private var shownTunnelAlert = false
|
@AppStorage("shownTunnelAlert") private var shownTunnelAlert = false
|
||||||
@StateObject private var tunnelManager = TunnelManager.shared
|
@StateObject private var tunnelManager = TunnelManager.shared
|
||||||
|
@AppStorage("hasNotCompletedSetup") private var hasNotCompletedSetup = true
|
||||||
|
|
||||||
@State private var showNetworkWarning = false
|
@State private var showNetworkWarning = false
|
||||||
|
|
||||||
@@ -843,7 +855,7 @@ struct SettingsView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.navigationTitle(Text("settings"))
|
.navigationTitle(Text("settings"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.tvOSNavigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button("done") {
|
Button("done") {
|
||||||
@@ -920,7 +932,7 @@ struct DataCollectionInfoView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
.navigationTitle(Text("data_collection_policy_nav"))
|
.navigationTitle(Text("data_collection_policy_nav"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.tvOSNavigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -932,7 +944,7 @@ struct ConnectionLogView: View {
|
|||||||
.font(.system(.body, design: .monospaced))
|
.font(.system(.body, design: .monospaced))
|
||||||
}
|
}
|
||||||
.navigationTitle(Text("logs_nav"))
|
.navigationTitle(Text("logs_nav"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.tvOSNavigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1023,7 +1035,7 @@ struct HelpView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(Text("help_and_support_nav"))
|
.navigationTitle(Text("help_and_support_nav"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.tvOSNavigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1078,6 +1090,8 @@ struct SetupView: View {
|
|||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.blue)
|
||||||
|
.foregroundColor(.white)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
@@ -1091,14 +1105,17 @@ struct SetupView: View {
|
|||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.blue)
|
||||||
|
.foregroundColor(.white)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.navigationTitle(Text("setup_nav"))
|
.navigationTitle(Text("setup_nav"))
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.tvOSNavigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button("setup_skip") { hasNotCompletedSetup = false; dismiss() }
|
Button("setup_skip") { hasNotCompletedSetup = false; dismiss() }
|
||||||
@@ -1123,25 +1140,25 @@ struct SetupPageView: View {
|
|||||||
let page: SetupPage
|
let page: SetupPage
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 30) {
|
VStack(spacing: tvOSSpacing) {
|
||||||
Image(systemName: page.imageName)
|
Image(systemName: page.imageName)
|
||||||
.font(.system(size: 80))
|
.font(.system(size: tvOSImageSize))
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
.padding(.top, 50)
|
.padding(.top, tvOSTopPadding)
|
||||||
|
|
||||||
Text(page.title)
|
Text(page.title)
|
||||||
.font(.title)
|
.font(tvOSTitleFont)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
Text(page.description)
|
Text(page.description)
|
||||||
.font(.headline)
|
.font(tvOSDescriptionFont)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
Text(page.details)
|
Text(page.details)
|
||||||
.font(.body)
|
.font(tvOSBodyFont)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
@@ -1150,8 +1167,58 @@ struct SetupPageView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Conditional sizes for tvOS
|
||||||
|
private var tvOSImageSize: CGFloat {
|
||||||
|
#if os(tvOS)
|
||||||
|
return 60
|
||||||
|
#else
|
||||||
|
return 80
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tvOSTopPadding: CGFloat {
|
||||||
|
#if os(tvOS)
|
||||||
|
return 30
|
||||||
|
#else
|
||||||
|
return 50
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tvOSSpacing: CGFloat {
|
||||||
|
#if os(tvOS)
|
||||||
|
return 20
|
||||||
|
#else
|
||||||
|
return 30
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tvOSTitleFont: Font {
|
||||||
|
#if os(tvOS)
|
||||||
|
return .headline //.system(size: 35).bold()
|
||||||
|
#else
|
||||||
|
return .title
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tvOSDescriptionFont: Font {
|
||||||
|
#if os(tvOS)
|
||||||
|
return .subheadline
|
||||||
|
#else
|
||||||
|
return .headline
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tvOSBodyFont: Font {
|
||||||
|
#if os(tvOS)
|
||||||
|
return .system(size: 18).bold()
|
||||||
|
#else
|
||||||
|
return .body
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LanguageManager: ObservableObject {
|
class LanguageManager: ObservableObject {
|
||||||
static let shared = LanguageManager()
|
static let shared = LanguageManager()
|
||||||
|
|
||||||
@@ -1172,6 +1239,54 @@ class LanguageManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
@ViewBuilder
|
||||||
|
func GroupBox<Content: View>(
|
||||||
|
label: some View,
|
||||||
|
@ViewBuilder content: @escaping () -> Content
|
||||||
|
) -> some View {
|
||||||
|
#if os(tvOS)
|
||||||
|
tvOSGroupBox(label: {
|
||||||
|
label
|
||||||
|
}, content: content)
|
||||||
|
#else
|
||||||
|
SwiftUI.GroupBox(label: label, content: content)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tvOSGroupBox<Label: View, Content: View>: View {
|
||||||
|
@ViewBuilder let label: () -> Label
|
||||||
|
@ViewBuilder let content: () -> Content
|
||||||
|
|
||||||
|
init(
|
||||||
|
@ViewBuilder label: @escaping () -> Label,
|
||||||
|
@ViewBuilder content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
self.label = label
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
label()
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
content()
|
||||||
|
.padding(.top, 4)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||||
|
.fill(.secondary)
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||||
|
.stroke(.separator, lineWidth: 0.5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentView()
|
ContentView()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user