Skip to content

Commit

Permalink
Merge pull request #28 from DatepollSystems/refactor/table-group
Browse files Browse the repository at this point in the history
Refactor/table group
  • Loading branch information
kaulex99 committed Jan 4, 2024
2 parents 612233a + 99c7f56 commit dc1e0f1
Show file tree
Hide file tree
Showing 16 changed files with 294 additions and 176 deletions.
60 changes: 0 additions & 60 deletions WaiterRobot/Core/Mvi/KotlinFlowPublisher.swift

This file was deleted.

23 changes: 17 additions & 6 deletions WaiterRobot/Core/Mvi/ObservableViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,40 @@
/// - https://proandroiddev.com/kotlin-multiplatform-mobile-sharing-the-ui-state-management-a67bd9a49882
/// - https://github.com/orbit-mvi/orbit-swift-gradle-plugin/blob/main/src/main/resources/stateObject.swift.mustache

import Combine
import Foundation
import shared

@MainActor
class ObservableViewModel<S: ViewModelState, E: ViewModelEffect, VM: AbstractViewModel<S, E>>: ObservableObject {
@Published public private(set) var state: S
public private(set) var sideEffect: AnyPublisher<NavOrViewModelEffect<E>, Never>

public let actual: VM

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

init(vm: VM) {
actual = vm
// 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! S
sideEffect = actual.container.sideEffectFlow.asPublisher() as AnyPublisher<NavOrViewModelEffect<E>, Never>

(actual.container.stateFlow.asPublisher() as AnyPublisher<S, Never>)
.receive(on: RunLoop.main)
.assign(to: &$state)
activate()
}

deinit {
actual.onCleared()
task?.cancel()
}

@MainActor
private func activate() {
guard task == nil else {
return
}
task = Task {
let logger = koin.logger(tag: VM.description())
for await state in actual.container.stateFlow {
self.state = state as! S
}
}
}
}
6 changes: 3 additions & 3 deletions WaiterRobot/Ui/Billing/BillingScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ struct BillingScreen: View {
}
.navigationTitle(localize.billing.title(value0: table.number.description, value1: table.groupName))
.navigationBarTitleDisplayMode(.inline)
.customBackNavigation(title: localize.dialog.cancel(), icon: nil, action: vm.actual.goBack) // TODO:
.customBackNavigation(title: localize.dialog.cancel(), icon: nil, action: { vm.actual.goBack() }) // TODO:
.confirmationDialog(localize.billing.notSent.title(), isPresented: Binding.constant(vm.state.showConfirmationDialog), titleVisibility: .visible) {
Button(localize.dialog.closeAnyway(), role: .destructive, action: vm.actual.abortBill)
Button(localize.dialog.cancel(), role: .cancel, action: vm.actual.keepBill)
Button(localize.dialog.closeAnyway(), role: .destructive, action: { vm.actual.abortBill() })
Button(localize.dialog.cancel(), role: .cancel, action: { vm.actual.keepBill() })
} message: {
Text(localize.billing.notSent.desc())
}
Expand Down
47 changes: 35 additions & 12 deletions WaiterRobot/Ui/Core/Navigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,45 @@ extension View {

@MainActor
func handleSideEffects<S, E, VM, OVM>(
of vm: OVM, _ navigator: UIPilot<Screen>,
of vm: OVM,
_ navigator: UIPilot<Screen>,
handler: ((E) -> Bool)? = nil
) -> some View where S: ViewModelState, E: ViewModelEffect, VM: AbstractViewModel<S, E>, OVM: ObservableViewModel<S, E, VM> {
onReceive(vm.sideEffect) { effect in
debugPrint("Got Sideeffect \(effect)")

switch effect {
case let navEffect as NavOrViewModelEffectNavEffect<E>:
navigator.navigate(navEffect.action)
handleSideEffects2(of: vm.actual, navigator, handler: handler)
}

case let sideEffect as NavOrViewModelEffectVMEffect<E>:
if handler?(sideEffect.effect) != true {
koin.logger(tag: "handleSideEffects").w { "Side effect \(sideEffect.effect) was not handled." }
@MainActor
func handleSideEffects2<S, E, VM>(
of vm: VM,
_ navigator: UIPilot<Screen>,
handler: ((E) -> Bool)? = nil
) -> some View where S: ViewModelState, E: ViewModelEffect, VM: AbstractViewModel<S, E> {
task {
let logger = koin.logger(tag: "handleSideEffects")
for await sideEffect in vm.container.sideEffectFlow {
logger.d { "Got sideEffect: \(sideEffect)" }
switch onEnum(of: sideEffect as! NavOrViewModelEffect<E>) {
case let .navEffect(navEffect):
navigator.navigate(navEffect.action)
case let .vMEffect(effect):
if handler?(effect.effect) != true {
logger.w { "Side effect \(effect.effect) was not handled." }
}
}
default:
koin.logger(tag: "handleSideEffects").w { "Unhandled effect type \(effect)." }
}
}
}

@MainActor
func observeState<S, E, VM>(
of vm: VM,
stateBinding: Binding<S>
) -> some View where S: ViewModelState, E: ViewModelEffect, VM: AbstractViewModel<S, E> {
task {
let logger = koin.logger(tag: "ObservableViewModel")
for await state in vm.container.stateFlow {
logger.d { "New state: \(state)" }
stateBinding.wrappedValue = state as! S
}
}
}
Expand Down
53 changes: 53 additions & 0 deletions WaiterRobot/Ui/Core/PullToRefresh.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// PullToRefresh.swift
// WaiterRobot
//
// Created by Fabian Schedler on 20.11.23.
//

import SwiftUI

struct PullToRefresh: View {
let coordinateSpaceName: String
let isRefreshing: Bool
let onRefresh: () -> Void

@State var needRefresh: Bool = false

var body: some View {
if isRefreshing {
EmptyView()
} else {
GeometryReader { geo in
if geo.frame(in: .named(coordinateSpaceName)).midY > 50 {
Spacer()
.onAppear {
needRefresh = true
print("NeedRefresh")
}
} else if geo.frame(in: .named(coordinateSpaceName)).maxY < 10 {
Spacer()
.onAppear {
if needRefresh {
onRefresh()
needRefresh = false
print("RefreshStarted")
}
}
}

let pullProgress = max(0, min(50, geo.frame(in: .named(coordinateSpaceName)).midY)) / 50

HStack {
Spacer()
ProgressView()
.opacity(pullProgress)
.controlSize(.large)
.tint(.gray)
.padding(.bottom, 50)
Spacer()
}
}.padding(.top, -50)
}
}
}
83 changes: 83 additions & 0 deletions WaiterRobot/Ui/Core/RefreshableScrollView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// RefreshableScrollView.swift
// WaiterRobot
//
// Created by Fabian Schedler on 20.11.23.
//

import shared
import SwiftUI

struct RefreshableScrollView<Content: View>: View {
private let isRefreshing: Bool
private let onRefresh: () -> Void
private let content: () -> Content

init(
isRefreshing: Bool,
onRefresh: @escaping () -> Void,
@ViewBuilder content: @escaping () -> Content
) {
self.isRefreshing = isRefreshing
self.onRefresh = onRefresh
self.content = content
}

init(
resource: Resource<some Any>,
onRefresh: @escaping () -> Void,
@ViewBuilder content: @escaping () -> Content
) {
self.init(for: onEnum(of: resource), onRefresh: onRefresh, content: content)
}

init(
for resource: Skie.org_datepollsystems_waiterrobot__shared.Resource.__Sealed<some Any>,
onRefresh: @escaping () -> Void,
@ViewBuilder content: @escaping () -> Content
) {
if case .loading = resource {
self.init(isRefreshing: true, onRefresh: onRefresh, content: content)
} else {
self.init(isRefreshing: false, onRefresh: onRefresh, content: content)
}
}

var body: some View {
ScrollView {
PullToRefresh(
coordinateSpaceName: "PullToRefresh",
isRefreshing: isRefreshing,
onRefresh: onRefresh
)
if isRefreshing {
ProgressView()
.controlSize(.large)
.tint(.gray)
}
content()
}.coordinateSpace(name: "PullToRefresh")
}
}

struct Refreshing_Preview: PreviewProvider {
static var previews: some View {
RefreshableScrollView(
isRefreshing: true,
onRefresh: {}
) {
Text("Refreshing")
}
}
}

struct PullToRefresh_Preview: PreviewProvider {
static var previews: some View {
RefreshableScrollView(
isRefreshing: false,
onRefresh: {}
) {
Text("Pull to refresh")
}
}
}
8 changes: 4 additions & 4 deletions WaiterRobot/Ui/Login/RegisterScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ struct RegisterScreen: View {

ScreenContainer(vm.state) {
VStack {
Text(localize.register_.name.desc())
Text(localize.register.name.desc())
.font(.body)

TextField(localize.register_.name.title(), text: $name)
TextField(localize.register.name.title(), text: $name)
.font(.body)
.fixedSize()
.padding()
Expand All @@ -35,12 +35,12 @@ struct RegisterScreen: View {
Button {
vm.actual.onRegister(name: name, createToken: createToken)
} label: {
Text(localize.register_.login())
Text(localize.register.login())
}
}
.padding()

Label(localize.register_.alreadyRegisteredInfo(), systemImage: "info.circle.fill")
Label(localize.register.alreadyRegisteredInfo(), systemImage: "info.circle.fill")
}
.padding()
}
Expand Down
6 changes: 3 additions & 3 deletions WaiterRobot/Ui/Order/OrderScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ struct OrderScreen: View {
.disabled(vm.state.currentOrder.isEmpty || vm.state.viewState != ViewState.Idle.shared)
}
}
.customBackNavigation(title: localize.dialog.cancel(), icon: "chevron.backward", action: vm.actual.goBack)
.customBackNavigation(title: localize.dialog.cancel(), icon: "chevron.backward", action: { vm.actual.goBack() })
.confirmationDialog(localize.order.notSent.title(), isPresented: Binding.constant(vm.state.showConfirmationDialog), titleVisibility: .visible) {
Button(localize.dialog.closeAnyway(), role: .destructive, action: vm.actual.abortOrder)
Button(localize.order.keepOrder(), role: .cancel, action: vm.actual.keepOrder)
Button(localize.dialog.closeAnyway(), role: .destructive, action: { vm.actual.abortOrder() })
Button(localize.order.keepOrder(), role: .cancel, action: { vm.actual.keepOrder() })
} message: {
Text(localize.order.notSent.desc())
}
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 @@ -56,7 +56,7 @@ struct ProductSearch: View {
}
.tabViewStyle(.page(indexDisplayMode: .never))
.searchable(text: $search, placement: .navigationBarDrawer(displayMode: .always))
.onChange(of: search, perform: vm.actual.filterProducts)
.onChange(of: search, perform: { vm.actual.filterProducts(filter: $0) })
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(localize.dialog.cancel()) {
Expand Down
10 changes: 7 additions & 3 deletions WaiterRobot/Ui/Settings/SettingsScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ struct SettingsScreen: View {
icon: "arrow.triangle.2.circlepath",
title: localize.settings.refresh.title(),
subtitle: localize.settings.refresh.desc(),
action: vm.actual.refreshAll
action: {
vm.actual.refreshAll()
}
)

SettingsItem(
icon: "person.3",
title: localize.switchEvent.title(),
subtitle: CommonApp.shared.settings.eventName,
action: vm.actual.switchEvent
action: {
vm.actual.switchEvent()
}
)
}

Expand Down Expand Up @@ -66,7 +70,7 @@ struct SettingsScreen: View {
}
}
.confirmationDialog(localize.settings.logout.title(value0: CommonApp.shared.settings.organisationName), isPresented: $showConfirmLogout, titleVisibility: .visible) {
Button(localize.settings.logout.action(), role: .destructive, action: vm.actual.logout)
Button(localize.settings.logout.action(), role: .destructive, action: { vm.actual.logout() })
Button(localize.settings.keepLoggedIn(), role: .cancel, action: { showConfirmLogout = false })
} message: {
Text(localize.settings.logout.desc(value0: CommonApp.shared.settings.organisationName))
Expand Down
Loading

0 comments on commit dc1e0f1

Please sign in to comment.