Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
kaulex99 committed Jun 17, 2024
2 parents 1883f91 + 7ab9c2f commit 76d7a8c
Show file tree
Hide file tree
Showing 19 changed files with 127 additions and 129 deletions.
15 changes: 15 additions & 0 deletions WaiterRobot/Core/KermitLoggerExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import shared

extension KermitLogger {
func d(message: @escaping () -> String) {
d(throwable: nil, tag: tag, message: message)
}

func w(message: @escaping () -> String) {
w(throwable: nil, tag: tag, message: message)
}

func e(message: @escaping () -> String) {
e(throwable: nil, tag: tag, message: message)
}
}
33 changes: 6 additions & 27 deletions WaiterRobot/Core/Mvi/ObservableViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,33 @@ class ObservableViewModel<State: ViewModelState, Effect: ViewModelEffect, ViewMo

public let actual: ViewModel

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

init(viewModel: ViewModel, subscribe: Bool = true) {
init(viewModel: ViewModel) {
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

if subscribe {
Task {
await activate()
}
}
}

@MainActor
func activate() {
guard task == nil else { return }
task = Task { [weak self] in
guard let stateFlow = self?.actual.container.stateFlow else { return }

for await state in stateFlow {
self?.state = state as! State
}
func activate() async {
for await state in actual.container.refCountStateFlow {
self.state = state as! State
}
}

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(), subscribe: false)
super.init(viewModel: koin.tableListVM())
}
}

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

