// // ConfigurationPage.swift // LoopKitUI // // Created by Michael Pangburn on 4/10/20. // Copyright © 2020 LoopKit Authors. All rights reserved. // import SwiftUI public enum ConfigurationPageActionButtonState { case enabled case loading case disabled } public struct ConfigurationPage: View { public typealias ActionButtonState = ConfigurationPageActionButtonState var title: Text var actionButtonTitle: Text var actionButtonState: ActionButtonState var cardListStyle: CardListStyle var actionAreaContent: ActionAreaContent var action: () -> Void public var body: some View { VStack(spacing: 0) { CardList(title: title, style: cardListStyle) VStack(spacing: 0) { actionAreaContent .padding([.top, .horizontal]) .transition(AnyTransition.opacity.combined(with: .move(edge: .bottom))) Button( action: action, label: { HStack(spacing: 12) { ActivityIndicator(isAnimating: .constant(false), style: .medium) .opacity(0) // For layout only, to ensure the button text is centered actionButtonTitle .animation(nil) ActivityIndicator(isAnimating: .constant(true), style: .medium, color: .white) .opacity(actionButtonState == .loading ? 1 : 0) } } ) .buttonStyle(ActionButtonStyle(.primary)) .disabled(actionButtonState != .enabled) .padding() } .padding(.bottom) // FIXME: unnecessary on iPhone 8 size devices .background(Color(.secondarySystemGroupedBackground).shadow(radius: 5)) } .edgesIgnoringSafeArea(.bottom) } } extension ConfigurationPage { public init( title: Text, actionButtonTitle: Text, actionButtonState: ActionButtonState = .enabled, @CardStackBuilder cards: () -> CardStack, @ViewBuilder actionAreaContent: () -> ActionAreaContent, action: @escaping () -> Void ) { self.title = title self.actionButtonTitle = actionButtonTitle self.actionButtonState = actionButtonState self.cardListStyle = .simple(cards()) self.actionAreaContent = actionAreaContent() self.action = action } /// Convenience initializer for a page whose action is 'Save' public init( title: Text, saveButtonState: ActionButtonState = .enabled, @CardStackBuilder cards: () -> CardStack, @ViewBuilder actionAreaContent: () -> ActionAreaContent, onSave save: @escaping () -> Void ) { self.init( title: title, actionButtonTitle: Text(LocalizedString("Save", comment: "The button text for saving on a configuration page")), actionButtonState: saveButtonState, cards: cards, actionAreaContent: actionAreaContent, action: save ) } /// Convenience initializer for a sectioned page whose action is 'Save' public init( title: Text, saveButtonState: ActionButtonState = .enabled, sections: [CardListSection], @ViewBuilder actionAreaContent: () -> ActionAreaContent, onSave save: @escaping () -> Void ) { self.init( title: title, actionButtonTitle: Text(LocalizedString("Save", comment: "The button text for saving on a configuration page")), actionButtonState: saveButtonState, cardListStyle: .sectioned(sections), actionAreaContent: actionAreaContent(), action: save ) } } struct ConfigurationPage_Previews: PreviewProvider { static var previews: some View { ConfigurationPage( title: Text("Example"), cards: { Text("A simple card") Text("A card whose text will wrap onto multiple lines if I continue to type for long enough—this length should do") Card { Text("Top component") Text("Bottom component") } Card(of: 1...3, id: \.self) { value in Text("Dynamic component #\(value)") } }, actionAreaContent: { Text("Above the save button") }, onSave: {} ) } }