From f2b16e0f87c48e2f042eb29519f9b37746d02919 Mon Sep 17 00:00:00 2001 From: Renaud Jenny Date: Sat, 22 Apr 2023 21:08:26 +0200 Subject: [PATCH] feat(App): improve `AppView` (previously `MainView`) --- telltime/TellTime.xcodeproj/project.pbxproj | 8 +- telltime/TellTime/MainScreen/AppView.swift | 147 ++++++++++++++++++ telltime/TellTime/MainView.swift | 161 -------------------- 3 files changed, 151 insertions(+), 165 deletions(-) create mode 100644 telltime/TellTime/MainScreen/AppView.swift delete mode 100644 telltime/TellTime/MainView.swift diff --git a/telltime/TellTime.xcodeproj/project.pbxproj b/telltime/TellTime.xcodeproj/project.pbxproj index b5e68f9..f7eca3b 100644 --- a/telltime/TellTime.xcodeproj/project.pbxproj +++ b/telltime/TellTime.xcodeproj/project.pbxproj @@ -22,7 +22,7 @@ 4C5FD3DA29F1CB73008EB5C9 /* SwiftPastTenDependency in Frameworks */ = {isa = PBXBuildFile; productRef = 4C5FD3D929F1CB73008EB5C9 /* SwiftPastTenDependency */; }; 4C86575425531EB20040EA04 /* RenaudJennyAboutView in Frameworks */ = {isa = PBXBuildFile; productRef = 4C86575325531EB20040EA04 /* RenaudJennyAboutView */; }; 4CB1D4BC22A7293300786385 /* TellTimeUKApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB1D4BB22A7293300786385 /* TellTimeUKApp.swift */; }; - 4CB1D4C022A7293300786385 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB1D4BF22A7293300786385 /* MainView.swift */; }; + 4CB1D4C022A7293300786385 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB1D4BF22A7293300786385 /* AppView.swift */; }; 4CB1D4C222A7293500786385 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CB1D4C122A7293500786385 /* Assets.xcassets */; }; 4CB1D4C522A7293500786385 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CB1D4C422A7293500786385 /* Preview Assets.xcassets */; }; 4CB9C77327EB0C30006BC36E /* SwiftClockUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4CB9C77227EB0C30006BC36E /* SwiftClockUI */; }; @@ -83,7 +83,7 @@ 4CB1756329C729C200972E74 /* telltime */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = telltime; path = ..; sourceTree = ""; }; 4CB1D4B822A7293300786385 /* Tell Time UK.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Tell Time UK.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4CB1D4BB22A7293300786385 /* TellTimeUKApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TellTimeUKApp.swift; sourceTree = ""; }; - 4CB1D4BF22A7293300786385 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + 4CB1D4BF22A7293300786385 /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; 4CB1D4C122A7293500786385 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4CB1D4C422A7293500786385 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 4CB1D4C922A7293500786385 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -174,7 +174,6 @@ 4C05AB5F2343FC2700970036 /* Common */, 4CB1D4C922A7293500786385 /* Info.plist */, 4CB1D4C322A7293500786385 /* Preview Content */, - 4CB1D4BF22A7293300786385 /* MainView.swift */, 4C28CDB12513F72000DAE010 /* LaunchScreen.storyboard */, ); path = TellTime; @@ -201,6 +200,7 @@ 4CFB1C6E25D7E05700AA7374 /* MainScreen */ = { isa = PBXGroup; children = ( + 4CB1D4BF22A7293300786385 /* AppView.swift */, 4CFB1C6925D726BA00AA7374 /* DateSelector.swift */, 4CFB1C6F25D7E0A800AA7374 /* Buttons.swift */, ); @@ -369,7 +369,7 @@ 4CFB1C7025D7E0A800AA7374 /* Buttons.swift in Sources */, 4CB1D4BC22A7293300786385 /* TellTimeUKApp.swift in Sources */, 4CFB1C6A25D726BA00AA7374 /* DateSelector.swift in Sources */, - 4CB1D4C022A7293300786385 /* MainView.swift in Sources */, + 4CB1D4C022A7293300786385 /* AppView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/telltime/TellTime/MainScreen/AppView.swift b/telltime/TellTime/MainScreen/AppView.swift new file mode 100644 index 0000000..05d1a62 --- /dev/null +++ b/telltime/TellTime/MainScreen/AppView.swift @@ -0,0 +1,147 @@ +import ConfigurationFeature +import SwiftUI +import ComposableArchitecture +import Combine +import SwiftClockUI +import RenaudJennyAboutView + +struct AppView: View { + struct ViewState: Equatable { + var date: Date + var time: String? + var recognizedUtterance: String? + var clockStyle: ClockStyle + var clockConfiguration: ClockConfiguration + var isConfigurationPresented: Bool + var isAboutPresented: Bool + + init(_ state: App.State) { + date = state.date + time = state.tellTime + recognizedUtterance = state.speechRecognizer.utterance + clockStyle = state.configuration.clockStyle + clockConfiguration = state.configuration.clock + isConfigurationPresented = state.configuration.isPresented + isAboutPresented = state.isAboutPresented + } + } + + @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? + @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass? + + let store: StoreOf + + var body: some View { + WithViewStore(store.stateless) { viewStore in + NavigationView { + content + .navigationBarTitle("Tell Time") + .padding() + } + .navigationViewStyle(StackNavigationViewStyle()) + .onAppear { viewStore.send(.appStarted) } + } + } + + private var content: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + VStack { + if verticalSizeClass == .regular && horizontalSizeClass == .compact { + Spacer() + clockView + Spacer() + timeText + Spacer() + DateSelector(store: store) + Spacer() + Buttons(store: store) + } else if verticalSizeClass == .compact { + HStack { + clockView(viewStore: viewStore).padding() + VStack { + timeText(viewStore: viewStore) + Spacer() + DateSelector(store: store) + Spacer() + Buttons(store: store) + } + } + } else { + VStack { + clockView(viewStore: viewStore).padding() + VStack { + timeText(viewStore: viewStore) + DateSelector(store: store) + } + HStack { + Spacer() + Buttons(store: store) + Spacer() + } + } + } + navigationLinks(viewStore: viewStore) + } + } + } + + private var clockView: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + ClockView() + .environment(\.clockDate, viewStore.binding(get: \.date, send: App.Action.setDate)) + .environment(\.clockStyle, viewStore.clockStyle) + .environment(\.clockConfiguration, viewStore.clockConfiguration) + } + } + + private var timeText: some View { + WithViewStore(store, observe: { $0.tellTime }) { viewStore in + viewStore.state.map { time in + Text(time) + .font(.title2) + .foregroundColor(.red) + .padding() + } + } + } + + private var navigationLinks: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + VStack { + NavigationLink( + destination: ConfigurationView(store: store.scope( + state: \.configuration, + action: { App.Action.configuration($0) } + )), + isActive: viewStore.binding(get: \.isConfigurationPresented, send: App.Action.configuration(.hide)), + label: EmptyView.init + ) + NavigationLink( + destination: AboutView(appId: "id1496541173") { + Image(uiImage: #imageLiteral(resourceName: "Logo")).shadow(radius: 5) + }, + isActive: viewStore.binding(get: \.isAboutPresented, send: App.Action.hideAbout), + label: EmptyView.init + ) + } + } + } +} + +#if DEBUG +struct AppView_Previews: PreviewProvider { + static var previews: some View { + Group { + AppView(store: .preview) + AppView(store: .preview) + .previewLayout(.fixed(width: 800, height: 400)) + .preferredColorScheme(.dark) + .environment(\.horizontalSizeClass, .compact) + .environment(\.verticalSizeClass, .compact) + AppView(store: .preview) + .previewLayout(.fixed(width: 1600, height: 1000)) + .environment(\.locale, Locale(identifier: "fr_FR")) + } + } +} +#endif diff --git a/telltime/TellTime/MainView.swift b/telltime/TellTime/MainView.swift deleted file mode 100644 index 5dd3e57..0000000 --- a/telltime/TellTime/MainView.swift +++ /dev/null @@ -1,161 +0,0 @@ -import ConfigurationFeature -import SwiftUI -import ComposableArchitecture -import Combine -import SwiftClockUI -import RenaudJennyAboutView - -struct MainView: View { - struct ViewState: Equatable { - var date: Date - var time: String? - var recognizedUtterance: String? - var clockStyle: ClockStyle - var clockConfiguration: ClockConfiguration - var isConfigurationPresented: Bool - var isAboutPresented: Bool - } - - enum ViewAction: Equatable { - case setDate(Date) - case hideAbout - case hideConfiguration - case appStarted - } - - @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? - @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass? - - let store: Store - - var body: some View { - WithViewStore(store.scope(state: { $0.view }, action: AppAction.view)) { viewStore in - NavigationView { - content(viewStore: viewStore) - .navigationBarTitle("Tell Time") - .padding() - } - .navigationViewStyle(StackNavigationViewStyle()) - .onAppear { viewStore.send(.appStarted) } - } - } - - private func content(viewStore: ViewStore) -> some View { - VStack { - if verticalSizeClass == .regular && horizontalSizeClass == .compact { - Spacer() - clockView(viewStore: viewStore) - Spacer() - timeText(viewStore: viewStore) - Spacer() - DateSelector(store: store) - Spacer() - Buttons(store: store) - } else if verticalSizeClass == .compact { - HStack { - clockView(viewStore: viewStore).padding() - VStack { - timeText(viewStore: viewStore) - Spacer() - DateSelector(store: store) - Spacer() - Buttons(store: store) - } - } - } else { - VStack { - clockView(viewStore: viewStore).padding() - VStack { - timeText(viewStore: viewStore) - DateSelector(store: store) - } - HStack { - Spacer() - Buttons(store: store) - Spacer() - } - } - } - navigationLinks(viewStore: viewStore) - } - } - - private func clockView(viewStore: ViewStore) -> some View { - ClockView() - .environment(\.clockDate, viewStore.binding(get: \.date, send: MainView.ViewAction.setDate)) - .environment(\.clockStyle, viewStore.clockStyle) - .environment(\.clockConfiguration, viewStore.clockConfiguration) - } - - private func timeText(viewStore: ViewStore) -> some View { - viewStore.time.map { time in - Text(time) - .font(.title2) - .foregroundColor(.red) - .padding() - } - } - - private func navigationLinks(viewStore: ViewStore) -> some View { - VStack { - NavigationLink( - destination: ConfigurationView(store: store.scope( - state: \.configuration, - action: { AppAction.configuration($0) } - )), - isActive: viewStore.binding(get: \.isConfigurationPresented, send: ViewAction.hideConfiguration), - label: EmptyView.init - ) - NavigationLink( - destination: AboutView(appId: "id1496541173") { - Image(uiImage: #imageLiteral(resourceName: "Logo")).shadow(radius: 5) - }, - isActive: viewStore.binding(get: \.isAboutPresented, send: ViewAction.hideAbout), - label: EmptyView.init - ) - } - } -} - -private extension AppState { - var view: MainView.ViewState { - MainView.ViewState( - date: date, - time: tellTime, - recognizedUtterance: speechRecognizer.utterance, - clockStyle: configuration.clockStyle, - clockConfiguration: configuration.clock, - isConfigurationPresented: configuration.isPresented, - isAboutPresented: isAboutPresented - ) - } -} - -private extension AppAction { - static func view(localAction: MainView.ViewAction) -> Self { - switch localAction { - case .setDate(let date): return setDate(date) - case .hideAbout: return hideAbout - case .hideConfiguration: return configuration(.hide) - case .appStarted: return .appStarted - } - } -} - -#if DEBUG -struct MainView_Previews: PreviewProvider { - static var previews: some View { - Group { - MainView(store: .preview) - MainView(store: .preview) - .previewLayout(.fixed(width: 800, height: 400)) - .preferredColorScheme(.dark) - .environment(\.horizontalSizeClass, .compact) - .environment(\.verticalSizeClass, .compact) - MainView(store: .preview) - .previewLayout(.fixed(width: 1600, height: 1000)) - .environment(\.locale, Locale(identifier: "fr_FR")) - } - } -} -#endif