Expand Down
10 changes: 10 additions & 0 deletions WaiterRobot/Entitlements/WaiterRobot.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:my.kellner.team</string>
</array>
</dict>
</plist>
10 changes: 10 additions & 0 deletions WaiterRobot/Entitlements/WaiterRobotLava.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:lava.kellner.team</string>
</array>
</dict>
</plist>
2 changes: 1 addition & 1 deletion WaiterRobot/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ struct MainView: View {
.padding()
}
}
.handleSideEffects(of: viewModel, navigator) { effect in
.withViewModel(viewModel, navigator) { effect in
switch onEnum(of: effect) {
case let .showSnackBar(snackBar):
snackBarMessage = snackBar.message
Expand Down
35 changes: 18 additions & 17 deletions WaiterRobot/Ui/Billing/BillingScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ struct BillingScreen: View {
}

var body: some View {
content()
let billItems = Array(viewModel.state.billItemsArray)

content(billItems: billItems)
.navigationTitle(localize.billing.title(value0: table.number.description, value1: table.groupName))
.navigationBarTitleDisplayMode(.inline)
.customBackNavigation(
Expand All @@ -42,36 +44,35 @@ struct BillingScreen: View {
} message: {
Text(localize.billing.notSent.desc())
}
// TODO: Needs shared modification to be accessible from here
// .refreshable {
// viewModel.actual.loadBill()
// }
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
viewModel.actual.selectAll()
} label: {
Image(systemName: "checkmark")
if !billItems.isEmpty {
Button {
viewModel.actual.selectAll()
} label: {
Image(systemName: "checkmark")
}
}

Button {
viewModel.actual.unselectAll()
} label: {
Image(systemName: "xmark")
if !billItems.isEmpty {
Button {
viewModel.actual.unselectAll()
} label: {
Image(systemName: "xmark")
}
}
}
}

// TODO: make only half screen when ios 15 is dropped
.sheet(isPresented: $showPayDialog) {
PayDialog(viewModel: viewModel)
}
.handleSideEffects(of: viewModel, navigator)
.withViewModel(viewModel, navigator)
}

@ViewBuilder
private func content() -> some View {
let billItems = Array(viewModel.state.billItemsArray)

private func content(billItems: [BillItem]) -> some View {
VStack {
List {
if billItems.isEmpty {
Expand Down
2 changes: 1 addition & 1 deletion WaiterRobot/Ui/Core/DynamicGrid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct DynamicGrid: Layout, Sendable {

public func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
proposal _: ProposedViewSize,
subviews: Subviews,
cache _: inout ()
) {
Expand Down
48 changes: 21 additions & 27 deletions WaiterRobot/Ui/Core/Navigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SwiftUI
import UIPilot

extension UIPilot<Screen> {
@MainActor
func navigate(_ navAction: NavAction) {
koin.logger(tag: "Navigation").d { "Handle navigation: \(navAction.description)" }

Expand Down Expand Up @@ -61,28 +62,18 @@ extension View {
}
}

@MainActor
func handleSideEffects<S, E>(
of viewModel: some ObservableViewModel<S, E, some AbstractViewModel<S, E>>,
_ navigator: UIPilot<Screen>,
handler: ((E) -> Bool)? = nil
) -> some View where S: ViewModelState, E: ViewModelEffect {
handleSideEffects2(of: viewModel.actual, navigator, handler: handler)
}

@MainActor
func handleSideEffects2<E>(
of viewModel: some AbstractViewModel<some ViewModelState, E>,
func handleSideEffects<State, SideEffect>(
of viewModel: some ObservableViewModel<State, SideEffect, some AbstractViewModel<State, SideEffect>>,
_ navigator: UIPilot<Screen>,
handler: ((E) -> Bool)? = nil
) -> some View where E: ViewModelEffect {
handler: ((SideEffect) -> Bool)? = nil
) -> some View where State: ViewModelState, SideEffect: ViewModelEffect {
task {
let logger = koin.logger(tag: "handleSideEffects")
for await sideEffect in viewModel.container.sideEffectFlow {
for await sideEffect in viewModel.actual.container.refCountSideEffectFlow {
logger.d { "Got sideEffect: \(sideEffect)" }
switch onEnum(of: sideEffect as! NavOrViewModelEffect<E>) {
switch onEnum(of: sideEffect as! NavOrViewModelEffect<SideEffect>) {
case let .navEffect(navEffect):
navigator.navigate(navEffect.action)
await navigator.navigate(navEffect.action)
case let .vMEffect(effect):
if handler?(effect.effect) != true {
logger.w { "Side effect \(effect.effect) was not handled." }
Expand All @@ -92,17 +83,20 @@ extension View {
}
}

@MainActor
func observeState<S>(
of viewModel: some AbstractViewModel<S, some ViewModelEffect>,
stateBinding: Binding<S>
) -> some View where S: ViewModelState {
func observeState<State, SideEffect>(
of viewModel: some ObservableViewModel<State, SideEffect, some AbstractViewModel<State, SideEffect>>
) -> some View where State: ViewModelState, SideEffect: ViewModelEffect {
task {
let logger = koin.logger(tag: "ObservableViewModel")
for await state in viewModel.container.stateFlow {
logger.d { "New state: \(state)" }
stateBinding.wrappedValue = state as! S
}
await viewModel.activate()
}
}

func withViewModel<State, SideEffect>(
_ viewModel: some ObservableViewModel<State, SideEffect, some AbstractViewModel<State, SideEffect>>,
_ navigator: UIPilot<Screen>,
handler _: ((SideEffect) -> Bool)? = nil
) -> some View where State: ViewModelState, SideEffect: ViewModelEffect {
handleSideEffects(of: viewModel, navigator)
.observeState(of: viewModel)
}
}
2 changes: 1 addition & 1 deletion WaiterRobot/Ui/Login/LoginScannerScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ struct LoginScannerScreen: View {
Text(localize.dialog.cancel())
}
}
.handleSideEffects(of: viewModel, navigator)
.withViewModel(viewModel, navigator)
}
}

Expand Down
2 changes: 1 addition & 1 deletion WaiterRobot/Ui/Login/LoginScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ struct LoginScreen: View {

Spacer()
}
.handleSideEffects(of: viewModel, navigator)
.withViewModel(viewModel, navigator)
}
}
2 changes: 1 addition & 1 deletion WaiterRobot/Ui/Login/RegisterScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ struct RegisterScreen: View {
}
.padding()
.navigationBarHidden(true)
.handleSideEffects(of: viewModel, navigator)
.withViewModel(viewModel, navigator)
}
}

Expand Down
15 changes: 9 additions & 6 deletions WaiterRobot/Ui/Order/OrderScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ struct OrderScreen: View {

case let .error(error):
Text(error.userMessage)
.foregroundStyle(.red)
.padding(.horizontal)

currentOder(error.data)

case let .success(resource):
if let data = resource.data {
currentOder(data)
}
currentOder(resource.data)
}
}
.navigationTitle(localize.order.title(value0: table.number.description, value1: table.groupName))
Expand All @@ -53,14 +55,15 @@ struct OrderScreen: View {
.sheet(isPresented: $showProductSearch) {
ProductSearch(viewModel: viewModel)
}
.handleSideEffects(of: viewModel, navigator)
.withViewModel(viewModel, navigator)
.animation(.default, value: viewModel.state.currentOrder)
}

@ViewBuilder
private func currentOder(
_ currentOrderArray: KotlinArray<OrderItem>
_ currentOrderArray: KotlinArray<OrderItem>?
) -> some View {
let currentOrder = Array(currentOrderArray)
let currentOrder = currentOrderArray.map { Array($0) } ?? Array()

VStack(spacing: 0) {
if currentOrder.isEmpty {
Expand Down
2 changes: 1 addition & 1 deletion WaiterRobot/Ui/Order/Search/ProductSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct ProductSearch: View {
productsGroupsList(productGroups: productGroups)
}
}
}
}.observeState(of: viewModel)
}

@ViewBuilder
Expand Down
2 changes: 1 addition & 1 deletion WaiterRobot/Ui/Settings/SettingsScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,6 @@ struct SettingsScreen: View {
} message: {
Text(localize.settings.logout.desc(value0: CommonApp.shared.settings.organisationName))
}
.handleSideEffects(of: viewModel, navigator)
.withViewModel(viewModel, navigator)
}
}
37 changes: 18 additions & 19 deletions WaiterRobot/Ui/SwitchEvent/SwitchEventScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ struct SwitchEventScreen: View {

@StateObject private var viewModel = ObservableSwitchEventViewModel()

@SwiftUI.State private var selectedEvent: Event?
@State private var selectedEvent: Event?

var body: some View {
switch viewModel.state.viewState {
case is ViewState.Loading:
ProgressView()
case is ViewState.Idle:
content()
case let error as ViewState.Error:
content()
.alert(isPresented: Binding.constant(true)) {
Alert(
title: Text(error.title),
message: Text(error.message),
dismissButton: .cancel(Text("OK"), action: error.onDismiss)
)
}
default:
fatalError("Unexpected ViewState: \(viewModel.state.viewState.description)")
}
VStack {
switch onEnum(of: viewModel.state.viewState) {
case .loading:
ProgressView()
case .idle:
content()
case let .error(error):
content()
.alert(isPresented: Binding.constant(true)) {
Alert(
title: Text(error.title),
message: Text(error.message),
dismissButton: .cancel(Text("OK"), action: error.onDismiss)
)
}
}
}.withViewModel(viewModel, navigator)
}

private func content() -> some View {
Expand Down Expand Up @@ -68,7 +68,6 @@ struct SwitchEventScreen: View {
viewModel.actual.loadEvents()
}
}
.handleSideEffects(of: viewModel, navigator)
}
}

Expand Down
Loading

0 comments on commit 76d7a8c

Please sign in to comment.