Add support for tvOS

This commit is contained in:
世界
2023-07-29 09:44:31 +08:00
parent 5a8f3a770f
commit bb0385f53c
63 changed files with 1542 additions and 389 deletions
+12
View File
@@ -0,0 +1,12 @@
import SwiftUI
@main
struct Application: App {
@UIApplicationDelegateAdaptor private var appDelegate: ApplicationDelegate
var body: some Scene {
WindowGroup {
MainView()
}
}
}
+21
View File
@@ -0,0 +1,21 @@
import ApplicationLibrary
import Foundation
import Libbox
import Library
import UIKit
class ApplicationDelegate: NSObject, UIApplicationDelegate {
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
NSLog("Here I stand")
LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, true)
Task.detached {
do {
try await UIProfileUpdateTask.setup()
NSLog("setup background task success")
} catch {
NSLog("setup background task error: \(error.localizedDescription)")
}
}
return true
}
}
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "tv - App Icon - AppStore - Back -1280 x 768 pt.png",
"idiom" : "tv"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,17 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.imagestacklayer"
},
{
"filename" : "Middle.imagestacklayer"
},
{
"filename" : "Back.imagestacklayer"
}
]
}
@@ -0,0 +1,11 @@
{
"images" : [
{
"idiom" : "tv"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "tv - App Icon - AppStore - Front -1280 x 768 pt.png",
"idiom" : "tv"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,17 @@
{
"images" : [
{
"filename" : "tv- App Icon Small - Back - 400 x 240 pt.png",
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,17 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.imagestacklayer"
},
{
"filename" : "Middle.imagestacklayer"
},
{
"filename" : "Back.imagestacklayer"
}
]
}
@@ -0,0 +1,16 @@
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,17 @@
{
"images" : [
{
"filename" : "tv- App Icon Small - Front - 400 x 240 pt.png",
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,32 @@
{
"assets" : [
{
"filename" : "App Icon - App Store.imagestack",
"idiom" : "tv",
"role" : "primary-app-icon",
"size" : "1280x768"
},
{
"filename" : "App Icon.imagestack",
"idiom" : "tv",
"role" : "primary-app-icon",
"size" : "400x240"
},
{
"filename" : "Top Shelf Image Wide.imageset",
"idiom" : "tv",
"role" : "top-shelf-image-wide",
"size" : "2320x720"
},
{
"filename" : "Top Shelf Image.imageset",
"idiom" : "tv",
"role" : "top-shelf-image",
"size" : "1920x720"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,16 @@
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,16 @@
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
+63
View File
@@ -0,0 +1,63 @@
import ApplicationLibrary
import Library
import SwiftUI
struct ContentView: View {
@Environment(\.selection) private var selection
@Environment(\.extensionProfile) private var extensionProfile
var body: some View {
viewBuilder {
if let profile = extensionProfile.wrappedValue {
ContentView0().environmentObject(profile)
} else {
ContentView1()
}
}
}
struct ContentView0: View {
@Environment(\.selection) private var selection
@EnvironmentObject private var extensionProfile: ExtensionProfile
var body: some View {
TabView(selection: selection) {
ForEach(NavigationPage.allCases.filter { it in
it.visible(extensionProfile)
}, id: \.self) { page in
NavigationView {
page.contentView
.focusSection()
}
.navigationViewStyle(.stack)
.tag(page)
.tabItem { page.label }
}
}.onChange(of: extensionProfile.status) { _ in
if !selection.wrappedValue.visible(extensionProfile) {
selection.wrappedValue = NavigationPage.dashboard
}
}
}
}
struct ContentView1: View {
@Environment(\.selection) private var selection
var body: some View {
TabView(selection: selection) {
ForEach(NavigationPage.allCases.filter { it in
it.visible(nil)
}, id: \.self) { page in
NavigationView {
page.contentView
.focusSection()
}
.navigationViewStyle(.stack)
.tag(page)
.tabItem { page.label }
}
}
}
}
}
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>NSApplicationServices</key>
<dict>
<key>Browses</key>
<array>
<dict>
<key>NSApplicationServiceIdentifier</key>
<string>sing-box:profile</string>
<key>NSApplicationServiceUsageDescription</key>
<string>Import sing-box profile from other devices</string>
<key>NSApplicationServicePlatformSupport</key>
<array>
<string>iOS</string>
<string>iPadOS</string>
</array>
</dict>
</array>
</dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>io.nekohasekai.sfa.update_profiles</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLIconFile</key>
<string>AppIcon.icns</string>
<key>CFBundleURLName</key>
<string>sing-box</string>
<key>CFBundleURLSchemes</key>
<array>
<string>sing-box</string>
</array>
</dict>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
</dict>
</plist>
+83
View File
@@ -0,0 +1,83 @@
import ApplicationLibrary
import Libbox
import Library
import SwiftUI
struct MainView: View {
@Environment(\.scenePhase) var scenePhase
@State private var selection = NavigationPage.dashboard
@State private var extensionProfile: ExtensionProfile?
@State private var profileLoading = true
@State private var logClient: LogClient!
@State private var importRemoteProfile: LibboxImportRemoteProfile?
var body: some View {
viewBuilder {
if profileLoading {
ProgressView().onAppear {
Task.detached {
logClient = LogClient(SharedPreferences.maxLogLines)
await loadProfile()
}
}
} else {
ContentView()
}
}
.onChange(of: scenePhase, perform: { newValue in
if newValue == .active {
Task.detached {
await loadProfile()
}
}
})
.environment(\.selection, $selection)
.environment(\.extensionProfile, $extensionProfile)
.environment(\.logClient, $logClient)
.environment(\.importRemoteProfile, $importRemoteProfile)
.onOpenURL(perform: openURL)
}
private func openURL(url: URL) {
if url.host == "import-remote-profile" {
var error: NSError?
importRemoteProfile = LibboxParseRemoteProfileImportLink(url.absoluteString, &error)
if error != nil {
return
}
if selection != .profiles {
selection = .profiles
}
}
}
private func loadProfile() async {
defer {
profileLoading = false
}
if ApplicationLibrary.inPreview {
return
}
if let newProfile = try? await ExtensionProfile.load() {
if extensionProfile == nil || extensionProfile?.status == .invalid {
newProfile.register()
extensionProfile = newProfile
}
} else {
extensionProfile = nil
}
}
private func connectLog() {
guard let profile = extensionProfile else {
return
}
guard let logClient else {
return
}
if profile.status.isConnected, !logClient.isConnected {
logClient.reconnect()
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.nekohasekai.sfa</string>
</array>
</dict>
</plist>