From 16eee555735c22fc58ddecbf9eeeed478bdb15ed Mon Sep 17 00:00:00 2001 From: Tim Bert <5411131+timbms@users.noreply.github.com> Date: Sat, 14 Sep 2024 19:54:14 +0200 Subject: [PATCH 1/2] Migrate app to SwiftUI (#801) * Migrate SettingsViewController to SwiftUI - Using LabeledContent instead of HStack - Proper handling of settingsSendCrashReports when already set - Integrated ClientCertificatesView, RTFTexView * Cleaning up Main.storyboard as OpenHABLegalViewContoller, OpenHABSettingsViewController are not needed anymore * Extend Migration to Drawer and Notifcations * Porting ColorPicker and SelectionView to SwiftUI * Reintegrating develop into branch * Merge current develop branch * Enabled Preview for DrawerView * Cleaning up Delete obsolete code Remove unnecessary part of storyboard * Checking tests * Update fastlane * Revert to ColorPickerViewController / to be readdressed when SitemapViewController is migrated to SwiftUI --------- Co-authored-by: Dan Cunningham --- .gitignore | 1 + BuildTools/.swiftlint.yml | 2 + .../OpenHABCore/Model/OpenHABItem.swift | 2 +- .../Sources/OpenHABCore/Util/Endpoint.swift | 24 +- openHAB.xcodeproj/project.pbxproj | 103 +- .../xcshareddata/xcschemes/openHAB.xcscheme | 3 +- .../xcshareddata/swiftpm/Package.resolved | 440 ++++---- openHAB/ClientCertificatesView.swift | 35 + openHAB/ClientCertificatesViewModel.swift | 37 + openHAB/ColorPickerView.swift | 77 ++ openHAB/DrawerUITableViewCell.swift | 31 - openHAB/DrawerView.swift | 235 +++++ openHAB/Main.storyboard | 977 ------------------ openHAB/NotificationsView.swift | 114 ++ ...nHABClientCertificatesViewController.swift | 59 -- openHAB/OpenHABDrawerItem.swift | 37 - .../OpenHABDrawerTableViewController.swift | 335 ------ openHAB/OpenHABLegalViewController.swift | 36 - openHAB/OpenHABNotification.swift | 12 +- .../OpenHABNotificationsViewController.swift | 147 --- openHAB/OpenHABRootViewController.swift | 120 ++- .../OpenHABSelectionTableViewController.swift | 70 -- openHAB/OpenHABSettingsViewController.swift | 333 ------ openHAB/OpenHABSitemapViewController.swift | 38 +- openHAB/RTFTextView.swift | 49 + openHAB/SelectionView.swift | 54 + openHAB/SettingsView.swift | 381 +++++++ openHAB/Throttler.swift | 2 +- openHABTestsSwift/LocalizationTests.swift | 1 + .../Views/Rows/ImageRow.swift | 2 +- 30 files changed, 1385 insertions(+), 2372 deletions(-) create mode 100644 openHAB/ClientCertificatesView.swift create mode 100644 openHAB/ClientCertificatesViewModel.swift create mode 100644 openHAB/ColorPickerView.swift delete mode 100644 openHAB/DrawerUITableViewCell.swift create mode 100644 openHAB/DrawerView.swift create mode 100644 openHAB/NotificationsView.swift delete mode 100644 openHAB/OpenHABClientCertificatesViewController.swift delete mode 100644 openHAB/OpenHABDrawerItem.swift delete mode 100644 openHAB/OpenHABDrawerTableViewController.swift delete mode 100644 openHAB/OpenHABLegalViewController.swift delete mode 100644 openHAB/OpenHABNotificationsViewController.swift delete mode 100644 openHAB/OpenHABSelectionTableViewController.swift delete mode 100644 openHAB/OpenHABSettingsViewController.swift create mode 100644 openHAB/RTFTextView.swift create mode 100644 openHAB/SelectionView.swift create mode 100644 openHAB/SettingsView.swift diff --git a/.gitignore b/.gitignore index e716fd90b..b449fc8f4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ openHAB.ipa build/ BuildTools/.build OpenHABCore/Package.resolved +OpenHABCore/swift-openapi-generator/ diff --git a/BuildTools/.swiftlint.yml b/BuildTools/.swiftlint.yml index c6622e780..fef541dbe 100644 --- a/BuildTools/.swiftlint.yml +++ b/BuildTools/.swiftlint.yml @@ -29,6 +29,8 @@ excluded: - ../fastlane - ../OpenHABCore/.build - .build + - ../OpenHABCore/Sources/OpenHABCore/GeneratedSources/* + - ../OpenHABCore/swift-openapi-generator nesting: type_level: 2 diff --git a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABItem.swift b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABItem.swift index f759251bb..8c921add5 100644 --- a/OpenHABCore/Sources/OpenHABCore/Model/OpenHABItem.swift +++ b/OpenHABCore/Sources/OpenHABCore/Model/OpenHABItem.swift @@ -142,7 +142,7 @@ public extension OpenHABItem { public extension OpenHABItem.CodingData { var openHABItem: OpenHABItem { let mappedMembers = members?.map(\.openHABItem) ?? [] - + // swiftlint:disable:next line_length return OpenHABItem(name: name, type: type, state: state, link: link, label: label, groupType: groupType, stateDescription: stateDescription?.openHABStateDescription, commandDescription: commandDescription?.openHABCommandDescription, members: mappedMembers, category: category, options: options) } } diff --git a/OpenHABCore/Sources/OpenHABCore/Util/Endpoint.swift b/OpenHABCore/Sources/OpenHABCore/Util/Endpoint.swift index d0a8449cf..28577079a 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/Endpoint.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/Endpoint.swift @@ -17,14 +17,34 @@ public enum ChartStyle { case light } -public enum IconType: Int { +public enum IconType: Int, CaseIterable, Identifiable, CustomStringConvertible { case png case svg + + public var id: Self { self } + + public var description: String { + switch self { + case .png: + "PNG" + case .svg: + "SVG" + } + } } -public enum SortSitemapsOrder: Int { +public enum SortSitemapsOrder: Int, CaseIterable, CustomStringConvertible { case label case name + + public var description: String { + switch self { + case .label: + "Label" + case .name: + "Name" + } + } } public struct Endpoint { diff --git a/openHAB.xcodeproj/project.pbxproj b/openHAB.xcodeproj/project.pbxproj index e10e7243a..5349b6e2c 100644 --- a/openHAB.xcodeproj/project.pbxproj +++ b/openHAB.xcodeproj/project.pbxproj @@ -68,7 +68,6 @@ 93F8065027AE7A830035A6B0 /* SideMenu in Frameworks */ = {isa = PBXBuildFile; productRef = 93F8064F27AE7A830035A6B0 /* SideMenu */; }; 93F8065327AE7B580035A6B0 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = 93F8065227AE7B580035A6B0 /* SVGKit */; }; A07ED02E2402EE6E006588FE /* OpenHABWatchTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07ED02D2402EE6E006588FE /* OpenHABWatchTracker.swift */; }; - A07EF7A02230C0A30040919F /* OpenHABClientCertificatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07EF79F2230C0A20040919F /* OpenHABClientCertificatesViewController.swift */; }; A3F4C3A51A49A5940019A09F /* MainLaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = A3F4C3A41A49A5940019A09F /* MainLaunchScreen.xib */; }; B7D5ECE121499E55001B0EC6 /* MapViewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7D5ECE021499E55001B0EC6 /* MapViewTableViewCell.swift */; }; DA0749DE23E0B5950057FA83 /* ColorPickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0749DD23E0B5950057FA83 /* ColorPickerRow.swift */; }; @@ -87,6 +86,7 @@ DA15BFBD23C6726400BD8ADA /* ObservableOpenHABDataObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA15BFBC23C6726400BD8ADA /* ObservableOpenHABDataObject.swift */; }; DA19E25B22FD801D002F8F2F /* OpenHABGeneralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA19E25A22FD801D002F8F2F /* OpenHABGeneralTests.swift */; }; DA21EAE22339621C001AB415 /* Throttler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA21EAE12339621C001AB415 /* Throttler.swift */; }; + DA242C622C83588600AFB10D /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA242C612C83588600AFB10D /* SettingsView.swift */; }; DA28C362225241DE00AB409C /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA28C361225241DE00AB409C /* WebKit.framework */; settings = {ATTRIBUTES = (Required, ); }; }; DA2DC23221F2736C00830730 /* OpenHABJSONParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2DC23121F2736C00830730 /* OpenHABJSONParserTests.swift */; }; DA2E0AA423DC96E9009B0A99 /* EncircledIconWithAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2E0AA323DC96E9009B0A99 /* EncircledIconWithAction.swift */; }; @@ -97,8 +97,14 @@ DA4D4DB5233F9ACB00B37E37 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = DA4D4DB4233F9ACB00B37E37 /* README.md */; }; DA50C7BD2B0A51BD0009F716 /* SliderWithSwitchSupportRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA50C7BC2B0A51BD0009F716 /* SliderWithSwitchSupportRow.swift */; }; DA50C7BF2B0A65300009F716 /* SliderWithSwitchSupportUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA50C7BE2B0A652F0009F716 /* SliderWithSwitchSupportUITableViewCell.swift */; }; + DA5ED9BE2C850955004875E0 /* ClientCertificatesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5ED9BD2C850955004875E0 /* ClientCertificatesViewModel.swift */; }; + DA5ED9C02C8509C2004875E0 /* ClientCertificatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5ED9BF2C8509C2004875E0 /* ClientCertificatesView.swift */; }; DA65871F236F83CE007E2E7F /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA65871E236F83CD007E2E7F /* UserDefaultsExtension.swift */; }; DA6587222370C9D8007E2E7F /* PreferencesHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6587212370C9D8007E2E7F /* PreferencesHostingController.swift */; }; + DA6B2EEF2C861BC900DF77CF /* DrawerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6B2EEE2C861BC900DF77CF /* DrawerView.swift */; }; + DA6B2EF12C87B59000DF77CF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6B2EF02C87B59000DF77CF /* NotificationsView.swift */; }; + DA6B2EF52C89F8F200DF77CF /* ColorPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6B2EF42C89F8F200DF77CF /* ColorPickerView.swift */; }; + DA6B2EF72C8B92E800DF77CF /* SelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6B2EF62C8B92E800DF77CF /* SelectionView.swift */; }; DA7224D223828D3400712D20 /* PreviewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA7224D123828D3300712D20 /* PreviewConstants.swift */; }; DA72E1B8236DEA0900B8EF3A /* AppMessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA72E1B5236DEA0900B8EF3A /* AppMessageService.swift */; }; DA7649DE23FC81A20085CE46 /* Unwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA7649DD23FC81A20085CE46 /* Unwrap.swift */; }; @@ -107,6 +113,7 @@ DA817E7A234BF39B00C91824 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = DA817E79234BF39B00C91824 /* CHANGELOG.md */; }; DA88F8C622EC377200B408E5 /* ReleaseNotes.md in Resources */ = {isa = PBXBuildFile; fileRef = DA88F8C522EC377100B408E5 /* ReleaseNotes.md */; }; DA9721C324E29A8F0092CCFD /* UserDefaultsBacked.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9721C224E29A8F0092CCFD /* UserDefaultsBacked.swift */; }; + DA9F81872C85020F00B47B72 /* RTFTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9F81862C85020F00B47B72 /* RTFTextView.swift */; }; DAA42BA821DC97E000244B2A /* NotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA42BA721DC97DF00244B2A /* NotificationTableViewCell.swift */; }; DAA42BAA21DC983B00244B2A /* VideoUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA42BA921DC983B00244B2A /* VideoUITableViewCell.swift */; }; DAA42BAC21DC984A00244B2A /* WebUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA42BAB21DC984A00244B2A /* WebUITableViewCell.swift */; }; @@ -118,7 +125,6 @@ DAC9AF4924F966FA006DAE93 /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC9AF4824F966FA006DAE93 /* LazyView.swift */; }; DACB636227D3FC6500041931 /* error.png in Resources */ = {isa = PBXBuildFile; fileRef = DACB636127D3FC6500041931 /* error.png */; }; DACB636327D3FC6500041931 /* error.png in Resources */ = {isa = PBXBuildFile; fileRef = DACB636127D3FC6500041931 /* error.png */; }; - DAEAA89B21E2611000267EA3 /* OpenHABNotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAEAA89A21E2611000267EA3 /* OpenHABNotificationsViewController.swift */; }; DAEAA89D21E6B06400267EA3 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAEAA89C21E6B06300267EA3 /* ReusableView.swift */; }; DAEAA89F21E6B16600267EA3 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAEAA89E21E6B16600267EA3 /* UITableView.swift */; }; DAF0A28B2C56E3A300A14A6A /* RollershutterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF0A28A2C56E3A300A14A6A /* RollershutterCell.swift */; }; @@ -142,13 +148,9 @@ DAF4581823DC4A050018B495 /* ImageRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF4581723DC4A050018B495 /* ImageRow.swift */; }; DAF4581E23DC60020018B495 /* ImageRawRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF4581D23DC60020018B495 /* ImageRawRow.swift */; }; DAF4F6C0222734D300C24876 /* NewImageUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF4F6BF222734D200C24876 /* NewImageUITableViewCell.swift */; }; - DF05EF121D00696200DD646D /* DrawerUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05EF111D00696200DD646D /* DrawerUITableViewCell.swift */; }; DF05FF231896BD2D00FF2F9B /* SelectionUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05FF221896BD2D00FF2F9B /* SelectionUITableViewCell.swift */; }; - DF06F1F618FE7A160011E7B9 /* OpenHABSelectionTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF06F1F518FE7A160011E7B9 /* OpenHABSelectionTableViewController.swift */; }; DF06F1FC18FEC2020011E7B9 /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF06F1FB18FEC2020011E7B9 /* ColorPickerViewController.swift */; }; DF1B302D1CF5C667009C921C /* OpenHABNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF1B302C1CF5C667009C921C /* OpenHABNotification.swift */; }; - DF4A022C1CF315BA006C3456 /* OpenHABDrawerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4A022B1CF315BA006C3456 /* OpenHABDrawerTableViewController.swift */; }; - DF4A02421CF34096006C3456 /* OpenHABDrawerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4A02411CF34096006C3456 /* OpenHABDrawerItem.swift */; }; DF4B84041885A53700F34902 /* OpenHABDataObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4B84031885A53700F34902 /* OpenHABDataObject.swift */; }; DF4B84071885AE0E00F34902 /* blankicon.png in Resources */ = {isa = PBXBuildFile; fileRef = DF4B84061885AE0E00F34902 /* blankicon.png */; }; DF4B84131886DAC400F34902 /* FrameUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4B84121886DAC400F34902 /* FrameUITableViewCell.swift */; }; @@ -163,8 +165,6 @@ DFB2624418830A3600D3244D /* OpenHABSitemapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB2624318830A3600D3244D /* OpenHABSitemapViewController.swift */; }; DFB2624618830A3600D3244D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DFB2624518830A3600D3244D /* Images.xcassets */; }; DFDA3CEA193CADB200888039 /* ping.wav in Resources */ = {isa = PBXBuildFile; fileRef = DFDA3CE9193CADB200888039 /* ping.wav */; }; - DFDEE3FD18831099008B26AC /* OpenHABSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFDEE3FC18831099008B26AC /* OpenHABSettingsViewController.swift */; }; - DFDF452F1932032B00A6E581 /* OpenHABLegalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFDF452E1932032B00A6E581 /* OpenHABLegalViewController.swift */; }; DFDF45311932042B00A6E581 /* legal.rtf in Resources */ = {isa = PBXBuildFile; fileRef = DFDF45301932042B00A6E581 /* legal.rtf */; }; DFE10414197415F900D94943 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFE10413197415F900D94943 /* Security.framework */; }; DFFD8FD118EDBD4F003B502A /* UICircleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFFD8FD018EDBD4F003B502A /* UICircleButton.swift */; }; @@ -318,7 +318,6 @@ 938BF9D224EFD0B700E6B52F /* UIViewController+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Localization.swift"; sourceTree = ""; }; 938EDCE022C4FEB800661CA1 /* ScaleAspectFitImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleAspectFitImageView.swift; sourceTree = ""; }; A07ED02D2402EE6E006588FE /* OpenHABWatchTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OpenHABWatchTracker.swift; path = "openHABWatch Extension/OpenHABWatchTracker.swift"; sourceTree = SOURCE_ROOT; }; - A07EF79F2230C0A20040919F /* OpenHABClientCertificatesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABClientCertificatesViewController.swift; sourceTree = ""; }; A3F4C3A41A49A5940019A09F /* MainLaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MainLaunchScreen.xib; path = ../MainLaunchScreen.xib; sourceTree = ""; }; B7D5ECE021499E55001B0EC6 /* MapViewTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewTableViewCell.swift; sourceTree = ""; }; DA0749DD23E0B5950057FA83 /* ColorPickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerRow.swift; sourceTree = ""; }; @@ -374,6 +373,7 @@ DA1C2E6D230DC28F00FACFB0 /* primary_category.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = primary_category.txt; sourceTree = ""; }; DA1C2E6E230DC28F00FACFB0 /* Snapfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Snapfile; sourceTree = ""; }; DA21EAE12339621C001AB415 /* Throttler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Throttler.swift; sourceTree = ""; }; + DA242C612C83588600AFB10D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; DA28C361225241DE00AB409C /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; DA2DC22F21F2736C00830730 /* openHABTestsSwift.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = openHABTestsSwift.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DA2DC23121F2736C00830730 /* OpenHABJSONParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABJSONParserTests.swift; sourceTree = ""; }; @@ -385,8 +385,14 @@ DA4D4E0E2340A00200B37E37 /* Changes.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Changes.md; sourceTree = ""; }; DA50C7BC2B0A51BD0009F716 /* SliderWithSwitchSupportRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderWithSwitchSupportRow.swift; sourceTree = ""; }; DA50C7BE2B0A652F0009F716 /* SliderWithSwitchSupportUITableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderWithSwitchSupportUITableViewCell.swift; sourceTree = ""; }; + DA5ED9BD2C850955004875E0 /* ClientCertificatesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCertificatesViewModel.swift; sourceTree = ""; }; + DA5ED9BF2C8509C2004875E0 /* ClientCertificatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCertificatesView.swift; sourceTree = ""; }; DA65871E236F83CD007E2E7F /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = ""; }; DA6587212370C9D8007E2E7F /* PreferencesHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesHostingController.swift; sourceTree = ""; }; + DA6B2EEE2C861BC900DF77CF /* DrawerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerView.swift; sourceTree = ""; }; + DA6B2EF02C87B59000DF77CF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; + DA6B2EF42C89F8F200DF77CF /* ColorPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerView.swift; sourceTree = ""; }; + DA6B2EF62C8B92E800DF77CF /* SelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionView.swift; sourceTree = ""; }; DA7224D123828D3300712D20 /* PreviewConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewConstants.swift; sourceTree = ""; }; DA72E1B0236DE9F200B8EF3A /* AppState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppState.swift; path = "openHABWatch Extension/app/AppState.swift"; sourceTree = ""; }; DA72E1B5236DEA0900B8EF3A /* AppMessageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppMessageService.swift; path = "openHABWatch Extension/external/AppMessageService.swift"; sourceTree = ""; }; @@ -397,6 +403,7 @@ DA817E79234BF39B00C91824 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; DA88F8C522EC377100B408E5 /* ReleaseNotes.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = ReleaseNotes.md; sourceTree = ""; }; DA9721C224E29A8F0092CCFD /* UserDefaultsBacked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsBacked.swift; sourceTree = ""; }; + DA9F81862C85020F00B47B72 /* RTFTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTFTextView.swift; sourceTree = ""; }; DAA42BA721DC97DF00244B2A /* NotificationTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTableViewCell.swift; sourceTree = ""; }; DAA42BA921DC983B00244B2A /* VideoUITableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoUITableViewCell.swift; sourceTree = ""; }; DAA42BAB21DC984A00244B2A /* WebUITableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebUITableViewCell.swift; sourceTree = ""; }; @@ -413,7 +420,6 @@ DAD488B3287DDDFE00414693 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = Resources/nb.lproj/Intents.strings; sourceTree = ""; }; DAD488B4287DDDFF00414693 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; DAD488B5287DDDFF00414693 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - DAEAA89A21E2611000267EA3 /* OpenHABNotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABNotificationsViewController.swift; sourceTree = ""; }; DAEAA89C21E6B06300267EA3 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; DAEAA89E21E6B16600267EA3 /* UITableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; DAF0A28A2C56E3A300A14A6A /* RollershutterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollershutterCell.swift; sourceTree = ""; }; @@ -435,13 +441,9 @@ DAF4581723DC4A050018B495 /* ImageRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRow.swift; sourceTree = ""; }; DAF4581D23DC60020018B495 /* ImageRawRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRawRow.swift; sourceTree = ""; }; DAF4F6BF222734D200C24876 /* NewImageUITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewImageUITableViewCell.swift; sourceTree = ""; }; - DF05EF111D00696200DD646D /* DrawerUITableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerUITableViewCell.swift; sourceTree = ""; }; DF05FF221896BD2D00FF2F9B /* SelectionUITableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectionUITableViewCell.swift; sourceTree = ""; }; - DF06F1F518FE7A160011E7B9 /* OpenHABSelectionTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABSelectionTableViewController.swift; sourceTree = ""; }; DF06F1FB18FEC2020011E7B9 /* ColorPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = ""; }; DF1B302C1CF5C667009C921C /* OpenHABNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABNotification.swift; sourceTree = ""; }; - DF4A022B1CF315BA006C3456 /* OpenHABDrawerTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABDrawerTableViewController.swift; sourceTree = ""; }; - DF4A02411CF34096006C3456 /* OpenHABDrawerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABDrawerItem.swift; sourceTree = ""; }; DF4B84031885A53700F34902 /* OpenHABDataObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABDataObject.swift; sourceTree = ""; }; DF4B84061885AE0E00F34902 /* blankicon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = blankicon.png; sourceTree = ""; }; DF4B84121886DAC400F34902 /* FrameUITableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrameUITableViewCell.swift; sourceTree = ""; }; @@ -459,9 +461,7 @@ DFB2624518830A3600D3244D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; DFB2624C18830A3600D3244D /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; DFDA3CE9193CADB200888039 /* ping.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = ping.wav; sourceTree = ""; }; - DFDEE3FC18831099008B26AC /* OpenHABSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABSettingsViewController.swift; sourceTree = ""; }; DFDEE4161883C6A5008B26AC /* OpenHABTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABTracker.swift; sourceTree = ""; }; - DFDF452E1932032B00A6E581 /* OpenHABLegalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenHABLegalViewController.swift; sourceTree = ""; }; DFDF45301932042B00A6E581 /* legal.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = legal.rtf; sourceTree = ""; }; DFE10413197415F900D94943 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; DFFD8FD018EDBD4F003B502A /* UICircleButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICircleButton.swift; sourceTree = ""; }; @@ -824,24 +824,6 @@ path = Utils; sourceTree = ""; }; - DF4A02291CF3157B006C3456 /* Drawer */ = { - isa = PBXGroup; - children = ( - DF4A02411CF34096006C3456 /* OpenHABDrawerItem.swift */, - DF4A022B1CF315BA006C3456 /* OpenHABDrawerTableViewController.swift */, - DF4A022D1CF315D3006C3456 /* Widgets */, - ); - name = Drawer; - sourceTree = ""; - }; - DF4A022D1CF315D3006C3456 /* Widgets */ = { - isa = PBXGroup; - children = ( - DF05EF111D00696200DD646D /* DrawerUITableViewCell.swift */, - ); - name = Widgets; - sourceTree = ""; - }; DF4B83FD18857FA100F34902 /* UI */ = { isa = PBXGroup; children = ( @@ -850,13 +832,13 @@ 65570A7C2476D16A00D524EA /* OpenHABWebViewController.swift */, DFB2624318830A3600D3244D /* OpenHABSitemapViewController.swift */, DAC65FC6236EDF3900F4501E /* SpinnerViewController.swift */, - DFDEE3FC18831099008B26AC /* OpenHABSettingsViewController.swift */, - DAEAA89A21E2611000267EA3 /* OpenHABNotificationsViewController.swift */, - DFDF452E1932032B00A6E581 /* OpenHABLegalViewController.swift */, - DF06F1F518FE7A160011E7B9 /* OpenHABSelectionTableViewController.swift */, - A07EF79F2230C0A20040919F /* OpenHABClientCertificatesViewController.swift */, + DA6B2EF62C8B92E800DF77CF /* SelectionView.swift */, + DA242C612C83588600AFB10D /* SettingsView.swift */, + DA5ED9BF2C8509C2004875E0 /* ClientCertificatesView.swift */, + DA9F81862C85020F00B47B72 /* RTFTextView.swift */, + DA6B2EF02C87B59000DF77CF /* NotificationsView.swift */, + DA6B2EEE2C861BC900DF77CF /* DrawerView.swift */, DF4B84101886DA9900F34902 /* Widgets */, - DF4A02291CF3157B006C3456 /* Drawer */, DFFD8FCE18EDBD30003B502A /* Util */, ); name = UI; @@ -895,6 +877,7 @@ DAEAA89E21E6B16600267EA3 /* UITableView.swift */, DA7E1E47222EB00B002AEFD8 /* PlayerView.swift */, DA21EAE12339621C001AB415 /* Throttler.swift */, + DA6B2EF42C89F8F200DF77CF /* ColorPickerView.swift */, ); name = Widgets; sourceTree = ""; @@ -954,8 +937,8 @@ A3F4C3A41A49A5940019A09F /* MainLaunchScreen.xib */, DFB2623A18830A3600D3244D /* AppDelegate.swift */, DFDEE3FE1883228C008B26AC /* Models */, - DF4B83FD18857FA100F34902 /* UI */, DF4B84051885AD4600F34902 /* Images */, + DF4B83FD18857FA100F34902 /* UI */, DFDEE3FF18832293008B26AC /* Util */, 1224F78B228A89E300750965 /* Watch */, DFB2623118830A3600D3244D /* Supporting Files */, @@ -983,6 +966,7 @@ children = ( DF4B84031885A53700F34902 /* OpenHABDataObject.swift */, DF1B302C1CF5C667009C921C /* OpenHABNotification.swift */, + DA5ED9BD2C850955004875E0 /* ClientCertificatesViewModel.swift */, ); name = Models; sourceTree = ""; @@ -1458,6 +1442,7 @@ buildActionMask = 2147483647; files = ( 93B7B33128018301009EB296 /* Intents.intentdefinition in Sources */, + DA242C622C83588600AFB10D /* SettingsView.swift in Sources */, DA7E1E4B2233986E002AEFD8 /* PlayerView.swift in Sources */, DA7E1E492230227E002AEFD8 /* OpenHABTracker.swift in Sources */, 65570A7D2476D16A00D524EA /* OpenHABWebViewController.swift in Sources */, @@ -1465,29 +1450,28 @@ DF06F1FC18FEC2020011E7B9 /* ColorPickerViewController.swift in Sources */, 1224F78F228A89FD00750965 /* WatchMessageService.swift in Sources */, DAA42BAC21DC984A00244B2A /* WebUITableViewCell.swift in Sources */, - DF05EF121D00696200DD646D /* DrawerUITableViewCell.swift in Sources */, DF4B84131886DAC400F34902 /* FrameUITableViewCell.swift in Sources */, DF4B84161886EACA00F34902 /* GenericUITableViewCell.swift in Sources */, 935B484625342B8E00E44CF0 /* URL+Static.swift in Sources */, B7D5ECE121499E55001B0EC6 /* MapViewTableViewCell.swift in Sources */, + DA6B2EF52C89F8F200DF77CF /* ColorPickerView.swift in Sources */, DAA42BAA21DC983B00244B2A /* VideoUITableViewCell.swift in Sources */, DFB2623B18830A3600D3244D /* AppDelegate.swift in Sources */, + DA6B2EF72C8B92E800DF77CF /* SelectionView.swift in Sources */, DF4B84041885A53700F34902 /* OpenHABDataObject.swift in Sources */, DAC65FC7236EDF3900F4501E /* SpinnerViewController.swift in Sources */, - DF4A02421CF34096006C3456 /* OpenHABDrawerItem.swift in Sources */, + DA9F81872C85020F00B47B72 /* RTFTextView.swift in Sources */, + DA6B2EF12C87B59000DF77CF /* NotificationsView.swift in Sources */, DA50C7BF2B0A65300009F716 /* SliderWithSwitchSupportUITableViewCell.swift in Sources */, - DF4A022C1CF315BA006C3456 /* OpenHABDrawerTableViewController.swift in Sources */, - DFDF452F1932032B00A6E581 /* OpenHABLegalViewController.swift in Sources */, + DA5ED9BE2C850955004875E0 /* ClientCertificatesViewModel.swift in Sources */, DA21EAE22339621C001AB415 /* Throttler.swift in Sources */, DAF4F6C0222734D300C24876 /* NewImageUITableViewCell.swift in Sources */, - DAEAA89B21E2611000267EA3 /* OpenHABNotificationsViewController.swift in Sources */, DF1B302D1CF5C667009C921C /* OpenHABNotification.swift in Sources */, + DA6B2EEF2C861BC900DF77CF /* DrawerView.swift in Sources */, 938BF9D324EFD0B700E6B52F /* UIViewController+Localization.swift in Sources */, - DF06F1F618FE7A160011E7B9 /* OpenHABSelectionTableViewController.swift in Sources */, DAA42BA821DC97E000244B2A /* NotificationTableViewCell.swift in Sources */, DAF0A28F2C56F1EE00A14A6A /* ColorPickerCell.swift in Sources */, 6595667E28E0BE8E00E8A53B /* MulticastDelegate.swift in Sources */, - DFDEE3FD18831099008B26AC /* OpenHABSettingsViewController.swift in Sources */, 938EDCE122C4FEB800661CA1 /* ScaleAspectFitImageView.swift in Sources */, DAEAA89F21E6B16600267EA3 /* UITableView.swift in Sources */, DFB2624418830A3600D3244D /* OpenHABSitemapViewController.swift in Sources */, @@ -1499,8 +1483,8 @@ DFA16EBB18883DE500EDB0BB /* SliderUITableViewCell.swift in Sources */, DFA13CB418872EBD006355C3 /* SwitchUITableViewCell.swift in Sources */, DFFD8FD118EDBD4F003B502A /* UICircleButton.swift in Sources */, - A07EF7A02230C0A30040919F /* OpenHABClientCertificatesViewController.swift in Sources */, 938BF9C624EFCC0700E6B52F /* UILabel+Localization.swift in Sources */, + DA5ED9C02C8509C2004875E0 /* ClientCertificatesView.swift in Sources */, 653B54C0285C0AC700298ECD /* OpenHABRootViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1652,12 +1636,10 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = openHABIntents/openHABIntents.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 12; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; @@ -1674,7 +1656,6 @@ PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.openHABIntents; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app.openHABIntents"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; @@ -1740,12 +1721,10 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 12; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -1766,7 +1745,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app.NotificationService"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -1922,13 +1901,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "openHABWatch Extension/openHABWatch Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 12; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = PBAPXHRAM9; - "DEVELOPMENT_TEAM[sdk=watchos*]" = PBAPXHRAM9; GCC_C_LANGUAGE_STANDARD = "compiler-default"; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = openHABWatch/Info.plist; @@ -1943,7 +1920,6 @@ PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*]" = "match AppStore org.openhab.app.watchkitapp"; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -2213,11 +2189,9 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = openHAB/openHAB.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 12; DEVELOPMENT_TEAM = PBAPXHRAM9; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PBAPXHRAM9; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "openHAB/openHAB-Prefix.pch"; INFOPLIST_FILE = "openHAB/openHAB-Info.plist"; @@ -2236,7 +2210,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore org.openhab.app"; SUPPORTS_MACCATALYST = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_INSTALL_OBJC_HEADER = NO; diff --git a/openHAB.xcodeproj/xcshareddata/xcschemes/openHAB.xcscheme b/openHAB.xcodeproj/xcshareddata/xcschemes/openHAB.xcscheme index fe4eb7c51..bd46b5166 100644 --- a/openHAB.xcodeproj/xcshareddata/xcschemes/openHAB.xcscheme +++ b/openHAB.xcodeproj/xcshareddata/xcschemes/openHAB.xcscheme @@ -65,8 +65,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" - allowLocationSimulation = "YES" - showNonLocalizedStrings = "YES"> + allowLocationSimulation = "YES"> String { + NetworkConnection.shared.clientCertificateManager.getIdentityName(index: index) + } +} diff --git a/openHAB/ColorPickerView.swift b/openHAB/ColorPickerView.swift new file mode 100644 index 000000000..7519187e8 --- /dev/null +++ b/openHAB/ColorPickerView.swift @@ -0,0 +1,77 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import Combine +import OpenHABCore +import os +import SwiftUI + +struct ColorPickerView: View { + @State private var selectedColor: Color = .white + @State private var hue: Double = 0.0 + @State private var saturation: Double = 0.0 + @State private var brightness: Double = 0.0 + + @ObservedObject var throttler = Throttler(maxInterval: 0.3) + + var widget: OpenHABWidget? // OpenHAB widget for sending commands + + private let logger = Logger(subsystem: "org.openhab.app", category: "ColorPickerView") + + var body: some View { + VStack { + // SwiftUI Color Picker + ColorPicker("Pick a Color", selection: $selectedColor) + .onChange(of: selectedColor) { newColor in + throttler.throttle { + updateHSB(from: newColor) + sendColorUpdate() + } + } + .padding() + + // Displaying HSB values + Text("Hue: \(hue, specifier: "%.2f")") + Text("Saturation: \(saturation, specifier: "%.2f")") + Text("Brightness: \(brightness, specifier: "%.2f")") + } + .onAppear { + // Set initial color from widget if available + if let initialColor = widget?.item?.stateAsUIColor() { + selectedColor = Color(initialColor) + } + } + .background(Color(UIColor.systemBackground)) + } + + // Update hue, saturation, brightness from Color + func updateHSB(from color: Color) { + let uiColor = UIColor(color) + // swiftlint:disable:next large_tuple + var (hue, saturation, brightness, alpha): (CGFloat, CGFloat, CGFloat, CGFloat) = (0.0, 0.0, 0.0, 0.0) + uiColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + + self.hue = Double(hue * 360) // Convert to degrees + self.saturation = Double(saturation * 100) + self.brightness = Double(brightness * 100) + } + + // Send the color update to the widget + func sendColorUpdate() { + let command = "\(hue),\(saturation),\(brightness)" + logger.debug("Sending command: \(command)") + widget?.sendCommand(command) + } +} + +#Preview { + ColorPickerView() +} diff --git a/openHAB/DrawerUITableViewCell.swift b/openHAB/DrawerUITableViewCell.swift deleted file mode 100644 index 0efe0d91b..000000000 --- a/openHAB/DrawerUITableViewCell.swift +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import os.log -import UIKit - -class DrawerUITableViewCell: UITableViewCell { - @IBOutlet private(set) var customTextLabel: UILabel! - @IBOutlet private(set) var customImageView: UIImageView! - - required init?(coder: NSCoder) { - os_log("DrawerUITableViewCell initWithCoder", log: .viewCycle, type: .info) - super.init(coder: coder) - - separatorInset = .zero - } - - // This is to fix possible different sizes of user icons - we fix size and position of UITableViewCell icons - override func layoutSubviews() { - super.layoutSubviews() - imageView?.frame = CGRect(x: 14, y: 6, width: 30, height: 30) - } -} diff --git a/openHAB/DrawerView.swift b/openHAB/DrawerView.swift new file mode 100644 index 000000000..291428a1d --- /dev/null +++ b/openHAB/DrawerView.swift @@ -0,0 +1,235 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import Kingfisher +import OpenHABCore +import os.log +import SafariServices +import SFSafeSymbols +import SwiftUI + +func deriveSitemaps(_ response: Data?) -> [OpenHABSitemap] { + var sitemaps = [OpenHABSitemap]() + + if let response { + do { + os_log("Response will be decoded by JSON", log: .remoteAccess, type: .info) + let sitemapsCodingData = try response.decoded(as: [OpenHABSitemap.CodingData].self) + for sitemapCodingDatum in sitemapsCodingData { + os_log("Sitemap %{PUBLIC}@", log: .remoteAccess, type: .info, sitemapCodingDatum.label) + sitemaps.append(sitemapCodingDatum.openHABSitemap) + } + } catch { + os_log("Should not throw %{PUBLIC}@", log: .notifications, type: .error, error.localizedDescription) + } + } + + return sitemaps +} + +struct UiTile: Decodable { + var name: String + var url: String + var imageUrl: String +} + +struct ImageView: View { + let url: String + + // App wide data access + var appData: OpenHABDataObject? { + AppDelegate.appDelegate.appData + } + + var body: some View { + if !url.isEmpty { + switch url { + case _ where url.hasPrefix("data:image"): + let provider = Base64ImageDataProvider(base64String: url.deletingPrefix("data:image/png;base64,"), cacheKey: UUID().uuidString) + return KFImage(source: .provider(provider)).resizable() + case _ where url.hasPrefix("http"): + return KFImage(URL(string: url)).resizable() + default: + let builtURL = Endpoint.resource(openHABRootUrl: appData?.openHABRootUrl ?? "", path: url.prepare()).url + return KFImage(builtURL).resizable() + } + } else { + // This will always fallback to placeholder + return KFImage(URL(string: "bundle://openHABIcon")).placeholder { Image("openHABIcon").resizable() } + } + } +} + +struct DrawerView: View { + @State private var sitemaps: [OpenHABSitemap] = [] + @State private var uiTiles: [OpenHABUiTile] = [] + @State private var selectedSection: Int? + + var openHABUsername = "" + var openHABPassword = "" + + var onDismiss: (TargetController) -> Void + @Environment(\.dismiss) private var dismiss + + // App wide data access + var appData: OpenHABDataObject? { + AppDelegate.appDelegate.appData + } + + @ScaledMetric var openHABIconwidth = 20.0 + @ScaledMetric var tilesIconwidth = 20.0 + @ScaledMetric var sitemapIconwidth = 20.0 + + var body: some View { + List { + Section(header: Text("Main")) { + HStack { + Image("openHABIcon") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: openHABIconwidth) + Text("Home") + } + .onTapGesture { + dismiss() + onDismiss(.webview) + } + } + + Section(header: Text("Tiles")) { + ForEach(uiTiles, id: \.url) { tile in + HStack { + ImageView(url: tile.imageUrl) + .aspectRatio(contentMode: .fit) + .frame(width: tilesIconwidth) + Text(tile.name) + } + .onTapGesture { + dismiss() + onDismiss(.tile(tile.url)) + } + } + } + + Section(header: Text("Sitemaps")) { + ForEach(sitemaps, id: \.name) { sitemap in + HStack { + let url = Endpoint.iconForDrawer(rootUrl: appData?.openHABRootUrl ?? "", icon: sitemap.icon).url + KFImage(url).placeholder { Image("openHABIcon").resizable() } + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: sitemapIconwidth) + Text(sitemap.label) + } + .onTapGesture { + dismiss() + onDismiss(.sitemap(sitemap.name)) + } + } + } + + Section(header: Text("System")) { + HStack { + Image(systemSymbol: .gear) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: openHABIconwidth) + Text(LocalizedStringKey("settings")) + } + .onTapGesture { + dismiss() + onDismiss(.settings) + } + + // check if we are using my.openHAB, add notifications menu item then + // Actually this should better test whether the host of the remoteUrl is on openhab.org + if Preferences.remoteUrl.contains("openhab.org"), !Preferences.demomode { + HStack { + Image(systemSymbol: .bell) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: openHABIconwidth) + Text(LocalizedStringKey("notifications")) + } + .onTapGesture { + dismiss() + onDismiss(.notifications) + } + } + } + } + .listStyle(.inset) + .onAppear(perform: loadData) + } + + private func loadData() { + // TODO: Replace network calls with appropriate @EnvironmentObject or other state management + loadSitemaps() + loadUiTiles() + } + + private func loadSitemaps() { + // Perform network call to load sitemaps and decode + // Update the sitemaps state + + NetworkConnection.sitemaps(openHABRootUrl: appData?.openHABRootUrl ?? "") { response in + switch response.result { + case let .success(data): + os_log("Sitemap response", log: .viewCycle, type: .info) + + sitemaps = deriveSitemaps(data) + + if sitemaps.last?.name == "_default", sitemaps.count > 1 { + sitemaps = Array(sitemaps.dropLast()) + } + + // Sort the sitemaps according to Settings selection. + switch SortSitemapsOrder(rawValue: Preferences.sortSitemapsby) ?? .label { + case .label: sitemaps.sort { $0.label < $1.label } + case .name: sitemaps.sort { $0.name < $1.name } + } + case let .failure(error): + os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) + } + } + } + + private func loadUiTiles() { + // Perform network call to load UI Tiles and decode + // Update the uiTiles state + NetworkConnection.uiTiles(openHABRootUrl: appData?.openHABRootUrl ?? "") { response in + switch response.result { + case .success: + os_log("ui tiles response", log: .viewCycle, type: .info) + guard let responseData = response.data else { + os_log("Error: did not receive data", log: OSLog.remoteAccess, type: .info) + return + } + do { + uiTiles = try JSONDecoder().decode([OpenHABUiTile].self, from: responseData) + } catch { + os_log("Error: did not receive data %{PUBLIC}@", log: OSLog.remoteAccess, type: .info, error.localizedDescription) + } + case let .failure(error): + os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) + } + } + } + + mutating func loadSettings() { + openHABUsername = Preferences.username + openHABPassword = Preferences.password + } +} + +#Preview { + DrawerView { _ in } +} diff --git a/openHAB/Main.storyboard b/openHAB/Main.storyboard index e071c7cd7..74fd991da 100644 --- a/openHAB/Main.storyboard +++ b/openHAB/Main.storyboard @@ -615,814 +615,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1455,46 +647,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1513,73 +670,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1598,67 +688,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1683,12 +712,6 @@ - - - - - - diff --git a/openHAB/NotificationsView.swift b/openHAB/NotificationsView.swift new file mode 100644 index 000000000..ca8d4b151 --- /dev/null +++ b/openHAB/NotificationsView.swift @@ -0,0 +1,114 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import Kingfisher +import OpenHABCore +import os.log +import SwiftUI + +struct NotificationRow: View { + var notification: OpenHABNotification + + // App wide data access + var appData: OpenHABDataObject? { + AppDelegate.appDelegate.appData + } + + var body: some View { + HStack { + KFImage(iconUrl) + .placeholder { + Image("openHABIcon").resizable() + } + .resizable() + .frame(width: 40, height: 40) + .cornerRadius(8) + VStack(alignment: .leading) { + Text(notification.message) + .font(.body) + if let timeStamp = notification.created { + Text(dateString(from: timeStamp)) + .font(.caption) + .foregroundColor(.gray) + } + } + } + + .padding(.vertical, 8) + } + + private var iconUrl: URL? { + if let appData { + return Endpoint.icon( + rootUrl: appData.openHABRootUrl, + version: appData.openHABVersion, + icon: notification.icon, + state: "", + iconType: .png, + iconColor: "" + ).url + } + return nil + } + + private func dateString(from date: Date) -> String { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .medium + formatter.timeZone = TimeZone.current + return formatter.string(from: date) + } +} + +struct NotificationsView: View { + @State var notifications: [OpenHABNotification] = [] + + var body: some View { + List(notifications, id: \.id) { notification in + NotificationRow(notification: notification) + } + .refreshable { + loadNotifications() + } + .navigationTitle("Notifications") + .onAppear { + loadNotifications() + } + } + + private func loadNotifications() { + NetworkConnection.notification(urlString: Preferences.remoteUrl) { response in + DispatchQueue.main.async { + switch response.result { + case let .success(data): + do { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full) + let codingDatas = try data.decoded(as: [OpenHABNotification.CodingData].self, using: decoder) + notifications = codingDatas.map(\.openHABNotification) + } catch { + os_log("%{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) + } + case let .failure(error): + os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) + } + } + } + } +} + +#Preview { + NotificationsView(notifications: [OpenHABNotification(message: "message1", created: Date.now, id: UUID().uuidString), OpenHABNotification(message: "message2", created: Date.now, id: UUID().uuidString)]) +} + +#Preview { + NotificationRow(notification: OpenHABNotification(message: "message3", created: Date.now)) +} diff --git a/openHAB/OpenHABClientCertificatesViewController.swift b/openHAB/OpenHABClientCertificatesViewController.swift deleted file mode 100644 index da74346fb..000000000 --- a/openHAB/OpenHABClientCertificatesViewController.swift +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import OpenHABCore -import os.log -import UIKit - -class OpenHABClientCertificatesViewController: UITableViewController { - static let tableViewCellIdentifier = "ClientCertificatesCell" - - var clientCertificates: [SecIdentity] = [] - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func viewDidLoad() { - super.viewDidLoad() - navigationItem.title = NSLocalizedString("client_certificates", comment: "") - os_log("OpenHABClientCertificatesViewController viewDidLoad", log: .default, type: .info) - - tableView.tableFooterView = UIView() - tableView.allowsMultipleSelectionDuringEditing = false - } - - override func viewWillAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - tableView.reloadData() - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // Return the number of rows in the section. - NetworkConnection.shared.clientCertificateManager.clientIdentities.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: OpenHABClientCertificatesViewController.tableViewCellIdentifier, for: indexPath) - cell.textLabel?.text = NetworkConnection.shared.clientCertificateManager.getIdentityName(index: indexPath.row) - return cell - } - - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == UITableViewCell.EditingStyle.delete { - let status = NetworkConnection.shared.clientCertificateManager.deleteFromKeychain(index: indexPath.row) - if status == noErr { - tableView.deleteRows(at: [indexPath], with: .fade) - } - } - } -} diff --git a/openHAB/OpenHABDrawerItem.swift b/openHAB/OpenHABDrawerItem.swift deleted file mode 100644 index ce8924bf5..000000000 --- a/openHAB/OpenHABDrawerItem.swift +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation - -enum OpenHABDrawerItem { - case settings - case notifications - - var localizedString: String { - switch self { - case .settings: - NSLocalizedString("settings", comment: "") - case .notifications: - NSLocalizedString("notifications", comment: "") - } - } - - static func openHABDrawerItem(localizedString: String) -> OpenHABDrawerItem { - switch localizedString { - case OpenHABDrawerItem.settings.localizedString: - OpenHABDrawerItem.settings - case OpenHABDrawerItem.notifications.localizedString: - OpenHABDrawerItem.notifications - default: - OpenHABDrawerItem.settings - } - } -} diff --git a/openHAB/OpenHABDrawerTableViewController.swift b/openHAB/OpenHABDrawerTableViewController.swift deleted file mode 100644 index d2f12070a..000000000 --- a/openHAB/OpenHABDrawerTableViewController.swift +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import OpenHABCore -import os.log -import SafariServices -import SFSafeSymbols -import UIKit - -func deriveSitemaps(_ response: Data?) -> [OpenHABSitemap] { - var sitemaps = [OpenHABSitemap]() - - if let response { - do { - os_log("Response will be decoded by JSON", log: .remoteAccess, type: .info) - let sitemapsCodingData = try response.decoded(as: [OpenHABSitemap.CodingData].self) - for sitemapCodingDatum in sitemapsCodingData { - os_log("Sitemap %{PUBLIC}@", log: .remoteAccess, type: .info, sitemapCodingDatum.label) - sitemaps.append(sitemapCodingDatum.openHABSitemap) - } - } catch { - os_log("Should not throw %{PUBLIC}@", log: .notifications, type: .error, error.localizedDescription) - } - } - - return sitemaps -} - -struct UiTile: Decodable { - var name: String - var url: String - var imageUrl: String -} - -class OpenHABDrawerTableViewController: UITableViewController { - static let tableViewCellIdentifier = "DrawerCell" - - var sitemaps: [OpenHABSitemap] = [] - var uiTiles: [OpenHABUiTile] = [] - var openHABUsername = "" - var openHABPassword = "" - var drawerItems: [OpenHABDrawerItem] = [] - weak var delegate: ModalHandler? - - // App wide data access - var appData: OpenHABDataObject? { - AppDelegate.appDelegate.appData - } - - init() { - super.init(nibName: nil, bundle: nil) - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func viewDidLoad() { - super.viewDidLoad() - tableView.tableFooterView = UIView() - drawerItems = [] - sitemaps = [] - loadSettings() - setStandardDrawerItems() - os_log("OpenHABDrawerTableViewController did load", log: .viewCycle, type: .info) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - os_log("OpenHABDrawerTableViewController viewWillAppear", log: .viewCycle, type: .info) - - NetworkConnection.sitemaps(openHABRootUrl: appData?.openHABRootUrl ?? "") { response in - switch response.result { - case let .success(data): - os_log("Sitemap response", log: .viewCycle, type: .info) - - self.sitemaps = deriveSitemaps(data) - - if self.sitemaps.last?.name == "_default", self.sitemaps.count > 1 { - self.sitemaps = Array(self.sitemaps.dropLast()) - } - - // Sort the sitemaps according to Settings selection. - switch SortSitemapsOrder(rawValue: Preferences.sortSitemapsby) ?? .label { - case .label: self.sitemaps.sort { $0.label < $1.label } - case .name: self.sitemaps.sort { $0.name < $1.name } - } - - self.drawerItems.removeAll() - self.setStandardDrawerItems() - self.tableView.reloadData() - case let .failure(error): - os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) - self.drawerItems.removeAll() - self.setStandardDrawerItems() - self.tableView.reloadData() - } - } - - NetworkConnection.uiTiles(openHABRootUrl: appData?.openHABRootUrl ?? "") { response in - switch response.result { - case .success: - UIApplication.shared.isNetworkActivityIndicatorVisible = false - os_log("ui tiles response", log: .viewCycle, type: .info) - guard let responseData = response.data else { - os_log("Error: did not receive data", log: OSLog.remoteAccess, type: .info) - return - } - do { - self.uiTiles = try JSONDecoder().decode([OpenHABUiTile].self, from: responseData) - self.tableView.reloadData() - } catch { - os_log("Error: did not receive data %{PUBLIC}@", log: OSLog.remoteAccess, type: .info, error.localizedDescription) - } - case let .failure(error): - UIApplication.shared.isNetworkActivityIndicatorVisible = false - os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) - } - } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - tableView.reloadData() - os_log("RightDrawerViewController viewDidAppear", log: .viewCycle, type: .info) - os_log("Sitemap count: %d", log: .viewCycle, type: .info, Int(sitemaps.count)) - os_log("Menu items count: %d", log: .viewCycle, type: .info, Int(drawerItems.count)) - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) // Call the super class implementation. - os_log("RightDrawerViewController viewDidDisappear", log: .viewCycle, type: .info) - } - - // MARK: - Table view data source - - override func numberOfSections(in tableView: UITableView) -> Int { - 4 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch section { - case 0: - 1 - case 1: - uiTiles.count - case 2: - sitemaps.count - case 3: - drawerItems.count - default: - 0 - } - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch section { - case 0: - "Main" - case 1: - "Tiles" - case 2: - "Sitemaps" - case 3: - "System" - default: - "Unknown" - } - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = (tableView.dequeueReusableCell(withIdentifier: OpenHABDrawerTableViewController.tableViewCellIdentifier) as? DrawerUITableViewCell)! - cell.customImageView.subviews.forEach { $0.removeFromSuperview() } - cell.accessoryView = nil - switch indexPath.section { - case 0: - cell.customTextLabel?.text = "Home" - cell.customImageView.image = UIImage(named: "openHABIcon") - if let currentView = appData?.currentView { - // if we already are on the webview, pressing this again will force a refresh - if currentView == .webview { - cell.accessoryView = UIImageView(image: UIImage(named: "arrow.triangle.2.circlepath")) - } - } - case 1: - let imageView = UIImageView(frame: cell.customImageView.bounds) - let tile = uiTiles[indexPath.row] - cell.customTextLabel?.text = tile.name - let passedURL = tile.imageUrl - // Dependent on $OPENHAB_CONF/services/runtime.cfg - // Can either be an absolute URL, a path (sometimes malformed) or the content to be displayed (for imageURL) - if !passedURL.isEmpty { - switch passedURL { - case _ where passedURL.hasPrefix("data:image"): - if let imageData = Data(base64Encoded: passedURL.deletingPrefix("data:image/png;base64,"), options: .ignoreUnknownCharacters) { - imageView.image = UIImage(data: imageData) - } // data;image/png;base64, - case _ where passedURL.hasPrefix("http"): - os_log("Loading %{PUBLIC}@", log: .default, type: .info, String(describing: passedURL)) - imageView.kf.setImage(with: URL(string: passedURL), placeholder: UIImage(named: "openHABIcon")) - default: - if let builtURL = Endpoint.resource(openHABRootUrl: appData?.openHABRootUrl ?? "", path: passedURL.prepare()).url { - os_log("Loading %{PUBLIC}@", log: .default, type: .info, String(describing: builtURL)) - imageView.kf.setImage(with: builtURL, placeholder: UIImage(named: "openHABIcon")) - } - } - } else { - imageView.image = UIImage(named: "openHABIcon") - } - cell.customImageView.image = imageView.image - case 2: - if !sitemaps.isEmpty { - let siteMapIndex = indexPath.row - let imageView = UIImageView(frame: cell.customImageView.bounds) - - cell.customTextLabel?.text = sitemaps[siteMapIndex].label - if !sitemaps[siteMapIndex].icon.isEmpty { - if let iconURL = Endpoint.iconForDrawer(rootUrl: appData?.openHABRootUrl ?? "", icon: sitemaps[siteMapIndex].icon).url { - imageView.kf.setImage(with: iconURL, placeholder: UIImage(named: "openHABIcon")) - } - } else { - imageView.image = UIImage(named: "openHABIcon") - } - cell.customImageView.image = imageView.image - } - case 3: - // Then menu items - let drawerItem = drawerItems[indexPath.row] - cell.customTextLabel?.text = drawerItem.localizedString - switch drawerItem { - case .notifications: - cell.customImageView.image = UIImage(systemSymbol: .bell) - case .settings: - cell.customImageView.image = UIImage(systemSymbol: .gear) - } - default: - break - } - cell.separatorInset = UIEdgeInsets(top: 0, left: 60, bottom: 0, right: 0) - - cell.preservesSuperviewLayoutMargins = false - cell.layoutMargins = .zero - - return cell - } - - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - 44 - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - // open a alert with an OK and cancel button - os_log("Clicked on drawer section %d row %d", log: .viewCycle, type: .info, indexPath.section, indexPath.row) - - tableView.deselectRow(at: indexPath, animated: false) - // First sitemaps - switch indexPath.section { - case 0: - dismiss(animated: true) { - self.delegate?.modalDismissed(to: .webview) - } - case 1: - let passedURL = uiTiles[indexPath.row].url - // Dependent on $OPENHAB_CONF/services/runtime.cfg - // Can either be an absolute URL, a path (sometimes malformed) - if !passedURL.isEmpty { - switch passedURL { - case _ where passedURL.hasPrefix("http"): - openURL(url: URL(string: passedURL)) - default: - let builtURL = Endpoint.resource(openHABRootUrl: appData?.openHABRootUrl ?? "", path: passedURL.prepare()) - openURL(url: builtURL.url) - } - } - case 2: - if !sitemaps.isEmpty { - let sitemap = sitemaps[indexPath.row] - Preferences.defaultSitemap = sitemap.name - appData?.sitemapViewController?.pageUrl = "" - dismiss(animated: true) { - os_log("self delegate %d", log: .viewCycle, type: .info, self.delegate != nil) - self.delegate?.modalDismissed(to: .sitemap) - } - } - case 3: - // Then menu items - let drawerItem = drawerItems[indexPath.row] - - switch drawerItem { - case .settings: - dismiss(animated: true) { - self.delegate?.modalDismissed(to: .settings) - } - case .notifications: - dismiss(animated: true) { - self.delegate?.modalDismissed(to: .notifications) - } - } - default: - break - } - } - - private func setStandardDrawerItems() { - // check if we are using my.openHAB, add notifications menu item then - // Actually this should better test whether the host of the remoteUrl is on openhab.org - if Preferences.remoteUrl.contains("openhab.org"), !Preferences.demomode { - drawerItems.append(.notifications) - } - // Settings always go last - drawerItems.append(.settings) - } - - func loadSettings() { - openHABUsername = Preferences.username - openHABPassword = Preferences.password - } - - private func openURL(url: URL?) { - if let url { - let config = SFSafariViewController.Configuration() - config.entersReaderIfAvailable = true - let vc = SFSafariViewController(url: url, configuration: config) - present(vc, animated: true) - } - } -} diff --git a/openHAB/OpenHABLegalViewController.swift b/openHAB/OpenHABLegalViewController.swift deleted file mode 100644 index 4c63aec40..000000000 --- a/openHAB/OpenHABLegalViewController.swift +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import OpenHABCore -import UIKit - -class OpenHABLegalViewController: UIViewController { - @IBOutlet private var legalTextView: UITextView! - - override func viewDidLoad() { - super.viewDidLoad() - let legalPath = Bundle.main.url(forResource: "legal", withExtension: "rtf") - var legalAttributedString: NSAttributedString? - if let legalPath { - legalAttributedString = try? NSAttributedString( - url: legalPath, - options: [.characterEncoding: String.Encoding.utf8.rawValue], - documentAttributes: nil - ) - } - if let legalAttributedString { - legalTextView.attributedText = legalAttributedString - } - - legalTextView.backgroundColor = .ohSystemBackground - legalTextView.textColor = .ohLabel - } -} diff --git a/openHAB/OpenHABNotification.swift b/openHAB/OpenHABNotification.swift index 4970cc65b..2862a9d27 100644 --- a/openHAB/OpenHABNotification.swift +++ b/openHAB/OpenHABNotification.swift @@ -16,15 +16,19 @@ class OpenHABNotification: NSObject { var created: Date? var icon = "" var severity = "" + var id = "" - init(message: String, created: Date?) { + init(message: String = "", created: Date? = nil, icon: String = "", severity: String = "", id: String = "") { self.message = message self.created = created + self.icon = icon + self.severity = severity + self.id = id } - init(dictionary: [String: Any]) { + convenience init(dictionary: [String: Any]) { let propertyNames: Set = ["message", "icon", "severity"] - super.init() + self.init() let keyArray = dictionary.keys for key in keyArray { if key as String == "created" { @@ -63,6 +67,6 @@ extension OpenHABNotification { // Convenience method to convert a decoded value into a proper OpenHABNotification instance extension OpenHABNotification.CodingData { var openHABNotification: OpenHABNotification { - OpenHABNotification(message: message, created: created) + OpenHABNotification(message: message, created: created, id: id) } } diff --git a/openHAB/OpenHABNotificationsViewController.swift b/openHAB/OpenHABNotificationsViewController.swift deleted file mode 100644 index c78ecdca7..000000000 --- a/openHAB/OpenHABNotificationsViewController.swift +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import OpenHABCore -import os.log -import SideMenu -import UIKit - -class OpenHABNotificationsViewController: UITableViewController { - static let tableViewCellIdentifier = "NotificationCell" - - var notifications: NSMutableArray = [] - var openHABRootUrl = "" - var openHABUsername = "" - var openHABPassword = "" - - var appData: OpenHABDataObject? { - AppDelegate.appDelegate.appData - } - - override func viewDidLoad() { - super.viewDidLoad() - notifications = [] - tableView.tableFooterView = UIView() - refreshControl = UIRefreshControl() - refreshControl?.addTarget(self, action: #selector(OpenHABNotificationsViewController.handleRefresh(_:)), for: .valueChanged) - if let refreshControl { - tableView.refreshControl = refreshControl - } - - navigationItem.largeTitleDisplayMode = .never - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - loadSettings() - loadNotifications() - } - - func loadNotifications() { - NetworkConnection.notification(urlString: Preferences.remoteUrl) { response in - switch response.result { - case let .success(data): - do { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full) - let codingDatas = try data.decoded(as: [OpenHABNotification.CodingData].self, using: decoder) - self.notifications = [] - for codingDatum in codingDatas { - self.notifications.add(codingDatum.openHABNotification) - } - } catch { - os_log("%{PUBLIC}@ ", log: .default, type: .error, error.localizedDescription) - } - - self.refreshControl?.endRefreshing() - self.tableView.reloadData() - - case let .failure(error): - os_log("%{PUBLIC}@", log: .default, type: .error, error.localizedDescription) - self.refreshControl?.endRefreshing() - } - } - } - - @objc - func handleRefresh(_ refreshControl: UIRefreshControl?) { - os_log("Refresh pulled", log: .default, type: .info) - loadNotifications() - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - notifications.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: OpenHABNotificationsViewController.tableViewCellIdentifier) as? NotificationTableViewCell - guard let notification = notifications[indexPath.row] as? OpenHABNotification else { return UITableViewCell() } - - cell?.customTextLabel?.text = notification.message - - if let timeStamp = notification.created { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .medium - dateFormatter.timeStyle = .medium - dateFormatter.timeZone = TimeZone.current - cell?.customDetailTextLabel?.text = dateFormatter.string(from: timeStamp) - } - - if let iconUrl = Endpoint.icon( - rootUrl: appData!.openHABRootUrl, - version: appData!.openHABVersion, - icon: notification.icon, - state: "", - iconType: .png, - iconColor: "" - ).url { - cell?.imageView?.kf.setImage( - with: iconUrl, - placeholder: UIImage(named: "openHABIcon") - ) - } - - if cell?.responds(to: #selector(setter: NotificationTableViewCell.preservesSuperviewLayoutMargins)) ?? false { - cell?.preservesSuperviewLayoutMargins = false - } - // Explictly set your cell's layout margins - if cell?.responds(to: #selector(setter: NotificationTableViewCell.layoutMargins)) ?? false { - cell?.layoutMargins = .zero - } - cell?.separatorInset = UIEdgeInsets(top: 0, left: 60, bottom: 0, right: 0) - return cell! - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - // open a alert with an OK and cancel button - tableView.deselectRow(at: indexPath, animated: false) - } - - func loadSettings() { - appData?.openHABUsername = Preferences.username - appData?.openHABPassword = Preferences.password - } -} - -extension UIBarButtonItem { - static func menuButton(_ target: Any?, action: Selector, imageName: String) -> UIBarButtonItem { - let button = UIButton(type: .system) - button.setImage(UIImage(named: imageName), for: .normal) - button.addTarget(target, action: action, for: .touchUpInside) - - let menuBarItem = UIBarButtonItem(customView: button) - menuBarItem.customView?.translatesAutoresizingMaskIntoConstraints = false - menuBarItem.customView?.heightAnchor.constraint(equalToConstant: 24).isActive = true - menuBarItem.customView?.widthAnchor.constraint(equalToConstant: 24).isActive = true - - return menuBarItem - } -} diff --git a/openHAB/OpenHABRootViewController.swift b/openHAB/OpenHABRootViewController.swift index d41e5a408..1f88675af 100644 --- a/openHAB/OpenHABRootViewController.swift +++ b/openHAB/OpenHABRootViewController.swift @@ -15,13 +15,16 @@ import OpenHABCore import os.log import SafariServices import SideMenu +import SwiftUI import UIKit enum TargetController { - case sitemap + case webview case settings + case sitemap(String) case notifications - case webview + case browser(String) + case tile(String) } protocol ModalHandler: AnyObject { @@ -127,13 +130,6 @@ class OpenHABRootViewController: UIViewController { // Define the menus - SideMenuManager.default.rightMenuNavigationController = storyboard!.instantiateViewController(withIdentifier: "RightMenuNavigationController") as? SideMenuNavigationController - - // Enable gestures. The left and/or right menus must be set up above for these to work. - // Note that these continue to work on the Navigation Controller independent of the View Controller it displays! - SideMenuManager.default.addPanGestureToPresent(toView: navigationController!.navigationBar) - SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: navigationController!.view, forMenu: .right) - let presentationStyle: SideMenuPresentationStyle = .viewSlideOutMenuIn presentationStyle.presentingEndAlpha = 1 presentationStyle.onTopShadowOpacity = 0.5 @@ -142,9 +138,74 @@ class OpenHABRootViewController: UIViewController { settings.statusBarEndAlpha = 0 SideMenuManager.default.rightMenuNavigationController?.settings = settings - if let menu = SideMenuManager.default.rightMenuNavigationController { - let drawer = menu.viewControllers.first as? OpenHABDrawerTableViewController - drawer?.delegate = self + + let drawerView = DrawerView { mode in + self.handleDismiss(mode: mode) + } + let hostingController = UIHostingController(rootView: drawerView) + let menu = SideMenuNavigationController(rootViewController: hostingController) + + SideMenuManager.default.rightMenuNavigationController = menu + + // Enable gestures. The left and/or right menus must be set up above for these to work. + // Note that these continue to work on the Navigation Controller independent of the View Controller it displays! + SideMenuManager.default.addPanGestureToPresent(toView: navigationController!.navigationBar) + SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: navigationController!.view, forMenu: .right) + } + + private func openTileURL(_ urlString: String) { + // Use SFSafariViewController in SwiftUI with UIViewControllerRepresentable + // Dependent on $OPENHAB_CONF/services/runtime.cfg + // Can either be an absolute URL, a path (sometimes malformed) + if !urlString.isEmpty { + let url: URL? = if urlString.hasPrefix("http") { + URL(string: urlString) + } else { + Endpoint.resource(openHABRootUrl: appData?.openHABRootUrl ?? "", path: urlString.prepare()).url + } + openURL(url: url) + } + } + + private func openURL(url: URL?) { + if let url { + let config = SFSafariViewController.Configuration() + config.entersReaderIfAvailable = true + let vc = SFSafariViewController(url: url, configuration: config) + present(vc, animated: true) + } + } + + private func handleDismiss(mode: TargetController) { + switch mode { + case .webview: + // Handle webview navigation or state update + print("Dismissed to WebView") + SideMenuManager.default.rightMenuNavigationController?.dismiss(animated: true) + switchView(target: .webview) + case .settings: + print("Dismissed to Settings") + SideMenuManager.default.rightMenuNavigationController?.dismiss(animated: true) { + self.modalDismissed(to: .settings) + } + case let .sitemap(sitemap): + Preferences.defaultSitemap = sitemap + appData?.sitemapViewController?.pageUrl = "" + SideMenuManager.default.rightMenuNavigationController?.dismiss(animated: true) { + self.modalDismissed(to: .sitemap(sitemap)) + } + case .notifications: + SideMenuManager.default.rightMenuNavigationController?.dismiss(animated: true) { + self.modalDismissed(to: .notifications) + } + case let .browser(urlString): + SideMenuManager.default.rightMenuNavigationController?.dismiss(animated: true) { + self.modalDismissed(to: .browser(urlString)) + } + case let .tile(urlString): + SideMenuManager.default.rightMenuNavigationController?.dismiss(animated: true) { + self.modalDismissed(to: .tile(urlString)) + } } } @@ -316,11 +377,9 @@ class OpenHABRootViewController: UIViewController { if let menu = SideMenuManager.default.rightMenuNavigationController { // don't try and push an already visible menu less you crash the app dismiss(animated: false) { - var topMostViewController: UIViewController? = if #available(iOS 13, *) { + var topMostViewController: UIViewController? = UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.last { $0.isKeyWindow }?.rootViewController - } else { - UIApplication.shared.keyWindow?.rootViewController - } + while let presentedViewController = topMostViewController?.presentedViewController { topMostViewController = presentedViewController } @@ -344,7 +403,12 @@ class OpenHABRootViewController: UIViewController { } private func switchView(target: TargetController) { - let targetView = target == .sitemap ? sitemapViewController : webViewController + let targetView = + if case .sitemap = target { + sitemapViewController + } else { + webViewController + } if currentView != targetView { if currentView != nil { @@ -367,10 +431,10 @@ class OpenHABRootViewController: UIViewController { private func switchToSavedView() { if Preferences.demomode { - switchView(target: .sitemap) + switchView(target: .sitemap("")) } else { os_log("OpenHABRootViewController switchToSavedView %@", log: .viewCycle, type: .info, Preferences.defaultView == "sitemap" ? "sitemap" : "web") - switchView(target: Preferences.defaultView == "sitemap" ? .sitemap : .webview) + switchView(target: Preferences.defaultView == "sitemap" ? .sitemap("") : .webview) } } } @@ -391,19 +455,17 @@ extension OpenHABRootViewController: ModalHandler { case .sitemap: switchView(target: to) case .settings: - if let newViewController = storyboard?.instantiateViewController(withIdentifier: "OpenHABSettingsViewController") as? OpenHABSettingsViewController { - navigationController?.pushViewController(newViewController, animated: true) - } + let hostingController = UIHostingController(rootView: SettingsView()) + navigationController?.pushViewController(hostingController, animated: true) case .notifications: - if navigationController?.visibleViewController is OpenHABNotificationsViewController { - os_log("Notifications are already open", log: .notifications, type: .info) - } else { - if let newViewController = storyboard?.instantiateViewController(withIdentifier: "OpenHABNotificationsViewController") as? OpenHABNotificationsViewController { - navigationController?.pushViewController(newViewController, animated: true) - } - } + let hostingController = UIHostingController(rootView: NotificationsView()) + navigationController?.pushViewController(hostingController, animated: true) case .webview: switchView(target: to) + case .browser: + break + case let .tile(urlString): + openTileURL(urlString) } } } diff --git a/openHAB/OpenHABSelectionTableViewController.swift b/openHAB/OpenHABSelectionTableViewController.swift deleted file mode 100644 index a03f5bd95..000000000 --- a/openHAB/OpenHABSelectionTableViewController.swift +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import OpenHABCore -import os.log -import UIKit - -// swiftlint:disable:next type_name -public protocol OpenHABSelectionTableViewControllerDelegate: NSObjectProtocol { - func didSelectWidgetMapping(_ selectedMapping: Int) -} - -class OpenHABSelectionTableViewController: UITableViewController { - static let tableViewCellIdentifier = "SelectionCell" - - var mappings: [AnyHashable] = [] - weak var delegate: OpenHABSelectionTableViewControllerDelegate? - var selectionItem: OpenHABItem? - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func viewDidLoad() { - super.viewDidLoad() - - os_log("I have %d mappings", log: .viewCycle, type: .info, mappings.count) - - // Uncomment the following line to preserve selection between presentations. - // self.clearsSelectionOnViewWillAppear = NO; - - // Uncomment the following line to display an Edit button in the navigation bar for this view controller. - // self.navigationItem.rightBarButtonItem = self.editButtonItem; - } - - // MARK: - Table view data source - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - mappings.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: OpenHABSelectionTableViewController.tableViewCellIdentifier, for: indexPath) - if let mapping = mappings[indexPath.row] as? OpenHABWidgetMapping { - cell.textLabel?.text = mapping.label - if selectionItem?.state == mapping.command { - os_log("This item is selected", log: .viewCycle, type: .info) - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - os_log("Selected mapping %d", log: .viewCycle, type: .info, indexPath.row) - - delegate?.didSelectWidgetMapping(indexPath.row) - navigationController?.popViewController(animated: true) - } -} diff --git a/openHAB/OpenHABSettingsViewController.swift b/openHAB/OpenHABSettingsViewController.swift deleted file mode 100644 index a75683041..000000000 --- a/openHAB/OpenHABSettingsViewController.swift +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) 2010-2024 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import FirebaseCrashlytics -import Kingfisher -import OpenHABCore -import os.log -import SafariServices -import UIKit -import WebKit - -class OpenHABSettingsViewController: UITableViewController, UITextFieldDelegate { - var settingsLocalUrl = "" - var settingsRemoteUrl = "" - var settingsUsername = "" - var settingsPassword = "" - var settingsAlwaysSendCreds = false - var settingsIgnoreSSL = false - var settingsDemomode = false - var settingsIdleOff = false - var settingsIconType: IconType = .png - var settingsRealTimeSliders = false - var settingsSendCrashReports = false - var settingsSortSitemapsBy: SortSitemapsOrder = .label - var settingsDefaultMainUIPath = "" - var settingsAlwaysAllowWebRTC = false - - var appData: OpenHABDataObject? { - AppDelegate.appDelegate.appData - } - - @IBOutlet private var settingsTableView: UITableView! - @IBOutlet private var demomodeSwitch: UISwitch! - @IBOutlet private var passwordTextField: UITextField! - @IBOutlet private var usernameTextField: UITextField! - @IBOutlet private var remoteUrlTextField: UITextField! - @IBOutlet private var localUrlTextField: UITextField! - @IBOutlet private var idleOffSwitch: UISwitch! - @IBOutlet private var ignoreSSLSwitch: UISwitch! - @IBOutlet private var iconSegmentedControl: UISegmentedControl! - @IBOutlet private var alwaysSendCredsSwitch: UISwitch! - @IBOutlet private var realTimeSlidersSwitch: UISwitch! - @IBOutlet private var sendCrashReportsSwitch: UISwitch! - @IBOutlet private var sendCrashReportsDummy: UIButton! - @IBOutlet private var sortSitemapsBy: UISegmentedControl! - @IBOutlet private var useCurrentMainUIPathButton: UIButton! - @IBOutlet private var defaultMainUIPathTextField: UITextField! - @IBOutlet private var appVersionLabel: UILabel! - @IBOutlet private var alwaysAllowWebRTCSwitch: UISwitch! - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func viewDidLoad() { - super.viewDidLoad() - os_log("OpenHABSettingsViewController viewDidLoad", log: .viewCycle, type: .info) - navigationItem.hidesBackButton = true - let leftBarButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(OpenHABSettingsViewController.cancelButtonPressed(_:))) - let rightBarButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(OpenHABSettingsViewController.saveButtonPressed(_:))) - navigationItem.leftBarButtonItem = leftBarButton - navigationItem.rightBarButtonItem = rightBarButton - loadSettings() - updateSettingsUi() - localUrlTextField?.delegate = self - remoteUrlTextField?.delegate = self - usernameTextField?.delegate = self - passwordTextField?.delegate = self - defaultMainUIPathTextField.delegate = self - demomodeSwitch?.addTarget(self, action: #selector(OpenHABSettingsViewController.demomodeSwitchChange(_:)), for: .valueChanged) - sendCrashReportsDummy.addTarget(self, action: #selector(crashReportingDummyPressed(_:)), for: .touchUpInside) - useCurrentMainUIPathButton?.addTarget(self, action: #selector(currentMainUIPathButtonPressed(_:)), for: .touchUpInside) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewDidAppear(animated) - if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { - settingsTableView.deselectRow(at: indexPathForSelectedRow, animated: true) - } - } - - // This is to automatically hide keyboard on done/enter pressing - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.resignFirstResponder() - return false - } - - @objc - private func cancelButtonPressed(_ sender: Any?) { - navigationController?.popViewController(animated: true) - os_log("Cancel button pressed", log: .viewCycle, type: .info) - } - - @objc - private func saveButtonPressed(_ sender: Any?) { - // TODO: Make a check if any of the preferences has changed - os_log("Save button pressed", log: .viewCycle, type: .info) - - updateSettings() - saveSettings() - appData?.sitemapViewController?.pageUrl = "" - NotificationCenter.default.post(name: NSNotification.Name("org.openhab.preferences.saved"), object: nil) - navigationController?.popToRootViewController(animated: true) - } - - @objc - private func demomodeSwitchChange(_ sender: Any?) { - if demomodeSwitch!.isOn { - os_log("Demo is ON", log: .viewCycle, type: .info) - disableConnectionSettings() - } else { - os_log("Demo is OFF", log: .viewCycle, type: .info) - enableConnectionSettings() - } - } - - @objc - private func privacyButtonPressed(_ sender: Any?) { - let webViewController = SFSafariViewController(url: URL.privacyPolicy) - webViewController.configuration.barCollapsingEnabled = true - - present(webViewController, animated: true) - } - - @objc - private func crashReportingDummyPressed(_ sender: Any?) { - if sendCrashReportsSwitch.isOn { - sendCrashReportsSwitch.setOn(!sendCrashReportsSwitch.isOn, animated: true) - Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(false) - } else { - let alertController = UIAlertController(title: NSLocalizedString("crash_reporting", comment: ""), message: NSLocalizedString("crash_reporting_info", comment: ""), preferredStyle: .actionSheet) - alertController.addAction( - UIAlertAction(title: NSLocalizedString("activate", comment: ""), style: .default) { [weak self] _ in - self?.sendCrashReportsSwitch.setOn(true, animated: true) - Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) - } - ) - alertController.addAction( - UIAlertAction(title: NSLocalizedString("privacy_policy", comment: ""), style: .default) { [weak self] _ in - self?.privacyButtonPressed(nil) - } - ) - alertController.addAction( - UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .default) - ) - - if let popOver = alertController.popoverPresentationController { - popOver.sourceView = sendCrashReportsSwitch - popOver.sourceRect = sendCrashReportsSwitch.bounds - } - present(alertController, animated: true) - } - } - - @objc - private func currentMainUIPathButtonPressed(_ sender: Any?) { - promptForDefaultWebView() - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // hide connection options when in demo mode - if section == 0, demomodeSwitch!.isOn { - return 1 - } - return super.tableView(tableView, numberOfRowsInSection: section) - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - settingsTableView.deselectRow(at: indexPath, animated: true) - os_log("Row selected %d %d", log: .notifications, type: .info, indexPath.section, indexPath.row) - switch tableView.cellForRow(at: indexPath)?.tag { - case 888: - privacyButtonPressed(nil) - case 998: - let websiteDataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache]) - let date = Date(timeIntervalSince1970: 0) - WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes as! Set, modifiedSince: date, completionHandler: {}) - alertCacheCleared() - case 999: - os_log("Clearing image cache", log: .viewCycle, type: .info) - KingfisherManager.shared.cache.clearMemoryCache() - KingfisherManager.shared.cache.clearDiskCache() - KingfisherManager.shared.cache.cleanExpiredDiskCache() - alertCacheCleared() - default: break - } - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch section { - case 0: - NSLocalizedString("openhab_connection", comment: "") - case 1: - NSLocalizedString("application_settings", comment: "") - case 2: - NSLocalizedString("mainui_settings", comment: "") - case 3: - NSLocalizedString("sitemap_settings", comment: "") - case 4: - NSLocalizedString("about_settings", comment: "") - default: - "" - } - } - - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - UITableView.automaticDimension - } - - func enableConnectionSettings() { - settingsTableView.reloadData() - } - - func disableConnectionSettings() { - settingsTableView.reloadData() - } - - func updateSettingsUi() { - localUrlTextField?.text = settingsLocalUrl - remoteUrlTextField?.text = settingsRemoteUrl - usernameTextField?.text = settingsUsername - passwordTextField?.text = settingsPassword - alwaysSendCredsSwitch?.isOn = settingsAlwaysSendCreds - ignoreSSLSwitch?.isOn = settingsIgnoreSSL - demomodeSwitch?.isOn = settingsDemomode - idleOffSwitch?.isOn = settingsIdleOff - realTimeSlidersSwitch?.isOn = settingsRealTimeSliders - sendCrashReportsSwitch?.isOn = settingsSendCrashReports - iconSegmentedControl?.selectedSegmentIndex = settingsIconType.rawValue - sortSitemapsBy?.selectedSegmentIndex = settingsSortSitemapsBy.rawValue - defaultMainUIPathTextField?.text = settingsDefaultMainUIPath - alwaysAllowWebRTCSwitch?.isOn = settingsAlwaysAllowWebRTC - if settingsDemomode == true { - disableConnectionSettings() - } else { - enableConnectionSettings() - } - - let appBuildString = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String - let appVersionString = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String - appVersionLabel?.text = "\(appVersionString ?? "") (\(appBuildString ?? ""))" - } - - func loadSettings() { - settingsLocalUrl = Preferences.localUrl - settingsRemoteUrl = Preferences.remoteUrl - settingsUsername = Preferences.username - settingsPassword = Preferences.password - settingsAlwaysSendCreds = Preferences.alwaysSendCreds - settingsIgnoreSSL = Preferences.ignoreSSL - settingsDemomode = Preferences.demomode - settingsIdleOff = Preferences.idleOff - settingsRealTimeSliders = Preferences.realTimeSliders - settingsSendCrashReports = Preferences.sendCrashReports - settingsIconType = IconType(rawValue: Preferences.iconType) ?? .png - settingsSortSitemapsBy = SortSitemapsOrder(rawValue: Preferences.sortSitemapsby) ?? .label - settingsDefaultMainUIPath = Preferences.defaultMainUIPath - settingsAlwaysAllowWebRTC = Preferences.alwaysAllowWebRTC - } - - func updateSettings() { - settingsLocalUrl = localUrlTextField?.text ?? "" - settingsRemoteUrl = remoteUrlTextField?.text ?? "" - settingsUsername = usernameTextField?.text ?? "" - settingsPassword = passwordTextField?.text ?? "" - settingsAlwaysSendCreds = alwaysSendCredsSwitch?.isOn ?? false - settingsIgnoreSSL = ignoreSSLSwitch?.isOn ?? false - NetworkConnection.shared.serverCertificateManager.ignoreSSL = settingsIgnoreSSL - settingsDemomode = demomodeSwitch?.isOn ?? false - settingsIdleOff = idleOffSwitch?.isOn ?? false - settingsRealTimeSliders = realTimeSlidersSwitch?.isOn ?? false - settingsSendCrashReports = sendCrashReportsSwitch?.isOn ?? false - settingsIconType = IconType(rawValue: iconSegmentedControl.selectedSegmentIndex) ?? .png - settingsSortSitemapsBy = SortSitemapsOrder(rawValue: sortSitemapsBy.selectedSegmentIndex) ?? .label - settingsDefaultMainUIPath = defaultMainUIPathTextField?.text ?? "" - settingsAlwaysAllowWebRTC = alwaysAllowWebRTCSwitch?.isOn ?? false - } - - func saveSettings() { - Preferences.localUrl = settingsLocalUrl - Preferences.remoteUrl = settingsRemoteUrl - Preferences.username = settingsUsername - Preferences.password = settingsPassword - Preferences.alwaysSendCreds = settingsAlwaysSendCreds - Preferences.ignoreSSL = settingsIgnoreSSL - Preferences.demomode = settingsDemomode - Preferences.idleOff = settingsIdleOff - Preferences.realTimeSliders = settingsRealTimeSliders - Preferences.iconType = settingsIconType.rawValue - Preferences.sendCrashReports = settingsSendCrashReports - Preferences.sortSitemapsby = settingsSortSitemapsBy.rawValue - Preferences.defaultMainUIPath = settingsDefaultMainUIPath - Preferences.alwaysAllowWebRTC = settingsAlwaysAllowWebRTC - WatchMessageService.singleton.syncPreferencesToWatch() - } - - func promptForDefaultWebView() { - DispatchQueue.main.async { - let alertController = UIAlertController(title: NSLocalizedString("uselastpath_settings", comment: ""), message: self.appData?.currentWebViewPath ?? "/", preferredStyle: .actionSheet) - // popover cords needed for iPad - if let ppc = alertController.popoverPresentationController { - ppc.sourceView = self.useCurrentMainUIPathButton as UIView - ppc.sourceRect = (self.useCurrentMainUIPathButton as UIView).bounds - } - let cancel = UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .cancel) { (_: UIAlertAction) in - } - let currentPath = UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default) { (_: UIAlertAction) in - if let path = self.appData?.currentWebViewPath { - self.defaultMainUIPathTextField?.text = path - self.settingsDefaultMainUIPath = path - } - } - alertController.addAction(currentPath) - alertController.addAction(cancel) - self.present(alertController, animated: true, completion: nil) - } - } - - func alertCacheCleared() { - let alertController = UIAlertController(title: NSLocalizedString("cache_cleared", comment: ""), message: "", preferredStyle: .alert) - let confirmed = UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default) - alertController.addAction(confirmed) - present(alertController, animated: true, completion: nil) - } -} diff --git a/openHAB/OpenHABSitemapViewController.swift b/openHAB/OpenHABSitemapViewController.swift index 37926003c..1fb720e9e 100644 --- a/openHAB/OpenHABSitemapViewController.swift +++ b/openHAB/OpenHABSitemapViewController.swift @@ -79,7 +79,6 @@ class OpenHABSitemapViewController: OpenHABViewController, GenericUITableViewCel private var filteredPage: OpenHABSitemapPage? private var serverProperties: OpenHABServerProperties? private let search = UISearchController(searchResultsController: nil) - private var webViewController: OpenHABWebViewController? private var isUserInteracting = false private var isWaitingToReload = false @@ -341,7 +340,7 @@ class OpenHABSitemapViewController: OpenHABViewController, GenericUITableViewCel return sitemapPageCodingData.openHABSitemapPage }() } catch { - //Printing the error is the only way to actually get the real issue, localizedDescription is pretty useless here + // Printing the error is the only way to actually get the real issue, localizedDescription is pretty useless here print(error) os_log("Should not throw %{PUBLIC}@", log: OSLog.remoteAccess, type: .error, error.localizedDescription) DispatchQueue.main.async { @@ -541,17 +540,6 @@ extension OpenHABSitemapViewController: OpenHABTrackerDelegate { } } -// MARK: - OpenHABSelectionTableViewControllerDelegate - -extension OpenHABSitemapViewController: OpenHABSelectionTableViewControllerDelegate { - // send command on selected selection widget mapping - func didSelectWidgetMapping(_ selectedMappingIndex: Int) { - let selectedWidget: OpenHABWidget? = relevantPage?.widgets[selectedWidgetRow] - let selectedMapping: OpenHABWidgetMapping? = selectedWidget?.mappingsOrItemOptions[selectedMappingIndex] - sendCommand(selectedWidget?.item, commandToSend: selectedMapping?.command) - } -} - // MARK: - UISearchResultsUpdating extension OpenHABSitemapViewController: UISearchResultsUpdating { @@ -694,7 +682,6 @@ extension OpenHABSitemapViewController: UITableViewDelegate, UITableViewDataSour os_log("Job failed: %{PUBLIC}@", log: .viewCycle, type: .info, error.localizedDescription) } } - cell.imageView?.kf.setImage( with: KF.ImageResource(downloadURL: urlc, cacheKey: urlc.path + (urlc.query ?? "")), placeholder: UIImage(named: "blankicon.png"), @@ -745,6 +732,7 @@ extension OpenHABSitemapViewController: UITableViewDelegate, UITableViewDataSour if let link = widget?.linkedPage?.link { os_log("Selected %{PUBLIC}@", log: .viewCycle, type: .info, link) } + selectedWidgetRow = indexPath.row let newViewController = (storyboard?.instantiateViewController(withIdentifier: "OpenHABPageViewController") as? OpenHABSitemapViewController)! newViewController.title = widget?.linkedPage?.title.components(separatedBy: "[")[0] @@ -753,15 +741,23 @@ extension OpenHABSitemapViewController: UITableViewDelegate, UITableViewDataSour navigationController?.pushViewController(newViewController, animated: true) } else if widget?.type == .selection { os_log("Selected selection widget", log: .viewCycle, type: .info) - selectedWidgetRow = indexPath.row - let selectionViewController = (storyboard?.instantiateViewController(withIdentifier: "OpenHABSelectionTableViewController") as? OpenHABSelectionTableViewController)! let selectedWidget: OpenHABWidget? = relevantWidget(indexPath: indexPath) - selectionViewController.title = selectedWidget?.labelText - selectionViewController.mappings = selectedWidget?.mappingsOrItemOptions ?? [] - selectionViewController.delegate = self - selectionViewController.selectionItem = selectedWidget?.item - navigationController?.pushViewController(selectionViewController, animated: true) + let hostingController = UIHostingController(rootView: SelectionView( + mappings: selectedWidget?.mappingsOrItemOptions ?? [], + selectionItem: + Binding( + get: { selectedWidget?.item }, + set: { selectedWidget?.item = $0 } + ), + onSelection: { selectedMappingIndex in + let selectedWidget: OpenHABWidget? = self.relevantPage?.widgets[self.selectedWidgetRow] + let selectedMapping: OpenHABWidgetMapping? = selectedWidget?.mappingsOrItemOptions[selectedMappingIndex] + self.sendCommand(selectedWidget?.item, commandToSend: selectedMapping?.command) + } + )) + hostingController.title = widget?.labelText + navigationController?.pushViewController(hostingController, animated: true) } if let index = widgetTableView.indexPathForSelectedRow { widgetTableView.deselectRow(at: index, animated: false) diff --git a/openHAB/RTFTextView.swift b/openHAB/RTFTextView.swift new file mode 100644 index 000000000..4dec2867d --- /dev/null +++ b/openHAB/RTFTextView.swift @@ -0,0 +1,49 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import SwiftUI +import UIKit + +struct RTFTextView: UIViewRepresentable { + let rtfFileName: String + + func makeUIView(context: Context) -> UITextView { + let textView = UITextView() + textView.isEditable = false + textView.isSelectable = true + textView.backgroundColor = UIColor.clear + return textView + } + + func updateUIView(_ uiView: UITextView, context: Context) { + if let url = Bundle.main.url(forResource: rtfFileName, withExtension: "rtf") { + do { + let attributedString = try NSAttributedString( + url: url, + + options: [.characterEncoding: String.Encoding.utf8.rawValue], + documentAttributes: nil + ) + uiView.attributedText = attributedString + uiView.backgroundColor = .ohSystemBackground + uiView.textColor = .ohLabel + } catch { + print("Failed to load RTF file: \(error.localizedDescription)") + } + } else { + print("RTF file not found") + } + } +} + +#Preview { + RTFTextView(rtfFileName: "") +} diff --git a/openHAB/SelectionView.swift b/openHAB/SelectionView.swift new file mode 100644 index 000000000..cd8c2d388 --- /dev/null +++ b/openHAB/SelectionView.swift @@ -0,0 +1,54 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import OpenHABCore +import os.log +import SwiftUI + +struct SelectionView: View { + var mappings: [OpenHABWidgetMapping] // List of mappings (instead of AnyHashable, we use a concrete type) + @Binding var selectionItem: OpenHABItem? // Binding to track the selected item state + var onSelection: (Int) -> Void // Closure to handle selection + + var body: some View { + List(0 ..< mappings.count, id: \.self) { index in + let mapping = mappings[index] + HStack { + Text(mapping.label) + Spacer() + if selectionItem?.state == mapping.command { + Image(systemSymbol: .checkmark) + .foregroundColor(.blue) + } + } + .contentShape(Rectangle()) // Ensures entire row is tappable + .onTapGesture { + os_log("Selected mapping %d", log: .viewCycle, type: .info, index) + onSelection(index) + } + } + .navigationTitle("Select Mapping") // Navigation title + } +} + +#Preview { + let selectedItem: OpenHABItem? = OpenHABItem(name: "", type: "", state: "command2", link: "", label: nil, groupType: nil, stateDescription: nil, commandDescription: nil, members: [], category: nil, options: nil) + + return SelectionView( + mappings: [ + OpenHABWidgetMapping(command: "command1", label: "Option 1"), + OpenHABWidgetMapping(command: "command2", label: "Option 2") + ], + selectionItem: .constant(selectedItem) + ) { selectedMappingIndex in + print("Selected mapping at index \(selectedMappingIndex)") + } +} diff --git a/openHAB/SettingsView.swift b/openHAB/SettingsView.swift new file mode 100644 index 000000000..8322022b6 --- /dev/null +++ b/openHAB/SettingsView.swift @@ -0,0 +1,381 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import FirebaseCrashlytics +import Kingfisher +import OpenHABCore +import os +import SafariServices +import SFSafeSymbols +import SwiftUI +import WebKit + +struct SettingsView: View { + @State var settingsDemomode = false + @State var settingsLocalUrl = "" + @State var settingsRemoteUrl = "" + @State var settingsUsername = "" + @State var settingsPassword = "" + @State var settingsAlwaysSendCreds = true + @State var settingsIdleOff = true + @State var settingsIgnoreSSL = true + @State var settingsRealTimeSliders = true + @State var settingsSendCrashReports = false + @State var settingsIconType: IconType = .png + @State var settingsSortSitemapsBy: SortSitemapsOrder = .label + @State var settingsDefaultMainUIPath = "" + @State var settingsAlwaysAllowWebRTC = true + + @State private var showingCacheAlert = false + @State private var showCrashReportingAlert = false + @State private var showUselastPathAlert = false + + @State private var hasBeenLoaded = false + + @Environment(\.dismiss) private var dismiss + + var appData: OpenHABDataObject? { + AppDelegate.appDelegate.appData + } + + var appVersion: String { + let appBuildString = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String + let appVersionString = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String + return "\(appVersionString ?? "") (\(appBuildString ?? ""))" + } + + private let logger = Logger(subsystem: "org.openhab.app", category: "SettingsView") + + var body: some View { + Form { + Section(header: Text(LocalizedStringKey("openhab_connection"))) { + Toggle(isOn: $settingsDemomode) { + Text("Demo Mode") + } + + if !settingsDemomode { + LabeledContent { + Spacer() + TextField( + "Local URL", + text: $settingsLocalUrl + ) + .fixedSize() + .keyboardType(.URL) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .font(.system(.caption)) + } label: { + Text("Local URL") + if settingsLocalUrl.isEmpty { + Text("Enter URL of local server") + } + } + + LabeledContent { + Spacer() + TextField( + "Remote URL", + text: $settingsRemoteUrl + ) + .fixedSize() + .keyboardType(.URL) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .font(.system(.caption)) + } label: { + Text("Remote URL") + if settingsRemoteUrl.isEmpty { + Text("Enter URL of remote server") + } + } + + LabeledContent { + TextField( + "Foo", + text: $settingsUsername + ) + .fixedSize() + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .font(.system(.caption)) + } label: { + Text("Username") + if settingsUsername.isEmpty { + Text("Enter username on server, if required") + } + } + + LabeledContent { + SecureField( + "1234", + text: $settingsPassword + ) + .fixedSize() + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .font(.system(.caption)) + + } label: { + Text("Password") + if settingsPassword.isEmpty { + Text("Enter password on server") + } + } + + Toggle(isOn: $settingsAlwaysSendCreds) { + Text("Always send credentials") + } + } + } + + Section(header: Text(LocalizedStringKey("application_settings"))) { + Toggle(isOn: $settingsIgnoreSSL) { + Text("Ignore SSL certificates") + } + + Toggle(isOn: $settingsIdleOff) { + Text("Disable Idle Timeout") + } + + Toggle(isOn: $settingsSendCrashReports) { + Text("Crash Reporting") + } + + .onAppear { + // Setting .onAppear of view required here because onAppear of entire view is run after onChange is active + // when migrating to iOS17 this + settingsSendCrashReports = Preferences.sendCrashReports + hasBeenLoaded = true + } + .onChange(of: settingsSendCrashReports) { newValue in + logger.debug("Detected change on settingsSendCrashReports") + if newValue, hasBeenLoaded { + showCrashReportingAlert = true + } + } + .confirmationDialog( + "crash_reporting", + isPresented: $showCrashReportingAlert + ) { + Button(role: .destructive) { + settingsSendCrashReports = true + } label: { + Text(LocalizedStringKey("activate")) + } + Button(LocalizedStringKey("privacy_policy")) { + presentPrivacyPolicy() + settingsSendCrashReports = false + } + Button(role: .cancel) { + settingsSendCrashReports = false + } label: { + Text(LocalizedStringKey("cancel")) + } + } message: { + Text(LocalizedStringKey("crash_reporting_info")) + } + + NavigationLink { + ClientCertificatesView() + } label: { + Text("Client Certificates") + } + } + + Section(header: Text(LocalizedStringKey("mainui_settings"))) { + Toggle(isOn: $settingsAlwaysAllowWebRTC) { + Text("Always allow WebRTC") + } + + LabeledContent { + TextField( + "/overview/", + text: $settingsDefaultMainUIPath + ) + .fixedSize() + Button { + showUselastPathAlert = true + } label: { + Image(systemSymbol: .plusCircle) + } + .confirmationDialog( + "uselastpath_settings", + isPresented: $showUselastPathAlert + ) { + Button("Ok") { + if let path = appData?.currentWebViewPath { + settingsDefaultMainUIPath = path + } + } + Button(role: .cancel) {} label: { + Text(LocalizedStringKey("cancel")) + } + Button("cancel", role: .cancel) {} + } message: { + Text(LocalizedStringKey("uselastpath_settings")) + } + + } label: { + Text("Default Path") + } + + Button { + let websiteDataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache]) + let date = Date(timeIntervalSince1970: 0) + WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes as! Set, modifiedSince: date) {} + showingCacheAlert = true + } label: { + NavigationLink("Clear Web Cache", destination: EmptyView()) + } + .foregroundColor(Color(uiColor: .label)) + .alert("cache_cleared", isPresented: $showingCacheAlert) { + Button("OK", role: .cancel) {} + } + } + + Section(header: Text(LocalizedStringKey("sitemap_settings"))) { + Toggle(isOn: $settingsRealTimeSliders) { + Text("Real-time Sliders") + } + + Button { + clearWebsiteCache() + showingCacheAlert = true + } label: { + NavigationLink("Clear Image Cache", destination: EmptyView()) + } + .foregroundColor(Color(uiColor: .label)) + .alert("cache_cleared", isPresented: $showingCacheAlert) { + Button("OK", role: .cancel) {} + } + + Picker(selection: $settingsIconType) { + ForEach(IconType.allCases, id: \.self) { icontype in + Text("\(icontype)").tag(icontype) + } + } label: { + Text("Icon Type") + } + + Picker(selection: $settingsSortSitemapsBy) { + ForEach(SortSitemapsOrder.allCases, id: \.self) { sortsitemaporder in + Text("\(sortsitemaporder)").tag(sortsitemaporder) + } + } label: { + Text("Sort sitemaps by") + } + } + + Section(header: Text(LocalizedStringKey("about_settings"))) { + LabeledContent("App Version", value: appVersion) + + NavigationLink { + RTFTextView(rtfFileName: "legal") + .navigationTitle("Legal") + .navigationBarTitleDisplayMode(.inline) + } label: { + Text("Legal") + } + + Button { + presentPrivacyPolicy() + } label: { + Text("privacy_policy") + } + } + } + .formStyle(.grouped) + .navigationBarBackButtonHidden(true) + .navigationBarTitle("Settings") + .toolbar { + ToolbarItemGroup(placement: .primaryAction) { + Button("Save") { + saveSettings() + appData?.sitemapViewController?.pageUrl = "" + NotificationCenter.default.post(name: NSNotification.Name("org.openhab.preferences.saved"), object: nil) + dismiss() + } + } + ToolbarItemGroup(placement: .cancellationAction) { + Button("Cancel") { + dismiss() + } + } + } + .onAppear { + loadSettings() + logger.debug("Loading Settings") + } + } + + func clearWebsiteCache() { + logger.debug("Clearing image cache") + KingfisherManager.shared.cache.clearMemoryCache() + KingfisherManager.shared.cache.clearDiskCache() + KingfisherManager.shared.cache.cleanExpiredDiskCache() + } + + func presentPrivacyPolicy() { + let vc = SFSafariViewController(url: .privacyPolicy) + UIApplication.shared.firstKeyWindow?.rootViewController?.present(vc, animated: true) + } + + func loadSettings() { + settingsLocalUrl = Preferences.localUrl + settingsRemoteUrl = Preferences.remoteUrl + settingsUsername = Preferences.username + settingsPassword = Preferences.password + settingsAlwaysSendCreds = Preferences.alwaysSendCreds + settingsIgnoreSSL = Preferences.ignoreSSL + settingsDemomode = Preferences.demomode + settingsIdleOff = Preferences.idleOff + settingsRealTimeSliders = Preferences.realTimeSliders + settingsSendCrashReports = Preferences.sendCrashReports + settingsIconType = IconType(rawValue: Preferences.iconType) ?? .png + settingsSortSitemapsBy = SortSitemapsOrder(rawValue: Preferences.sortSitemapsby) ?? .label + settingsDefaultMainUIPath = Preferences.defaultMainUIPath + settingsAlwaysAllowWebRTC = Preferences.alwaysAllowWebRTC + } + + func saveSettings() { + Preferences.localUrl = settingsLocalUrl + Preferences.remoteUrl = settingsRemoteUrl + Preferences.username = settingsUsername + Preferences.password = settingsPassword + Preferences.alwaysSendCreds = settingsAlwaysSendCreds + Preferences.ignoreSSL = settingsIgnoreSSL + Preferences.demomode = settingsDemomode + Preferences.idleOff = settingsIdleOff + Preferences.realTimeSliders = settingsRealTimeSliders + Preferences.iconType = settingsIconType.rawValue + Preferences.sendCrashReports = settingsSendCrashReports + Preferences.sortSitemapsby = settingsSortSitemapsBy.rawValue + Preferences.defaultMainUIPath = settingsDefaultMainUIPath + Preferences.alwaysAllowWebRTC = settingsAlwaysAllowWebRTC + WatchMessageService.singleton.syncPreferencesToWatch() + Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(settingsSendCrashReports) + logger.debug("setCrashlyticsCollectionEnabled to \(settingsSendCrashReports)") + } +} + +extension UIApplication { + var firstKeyWindow: UIWindow? { + UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .filter { $0.activationState == .foregroundActive } + .first?.keyWindow + } +} + +#Preview { + SettingsView() +} diff --git a/openHAB/Throttler.swift b/openHAB/Throttler.swift index 243cb890e..f6134454f 100644 --- a/openHAB/Throttler.swift +++ b/openHAB/Throttler.swift @@ -12,7 +12,7 @@ import Foundation // Inspired by http://danielemargutti.com/2017/10/19/throttle-in-swift/ -public class Throttler { +public class Throttler: ObservableObject { private let queue: DispatchQueue = .global(qos: .background) private var job = DispatchWorkItem {} diff --git a/openHABTestsSwift/LocalizationTests.swift b/openHABTestsSwift/LocalizationTests.swift index 16612b054..e6b46f844 100644 --- a/openHABTestsSwift/LocalizationTests.swift +++ b/openHABTestsSwift/LocalizationTests.swift @@ -42,6 +42,7 @@ class LocalizationTests: XCTestCase { continue } XCTAssertNotEqual(translation, "__MISSING__", "Missing translation for key '\(tuple.key)' in language '\(language)'.") + // swiftlint:disable:next opening_brace let regex = /%(?:\d+\$)?[+-]?(?:[lh]{0,2})(?:[qLztj])?(?:[ 0]|'.{1})?\d*(?:\\.\d?)?[@dDiuUxXoOfeEgGcCsSpaAFn]/ let numberOfMatches = translation.matches(of: regex).count XCTAssertEqual(numberOfMatches, tuple.arguments.count, "Invalid number of format specifiers for key '\(tuple.key)' in language '\(language)'.") diff --git a/openHABWatch Extension/Views/Rows/ImageRow.swift b/openHABWatch Extension/Views/Rows/ImageRow.swift index feb886a3d..c80362f46 100644 --- a/openHABWatch Extension/Views/Rows/ImageRow.swift +++ b/openHABWatch Extension/Views/Rows/ImageRow.swift @@ -26,7 +26,7 @@ struct ImageRow: View { os_log("Failure loading icon: %{PUBLIC}s", log: .notifications, type: .debug, kingfisherError.localizedDescription) } .placeholder { - Image(systemSymbol: .arrow2CirclepathCircle) + Image(systemSymbol: .arrowTriangle2Circlepath) .font(.callout) .opacity(0.3) } From a0b2c2277d9e8a86a36e932d52fdd0555ecb54cd Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Sat, 14 Sep 2024 11:15:33 -0700 Subject: [PATCH 2/2] Fixes broken notification view with newer notifications (#815) Signed-off-by: Dan Cunningham --- openHAB/NotificationsView.swift | 2 +- openHAB/OpenHABNotification.swift | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/openHAB/NotificationsView.swift b/openHAB/NotificationsView.swift index ca8d4b151..d3eaf56c3 100644 --- a/openHAB/NotificationsView.swift +++ b/openHAB/NotificationsView.swift @@ -32,7 +32,7 @@ struct NotificationRow: View { .frame(width: 40, height: 40) .cornerRadius(8) VStack(alignment: .leading) { - Text(notification.message) + Text(notification.message ?? "") .font(.body) if let timeStamp = notification.created { Text(dateString(from: timeStamp)) diff --git a/openHAB/OpenHABNotification.swift b/openHAB/OpenHABNotification.swift index 2862a9d27..cab69b023 100644 --- a/openHAB/OpenHABNotification.swift +++ b/openHAB/OpenHABNotification.swift @@ -12,13 +12,12 @@ import Foundation class OpenHABNotification: NSObject { - var message = "" + var message: String? var created: Date? - var icon = "" - var severity = "" + var icon: String? + var severity: String? var id = "" - - init(message: String = "", created: Date? = nil, icon: String = "", severity: String = "", id: String = "") { + init(message: String? = nil, created: Date? = nil, icon: String? = nil, severity: String? = nil, id: String = "") { self.message = message self.created = created self.icon = icon @@ -51,7 +50,7 @@ class OpenHABNotification: NSObject { extension OpenHABNotification { public struct CodingData: Decodable { let id: String - let message: String + let message: String? let v: Int let created: Date?