Add Apple TV support and Fix UI

This commit is contained in:
Stossy11
2025-10-02 14:38:54 +10:00
parent b83ab41a5a
commit 7114b86f7d
4 changed files with 144 additions and 13 deletions

View File

@@ -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`

View File

@@ -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;
}; };

View File

@@ -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()
} }