From ad888b8ce7bf542795425978b379716b70cfbfc0 Mon Sep 17 00:00:00 2001 From: Michal Polanski Date: Tue, 21 May 2024 12:38:23 +0200 Subject: [PATCH] Issue #379: Support projector screen --- SUPLA.xcodeproj/project.pbxproj | 46 +++- SUPLA/Core/UI/Details/StandardDetailVC.swift | 13 ++ .../UI/TableView/BaseTableViewModel.swift | 4 +- .../ChannelBaseTableViewController.swift | 3 +- .../Features/ChannelList/Cells/IconCell.swift | 6 +- .../Base/Model/ProjectorScreenState.swift | 33 +++ .../ProjectorScreenColors.swift | 45 ++++ .../ProjectorScreen/ProjectorScreenView.swift | 205 ++++++++++++++++++ .../Base/UI/RoofWindow/RoofWindowColors.swift | 39 ++++ .../Base/UI/RoofWindow/RoofWindowView.swift | 6 +- .../TerraceAwning/TerraceAwningColors.swift | 48 ++++ .../UI/TerraceAwning/TerraceAwningView.swift | 26 +-- .../UI/{ => WindowView}/WindowColors.swift | 0 .../ProjectorScreen/ProjectorScreenVC.swift | 34 +++ .../ProjectorScreen/ProjectorScreenVM.swift | 96 ++++++++ SUPLA/Resources/Default.strings | 1 + .../Contents.json | 22 ++ .../projector screen_dark modeclosed.svg | 7 + .../projector screenclosed.svg | 7 + .../Contents.json | 22 ++ .../projector screen_dark modeopened.svg | 15 ++ .../projector screenopened.svg | 15 ++ SUPLA/Resources/Strings.swift | 1 + SUPLA/Resources/de.lproj/Localizable.strings | 1 + SUPLA/Resources/pl.lproj/Localizable.strings | 1 + .../GetChannelBaseDefaultCaptionUseCase.swift | 2 + .../GetChannelBaseStateUseCase.swift | 3 + .../Detail/ProvideDetailTypeUseCase.swift | 3 + .../Icon/GetDefaultIconNameUseCase.swift | 1 + 29 files changed, 683 insertions(+), 22 deletions(-) create mode 100644 SUPLA/Features/Details/WindowDetail/Base/Model/ProjectorScreenState.swift create mode 100644 SUPLA/Features/Details/WindowDetail/Base/UI/ProjectorScreen/ProjectorScreenColors.swift create mode 100644 SUPLA/Features/Details/WindowDetail/Base/UI/ProjectorScreen/ProjectorScreenView.swift create mode 100644 SUPLA/Features/Details/WindowDetail/Base/UI/RoofWindow/RoofWindowColors.swift create mode 100644 SUPLA/Features/Details/WindowDetail/Base/UI/TerraceAwning/TerraceAwningColors.swift rename SUPLA/Features/Details/WindowDetail/Base/UI/{ => WindowView}/WindowColors.swift (100%) create mode 100644 SUPLA/Features/Details/WindowDetail/ProjectorScreen/ProjectorScreenVC.swift create mode 100644 SUPLA/Features/Details/WindowDetail/ProjectorScreen/ProjectorScreenVM.swift create mode 100644 SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/Contents.json create mode 100644 SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/projector screen_dark modeclosed.svg create mode 100644 SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/projector screenclosed.svg create mode 100644 SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/Contents.json create mode 100644 SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/projector screen_dark modeopened.svg create mode 100644 SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/projector screenopened.svg diff --git a/SUPLA.xcodeproj/project.pbxproj b/SUPLA.xcodeproj/project.pbxproj index ad496780..e98a7de5 100644 --- a/SUPLA.xcodeproj/project.pbxproj +++ b/SUPLA.xcodeproj/project.pbxproj @@ -346,6 +346,13 @@ A50B5D2D2BF49F7700918D18 /* TerraceAwningWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D2C2BF49F7700918D18 /* TerraceAwningWindowState.swift */; }; A50B5D302BF4A10200918D18 /* TerraceAwningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D2F2BF4A10200918D18 /* TerraceAwningView.swift */; }; A50B5D332BF54FF300918D18 /* TerraceAwningVMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D322BF54FF300918D18 /* TerraceAwningVMTests.swift */; }; + A50B5D362BFB6CD100918D18 /* ProjectorScreenVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D352BFB6CD100918D18 /* ProjectorScreenVM.swift */; }; + A50B5D382BFB6D1400918D18 /* ProjectorScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D372BFB6D1400918D18 /* ProjectorScreenState.swift */; }; + A50B5D3A2BFB6E3500918D18 /* ProjectorScreenVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D392BFB6E3500918D18 /* ProjectorScreenVC.swift */; }; + A50B5D3D2BFB6EA200918D18 /* ProjectorScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D3C2BFB6EA200918D18 /* ProjectorScreenView.swift */; }; + A50B5D3F2BFC8BEA00918D18 /* ProjectorScreenColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D3E2BFC8BEA00918D18 /* ProjectorScreenColors.swift */; }; + A50B5D412BFC914000918D18 /* TerraceAwningColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D402BFC914000918D18 /* TerraceAwningColors.swift */; }; + A50B5D432BFC921B00918D18 /* RoofWindowColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B5D422BFC921B00918D18 /* RoofWindowColors.swift */; }; A50CD3D12A4D78610012DD9B /* libc++abi.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A50CD3D02A4D78610012DD9B /* libc++abi.tbd */; }; A50CD3D32A4D82ED0012DD9B /* DateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50CD3D22A4D82ED0012DD9B /* DateProvider.swift */; }; A50CD3D62A4D99E60012DD9B /* UpdateTokenTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50CD3D52A4D99E60012DD9B /* UpdateTokenTaskTests.swift */; }; @@ -1636,6 +1643,13 @@ A50B5D2C2BF49F7700918D18 /* TerraceAwningWindowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerraceAwningWindowState.swift; sourceTree = ""; }; A50B5D2F2BF4A10200918D18 /* TerraceAwningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerraceAwningView.swift; sourceTree = ""; }; A50B5D322BF54FF300918D18 /* TerraceAwningVMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerraceAwningVMTests.swift; sourceTree = ""; }; + A50B5D352BFB6CD100918D18 /* ProjectorScreenVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectorScreenVM.swift; sourceTree = ""; }; + A50B5D372BFB6D1400918D18 /* ProjectorScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectorScreenState.swift; sourceTree = ""; }; + A50B5D392BFB6E3500918D18 /* ProjectorScreenVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectorScreenVC.swift; sourceTree = ""; }; + A50B5D3C2BFB6EA200918D18 /* ProjectorScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectorScreenView.swift; sourceTree = ""; }; + A50B5D3E2BFC8BEA00918D18 /* ProjectorScreenColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectorScreenColors.swift; sourceTree = ""; }; + A50B5D402BFC914000918D18 /* TerraceAwningColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerraceAwningColors.swift; sourceTree = ""; }; + A50B5D422BFC921B00918D18 /* RoofWindowColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoofWindowColors.swift; sourceTree = ""; }; A50CD3D02A4D78610012DD9B /* libc++abi.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++abi.tbd"; path = "usr/lib/libc++abi.tbd"; sourceTree = SDKROOT; }; A50CD3D22A4D82ED0012DD9B /* DateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateProvider.swift; sourceTree = ""; }; A50CD3D52A4D99E60012DD9B /* UpdateTokenTaskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTokenTaskTests.swift; sourceTree = ""; }; @@ -3020,13 +3034,13 @@ A5074BBB2BCE5CBF0081B6B1 /* UI */ = { isa = PBXGroup; children = ( + A50B5D3B2BFB6E9100918D18 /* ProjectorScreen */, A50B5D2E2BF4A02B00918D18 /* TerraceAwning */, A5074BCA2BCE6F2C0081B6B1 /* FacadeBlinds */, A5074BC32BCE64A30081B6B1 /* WindowView */, A5B3A4B72BB57B4D0001D006 /* RoofWindow */, A5B3A4B62BB57B410001D006 /* RollerShutter */, A55A8D832BAAD06C00C540D4 /* UpDownControllButton.swift */, - A55A8D812BA9B6B400C540D4 /* WindowColors.swift */, A5B3A4BA2BB5B8F20001D006 /* WindowType.swift */, A50B5D012BEA41D700918D18 /* WindowGroupedValue.swift */, ); @@ -3045,6 +3059,7 @@ A5074BC32BCE64A30081B6B1 /* WindowView */ = { isa = PBXGroup; children = ( + A55A8D812BA9B6B400C540D4 /* WindowColors.swift */, A5B3A4B22BB552590001D006 /* BaseWindowView.swift */, A5074BC42BCE66070081B6B1 /* DefaultWindowDimens.swift */, A5074BC62BCE66930081B6B1 /* RuntimeWindowDimens.swift */, @@ -3202,6 +3217,7 @@ isa = PBXGroup; children = ( A50B5D2F2BF4A10200918D18 /* TerraceAwningView.swift */, + A50B5D402BFC914000918D18 /* TerraceAwningColors.swift */, ); path = TerraceAwning; sourceTree = ""; @@ -3214,6 +3230,24 @@ path = TerraceAwning; sourceTree = ""; }; + A50B5D342BFB6BDB00918D18 /* ProjectorScreen */ = { + isa = PBXGroup; + children = ( + A50B5D352BFB6CD100918D18 /* ProjectorScreenVM.swift */, + A50B5D392BFB6E3500918D18 /* ProjectorScreenVC.swift */, + ); + path = ProjectorScreen; + sourceTree = ""; + }; + A50B5D3B2BFB6E9100918D18 /* ProjectorScreen */ = { + isa = PBXGroup; + children = ( + A50B5D3C2BFB6EA200918D18 /* ProjectorScreenView.swift */, + A50B5D3E2BFC8BEA00918D18 /* ProjectorScreenColors.swift */, + ); + path = ProjectorScreen; + sourceTree = ""; + }; A50CD3D42A4D99CF0012DD9B /* UpdateToken */ = { isa = PBXGroup; children = ( @@ -3817,6 +3851,7 @@ A55A8D6C2BA831AD00C540D4 /* WindowDetail */ = { isa = PBXGroup; children = ( + A50B5D342BFB6BDB00918D18 /* ProjectorScreen */, A50B5D272BF49F0D00918D18 /* TerraceAwning */, A5074BB62BCE58B30081B6B1 /* Base */, A5074BCD2BCE72110081B6B1 /* FacadeBlinds */, @@ -3886,6 +3921,7 @@ A50B5D072BEA4E2A00918D18 /* FacadeBlindWindowState.swift */, A50B5D092BEA4EC700918D18 /* FacadeBlindMarker.swift */, A50B5D2C2BF49F7700918D18 /* TerraceAwningWindowState.swift */, + A50B5D372BFB6D1400918D18 /* ProjectorScreenState.swift */, ); path = Model; sourceTree = ""; @@ -4631,6 +4667,7 @@ children = ( A5B3A4B42BB558B70001D006 /* RoofWindowView.swift */, A5B3A4B82BB57B690001D006 /* RoofWindowDimensBuilder.swift */, + A50B5D422BFC921B00918D18 /* RoofWindowColors.swift */, ); path = RoofWindow; sourceTree = ""; @@ -5672,6 +5709,7 @@ A5F29BE62A27739000ED700A /* MoveableCell.swift in Sources */, A5AE7A8F2A3AE0290097FA8B /* TitleArrowButtonCell.swift in Sources */, A5CE73292B4607AE003F882C /* EspConfigResult.swift in Sources */, + A50B5D382BFB6D1400918D18 /* ProjectorScreenState.swift in Sources */, A55A8D752BA84D5400C540D4 /* RollerShutterVM.swift in Sources */, A530EE1A2A56FBBC00F8DAEE /* PowerSwitchIconNameProducer.swift in Sources */, A5F29B962A20B97B00ED700A /* CreateProfileChannelsListUseCase.swift in Sources */, @@ -5785,6 +5823,7 @@ A50B5D022BEA41D700918D18 /* WindowGroupedValue.swift in Sources */, A54149372B63BC0500B44BD6 /* DownloadGeneralPurposeMeasurementLogUseCase.swift in Sources */, 01511CFA24F50B470081A1EC /* SAPreloaderPopup.m in Sources */, + A50B5D412BFC914000918D18 /* TerraceAwningColors.swift in Sources */, A52BFEAF2B060EAA00A2F64C /* Hour.swift in Sources */, A530EE1C2A56FC5000F8DAEE /* LightSwitchIconNameProducer.swift in Sources */, A530EE2E2A57E23D00F8DAEE /* DigiglassHorizontalIconNameProducer.swift in Sources */, @@ -5812,6 +5851,7 @@ A530EE282A57E02C00F8DAEE /* MailSensorIconNameProducer.swift in Sources */, 01D0616E22C294D90043C947 /* ElectricityMeterDetailView.m in Sources */, A5FE67692A666FF800147D1F /* RequestHelper.swift in Sources */, + A50B5D362BFB6CD100918D18 /* ProjectorScreenVM.swift in Sources */, 01F8856C22E5E79100D18373 /* SAUserIcon+CoreDataClass.m in Sources */, 013D73B022DD105400E3515A /* SAChartFilterField.m in Sources */, A5F29C022A2DC56600ED700A /* NSPersistentStoreCoordinator+Ext.swift in Sources */, @@ -5948,6 +5988,7 @@ A51BE9132AAAFFEB00718F2F /* SuplaChannelConfig.swift in Sources */, A51BE9182AAB027400718F2F /* QuarterOfHour.swift in Sources */, A530EE4E2A5C357100F8DAEE /* StartTimerUseCase.swift in Sources */, + A50B5D3A2BFB6E3500918D18 /* ProjectorScreenVC.swift in Sources */, A56233E92AB31A26001CB948 /* CGPoint+Ext.swift in Sources */, A5B3CC0E2B62934D00F95AC3 /* SAChannelConfig+Ext.swift in Sources */, A530EE4C2A5C2A3E00F8DAEE /* UIBorderedButton.swift in Sources */, @@ -6039,6 +6080,7 @@ A5074BAA2BC945DA0081B6B1 /* WebContentVM.swift in Sources */, A503ABB32B6A9ED9008CDA1F /* GeneralPurposeMeterItemRepository.swift in Sources */, A57668ED2AEA85150025509D /* Array+Ext.swift in Sources */, + A50B5D3F2BFC8BEA00918D18 /* ProjectorScreenColors.swift in Sources */, A5074BA82BC923AE0081B6B1 /* BrandingConfiguration.swift in Sources */, A51BE90D2AAAF82000718F2F /* GetChannelConfigUseCase.swift in Sources */, A5F29B5B2A1E15D600ED700A /* MainVC.swift in Sources */, @@ -6046,6 +6088,7 @@ A573B0962A5FD152001E19D0 /* FatalError.swift in Sources */, A5AE7A8D2A3ADFAD0097FA8B /* RsOpenningClosingPersentageCell.swift in Sources */, A57B03C62B29D68B00C22966 /* HomePlusDetailRefreshHelper.swift in Sources */, + A50B5D432BFC921B00918D18 /* RoofWindowColors.swift in Sources */, A5F29B742A1E33AC00ED700A /* NSManagedObjectContext+Rx.swift in Sources */, 40E8BE8A1FC06A5600FB2FE6 /* XPathQuery.m in Sources */, 01EE9BBC22BA50D90029A142 /* SAChannelValueBase+CoreDataProperties.m in Sources */, @@ -6202,6 +6245,7 @@ A57785BF29E80406001C631E /* SuplaAppWrapper.swift in Sources */, A5E490502A3C79A4006801FE /* UserNotificationCenter.swift in Sources */, A55A8D702BA831D900C540D4 /* WindowDetailVM.swift in Sources */, + A50B5D3D2BFB6EA200918D18 /* ProjectorScreenView.swift in Sources */, A51BE9112AAAFFC200718F2F /* SuplaChannelWeeklyScheduleConfig.swift in Sources */, A55501F32B8382C500FD3296 /* NotificationsLogVM.swift in Sources */, AEB195DE276E5A040091D314 /* FadeTransition.swift in Sources */, diff --git a/SUPLA/Core/UI/Details/StandardDetailVC.swift b/SUPLA/Core/UI/Details/StandardDetailVC.swift index ffc314da..206916f4 100644 --- a/SUPLA/Core/UI/Details/StandardDetailVC.swift +++ b/SUPLA/Core/UI/Details/StandardDetailVC.swift @@ -91,6 +91,8 @@ class StandardDetailVC viewControllers.append(facadeBlindDetail()) case .terraceAwning: viewControllers.append(terraceAwningDetail()) + case .projectorScreen: + viewControllers.append(projectorScreenDetail()) } } @@ -250,6 +252,17 @@ class StandardDetailVC ) return vc } + + private func projectorScreenDetail() -> ProjectorScreenVC { + let vc = ProjectorScreenVC(itemBundle: item) + vc.navigationCoordinator = navigationCoordinator + vc.tabBarItem = UITabBarItem( + title: settings.showBottomLabels ? Strings.StandardDetail.tabGeneral : nil, + image: .iconGeneral, + tag: DetailTabTag.Window.rawValue + ) + return vc + } } protocol NavigationItemProvider: AnyObject { diff --git a/SUPLA/Core/UI/TableView/BaseTableViewModel.swift b/SUPLA/Core/UI/TableView/BaseTableViewModel.swift index 3f11d9de..41bf47e5 100644 --- a/SUPLA/Core/UI/TableView/BaseTableViewModel.swift +++ b/SUPLA/Core/UI/TableView/BaseTableViewModel.swift @@ -60,7 +60,9 @@ class BaseTableViewModel: BaseViewModel { SUPLA_CHANNELFNC_GENERAL_PURPOSE_MEASUREMENT, SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER, SUPLA_CHANNELFNC_CONTROLLINGTHEROOFWINDOW, - SUPLA_CHANNELFNC_CONTROLLINGTHEFACADEBLIND: + SUPLA_CHANNELFNC_CONTROLLINGTHEFACADEBLIND, + SUPLA_CHANNELFNC_TERRACE_AWNING, + SUPLA_CHANNELFNC_PROJECTOR_SCREEN: return true case SUPLA_CHANNELFNC_LIGHTSWITCH, SUPLA_CHANNELFNC_POWERSWITCH, diff --git a/SUPLA/Core/UI/TableView/ChannelBaseTableViewController.swift b/SUPLA/Core/UI/TableView/ChannelBaseTableViewController.swift index 0aa1fb78..f8e13736 100644 --- a/SUPLA/Core/UI/TableView/ChannelBaseTableViewController.swift +++ b/SUPLA/Core/UI/TableView/ChannelBaseTableViewController.swift @@ -194,7 +194,8 @@ class ChannelBaseTableViewController { case SUPLA_CHANNELFNC_CONTROLLINGTHEROOFWINDOW, SUPLA_CHANNELFNC_CONTROLLINGTHEFACADEBLIND, SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER, - SUPLA_CHANNELFNC_TERRACE_AWNING: true + SUPLA_CHANNELFNC_TERRACE_AWNING, + SUPLA_CHANNELFNC_PROJECTOR_SCREEN: true default: false } } @@ -114,7 +115,8 @@ final class IconCell: BaseCell { case SUPLA_CHANNELFNC_CONTROLLINGTHEROOFWINDOW, SUPLA_CHANNELFNC_CONTROLLINGTHEFACADEBLIND, SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER, - SUPLA_CHANNELFNC_TERRACE_AWNING: true + SUPLA_CHANNELFNC_TERRACE_AWNING, + SUPLA_CHANNELFNC_PROJECTOR_SCREEN: true default: false } } diff --git a/SUPLA/Features/Details/WindowDetail/Base/Model/ProjectorScreenState.swift b/SUPLA/Features/Details/WindowDetail/Base/Model/ProjectorScreenState.swift new file mode 100644 index 00000000..55ca6b23 --- /dev/null +++ b/SUPLA/Features/Details/WindowDetail/Base/Model/ProjectorScreenState.swift @@ -0,0 +1,33 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +struct ProjectorScreenState: WindowState, Equatable, Changeable { + /** + * The blind roller position in percentage + * 0 - open + * 100 - closed + */ + var position: WindowGroupedValue + + var positionTextFormat: WindowGroupedValueFormat = .percentage + + /** + * Used for groups - shows positions of single roller shutter + */ + var markers: [CGFloat] = [] +} diff --git a/SUPLA/Features/Details/WindowDetail/Base/UI/ProjectorScreen/ProjectorScreenColors.swift b/SUPLA/Features/Details/WindowDetail/Base/UI/ProjectorScreen/ProjectorScreenColors.swift new file mode 100644 index 00000000..d44d5047 --- /dev/null +++ b/SUPLA/Features/Details/WindowDetail/Base/UI/ProjectorScreen/ProjectorScreenColors.swift @@ -0,0 +1,45 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +struct ProjectorScreenColors { + let screen: UIColor + let shadow: UIColor + let bottomRect: UIColor + let topRect: UIColor + let logoColor: UIColor + + static func standard(_ traitCollection: UITraitCollection) -> ProjectorScreenColors { + ProjectorScreenColors( + screen: .rollerShutterWindow.resolvedColor(with: traitCollection), + shadow: .black, + bottomRect: .rollerShutterSlatBackground.resolvedColor(with: traitCollection), + topRect: .gray.resolvedColor(with: traitCollection), + logoColor: .primaryVariant.copy(alpha: 0.2) + ) + } + + static func offline(_ traitCollection: UITraitCollection) -> ProjectorScreenColors { + ProjectorScreenColors( + screen: .surface.resolvedColor(with: traitCollection), + shadow: .black, + bottomRect: .rollerShutterSlatBackground.resolvedColor(with: traitCollection), + topRect: .disabled.resolvedColor(with: traitCollection), + logoColor: .disabled.copy(alpha: 0.2) + ) + } +} diff --git a/SUPLA/Features/Details/WindowDetail/Base/UI/ProjectorScreen/ProjectorScreenView.swift b/SUPLA/Features/Details/WindowDetail/Base/UI/ProjectorScreen/ProjectorScreenView.swift new file mode 100644 index 00000000..c27fde83 --- /dev/null +++ b/SUPLA/Features/Details/WindowDetail/Base/UI/ProjectorScreen/ProjectorScreenView.swift @@ -0,0 +1,205 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +import Foundation + +final class ProjectorScreenView: BaseWindowView { + + override var isEnabled: Bool { + didSet { + if (isEnabled) { + colors = ProjectorScreenColors.standard(traitCollection) + } else { + colors = ProjectorScreenColors.offline(traitCollection) + } + logo = UIImage.logo?.withTintColor(colors.logoColor) + setNeedsDisplay() + } + } + + override var touchRect: CGRect { dimens.canvasRect } + + private let dimens = RuntimeDimens() + private lazy var colors = ProjectorScreenColors.standard(traitCollection) + private lazy var logo = UIImage.logo?.withTintColor(colors.logoColor) + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + dimens.update(frame) + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + + guard let context = UIGraphicsGetCurrentContext() else { return } + + context.setShouldAntialias(true) + context.setLineWidth(1) + + let position = (windowState?.markers.isEmpty == true ? windowState?.position.value : windowState?.markers.max()) ?? 0 + let screenHeight = dimens.screenMaxHeight * position / 100 + let bottomRect = dimens.bottomRect.offsetBy(dx: 0, dy: screenHeight) + let bottomRectRadius = bottomRect.height / 2 + + // screen + drawPath(context, fillColor: colors.screen) { + let rect = CGRect(origin: dimens.screenTopLeft, size: CGSize(width: dimens.screenWidth, height: screenHeight)) + return UIBezierPath(rect: rect).cgPath + } + // Logo + if let logo = logo { + let verticalCorrection = screenHeight - dimens.screenMaxHeight + logo.draw(in: dimens.logoRect.offsetBy(dx: 0, dy: verticalCorrection)) + } + // top part + drawPath(context, fillColor: colors.topRect, withShadow: true) { + UIBezierPath(rect: dimens.topRect).cgPath + } + // Bottom part + drawPath(context, fillColor: colors.bottomRect) { + UIBezierPath(roundedRect: bottomRect, cornerRadius: bottomRectRadius).cgPath + } + drawHandle(context, bottomRect, colors.bottomRect) + // Markers + drawMarkers(context, bottomRect, position) + + } + + private func drawHandle(_ context: CGContext, _ bottomRect: CGRect, _ color: UIColor) { + context.setLineWidth(1.5) + drawPath(context, strokeColor: color) { + let topPoint = CGPoint(x: (bottomRect.maxX - bottomRect.minX) / 2, y: bottomRect.maxY) + let bottomPoint = topPoint.insetBy(x: 0, y: 6) + let path = UIBezierPath(ovalIn: CGRect(origin: bottomPoint.insetBy(x: -4, y: 0), size: CGSize(width: 8, height: 8))) + path.move(to: topPoint) + path.addLine(to: bottomPoint) + return path.cgPath + } + } + + private func drawMarkers(_ context: CGContext, _ bottomRect: CGRect, _ position: CGFloat) { + if let markers = windowState?.markers, !markers.isEmpty { + let markerColor = colors.bottomRect.copy(alpha: 0.5) + let radius = bottomRect.height / 2 + markers.forEach { marker in + if (marker != position) { + let markerScreenHeight = dimens.screenMaxHeight * marker / 100 + let markerBottomRect = dimens.bottomRect.offsetBy(dx: 0, dy: markerScreenHeight) + drawPath(context, fillColor: markerColor) { + UIBezierPath(roundedRect: markerBottomRect, cornerRadius: radius).cgPath + } + drawHandle(context, markerBottomRect, markerColor) + } + } + } + } + + private func setupView() { + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .transparent + clipsToBounds = false + } + + override class var requiresConstraintBasedLayout: Bool { + return true + } +} + +private enum DefaultDimens { + static let width: CGFloat = 320 + static let height: CGFloat = 260 + static var ratio: CGFloat { width / height } + + static let topRectHeight: CGFloat = 16 + static let bottomRectHeight: CGFloat = 8 + static let bottomRectWidth: CGFloat = 304 + static let screenWidth: CGFloat = 288 + + static let logoWidth: CGFloat = 120 + static let logoHeight: CGFloat = 137 + static let logoTopMargin: CGFloat = 50 +} + +private class RuntimeDimens { + var scale: CGFloat = 1 + + var canvasRect: CGRect = .zero + var topRect: CGRect = .zero + var bottomRect: CGRect = .zero + var screenTopLeft: CGPoint = .zero + var screenWidth: CGFloat = 0 + var screenMaxHeight: CGFloat = 0 + var logoRect: CGRect = .zero + + func update(_ frame: CGRect) { + createCanvasRect(frame) + scale = canvasRect.width / DefaultDimens.width + topRect = CGRect(origin: canvasRect.origin, size: CGSize(width: canvasRect.width, height: DefaultDimens.topRectHeight * scale)) + bottomRect = createBottomRect() + screenWidth = DefaultDimens.screenWidth * scale + screenTopLeft = createScreenTopLeft() + screenMaxHeight = canvasRect.height - topRect.height - bottomRect.height + logoRect = createLogoRect() + } + + private func createCanvasRect(_ frame: CGRect) { + let size = getSize(frame) + canvasRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) + } + + private func createBottomRect() -> CGRect { + let size = CGSize(width: DefaultDimens.bottomRectWidth * scale, height: DefaultDimens.bottomRectHeight * scale) + let left = topRect.minX + (topRect.width - size.width) / 2 + return CGRect(origin: CGPoint(x: left, y: topRect.maxY), size: size) + } + + private func createScreenTopLeft() -> CGPoint { + let screenWidth = DefaultDimens.screenWidth * scale + return CGPoint(x: topRect.minX + (topRect.width - screenWidth) / 2, y: topRect.maxY) + } + + private func createLogoRect() -> CGRect { + let logoWidth = DefaultDimens.logoWidth * scale + let logoHeight = DefaultDimens.logoHeight * scale + let left = (topRect.width - logoWidth) / 2 + return CGRect( + origin: CGPoint(x: left, y: topRect.maxY + DefaultDimens.logoTopMargin * scale), + size: CGSize(width: logoWidth, height: logoHeight) + ) + } + + private func getSize(_ frame: CGRect) -> CGSize { + let canvasRatio = frame.width / frame.height + if (canvasRatio > DefaultDimens.ratio) { + return CGSize(width: frame.height * DefaultDimens.ratio, height: frame.height) + } else { + return CGSize(width: frame.width, height: frame.width / DefaultDimens.ratio) + } + } +} diff --git a/SUPLA/Features/Details/WindowDetail/Base/UI/RoofWindow/RoofWindowColors.swift b/SUPLA/Features/Details/WindowDetail/Base/UI/RoofWindow/RoofWindowColors.swift new file mode 100644 index 00000000..d41fbd98 --- /dev/null +++ b/SUPLA/Features/Details/WindowDetail/Base/UI/RoofWindow/RoofWindowColors.swift @@ -0,0 +1,39 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +struct RoofWindowColors { + let window: UIColor + let shadow: UIColor + let glassTop: UIColor + + static func standard(_ traitCollection: UITraitCollection) -> RoofWindowColors { + RoofWindowColors( + window: .rollerShutterWindow.resolvedColor(with: traitCollection), + shadow: .black, + glassTop: .rollerShutterGlassTop.resolvedColor(with: traitCollection) + ) + } + + static func offline(_ traitCollection: UITraitCollection) -> RoofWindowColors { + RoofWindowColors( + window: .surface.resolvedColor(with: traitCollection), + shadow: .black, + glassTop: .rollerShutterDisabledGlassTop.resolvedColor(with: traitCollection) + ) + } +} diff --git a/SUPLA/Features/Details/WindowDetail/Base/UI/RoofWindow/RoofWindowView.swift b/SUPLA/Features/Details/WindowDetail/Base/UI/RoofWindow/RoofWindowView.swift index 1362e431..14e92584 100644 --- a/SUPLA/Features/Details/WindowDetail/Base/UI/RoofWindow/RoofWindowView.swift +++ b/SUPLA/Features/Details/WindowDetail/Base/UI/RoofWindow/RoofWindowView.swift @@ -35,9 +35,9 @@ final class RoofWindowView: BaseWindowView { override var isEnabled: Bool { didSet { if (isEnabled) { - colors = WindowColors.standard(traitCollection) + colors = RoofWindowColors.standard(traitCollection) } else { - colors = WindowColors.offline(traitCollection) + colors = RoofWindowColors.offline(traitCollection) } setNeedsDisplay() } @@ -58,7 +58,7 @@ final class RoofWindowView: BaseWindowView { private var openedOffset: CGFloat { toXOffset(windowState?.position.value ?? 0) } private let dimens = RuntimeDimens() - private lazy var colors = WindowColors.standard(traitCollection) + private lazy var colors = RoofWindowColors.standard(traitCollection) override init(frame: CGRect) { super.init(frame: frame) diff --git a/SUPLA/Features/Details/WindowDetail/Base/UI/TerraceAwning/TerraceAwningColors.swift b/SUPLA/Features/Details/WindowDetail/Base/UI/TerraceAwning/TerraceAwningColors.swift new file mode 100644 index 00000000..70dfe093 --- /dev/null +++ b/SUPLA/Features/Details/WindowDetail/Base/UI/TerraceAwning/TerraceAwningColors.swift @@ -0,0 +1,48 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +struct TerraceAwningColors { + let window: UIColor + let shadow: UIColor + let glassTop: UIColor + let glassBottom: UIColor + let awningBackground: UIColor + let awningBorder: UIColor + + static func standard(_ traitCollection: UITraitCollection) -> TerraceAwningColors { + TerraceAwningColors( + window: .rollerShutterWindow.resolvedColor(with: traitCollection), + shadow: .black, + glassTop: .rollerShutterGlassTop.resolvedColor(with: traitCollection), + glassBottom: .rollerShutterGlassBottom.resolvedColor(with: traitCollection), + awningBackground: .rollerShutterSlatBackground.resolvedColor(with: traitCollection), + awningBorder: .rollerShutterSlatBorder.resolvedColor(with: traitCollection) + ) + } + + static func offline(_ traitCollection: UITraitCollection) -> TerraceAwningColors { + TerraceAwningColors( + window: .surface.resolvedColor(with: traitCollection), + shadow: .black, + glassTop: .rollerShutterDisabledGlassTop.resolvedColor(with: traitCollection), + glassBottom: .rollerShutterDisabledGlassBottom.resolvedColor(with: traitCollection), + awningBackground: .rollerShutterDisabledSlatBackground.resolvedColor(with: traitCollection), + awningBorder: .rollerShutterDisabledSlatBorder.resolvedColor(with: traitCollection) + ) + } +} diff --git a/SUPLA/Features/Details/WindowDetail/Base/UI/TerraceAwning/TerraceAwningView.swift b/SUPLA/Features/Details/WindowDetail/Base/UI/TerraceAwning/TerraceAwningView.swift index a0f347bb..1111feec 100644 --- a/SUPLA/Features/Details/WindowDetail/Base/UI/TerraceAwning/TerraceAwningView.swift +++ b/SUPLA/Features/Details/WindowDetail/Base/UI/TerraceAwning/TerraceAwningView.swift @@ -23,9 +23,9 @@ final class TerraceAwningView: BaseWindowView { override var isEnabled: Bool { didSet { if (isEnabled) { - colors = WindowColors.standard(traitCollection) + colors = TerraceAwningColors.standard(traitCollection) } else { - colors = WindowColors.offline(traitCollection) + colors = TerraceAwningColors.offline(traitCollection) } setNeedsDisplay() } @@ -34,7 +34,7 @@ final class TerraceAwningView: BaseWindowView { override var touchRect: CGRect { dimens.touchRect } private let dimens = RuntimeDimens() - private lazy var colors = WindowColors.standard(traitCollection) + private lazy var colors = TerraceAwningColors.standard(traitCollection) override init(frame: CGRect) { super.init(frame: frame) @@ -141,8 +141,8 @@ final class TerraceAwningView: BaseWindowView { path.addLine(to: CGPoint(x: awningLeft - widthDeltaByPosition / 2, y: awningTop + deepByPosition)) path.close() - drawPath(context, fillColor: colors.slatBackground) { path.cgPath } - drawPath(context, strokeColor: colors.slatBorder) { path.cgPath } + drawPath(context, fillColor: colors.awningBackground) { path.cgPath } + drawPath(context, strokeColor: colors.awningBorder) { path.cgPath } let frontHeight = frontMinHeight + (dimens.awningFrontHeight - frontMinHeight) * position / 100 let frontRect = CGRect( @@ -151,8 +151,8 @@ final class TerraceAwningView: BaseWindowView { ) let frontPath = UIBezierPath(rect: frontRect) - drawPath(context, fillColor: colors.slatBackground) { frontPath.cgPath } - drawPath(context, strokeColor: colors.slatBorder) { frontPath.cgPath } + drawPath(context, fillColor: colors.awningBackground) { frontPath.cgPath } + drawPath(context, strokeColor: colors.awningBorder) { frontPath.cgPath } } private func drawAwningLikeMarker(_ context: CGContext, position: CGFloat, withFront: Bool) { @@ -172,8 +172,8 @@ final class TerraceAwningView: BaseWindowView { path.addLine(to: CGPoint(x: awningLeft - widthDeltaByPosition / 2, y: awningTop + deepByPosition)) path.close() - drawPath(context, fillColor: colors.slatBackground.copy(alpha: 0.06)) { path.cgPath } - drawPath(context, strokeColor: colors.slatBorder) { path.cgPath } + drawPath(context, fillColor: colors.awningBackground.copy(alpha: 0.06)) { path.cgPath } + drawPath(context, strokeColor: colors.awningBorder) { path.cgPath } if (withFront) { let frontHeight = frontMinHeight + (dimens.awningFrontHeight - frontMinHeight) * position / 100 @@ -183,20 +183,18 @@ final class TerraceAwningView: BaseWindowView { ) let frontPath = UIBezierPath(rect: frontRect) - drawPath(context, fillColor: colors.slatBackground) { frontPath.cgPath } - drawPath(context, strokeColor: colors.slatBorder) { frontPath.cgPath } + drawPath(context, fillColor: colors.awningBackground) { frontPath.cgPath } + drawPath(context, strokeColor: colors.awningBorder) { frontPath.cgPath } } } private func drawAwningShadow(_ context: CGContext, position: CGFloat) { let shadowLeft = (dimens.canvasRect.width - dimens.awningClosedWidth) / 2 let shadowTop = dimens.windowRect.maxY - let frontMinHeight = dimens.awningFrontHeight * 0.6 let deepByPosition = dimens.awninigMaxDepp * position / 100 let widthDeltaByPosition = (dimens.awningOpenedWidth - dimens.awningClosedWidth) * position / 100 let maxWidthByPosition = dimens.awningClosedWidth + widthDeltaByPosition - let maxWidthMarginByPosition = (dimens.canvasRect.width - maxWidthByPosition) / 2 let path = UIBezierPath() path.move(to: CGPoint(x: shadowLeft, y: shadowTop)) @@ -205,7 +203,7 @@ final class TerraceAwningView: BaseWindowView { path.addLine(to: CGPoint(x: shadowLeft - widthDeltaByPosition / 2, y: shadowTop + deepByPosition)) path.close() - drawPath(context, fillColor: colors.slatBackground.copy(alpha: 0.06)) { path.cgPath } + drawPath(context, fillColor: colors.awningBackground.copy(alpha: 0.06)) { path.cgPath } } override class var requiresConstraintBasedLayout: Bool { diff --git a/SUPLA/Features/Details/WindowDetail/Base/UI/WindowColors.swift b/SUPLA/Features/Details/WindowDetail/Base/UI/WindowView/WindowColors.swift similarity index 100% rename from SUPLA/Features/Details/WindowDetail/Base/UI/WindowColors.swift rename to SUPLA/Features/Details/WindowDetail/Base/UI/WindowView/WindowColors.swift diff --git a/SUPLA/Features/Details/WindowDetail/ProjectorScreen/ProjectorScreenVC.swift b/SUPLA/Features/Details/WindowDetail/ProjectorScreen/ProjectorScreenVC.swift new file mode 100644 index 00000000..f9b9edf9 --- /dev/null +++ b/SUPLA/Features/Details/WindowDetail/ProjectorScreen/ProjectorScreenVC.swift @@ -0,0 +1,34 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +final class ProjectorScreenVC: BaseWindowVC { + init(itemBundle: ItemBundle) { + super.init(itemBundle: itemBundle, viewModel: ProjectorScreenVM()) + } + + override func getWindowView() -> ProjectorScreenView { ProjectorScreenView() } + + override func handle(state: ProjectorScreenViewState) { + windowView.windowState = state.projectorScreenState + + slatTiltSlider.isHidden = true + topView.valueBottom = nil + + super.handle(state: state) + } +} diff --git a/SUPLA/Features/Details/WindowDetail/ProjectorScreen/ProjectorScreenVM.swift b/SUPLA/Features/Details/WindowDetail/ProjectorScreen/ProjectorScreenVM.swift new file mode 100644 index 00000000..4b0f10a8 --- /dev/null +++ b/SUPLA/Features/Details/WindowDetail/ProjectorScreen/ProjectorScreenVM.swift @@ -0,0 +1,96 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +final class ProjectorScreenVM: BaseWindowVM { + override func defaultViewState() -> ProjectorScreenViewState { ProjectorScreenViewState() } + + override func handleChannel(_ channel: SAChannel) { + guard let value = channel.value?.asRollerShutterValue() else { return } + + updateView { + if ($0.manualMoving) { + return $0 + } + + let position = value.hasValidPosition ? value.position : 0 + let positionValue: WindowGroupedValue = .similar(value.online ? CGFloat(position) : 25) + let windowState = $0.projectorScreenState + .changing(path: \.position, to: positionValue) + .changing(path: \.positionTextFormat, to: positionTextFormat) + + return updateChannel($0, channel, value) { + $0.changing(path: \.projectorScreenState, to: windowState) + } + } + } + + override func handleGroup(_ group: SAChannelGroup, _ onlineSummary: GroupOnlineSummary) { + updateView { + if ($0.manualMoving) { + return $0 + } + + let positions = group.getRollerShutterPositions() + let overallPosition = getGroupPercentage(positions, !$0.projectorScreenState.markers.isEmpty) + let windowState = $0.projectorScreenState + .changing(path: \.position, to: group.isOnline() ? overallPosition : .similar(25)) + .changing(path: \.positionTextFormat, to: positionTextFormat) + .changing(path: \.markers, to: overallPosition.isDifferent() ? positions : []) + + return updateGroup($0, group, onlineSummary) { + $0.changing(path: \.projectorScreenState, to: windowState) + .changing(path: \.positionUnknown, to: overallPosition == .invalid) + } + } + } +} + +struct ProjectorScreenViewState: BaseWindowViewState { + var remoteId: Int32? = nil + var projectorScreenState: ProjectorScreenState = .init(position: .similar(0)) + var issues: [ChannelIssueItem] = [] + var offline: Bool = true + var showClosingPercentage: Bool = false + var calibrating: Bool = false + var calibrationPossible: Bool = false + var positionUnknown: Bool = false + var touchTime: CGFloat? = nil + var isGroup: Bool = false + var onlineStatusString: String? = nil + var moveStartTime: TimeInterval? = nil + var manualMoving: Bool = false + + var windowState: any WindowState { projectorScreenState } +} + +private extension SAChannelGroup { + func getRollerShutterPositions() -> [CGFloat] { + guard let totalValue = total_value as? GroupTotalValue else { return [] } + return totalValue.values.compactMap { valueToPosition($0) } + } + + private func valueToPosition(_ baseGroupValue: BaseGroupValue) -> CGFloat? { + guard let value = baseGroupValue as? RollerShutterGroupValue else { return nil } + + return if (value.position < 100 && value.closedSensorActive) { + CGFloat(100) + } else { + CGFloat(value.position) + } + } +} diff --git a/SUPLA/Resources/Default.strings b/SUPLA/Resources/Default.strings index 74442665..577125ff 100644 --- a/SUPLA/Resources/Default.strings +++ b/SUPLA/Resources/Default.strings @@ -67,6 +67,7 @@ "channel_caption_general_purpose_meter" = "Meter channel"; "channel_caption_facade_blinds" = "Facade blinds"; "channel_caption_terrace_awning" = "Terrace awning"; +"channel_caption_projector_screen" = "Projector screen"; /* Main */ "dialog_new_gesture_info_text" = "Swipe gesture to open details was removed, tap on particular channel to open it."; diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/Contents.json new file mode 100644 index 00000000..59c612dd --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "projector screenclosed.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "projector screen_dark modeclosed.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/projector screen_dark modeclosed.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/projector screen_dark modeclosed.svg new file mode 100644 index 00000000..530bb54b --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/projector screen_dark modeclosed.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/projector screenclosed.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/projector screenclosed.svg new file mode 100644 index 00000000..48b596be --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-closed.imageset/projector screenclosed.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/Contents.json b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/Contents.json new file mode 100644 index 00000000..72960c50 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "projector screenopened.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "projector screen_dark modeopened.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/projector screen_dark modeopened.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/projector screen_dark modeopened.svg new file mode 100644 index 00000000..af49ad79 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/projector screen_dark modeopened.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/projector screenopened.svg b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/projector screenopened.svg new file mode 100644 index 00000000..71097523 --- /dev/null +++ b/SUPLA/Resources/Resources.xcassets/Images/FunctionIcons/fnc_projector_screen-open.imageset/projector screenopened.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/SUPLA/Resources/Strings.swift b/SUPLA/Resources/Strings.swift index 97a01483..411c390d 100644 --- a/SUPLA/Resources/Strings.swift +++ b/SUPLA/Resources/Strings.swift @@ -304,6 +304,7 @@ struct Strings { static let captionGeneralPurposeMeter = "channel_caption_general_purpose_meter".toLocalized() static let captionFacadeBlinds = "channel_caption_facade_blinds".toLocalized() static let captionTerraceAwning = "channel_caption_terrace_awning".toLocalized() + static let captionProjectorScreen = "channel_caption_projector_screen".toLocalized() } } diff --git a/SUPLA/Resources/de.lproj/Localizable.strings b/SUPLA/Resources/de.lproj/Localizable.strings index d0260529..7962dc27 100644 --- a/SUPLA/Resources/de.lproj/Localizable.strings +++ b/SUPLA/Resources/de.lproj/Localizable.strings @@ -324,6 +324,7 @@ "channel_caption_general_purpose_meter" = "Zählerkanal"; "channel_caption_facade_blinds" = "Fassadenjalousien"; "channel_caption_terrace_awning" = "Terrassenmarkise"; +"channel_caption_projector_screen" = "Leinwand"; /* Main */ "dialog_new_gesture_info_text" = "Wischgeste zum Öffnen der Kanaldetails wurde gelöscht, tippe auf dem Kanal, um sie zu sehen."; diff --git a/SUPLA/Resources/pl.lproj/Localizable.strings b/SUPLA/Resources/pl.lproj/Localizable.strings index f3338df0..f78f367a 100644 --- a/SUPLA/Resources/pl.lproj/Localizable.strings +++ b/SUPLA/Resources/pl.lproj/Localizable.strings @@ -349,6 +349,7 @@ "channel_caption_general_purpose_meter" = "Kanał licznikowy"; "channel_caption_facade_blinds" = "Żaluzja fasadowa"; "channel_caption_terrace_awning" = "Markiza tarasowa"; +"channel_caption_projector_screen" = "Ekran projekcyjny"; /* Main */ "dialog_new_gesture_info_text" = "Usunęliśmy gest przesunięcia otwierający szczegóły, aby je zobaczyć dotknij wybrany kanał."; diff --git a/SUPLA/UseCase/ChannelBase/GetChannelBaseDefaultCaptionUseCase.swift b/SUPLA/UseCase/ChannelBase/GetChannelBaseDefaultCaptionUseCase.swift index 57a9f64f..9fceeeca 100644 --- a/SUPLA/UseCase/ChannelBase/GetChannelBaseDefaultCaptionUseCase.swift +++ b/SUPLA/UseCase/ChannelBase/GetChannelBaseDefaultCaptionUseCase.swift @@ -115,6 +115,8 @@ final class GetChannelBaseDefaultCaptionUseCaseImpl: GetChannelBaseDefaultCaptio return Strings.General.Channel.captionFacadeBlinds case SUPLA_CHANNELFNC_TERRACE_AWNING: return Strings.General.Channel.captionTerraceAwning + case SUPLA_CHANNELFNC_PROJECTOR_SCREEN: + return Strings.General.Channel.captionProjectorScreen default: return NSLocalizedString("Not supported function", comment: "") } diff --git a/SUPLA/UseCase/ChannelBase/GetChannelBaseStateUseCase.swift b/SUPLA/UseCase/ChannelBase/GetChannelBaseStateUseCase.swift index 3697f4cc..76c4a6fe 100644 --- a/SUPLA/UseCase/ChannelBase/GetChannelBaseStateUseCase.swift +++ b/SUPLA/UseCase/ChannelBase/GetChannelBaseStateUseCase.swift @@ -57,6 +57,8 @@ final class GetChannelBaseStateUseCaseImpl: GetChannelBaseStateUseCase { } else { return .opened } + case SUPLA_CHANNELFNC_PROJECTOR_SCREEN: + return activeValue != 0 ? .opened : .closed case SUPLA_CHANNELFNC_POWERSWITCH, SUPLA_CHANNELFNC_STAIRCASETIMER, SUPLA_CHANNELFNC_NOLIQUIDSENSOR, @@ -104,6 +106,7 @@ final class GetChannelBaseStateUseCaseImpl: GetChannelBaseStateUseCase { SUPLA_CHANNELFNC_OPENINGSENSOR_WINDOW, SUPLA_CHANNELFNC_OPENINGSENSOR_ROOFWINDOW, SUPLA_CHANNELFNC_TERRACE_AWNING, + SUPLA_CHANNELFNC_PROJECTOR_SCREEN, SUPLA_CHANNELFNC_VALVE_OPENCLOSE, SUPLA_CHANNELFNC_VALVE_PERCENTAGE: .opened case SUPLA_CHANNELFNC_POWERSWITCH, diff --git a/SUPLA/UseCase/Detail/ProvideDetailTypeUseCase.swift b/SUPLA/UseCase/Detail/ProvideDetailTypeUseCase.swift index b72d393a..4cd06b45 100644 --- a/SUPLA/UseCase/Detail/ProvideDetailTypeUseCase.swift +++ b/SUPLA/UseCase/Detail/ProvideDetailTypeUseCase.swift @@ -38,6 +38,8 @@ final class ProvideDetailTypeUseCaseImpl: ProvideDetailTypeUseCase { return .windowDetail(pages: [.facadeBlind]) case SUPLA_CHANNELFNC_TERRACE_AWNING: return .windowDetail(pages: [.terraceAwning]) + case SUPLA_CHANNELFNC_PROJECTOR_SCREEN: + return .windowDetail(pages: [.projectorScreen]) case SUPLA_CHANNELFNC_LIGHTSWITCH, SUPLA_CHANNELFNC_POWERSWITCH, @@ -136,4 +138,5 @@ enum DetailPage { case roofWindow case facadeBlind case terraceAwning + case projectorScreen } diff --git a/SUPLA/UseCase/Icon/GetDefaultIconNameUseCase.swift b/SUPLA/UseCase/Icon/GetDefaultIconNameUseCase.swift index ef93fe7f..dba84dc9 100644 --- a/SUPLA/UseCase/Icon/GetDefaultIconNameUseCase.swift +++ b/SUPLA/UseCase/Icon/GetDefaultIconNameUseCase.swift @@ -77,6 +77,7 @@ final class GetDefaultIconNameUseCaseImpl: GetDefaultIconNameUseCase { StaticIconNameProducer(function: SUPLA_CHANNELFNC_CONTROLLINGTHEROOFWINDOW, name: "roofwindow"), StaticIconNameProducer(function: SUPLA_CHANNELFNC_CONTROLLINGTHEFACADEBLIND, name: "fnc_facade_blind"), StaticIconNameProducer(function: SUPLA_CHANNELFNC_TERRACE_AWNING, name: "fnc_terrace_awning"), + StaticIconNameProducer(function: SUPLA_CHANNELFNC_PROJECTOR_SCREEN, name: "fnc_projector_screen"), PowerSwitchIconNameProducer(), LightSwitchIconNameProducer(), StaircaseTimerIconNameProducer(),