Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements and shared update #35

Merged
merged 15 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# xcodegen
WaiterRobot.xcodeproj/
.generated/

# Created by https://www.toptal.com/developers/gitignore/api/fastlane,xcode,intellij,appcode,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=fastlane,xcode,intellij,appcode,macos

Expand Down Expand Up @@ -240,14 +236,6 @@ Temporary Items
.apdisk
.build

### Xcode ###
## User settings
xcuserdata/

## Xcode 8 and earlier
*.xcscmblueprint
*.xccheckout

## Obj-C/Swift specific
*.hmap
*.ipa
Expand All @@ -256,6 +244,11 @@ xcuserdata/

# End of https://www.toptal.com/developers/gitignore/api/fastlane,xcode,intellij,appcode,macos

# Xcodegen
.generated/
WaiterRobot.xcodeproj/*
!WaiterRobot.xcodeproj/project.xcworkspace/xcshareddata/swiftpm

.keys*
.env.*
!.env.example
Expand Down
35 changes: 23 additions & 12 deletions WaiterRobot/Core/Mvi/ObservableViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,28 @@
import Foundation
import shared

@MainActor
class ObservableViewModel<State: ViewModelState, Effect: ViewModelEffect, ViewModel: AbstractViewModel<State, Effect>>: ObservableObject {
@Published public private(set) var state: State
@Published
public private(set) var state: State

public let actual: ViewModel

private var task: Task<Void, Error>? = nil

init(viewModel: ViewModel) {
init(viewModel: ViewModel, subscribe: Bool = true) {
actual = viewModel
// This is save, as the constraint is required by the generics (S must be the state of the provided VM)
state = actual.container.stateFlow.value as! State

activate()
}

deinit {
actual.onCleared()
task?.cancel()
if subscribe {
Task {
await activate()
}
}
}

@MainActor
private func activate() {
func activate() {
guard task == nil else { return }
task = Task { [weak self] in
guard let stateFlow = self?.actual.container.stateFlow else { return }
Expand All @@ -38,17 +37,29 @@ class ObservableViewModel<State: ViewModelState, Effect: ViewModelEffect, ViewMo
}
}
}

func deactivate() {
task?.cancel()
task = nil
}

deinit {
actual.onCleared()

task?.cancel()
task = nil
}
}

class ObservableTableListViewModel: ObservableViewModel<TableListState, TableListEffect, TableListViewModel> {
init() {
super.init(viewModel: koin.tableListVM())
super.init(viewModel: koin.tableListVM(), subscribe: false)
}
}

class ObservableTableDetailViewModel: ObservableViewModel<TableDetailState, TableDetailEffect, TableDetailViewModel> {
init(table: Table) {
super.init(viewModel: koin.tableDetailVM(table: table))
super.init(viewModel: koin.tableDetailVM(table: table), subscribe: false)
}
}

Expand Down
2 changes: 1 addition & 1 deletion WaiterRobot/Core/PreviewView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import UIPilot
/// Helper view which sets up everything needed for previewing content
struct PreviewView<Content: View>: View {
@StateObject
private var navigator = UIPilot<Screen>(initial: Screen.RootScreen.shared, debug: true)
private var navigator = UIPilot<Screen>(initial: CommonApp.shared.getNextRootScreen(), debug: true)

private let withUIPilot: Bool

Expand Down
9 changes: 5 additions & 4 deletions WaiterRobot/LaunchScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct LaunchScreen: View {
@State private var startupFinished = false

var body: some View {
VStack {
ZStack {
if case .phone = device {
VStack {
Spacer()
Expand Down Expand Up @@ -37,6 +37,10 @@ struct LaunchScreen: View {
}
}
}

if startupFinished {
MainView()
}
}
.onAppear {
// This is needed otherwise previews will crash randomly
Expand All @@ -52,9 +56,6 @@ struct LaunchScreen: View {
}
}
}
.fullScreenCover(isPresented: $startupFinished) {
MainView()
}
}

private func delay() async {
Expand Down
69 changes: 43 additions & 26 deletions WaiterRobot/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ import SwiftUI
import UIPilot

struct MainView: View {
@State private var snackBarMessage: String?
@State private var showUpdateAvailableAlert: Bool = false
@StateObject private var navigator: UIPilot<Screen> = UIPilot(initial: Screen.RootScreen.shared, debug: true)
@StateObject private var viewModel = ObservableRootViewModel()
@State
private var snackBarMessage: String?

@State
private var showUpdateAvailableAlert: Bool = false

@StateObject
private var navigator: UIPilot<Screen> = UIPilot(initial: CommonApp.shared.getNextRootScreen(), debug: true)

@StateObject
private var viewModel = ObservableRootViewModel()

private var selectedScheme: ColorScheme? {
switch viewModel.state.selectedTheme {
Expand All @@ -29,29 +36,39 @@ struct MainView: View {
var body: some View {
ZStack {
UIPilotHost(navigator) { route in
switch route {
case is Screen.RootScreen: RootScreen(viewModel: viewModel)
case is Screen.LoginScannerScreen: LoginScannerScreen()
case is Screen.SwitchEventScreen: SwitchEventScreen()
case is Screen.SettingsScreen: SettingsScreen()
case is Screen.UpdateApp: UpdateAppScreen()
case let screen as Screen.RegisterScreen: RegisterScreen(createToken: screen.createToken)
case let screen as Screen.TableDetailScreen: TableDetailScreen(table: screen.table)
case let screen as Screen.OrderScreen: OrderScreen(table: screen.table, initialItemId: screen.initialItemId)
case let screen as Screen.BillingScreen: BillingScreen(table: screen.table)
default:
VStack {
Text("No view defined for \(route.description)")
switch onEnum(of: route) {
case .loginScreen:
LoginScreen()

Button {
navigator.pop()
} label: {
Text("Back")
}
.onAppear {
koin.logger(tag: "WaiterRobotApp").e { "No view defined for \(route.description)" }
}
}
case .tableListScreen:
TableListScreen()

case .switchEventScreen:
SwitchEventScreen()

case .settingsScreen:
SettingsScreen()

case let .registerScreen(screen):
RegisterScreen(deepLink: screen.registerLink)

case .updateApp:
UpdateAppScreen()

case let .tableDetailScreen(screen):
TableDetailScreen(table: screen.table)

case let .orderScreen(screen):
OrderScreen(table: screen.table, initialItemId: screen.initialItemId)

case let .billingScreen(screen):
BillingScreen(table: screen.table)

case .loginScannerScreen:
LoginScannerScreen()

case .stripeInitializationScreen:
EmptyView()
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions WaiterRobot/Ui/Billing/BillListItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ struct BillListItem: View {
List {
BillListItem(
item: BillItem(
productId: 1,
baseProductId: 1,
name: "Beer",
ordered: 10,
selectedForBill: 5,
pricePerPiece: Money(cents: 3390)
pricePerPiece: Money(cents: 3390),
orderProductIds: [1, 2]
),
addOne: {},
addAll: {},
Expand All @@ -80,11 +81,12 @@ struct BillListItem: View {
)
BillListItem(
item: BillItem(
productId: 2,
baseProductId: 2,
name: "Wine",
ordered: 15,
selectedForBill: 8,
pricePerPiece: Money(cents: 390)
pricePerPiece: Money(cents: 390),
orderProductIds: [1, 2]
),
addOne: {},
addAll: {},
Expand Down
10 changes: 5 additions & 5 deletions WaiterRobot/Ui/Billing/BillingScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,20 @@ struct BillingScreen: View {
.padding()
} else {
Section {
ForEach(billItems, id: \.self) { item in
ForEach(billItems, id: \.virtualId) { item in
BillListItem(
item: item,
addOne: {
viewModel.actual.addItem(id: item.productId, amount: 1)
viewModel.actual.addItem(virtualId: item.virtualId, amount: 1)
},
addAll: {
viewModel.actual.addItem(id: item.productId, amount: item.ordered - item.selectedForBill)
viewModel.actual.addItem(virtualId: item.virtualId, amount: item.ordered - item.selectedForBill)
},
removeOne: {
viewModel.actual.addItem(id: item.productId, amount: -1)
viewModel.actual.addItem(virtualId: item.virtualId, amount: -1)
},
removeAll: {
viewModel.actual.addItem(id: item.productId, amount: -item.selectedForBill)
viewModel.actual.addItem(virtualId: item.virtualId, amount: -item.selectedForBill)
}
)
}
Expand Down
43 changes: 0 additions & 43 deletions WaiterRobot/Ui/Common/TextEditorWithPlaceholder.swift

This file was deleted.

18 changes: 8 additions & 10 deletions WaiterRobot/Ui/Core/FloatingActionButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,13 @@ struct FloatingActionButtonStyle: ButtonStyle {
}
}

struct FloatingActionButton_Previews: PreviewProvider {
static var previews: some View {
ZStack {
EmbeddedFloatingActionButton(
icon: "plus",
action: {
print("Test")
}
)
}
#Preview {
ZStack {
EmbeddedFloatingActionButton(
icon: "plus",
action: {
print("Test")
}
)
}
}
27 changes: 18 additions & 9 deletions WaiterRobot/Ui/Core/Navigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,31 @@ import UIPilot
extension UIPilot<Screen> {
func navigate(_ navAction: NavAction) {
koin.logger(tag: "Navigation").d { "Handle navigation: \(navAction.description)" }
switch navAction {
case is NavAction.Pop:

switch onEnum(of: navAction) {
case .pop:
pop()
case let nav as NavAction.Push:

case let .push(nav):
push(nav.screen)
case let nav as NavAction.PopUpTo:

case let .popUpTo(nav):
popTo(nav.screen, inclusive: nav.inclusive)
case let nav as NavAction.PopUpAndPush:

case let .popUpAndPush(nav):
popTo(nav.popUpTo, inclusive: nav.inclusive)
push(nav.screen)
default:
koin.logger(tag: "Navigation").e {
"No nav action for nav effect \(navAction.self.description)"
}

case let .replaceRoot(nav):
guard let topmost = topmostScreen else { return }
popTo(topmost, inclusive: true)
push(nav.screen)
}
}

var topmostScreen: Screen? {
stack.first
}
}

extension View {
Expand Down
Loading
Loading