Minor fixes

This commit is contained in:
世界
2025-12-13 19:30:33 +08:00
parent 14c0b34368
commit 9be752ea94
7 changed files with 62 additions and 18 deletions
@@ -5,7 +5,7 @@ public extension View {
func presentationDetentsIfAvailable() -> some View { func presentationDetentsIfAvailable() -> some View {
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
if #available(iOS 16.0, tvOS 17.0, *) { if #available(iOS 16.0, tvOS 17.0, *) {
self.presentationDetents([.large]) presentationDetents([.large])
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
} else { } else {
self self
@@ -31,7 +31,7 @@ public extension View {
ActionButtonWrapper { self } ActionButtonWrapper { self }
#else #else
if #available(iOS 26.0, macOS 26.0, *) { if #available(iOS 26.0, macOS 26.0, *) {
self.frame(width: 44, height: 32) frame(width: 44, height: 32)
.glassEffect(.regular.interactive(), in: .rect(cornerRadius: 8)) .glassEffect(.regular.interactive(), in: .rect(cornerRadius: 8))
} else { } else {
frame(width: 44, height: 32) frame(width: 44, height: 32)
@@ -81,7 +81,7 @@ private extension View {
@ViewBuilder @ViewBuilder
func selectorBackground() -> some View { func selectorBackground() -> some View {
if #available(iOS 26.0, macOS 26.0, tvOS 26.0, *) { if #available(iOS 26.0, macOS 26.0, tvOS 26.0, *) {
self.glassEffect(.regular.interactive(), in: .rect(cornerRadius: 12)) glassEffect(.regular.interactive(), in: .rect(cornerRadius: 12))
} else { } else {
background( background(
RoundedRectangle(cornerRadius: 12) RoundedRectangle(cornerRadius: 12)
@@ -41,6 +41,7 @@ public struct StartStopButton: View {
@EnvironmentObject private var profile: ExtensionProfile @EnvironmentObject private var profile: ExtensionProfile
@State private var alert: AlertState? @State private var alert: AlertState?
@State private var currentTime = Date() @State private var currentTime = Date()
@State private var isStarting = false
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@@ -109,6 +110,21 @@ public struct StartStopButton: View {
.onReceive(timer) { _ in .onReceive(timer) { _ in
currentTime = Date() currentTime = Date()
} }
.onChangeCompat(of: profile.status) { status in
if isStarting {
if status == .disconnected {
isStarting = false
if #available(iOS 16.0, macOS 13.0, tvOS 17.0, *) {
Task {
await checkStartupError()
}
}
} else if status.isConnectedStrict {
isStarting = false
environments.commandClient.connect()
}
}
}
} }
#if os(iOS) #if os(iOS)
@@ -136,16 +152,26 @@ public struct StartStopButton: View {
} }
} }
@available(iOS 16.0, macOS 13.0, tvOS 17.0, *)
private func checkStartupError() async {
do {
try await profile.fetchLastDisconnectError()
} catch {
alert = AlertState(title: String(localized: "Service Error"), message: error.localizedDescription)
}
}
private nonisolated func switchProfile(_ isEnabled: Bool) async { private nonisolated func switchProfile(_ isEnabled: Bool) async {
do { do {
if isEnabled { if isEnabled {
await MainActor.run { isStarting = true }
try await profile.start() try await profile.start()
await environments.commandClient.connect()
} else { } else {
try await profile.stop() try await profile.stop()
} }
} catch { } catch {
await MainActor.run { await MainActor.run {
isStarting = false
alert = AlertState(error: error) alert = AlertState(error: error)
} }
} }
+15 -2
View File
@@ -147,8 +147,8 @@ class LogCoordinator {
ScrollView { ScrollView {
LogUITextView(logs: logs, searchText: searchText) LogUITextView(logs: logs, searchText: searchText)
.font(font) .font(font)
// Explicit height ensures navigation bar large title collapse animation works correctly with UITextView .fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .topLeading)
.padding() .padding()
.id("logContent") .id("logContent")
} }
@@ -226,6 +226,15 @@ class LogCoordinator {
// Update cache to full content // Update cache to full content
context.coordinator.cachedAttributedString = NSAttributedString(attributedString: textStorage) context.coordinator.cachedAttributedString = NSAttributedString(attributedString: textStorage)
} }
textView.invalidateIntrinsicContentSize()
}
@available(iOS 16.0, *)
func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context _: Context) -> CGSize? {
guard let width = proposal.width, width > 0 else { return nil }
let size = uiView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
return CGSize(width: width, height: size.height)
} }
func makeCoordinator() -> LogCoordinator { func makeCoordinator() -> LogCoordinator {
@@ -302,6 +311,10 @@ class LogCoordinator {
context.coordinator.cachedAttributedString = NSAttributedString(attributedString: textStorage) context.coordinator.cachedAttributedString = NSAttributedString(attributedString: textStorage)
} }
if let textContainer = textView.textContainer {
textView.layoutManager?.ensureLayout(for: textContainer)
}
let shouldScroll = shouldAutoScroll && logs.count != lastCount let shouldScroll = shouldAutoScroll && logs.count != lastCount
if shouldScroll { if shouldScroll {
DispatchQueue.main.async { DispatchQueue.main.async {
@@ -87,7 +87,7 @@ public struct NavigationSheet<Content: View>: View {
@ViewBuilder @ViewBuilder
func sheetDetent(_ size: SheetSize) -> some View { func sheetDetent(_ size: SheetSize) -> some View {
if #available(iOS 16.0, tvOS 17.0, *) { if #available(iOS 16.0, tvOS 17.0, *) {
self.presentationDetents([size.presentationDetent]) presentationDetents([size.presentationDetent])
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
} else { } else {
self self
+8 -11
View File
@@ -112,6 +112,7 @@ public class CommandClient: ObservableObject {
} }
private func flushPendingLogs() { private func flushPendingLogs() {
logBatchTimer = nil
guard !pendingLogs.isEmpty else { return } guard !pendingLogs.isEmpty else { return }
// Batch append all pending logs // Batch append all pending logs
@@ -254,19 +255,15 @@ public class CommandClient: ObservableObject {
guard !newLogs.isEmpty else { return } guard !newLogs.isEmpty else { return }
DispatchQueue.main.async { [self] in DispatchQueue.main.async { [self] in
// Add to pending batch
commandClient.pendingLogs.append(contentsOf: newLogs) commandClient.pendingLogs.append(contentsOf: newLogs)
if commandClient.logBatchTimer == nil {
// Cancel existing timer let workItem = DispatchWorkItem { [weak commandClient] in
commandClient.logBatchTimer?.cancel() guard let commandClient else { return }
commandClient.flushPendingLogs()
// Schedule batch flush }
let workItem = DispatchWorkItem { [weak commandClient] in commandClient.logBatchTimer = workItem
guard let commandClient else { return } DispatchQueue.main.asyncAfter(deadline: .now() + commandClient.logBatchInterval, execute: workItem)
commandClient.flushPendingLogs()
} }
commandClient.logBatchTimer = workItem
DispatchQueue.main.asyncAfter(deadline: .now() + commandClient.logBatchInterval, execute: workItem)
} }
} }
+8
View File
@@ -17,3 +17,11 @@ extension ExtensionStartupError: LocalizedError {
message message
} }
} }
extension ExtensionStartupError: CustomNSError {
public static var errorDomain: String { "ExtensionStartupError" }
public var errorCode: Int { 1 }
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: message]
}
}