feat: term session mgr (#846)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import UIKit
|
||||
import WidgetKit
|
||||
import Flutter
|
||||
import ActivityKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
@@ -11,14 +12,48 @@ import Flutter
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||
let methodChannel = FlutterMethodChannel(name: "tech.lolli.toolbox/home_widget", binaryMessenger: controller.binaryMessenger)
|
||||
methodChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||
// Home widget channel (legacy)
|
||||
let homeWidgetChannel = FlutterMethodChannel(name: "tech.lolli.toolbox/home_widget", binaryMessenger: controller.binaryMessenger)
|
||||
homeWidgetChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||
if call.method == "update" {
|
||||
if #available(iOS 14.0, *) {
|
||||
WidgetCenter.shared.reloadTimelines(ofKind: "StatusWidget")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Main channel for cross-platform calls (incl. Live Activities)
|
||||
let mainChannel = FlutterMethodChannel(name: "tech.lolli.toolbox/main_chan", binaryMessenger: controller.binaryMessenger)
|
||||
mainChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||
switch call.method {
|
||||
case "updateHomeWidget":
|
||||
if #available(iOS 14.0, *) {
|
||||
WidgetCenter.shared.reloadTimelines(ofKind: "StatusWidget")
|
||||
}
|
||||
result(nil)
|
||||
case "startLiveActivity":
|
||||
if #available(iOS 16.2, *) {
|
||||
if let payload = call.arguments as? String {
|
||||
LiveActivityManager.start(json: payload)
|
||||
}
|
||||
}
|
||||
result(nil)
|
||||
case "updateLiveActivity":
|
||||
if #available(iOS 16.2, *) {
|
||||
if let payload = call.arguments as? String {
|
||||
LiveActivityManager.update(json: payload)
|
||||
}
|
||||
}
|
||||
result(nil)
|
||||
case "stopLiveActivity":
|
||||
if #available(iOS 16.2, *) {
|
||||
LiveActivityManager.stop()
|
||||
}
|
||||
result(nil)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
})
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
@@ -30,4 +65,11 @@ import Flutter
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Stop Live Activity when app is about to terminate
|
||||
if #available(iOS 16.2, *) {
|
||||
LiveActivityManager.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
<array>
|
||||
<string>ConfigurationIntent</string>
|
||||
</array>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true />
|
||||
<key>UIBackgroundModes</key>
|
||||
@@ -78,4 +80,4 @@
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Get QR code and etc.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<string>en</string>
|
||||
<string>zh</string>
|
||||
</array>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
<key>CFBundleName</key>
|
||||
<string>ServerBox</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<string>en</string>
|
||||
<string>zh</string>
|
||||
</array>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
<key>CFBundleName</key>
|
||||
<string>ServerBox</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
@@ -68,4 +70,4 @@
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Get QR code and etc.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
||||
95
ios/Runner/LiveActivityManager.swift
Normal file
95
ios/Runner/LiveActivityManager.swift
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// LiveActivityManager.swift
|
||||
// Runner
|
||||
//
|
||||
// Handles starting/updating/stopping Terminal Live Activities from Flutter via MethodChannel.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ActivityKit
|
||||
|
||||
@available(iOS 16.2, *)
|
||||
class LiveActivityManager {
|
||||
static var current: Activity<TerminalAttributes>?
|
||||
|
||||
struct Payload: Decodable {
|
||||
let id: String
|
||||
let title: String
|
||||
let subtitle: String
|
||||
let startTimeMs: Int
|
||||
let status: String
|
||||
let hasTerminal: Bool?
|
||||
let connectionCount: Int?
|
||||
}
|
||||
|
||||
private static func parse(_ json: String) -> Payload? {
|
||||
guard let data = json.data(using: .utf8) else { return nil }
|
||||
return try? JSONDecoder().decode(Payload.self, from: data)
|
||||
}
|
||||
|
||||
static func start(json: String) {
|
||||
guard #available(iOS 16.2, *) else { return }
|
||||
guard let p = parse(json) else { return }
|
||||
let attributes = TerminalAttributes(id: p.id)
|
||||
let date = Date(timeIntervalSince1970: TimeInterval(p.startTimeMs) / 1000.0)
|
||||
// Localize multi-connection title/subtitle on iOS side
|
||||
let isMulti = (p.id == "multi_connections")
|
||||
let title = isMulti
|
||||
? String(format: NSLocalizedString("%d connections", comment: "Title for multiple connections"), p.connectionCount ?? 1)
|
||||
: p.title
|
||||
let subtitle = isMulti
|
||||
? NSLocalizedString("Multiple SSH sessions active", comment: "Subtitle for multiple connections")
|
||||
: p.subtitle
|
||||
let state = TerminalAttributes.ContentState(
|
||||
id: p.id,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
status: p.status,
|
||||
startTime: date,
|
||||
hasTerminal: p.hasTerminal ?? true,
|
||||
connectionCount: p.connectionCount ?? 1
|
||||
)
|
||||
let content = ActivityContent(state: state, staleDate: nil)
|
||||
do {
|
||||
current = try Activity<TerminalAttributes>.request(attributes: attributes, content: content, pushType: nil)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
static func update(json: String) {
|
||||
guard #available(iOS 16.2, *) else { return }
|
||||
guard let p = parse(json) else { return }
|
||||
let date = Date(timeIntervalSince1970: TimeInterval(p.startTimeMs) / 1000.0)
|
||||
// Localize multi-connection title/subtitle on iOS side
|
||||
let isMulti = (p.id == "multi_connections")
|
||||
let title = isMulti
|
||||
? String(format: NSLocalizedString("%d connections", comment: "Title for multiple connections"), p.connectionCount ?? 1)
|
||||
: p.title
|
||||
let subtitle = isMulti
|
||||
? NSLocalizedString("Multiple SSH sessions active", comment: "Subtitle for multiple connections")
|
||||
: p.subtitle
|
||||
let state = TerminalAttributes.ContentState(
|
||||
id: p.id,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
status: p.status,
|
||||
startTime: date,
|
||||
hasTerminal: p.hasTerminal ?? true,
|
||||
connectionCount: p.connectionCount ?? 1
|
||||
)
|
||||
if let activity = current {
|
||||
Task { await activity.update(ActivityContent(state: state, staleDate: nil)) }
|
||||
} else {
|
||||
start(json: json)
|
||||
}
|
||||
}
|
||||
|
||||
static func stop() {
|
||||
guard #available(iOS 16.2, *) else { return }
|
||||
if let activity = current {
|
||||
Task { await activity.end(dismissalPolicy: .immediate) }
|
||||
current = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
39
ios/Runner/TerminalLiveActivityAttributes.swift
Normal file
39
ios/Runner/TerminalLiveActivityAttributes.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// TerminalLiveActivityAttributes.swift
|
||||
// Runner
|
||||
//
|
||||
// Mirror of the ActivityKit attributes used in the extension.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ActivityKit
|
||||
|
||||
@available(iOS 16.1, *)
|
||||
public struct TerminalAttributes: ActivityAttributes {
|
||||
public struct ContentState: Codable, Hashable {
|
||||
public var id: String
|
||||
public var title: String
|
||||
public var subtitle: String
|
||||
public var status: String
|
||||
public var startTime: Date
|
||||
public var hasTerminal: Bool
|
||||
public var connectionCount: Int
|
||||
|
||||
public init(id: String, title: String, subtitle: String, status: String, startTime: Date, hasTerminal: Bool, connectionCount: Int = 1) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.status = status
|
||||
self.startTime = startTime
|
||||
self.hasTerminal = hasTerminal
|
||||
self.connectionCount = connectionCount
|
||||
}
|
||||
}
|
||||
|
||||
public var id: String
|
||||
|
||||
public init(id: String) {
|
||||
self.id = id
|
||||
}
|
||||
}
|
||||
|
||||
8
ios/Runner/de.lproj/Localizable.strings
Normal file
8
ios/Runner/de.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "Terminal";
|
||||
"Connected" = "Verbunden";
|
||||
"Connecting" = "Verbindung wird hergestellt";
|
||||
"Disconnected" = "Getrennt";
|
||||
"Multiple SSH sessions active" = "Mehrere aktive SSH-Sitzungen";
|
||||
"1 connection" = "1 Verbindung";
|
||||
"%d connections" = "%d Verbindungen";
|
||||
|
||||
8
ios/Runner/en.lproj/Localizable.strings
Normal file
8
ios/Runner/en.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "Terminal";
|
||||
"Connected" = "Connected";
|
||||
"Connecting" = "Connecting";
|
||||
"Disconnected" = "Disconnected";
|
||||
"Multiple SSH sessions active" = "Multiple SSH sessions active";
|
||||
"1 connection" = "1 connection";
|
||||
"%d connections" = "%d connections";
|
||||
|
||||
8
ios/Runner/es.lproj/Localizable.strings
Normal file
8
ios/Runner/es.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "Terminal";
|
||||
"Connected" = "Conectado";
|
||||
"Connecting" = "Conectando";
|
||||
"Disconnected" = "Desconectado";
|
||||
"Multiple SSH sessions active" = "Varias sesiones SSH activas";
|
||||
"1 connection" = "1 conexión";
|
||||
"%d connections" = "%d conexiones";
|
||||
|
||||
8
ios/Runner/fr.lproj/Localizable.strings
Normal file
8
ios/Runner/fr.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "Terminal";
|
||||
"Connected" = "Connecté";
|
||||
"Connecting" = "Connexion en cours";
|
||||
"Disconnected" = "Déconnecté";
|
||||
"Multiple SSH sessions active" = "Plusieurs sessions SSH actives";
|
||||
"1 connection" = "1 connexion";
|
||||
"%d connections" = "%d connexions";
|
||||
|
||||
8
ios/Runner/id.lproj/Localizable.strings
Normal file
8
ios/Runner/id.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "Terminal";
|
||||
"Connected" = "Terhubung";
|
||||
"Connecting" = "Menghubungkan";
|
||||
"Disconnected" = "Terputus";
|
||||
"Multiple SSH sessions active" = "Beberapa sesi SSH aktif";
|
||||
"1 connection" = "1 koneksi";
|
||||
"%d connections" = "%d koneksi";
|
||||
|
||||
8
ios/Runner/ja.lproj/Localizable.strings
Normal file
8
ios/Runner/ja.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "ターミナル";
|
||||
"Connected" = "接続済み";
|
||||
"Connecting" = "接続中";
|
||||
"Disconnected" = "切断";
|
||||
"Multiple SSH sessions active" = "複数の SSH セッションがアクティブ";
|
||||
"1 connection" = "1 件の接続";
|
||||
"%d connections" = "%d 件の接続";
|
||||
|
||||
8
ios/Runner/pt-BR.lproj/Localizable.strings
Normal file
8
ios/Runner/pt-BR.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "Terminal";
|
||||
"Connected" = "Conectado";
|
||||
"Connecting" = "Conectando";
|
||||
"Disconnected" = "Desconectado";
|
||||
"Multiple SSH sessions active" = "Várias sessões SSH ativas";
|
||||
"1 connection" = "1 conexão";
|
||||
"%d connections" = "%d conexões";
|
||||
|
||||
8
ios/Runner/ru.lproj/Localizable.strings
Normal file
8
ios/Runner/ru.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "Терминал";
|
||||
"Connected" = "Подключено";
|
||||
"Connecting" = "Подключение";
|
||||
"Disconnected" = "Отключено";
|
||||
"Multiple SSH sessions active" = "Несколько активных сеансов SSH";
|
||||
"1 connection" = "1 подключение";
|
||||
"%d connections" = "%d подключений";
|
||||
|
||||
8
ios/Runner/zh-Hans.lproj/Localizable.strings
Normal file
8
ios/Runner/zh-Hans.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "终端";
|
||||
"Connected" = "已连接";
|
||||
"Connecting" = "连接中";
|
||||
"Disconnected" = "已断开连接";
|
||||
"Multiple SSH sessions active" = "多个 SSH 会话正在活动";
|
||||
"1 connection" = "1 个连接";
|
||||
"%d connections" = "%d 个连接";
|
||||
|
||||
8
ios/Runner/zh-Hant.lproj/Localizable.strings
Normal file
8
ios/Runner/zh-Hant.lproj/Localizable.strings
Normal file
@@ -0,0 +1,8 @@
|
||||
"Terminal" = "終端機";
|
||||
"Connected" = "已連線";
|
||||
"Connecting" = "連線中";
|
||||
"Disconnected" = "已中斷連線";
|
||||
"Multiple SSH sessions active" = "多個 SSH 連線運行中";
|
||||
"1 connection" = "1 個連線";
|
||||
"%d connections" = "%d 個連線";
|
||||
|
||||
Reference in New Issue
Block a user