From c8a00441857a16a7c5b4f5ff527e2ba2463e457c Mon Sep 17 00:00:00 2001 From: bsorrentino Date: Wed, 27 Nov 2024 18:30:02 +0100 Subject: [PATCH] feat: add network monitoring service to app work on #35 --- PlantUML/NetworkObservableService.swift | 68 +++++++++++++++++++++++ PlantUML/PlantUMLDocumentView.swift | 71 ++++++++++++++++++------- PlantUML/PlantUMLDrawingView.swift | 8 +-- PlantUML/PlantUMLOpenAIView.swift | 39 +++----------- PlantUMLApp.xcodeproj/project.pbxproj | 4 ++ 5 files changed, 134 insertions(+), 56 deletions(-) create mode 100644 PlantUML/NetworkObservableService.swift diff --git a/PlantUML/NetworkObservableService.swift b/PlantUML/NetworkObservableService.swift new file mode 100644 index 0000000..185bb2a --- /dev/null +++ b/PlantUML/NetworkObservableService.swift @@ -0,0 +1,68 @@ +// +// NetworkMonitor.swift +// PlantUMLApp +// +// Created by bsorrentino on 27/11/24. +// + +import Network +import SwiftUI + +class NetworkObservableService: ObservableObject { + let monitor = NWPathMonitor() + private let queue = DispatchQueue(label: "NetworkMonitor") + + @Published var isConnected: Bool = true + + + init() { + monitor.pathUpdateHandler = { path in + print( "network path update: \(path)") + DispatchQueue.main.async { + self.isConnected = path.status == .satisfied + } + } + monitor.start(queue: queue) + } +} + + +struct NetworkEnabledModifier: ViewModifier { + + @ObservedObject var networkService: NetworkObservableService + + func body(content: Content) -> some View { + content + .disabled(!networkService.isConnected) + } +} + +struct NetworkEnabledStyleModifier: ViewModifier { + + @ObservedObject var networkService: NetworkObservableService + + func body(content: Content) -> some View { + content + // Change color when disabled + // .foregroundColor(networkService.isConnected ? .blue : .gray) + // Reduce opacity when disabled + .opacity(networkService.isConnected ? 1 : 0.5) + } +} + +// +// MARK: - Metwork extension +// + +extension View { + + func networkEnabled( _ networkService: NetworkObservableService ) -> some View { + modifier(NetworkEnabledModifier(networkService: networkService)) + } + + func networkEnabledStyle( _ networkService: NetworkObservableService ) -> some View { + modifier(NetworkEnabledStyleModifier(networkService: networkService)) + } + + +} diff --git a/PlantUML/PlantUMLDocumentView.swift b/PlantUML/PlantUMLDocumentView.swift index 782fbfc..7031e0c 100644 --- a/PlantUML/PlantUMLDocumentView.swift +++ b/PlantUML/PlantUMLDocumentView.swift @@ -31,8 +31,8 @@ struct PlantUMLDocumentView: View { @AppStorage("fontSize") var fontSize:Int = 15 @StateObject var document: PlantUMLObservableDocument - @StateObject private var openAIService = OpenAIObservableService() - + @StateObject var openAIService = OpenAIObservableService() + @StateObject var networkService = NetworkObservableService() @State var isOpenAIVisible = false @State var keyboardTab: String = "general" @@ -48,7 +48,6 @@ struct PlantUMLDocumentView: View { VStack { GeometryReader { geometry in - VStack { AceEditorView( content: $document.text, options: AceEditorView.Options( @@ -73,8 +72,11 @@ struct PlantUMLDocumentView: View { } if isOpenAIVisible /* && interfaceOrientation.value.isPortrait */ { OpenAIView( service: openAIService, - document: document, - drawingView: { DiagramDrawingView } ) + document: document ) { + DiagramDrawingView + .environmentObject(networkService) + } + .environmentObject(networkService) .frame( height: 200 ) .onChange(of: openAIService.status ) { newStatus in if( .Ready == newStatus ) { @@ -96,15 +98,7 @@ struct PlantUMLDocumentView: View { saving = false } } - .onRotate(perform: { orientation in - // if (orientation.isPortrait && isDiagramVisible) || - // (orientation.isLandscape && isEditorVisible) - // { - // isEditorVisible.toggle() - // } - }) -// .navigationBarTitle(Text( "📝 Diagram Editor" ), displayMode: .inline) - +// .onRotate(perform: { orientation in }) .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { } ToolbarItemGroup(placement: .navigationBarTrailing) { @@ -130,6 +124,7 @@ struct PlantUMLDocumentView: View { } } + // // MARK: - Drawing extension - // @@ -236,29 +231,65 @@ extension PlantUMLDocumentView { } +// +// MARK: - AI extension - +// +extension PlantUMLDocumentView { + + var ToggleOpenAIButton: some View { + + Button { + isOpenAIVisible.toggle() + } + label: { + Label { + Text("OpenAI Editor") + } icon: { + #if __OPENAI_LOGO + // [How can I set an image tint in SwiftUI?](https://stackoverflow.com/a/73289182/521197) + Image("openai") + .resizable() + .colorMultiply(isOpenAIVisible ? .blue : .gray) + .frame( width: 28, height: 28) + #else + Image( systemName: "brain" ) + .resizable() + .frame( width: 24, height: 20) + #endif + } + .environment(\.symbolVariants, .fill) + .labelStyle(.iconOnly) + } + .networkEnabled(networkService) + .accessibilityIdentifier("openai") + } + +} + + // // MARK: - Diagram extension - // extension PlantUMLDocumentView { func ToggleDiagramButton() -> some View { - - NavigationLink( destination: { - PlantUMLDiagramView( url: document.buildURL()) + NavigationLink(destination: { + PlantUMLDiagramView(url: document.buildURL()) .toolbarRole(.navigationStack) }) { - Label( "Preview >", systemImage: "photo.fill" ) + Label("Preview >", systemImage: "photo.fill") .labelStyle(.titleOnly) - .foregroundColor( .blue ) } .accessibilityIdentifier("diagram_preview") .padding(.leading, 15) - + .networkEnabled(networkService) + } } + // MARK: - Preview - #Preview { diff --git a/PlantUML/PlantUMLDrawingView.swift b/PlantUML/PlantUMLDrawingView.swift index 6cc6343..4d3092c 100644 --- a/PlantUML/PlantUMLDrawingView.swift +++ b/PlantUML/PlantUMLDrawingView.swift @@ -25,6 +25,7 @@ struct PlantUMLDrawingView: View { @Environment(\.dismiss) var dismiss @ObservedObject var service:OpenAIObservableService @ObservedObject var document: PlantUMLObservableDocument + @EnvironmentObject var networkService: NetworkObservableService @State var isScrollEnabled = true @State var isUseDrawingTool = false @State var processing = false @@ -71,11 +72,12 @@ struct PlantUMLDrawingView: View { Label( "process", systemImage: "eye") .foregroundColor(Color.orange) .labelStyle(.titleOnly) + .networkEnabledStyle(networkService) }) .accessibilityIdentifier("drawing_process") - } - ) + .networkEnabled(networkService) + }) } .onCancel { processImageTask?.cancel() @@ -93,7 +95,7 @@ struct PlantUMLDrawingView: View { } private func updateDiagram() { -// document.drawingData = canvas.drawing.dataRepresentation() + // document.drawingData = canvas.drawing.dataRepresentation() } } diff --git a/PlantUML/PlantUMLOpenAIView.swift b/PlantUML/PlantUMLOpenAIView.swift index 128d482..179be85 100644 --- a/PlantUML/PlantUMLOpenAIView.swift +++ b/PlantUML/PlantUMLOpenAIView.swift @@ -17,8 +17,10 @@ struct OpenAIView : View { case Settings } - @ObservedObject var service:OpenAIObservableService + @ObservedObject var service: OpenAIObservableService @ObservedObject var document: PlantUMLObservableDocument + @EnvironmentObject var networkService: NetworkObservableService + @State var instruction:String = "" @State private var tabs: Tab = .Prompt @State private var hideOpenAISecrets = true @@ -48,11 +50,13 @@ struct OpenAIView : View { .accessibilityIdentifier("openai_drawing") .disabled( !service.isSettingsValid ) + Button( action: { tabs = .Prompt } ) { Label( "Prompt", systemImage: "keyboard.onehanded.right") } .accessibilityIdentifier("openai_prompt") .disabled( !service.isSettingsValid ) + .networkEnabled( networkService ) // Divider().frame(height: 20 ) Button( action: { tabs = .PromptHistory } ) { @@ -192,6 +196,7 @@ extension OpenAIView { }) .disabled( isEditing ) .accessibilityIdentifier("openai_submit") + .networkEnabled(networkService) } } .padding(EdgeInsets( top: 10, leading: 0, bottom: 5, trailing: 10)) @@ -301,38 +306,6 @@ extension OpenAIView { } -extension PlantUMLDocumentView { - - var ToggleOpenAIButton: some View { - - Button { - isOpenAIVisible.toggle() - } - label: { - Label { - Text("OpenAI Editor") - } icon: { - #if __OPENAI_LOGO - // [How can I set an image tint in SwiftUI?](https://stackoverflow.com/a/73289182/521197) - Image("openai") - .resizable() - .colorMultiply(isOpenAIVisible ? .blue : .gray) - .frame( width: 28, height: 28) - #else - Image( systemName: "brain" ) - .resizable() - .foregroundColor( isOpenAIVisible ? .blue : .gray) - .frame( width: 24, height: 20) - #endif - } - .environment(\.symbolVariants, .fill) - .labelStyle(.iconOnly) - } - .accessibilityIdentifier("openai") - } - -} - diff --git a/PlantUMLApp.xcodeproj/project.pbxproj b/PlantUMLApp.xcodeproj/project.pbxproj index d71a2c8..46ce766 100644 --- a/PlantUMLApp.xcodeproj/project.pbxproj +++ b/PlantUMLApp.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ A0A42A76289ABC2D00E929EB /* PlantUMLDiagramView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A42A75289ABC2D00E929EB /* PlantUMLDiagramView.swift */; }; A0A42A79289AC37C00E929EB /* PlantUMLObservableDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A42A78289AC37C00E929EB /* PlantUMLObservableDocument.swift */; }; A0A80A762B3ED90A00CB08CA /* SwiftUI+PencilKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A80A752B3ED90A00CB08CA /* SwiftUI+PencilKit.swift */; }; + A0AC4EAD2CF760AE00E5090A /* NetworkObservableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0AC4EAC2CF760AC00E5090A /* NetworkObservableService.swift */; }; A0BADC7129D4C2740056A098 /* SwiftUI+Conditional.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0BADC7029D4C2740056A098 /* SwiftUI+Conditional.swift */; }; A0BFBA9A290D551F008340E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A0BFBA9C290D551F008340E3 /* Localizable.strings */; }; A0C0C3282B5D95DD00CE66A2 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0C0C3272B5D95DD00CE66A2 /* Errors.swift */; }; @@ -99,6 +100,7 @@ A0A42A75289ABC2D00E929EB /* PlantUMLDiagramView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlantUMLDiagramView.swift; sourceTree = ""; }; A0A42A78289AC37C00E929EB /* PlantUMLObservableDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlantUMLObservableDocument.swift; sourceTree = ""; }; A0A80A752B3ED90A00CB08CA /* SwiftUI+PencilKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftUI+PencilKit.swift"; sourceTree = ""; }; + A0AC4EAC2CF760AC00E5090A /* NetworkObservableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObservableService.swift; sourceTree = ""; }; A0BADC7029D4C2740056A098 /* SwiftUI+Conditional.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftUI+Conditional.swift"; sourceTree = ""; }; A0BFBA9B290D551F008340E3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; A0C0C3272B5D95DD00CE66A2 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; @@ -178,6 +180,7 @@ children = ( A0A42A78289AC37C00E929EB /* PlantUMLObservableDocument.swift */, A02BB3E72B41C9430073D432 /* OpenAIObservableService.swift */, + A0AC4EAC2CF760AC00E5090A /* NetworkObservableService.swift */, ); name = Observable; sourceTree = ""; @@ -465,6 +468,7 @@ A068572E29D8B31100E82C2F /* View+Clipboard.swift in Sources */, A0A139352A13C49A00B69FBC /* View+Secure.swift in Sources */, A0F2B14229353C2D00A44481 /* SwiftUI+Rotate.swift in Sources */, + A0AC4EAD2CF760AE00E5090A /* NetworkObservableService.swift in Sources */, A04512F12B3DFCB900CD1158 /* PlantUMLDiagramMenu.swift in Sources */, A0C0C3282B5D95DD00CE66A2 /* Errors.swift in Sources */, A0A42A76289ABC2D00E929EB /* PlantUMLDiagramView.swift in Sources */,