615 lines
26 KiB
Swift
615 lines
26 KiB
Swift
//
|
|
// OrdersViewController.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import CoreLocation
|
|
import SnapKit
|
|
import UIKit
|
|
|
|
/// 订单 Tab 根页面,按角色分流摄影师/景区管理员/店铺管理员。
|
|
final class OrdersViewController: BaseViewController, UITableViewDelegate {
|
|
|
|
private let appStore: AppStore
|
|
private let orderAPI: OrderAPI
|
|
private let orderListViewModel: OrderListViewModel
|
|
private let depositViewModel: DepositOrderListViewModel
|
|
|
|
/// 表格快照持有完整行数据,避免刷新清空 ViewModel 后仍按旧行号取值。
|
|
private struct OrderRow: Hashable {
|
|
/// 同一快照内按位置区分行,兼容接口返回重复订单号。
|
|
let position: Int
|
|
let content: Content
|
|
|
|
/// 摄影师和店铺订单的不可变展示数据。
|
|
enum Content: Equatable {
|
|
case photographer(OrderEntity, source: OrderSourceSelection?, readOnly: Bool, showSource: Bool)
|
|
case deposit(StoreOrderItem)
|
|
}
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
hasher.combine(position)
|
|
switch content {
|
|
case .photographer(let order, _, _, _):
|
|
hasher.combine(0)
|
|
hasher.combine(order.orderNumber)
|
|
case .deposit(let order):
|
|
hasher.combine(1)
|
|
hasher.combine(order.orderNumber)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var dataSource: UITableViewDiffableDataSource<Int, OrderRow>?
|
|
|
|
/// 注入订单数据依赖;默认使用应用共享 API 和独立的页面 ViewModel。
|
|
init(
|
|
orderAPI: OrderAPI? = nil,
|
|
orderListViewModel: OrderListViewModel? = nil,
|
|
depositViewModel: DepositOrderListViewModel? = nil,
|
|
appStore: AppStore = .shared
|
|
) {
|
|
self.appStore = appStore
|
|
self.orderAPI = orderAPI ?? NetworkServices.shared.orderAPI
|
|
self.orderListViewModel = orderListViewModel ?? OrderListViewModel(appStore: appStore)
|
|
self.depositViewModel = depositViewModel ?? DepositOrderListViewModel(appStore: appStore)
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private let filterBar = OrderFilterBarView()
|
|
private let listView = OrderListView()
|
|
|
|
private var hasInitialized = false
|
|
private var isStoreMode: Bool { appStore.session.currentAppRole == .storeAdmin }
|
|
private var pendingOrderSourceNumber: String?
|
|
private weak var orderSourcePicker: OrderSourcePickerViewController?
|
|
private var pendingGiftOrderNumber: String?
|
|
private var isFetchingMultiVerifySpots = false
|
|
private let locationProvider: any LocationProviding = LocationProvider.shared
|
|
|
|
override func setupNavigationBar() {
|
|
title = "订单"
|
|
}
|
|
|
|
override func setupUI() {
|
|
view.addSubview(filterBar)
|
|
view.addSubview(listView)
|
|
listView.tableView.delegate = self
|
|
listView.tableView.register(OrderCardCell.self, forCellReuseIdentifier: OrderCardCell.reuseIdentifier)
|
|
listView.tableView.register(DepositOrderCardCell.self, forCellReuseIdentifier: DepositOrderCardCell.reuseIdentifier)
|
|
configureDataSource()
|
|
}
|
|
|
|
override func setupConstraints() {
|
|
filterBar.snp.makeConstraints { make in
|
|
make.top.equalTo(view.safeAreaLayoutGuide)
|
|
make.leading.trailing.equalToSuperview()
|
|
}
|
|
listView.snp.makeConstraints { make in
|
|
make.top.equalTo(filterBar.snp.bottom)
|
|
make.leading.trailing.bottom.equalToSuperview()
|
|
}
|
|
}
|
|
|
|
override func bindActions() {
|
|
orderListViewModel.onStateChange = { [weak self] in
|
|
Task { @MainActor in
|
|
self?.applyViewModel()
|
|
self?.orderSourcePicker?.reloadFromViewModel()
|
|
}
|
|
}
|
|
orderListViewModel.onShowMessage = { [weak self] message in
|
|
Task { @MainActor in self?.showToast(message) }
|
|
}
|
|
orderListViewModel.onNavigateBindAcquirer = { [weak self] saleUserId in
|
|
Task { @MainActor in self?.presentBindAcquirer(saleUserId: saleUserId) }
|
|
}
|
|
depositViewModel.onStateChange = { [weak self] in
|
|
Task { @MainActor in self?.applyViewModel() }
|
|
}
|
|
depositViewModel.onShowMessage = { [weak self] message in
|
|
Task { @MainActor in self?.showToast(message) }
|
|
}
|
|
|
|
filterBar.onSearchChanged = { [weak self] text in
|
|
guard let self else { return }
|
|
if self.isStoreMode {
|
|
self.depositViewModel.onSearchTextChange(text, api: self.orderAPI)
|
|
} else {
|
|
self.orderListViewModel.onSearchTextChange(text)
|
|
}
|
|
}
|
|
filterBar.onSearchSubmitted = { [weak self] in
|
|
guard let self else { return }
|
|
Task {
|
|
if self.isStoreMode {
|
|
await self.depositViewModel.refreshOrderList(api: self.orderAPI)
|
|
} else {
|
|
await self.orderListViewModel.refreshOrderList(api: self.orderAPI)
|
|
}
|
|
}
|
|
}
|
|
filterBar.onFilterSelected = { [weak self] tab, rTab in
|
|
guard let self else { return }
|
|
Task {
|
|
if self.isStoreMode {
|
|
self.depositViewModel.onFilterChange(tab: tab)
|
|
await self.depositViewModel.refreshOrderList(api: self.orderAPI)
|
|
} else {
|
|
self.orderListViewModel.onFilterChange(orderStatus: tab, isRefined: rTab)
|
|
await self.orderListViewModel.refreshOrderList(api: self.orderAPI)
|
|
}
|
|
}
|
|
}
|
|
filterBar.onDateRangeTap = { [weak self] in
|
|
self?.presentDateRangeSheet()
|
|
}
|
|
listView.onRefresh = { [weak self] in
|
|
guard let self else { return }
|
|
Task {
|
|
if self.isStoreMode {
|
|
await self.depositViewModel.refreshOrderList(api: self.orderAPI)
|
|
} else {
|
|
await self.orderListViewModel.refreshOrderList(api: self.orderAPI)
|
|
}
|
|
}
|
|
}
|
|
listView.onLoadMore = { [weak self] index in
|
|
guard let self else { return }
|
|
Task {
|
|
if self.isStoreMode {
|
|
guard self.depositViewModel.shouldLoadMore(at: index) else { return }
|
|
await self.depositViewModel.loadMore(api: self.orderAPI)
|
|
} else {
|
|
guard self.orderListViewModel.shouldLoadMore(at: index) else { return }
|
|
await self.orderListViewModel.loadMore(api: self.orderAPI)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
guard !hasInitialized else { return }
|
|
hasInitialized = true
|
|
Task { await initializeList() }
|
|
}
|
|
|
|
/// 从订单通知进入时恢复默认查询,并刷新已有或首次打开的列表。
|
|
func openFromOrderNotification() {
|
|
loadViewIfNeeded()
|
|
hasInitialized = true
|
|
if isStoreMode {
|
|
depositViewModel.resetFiltersForOrderNotification()
|
|
} else {
|
|
orderListViewModel.resetFiltersForOrderNotification()
|
|
}
|
|
applyViewModel()
|
|
Task { @MainActor [weak self] in
|
|
await self?.initializeList()
|
|
}
|
|
}
|
|
|
|
private func initializeList() async {
|
|
if isStoreMode {
|
|
await depositViewModel.refreshOrderList(api: orderAPI)
|
|
} else {
|
|
await orderListViewModel.refreshOrderList(api: orderAPI)
|
|
}
|
|
}
|
|
|
|
private func applyViewModel() {
|
|
applyOrderSnapshot()
|
|
if isStoreMode {
|
|
filterBar.configure(
|
|
isStoreMode: true,
|
|
filterLabel: depositViewModel.filterLabel,
|
|
dateRangeText: dateRangeText(start: depositViewModel.startTime, end: depositViewModel.endTime),
|
|
searchText: depositViewModel.searchText
|
|
)
|
|
listView.apply(
|
|
isRefreshing: depositViewModel.isRefreshing,
|
|
canLoadMore: depositViewModel.canLoadMore,
|
|
isEmpty: depositViewModel.orderList.isEmpty,
|
|
isLoading: depositViewModel.isRefreshing && depositViewModel.orderList.isEmpty,
|
|
isLoadingMore: depositViewModel.isLoadingMore
|
|
)
|
|
if let response = depositViewModel.writeOffResponse {
|
|
depositViewModel.dismissWriteOffDialog()
|
|
WriteOffResultAlertController.present(from: self, response: response)
|
|
}
|
|
} else {
|
|
filterBar.configure(
|
|
isStoreMode: false,
|
|
filterLabel: orderListViewModel.filterLabel,
|
|
dateRangeText: dateRangeText(start: orderListViewModel.startTime, end: orderListViewModel.endTime),
|
|
searchText: orderListViewModel.searchText
|
|
)
|
|
listView.apply(
|
|
isRefreshing: orderListViewModel.isRefreshing,
|
|
canLoadMore: orderListViewModel.canLoadMore,
|
|
isEmpty: orderListViewModel.orderList.isEmpty,
|
|
isLoading: orderListViewModel.isRefreshing && orderListViewModel.orderList.isEmpty,
|
|
isLoadingMore: orderListViewModel.isLoadingMore
|
|
)
|
|
if let response = orderListViewModel.signInResponse {
|
|
orderListViewModel.clearSignInResponse()
|
|
OrderSignInAlertController.present(from: self, response: response)
|
|
}
|
|
if let response = orderListViewModel.writeOffResponse {
|
|
orderListViewModel.clearWriteOffResponse()
|
|
WriteOffResultAlertController.present(from: self, response: response)
|
|
}
|
|
if orderListViewModel.multiVerifyPendingData != nil,
|
|
orderListViewModel.multiVerifyScenicSpotList.isEmpty,
|
|
!isFetchingMultiVerifySpots {
|
|
Task { await self.fetchMultiVerifySpotsIfNeeded() }
|
|
}
|
|
if !orderListViewModel.multiVerifyScenicSpotList.isEmpty {
|
|
presentMultiVerifySheet()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func dateRangeText(start: Date?, end: Date?) -> String {
|
|
let formatter = DateFormatter()
|
|
formatter.dateFormat = "yyyy-MM-dd"
|
|
if let start, let end {
|
|
return "\(formatter.string(from: start))-\(formatter.string(from: end))"
|
|
}
|
|
return "时间筛选"
|
|
}
|
|
|
|
private func presentDateRangeSheet() {
|
|
let start = isStoreMode ? depositViewModel.startTime : orderListViewModel.startTime
|
|
let end = isStoreMode ? depositViewModel.endTime : orderListViewModel.endTime
|
|
let controller = DateRangePickerViewController(rangeStart: start, rangeEnd: end)
|
|
controller.onShortcutSelected = { [weak self, weak controller] shortcut in
|
|
guard let self else { return }
|
|
let range = self.resolveShortcutRange(shortcut)
|
|
Task {
|
|
if self.isStoreMode {
|
|
self.depositViewModel.onDateRangeSelected(start: range.start, end: range.end)
|
|
await self.depositViewModel.refreshOrderList(api: self.orderAPI)
|
|
} else {
|
|
self.orderListViewModel.onDateRangeSelected(start: range.start, end: range.end)
|
|
await self.orderListViewModel.refreshOrderList(api: self.orderAPI)
|
|
}
|
|
controller?.dismiss(animated: true)
|
|
}
|
|
}
|
|
controller.onRangeSelected = { [weak self, weak controller] start, end in
|
|
guard let self else { return }
|
|
Task {
|
|
if self.isStoreMode {
|
|
self.depositViewModel.onDateRangeSelected(start: start, end: end)
|
|
await self.depositViewModel.refreshOrderList(api: self.orderAPI)
|
|
} else {
|
|
self.orderListViewModel.onDateRangeSelected(start: start, end: end)
|
|
await self.orderListViewModel.refreshOrderList(api: self.orderAPI)
|
|
}
|
|
controller?.dismiss(animated: true)
|
|
}
|
|
}
|
|
if let sheet = controller.sheetPresentationController {
|
|
sheet.detents = [.medium(), .large()]
|
|
}
|
|
present(controller, animated: true)
|
|
}
|
|
|
|
private func resolveShortcutRange(_ shortcut: String) -> (start: Date?, end: Date?) {
|
|
let calendar = Calendar.current
|
|
let today = calendar.startOfDay(for: Date())
|
|
switch shortcut {
|
|
case StatisticsRangeShortcut.today.rawValue:
|
|
return (today, today)
|
|
case StatisticsRangeShortcut.yesterday.rawValue:
|
|
let yesterday = calendar.date(byAdding: .day, value: -1, to: today)!
|
|
return (yesterday, yesterday)
|
|
case StatisticsRangeShortcut.last7Days.rawValue:
|
|
let start = calendar.date(byAdding: .day, value: -6, to: today)!
|
|
return (start, today)
|
|
default:
|
|
return (nil, nil)
|
|
}
|
|
}
|
|
|
|
private func presentScanner(onResult: @escaping (String) -> Void) {
|
|
let scanner = QRCodeScannerViewController()
|
|
scanner.onScanResult = onResult
|
|
let nav = UINavigationController(rootViewController: scanner)
|
|
nav.modalPresentationStyle = .fullScreen
|
|
present(nav, animated: true)
|
|
}
|
|
|
|
private func presentBindAcquirer(saleUserId: Int) {
|
|
dismiss(animated: true) { [weak self] in
|
|
let controller = BindAcquirerViewController(saleUserId: saleUserId)
|
|
self?.navigationController?.pushViewController(controller, animated: true)
|
|
}
|
|
}
|
|
|
|
private func fetchMultiVerifySpotsIfNeeded() async {
|
|
isFetchingMultiVerifySpots = true
|
|
defer { isFetchingMultiVerifySpots = false }
|
|
do {
|
|
let coordinate = try await locationProvider.requestCoordinate()
|
|
await orderListViewModel.fetchScenicSpotsAndShowSheet(
|
|
lat: coordinate.latitude,
|
|
lng: coordinate.longitude,
|
|
api: orderAPI
|
|
)
|
|
applyViewModel()
|
|
} catch {
|
|
showToast(error.localizedDescription)
|
|
orderListViewModel.clearMultiVerifyPending()
|
|
}
|
|
}
|
|
|
|
private func presentMultiVerifySheet() {
|
|
let spots = orderListViewModel.multiVerifyScenicSpotList
|
|
guard !spots.isEmpty else { return }
|
|
let controller = MultiVerifyScenicSpotViewController(
|
|
spots: spots,
|
|
selected: orderListViewModel.selectedMultiVerifySpot
|
|
)
|
|
controller.onConfirm = { [weak self] spot in
|
|
guard let self else { return }
|
|
self.orderListViewModel.onMultiVerifySpotSelected(spot)
|
|
Task { await self.orderListViewModel.onMultiVerifyConfirm(api: self.orderAPI) }
|
|
}
|
|
controller.onCancel = { [weak self] in
|
|
self?.orderListViewModel.clearMultiVerifySheet()
|
|
}
|
|
let nav = UINavigationController(rootViewController: controller)
|
|
if let sheet = nav.sheetPresentationController {
|
|
sheet.detents = [.medium(), .large()]
|
|
}
|
|
present(nav, animated: true)
|
|
orderListViewModel.clearMultiVerifySheet()
|
|
}
|
|
|
|
// MARK: - UITableView
|
|
|
|
private func configureDataSource() {
|
|
dataSource = UITableViewDiffableDataSource<Int, OrderRow>(tableView: listView.tableView) { [weak self] tableView, _, row in
|
|
guard let self else { return nil }
|
|
switch row.content {
|
|
case .deposit(let item):
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: DepositOrderCardCell.reuseIdentifier) as! DepositOrderCardCell
|
|
cell.configure(item: item, actions: self.makeDepositActions(for: item))
|
|
return cell
|
|
case .photographer(let order, let source, let readOnly, let showSource):
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: OrderCardCell.reuseIdentifier) as! OrderCardCell
|
|
cell.configure(
|
|
order: order,
|
|
hideWriteActions: readOnly,
|
|
actions: self.makeOrderActions(for: order),
|
|
showOrderSource: showSource,
|
|
sourceSelection: source
|
|
)
|
|
return cell
|
|
}
|
|
}
|
|
}
|
|
|
|
private func applyOrderSnapshot() {
|
|
let rows: [OrderRow]
|
|
if isStoreMode {
|
|
rows = depositViewModel.orderList.enumerated().map {
|
|
OrderRow(position: $0.offset, content: .deposit($0.element))
|
|
}
|
|
} else {
|
|
rows = orderListViewModel.orderList.enumerated().map { index, order in
|
|
OrderRow(position: index, content: .photographer(
|
|
order,
|
|
source: orderListViewModel.orderSourceSelection(for: order),
|
|
readOnly: orderListViewModel.isScenicAdminReadOnly,
|
|
showSource: orderListViewModel.shouldShowOrderSource(for: order)
|
|
))
|
|
}
|
|
}
|
|
var snapshot = NSDiffableDataSourceSnapshot<Int, OrderRow>()
|
|
snapshot.appendSections([0])
|
|
snapshot.appendItems(rows)
|
|
dataSource?.apply(snapshot, animatingDifferences: true)
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
|
guard let row = dataSource?.itemIdentifier(for: indexPath) else { return }
|
|
let shouldLoad: Bool
|
|
switch row.content {
|
|
case .deposit:
|
|
shouldLoad = isStoreMode && depositViewModel.shouldLoadMore(at: row.position)
|
|
case .photographer:
|
|
shouldLoad = !isStoreMode && orderListViewModel.shouldLoadMore(at: row.position)
|
|
}
|
|
listView.notifyLoadMoreIfNeeded(at: row.position, shouldLoad: shouldLoad)
|
|
}
|
|
|
|
private func makeOrderActions(for order: OrderEntity) -> OrderCardActions {
|
|
var actions = OrderCardActions()
|
|
actions.onRefund = { [weak self] in
|
|
guard let self else { return }
|
|
OrderRefundAlertController.present(from: self, order: order) { request in
|
|
Task { await self.orderListViewModel.refundOrder(request, api: self.orderAPI) }
|
|
}
|
|
}
|
|
actions.onVerify = { [weak self] in
|
|
self?.presentScanner { result in
|
|
Task { await self?.orderListViewModel.processScanResult(result, api: self?.orderAPI ?? NetworkServices.shared.orderAPI) }
|
|
}
|
|
}
|
|
actions.onUploadTask = { [weak self] in
|
|
self?.showToast("上传任务待接入")
|
|
}
|
|
actions.onCancel = { [weak self] in
|
|
guard let self else { return }
|
|
Task { await self.orderListViewModel.cancelOrder(orderNumber: order.orderNumber, api: self.orderAPI) }
|
|
}
|
|
actions.onCallPhone = {
|
|
guard let url = URL(string: "tel://\(order.phone)") else { return }
|
|
UIApplication.shared.open(url)
|
|
}
|
|
actions.onRefinedChanged = { [weak self] value in
|
|
guard let self else { return }
|
|
Task { await self.orderListViewModel.updateRefined(orderNumber: order.orderNumber, isRefined: value, api: self.orderAPI) }
|
|
}
|
|
actions.onHistoricalPhoto = { [weak self] in
|
|
let controller = HistoricalShootingInfoViewController(orderNumber: order.orderNumber)
|
|
self?.navigationController?.pushViewController(controller, animated: true)
|
|
}
|
|
actions.onOrderSourceTap = { [weak self] in
|
|
self?.presentOrderSourcePicker(for: order)
|
|
}
|
|
actions.onOrderSourceImageTap = { [weak self] images, index in
|
|
self?.presentImagePreview(imageURLs: images, startIndex: index)
|
|
}
|
|
if let travel = order.photoTravel, travel.canGiftRetouch {
|
|
actions.onSendGift = { [weak self] in
|
|
guard let self else { return }
|
|
self.pendingGiftOrderNumber = order.orderNumber
|
|
SendGiftAlertController.present(
|
|
from: self,
|
|
photoNum: travel.retouchGiftPhotoNum,
|
|
videoNum: travel.retouchGiftVideoNum
|
|
) { photo, video in
|
|
Task {
|
|
await self.orderListViewModel.sendGift(
|
|
orderNumber: order.orderNumber,
|
|
photoNum: photo,
|
|
videoNum: video,
|
|
api: self.orderAPI
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return actions
|
|
}
|
|
|
|
private func makeDepositActions(for item: StoreOrderItem) -> DepositOrderCardActions {
|
|
var actions = DepositOrderCardActions()
|
|
actions.onRefund = { [weak self] in
|
|
guard let self else { return }
|
|
DepositOrderRefundAlertController.present(
|
|
from: self,
|
|
orderNumber: item.orderNumber,
|
|
amount: item.actualPayAmount,
|
|
refundType: 1
|
|
) { refundType, amount, reason in
|
|
Task {
|
|
await self.depositViewModel.requestRefund(
|
|
orderNumber: item.orderNumber,
|
|
refundType: refundType,
|
|
refundAmount: amount,
|
|
refundReason: reason,
|
|
api: self.orderAPI
|
|
)
|
|
}
|
|
}
|
|
}
|
|
actions.onPartialRefund = { [weak self] in
|
|
guard let self else { return }
|
|
DepositOrderRefundAlertController.present(
|
|
from: self,
|
|
orderNumber: item.orderNumber,
|
|
amount: item.actualPayAmount,
|
|
refundType: 3
|
|
) { refundType, amount, reason in
|
|
Task {
|
|
await self.depositViewModel.requestRefund(
|
|
orderNumber: item.orderNumber,
|
|
refundType: refundType,
|
|
refundAmount: amount,
|
|
refundReason: reason,
|
|
api: self.orderAPI
|
|
)
|
|
}
|
|
}
|
|
}
|
|
actions.onVerify = { [weak self] in
|
|
self?.presentScanner { result in
|
|
Task { await self?.depositViewModel.processScanResult(result, api: self?.orderAPI ?? NetworkServices.shared.orderAPI) }
|
|
}
|
|
}
|
|
actions.onCallPhone = {
|
|
guard let url = URL(string: "tel://\(item.phone)") else { return }
|
|
UIApplication.shared.open(url)
|
|
}
|
|
actions.onTap = { [weak self] in
|
|
guard let self else { return }
|
|
guard self.depositViewModel.canOpenDetail(for: item) else {
|
|
self.showToast("该订单类型暂无详情")
|
|
return
|
|
}
|
|
let controller = DepositOrderDetailViewController(orderNumber: item.orderNumber)
|
|
self.navigationController?.pushViewController(controller, animated: true)
|
|
}
|
|
return actions
|
|
}
|
|
|
|
private func presentOrderSourcePicker(for order: OrderEntity) {
|
|
pendingOrderSourceNumber = order.orderNumber
|
|
Task {
|
|
await orderListViewModel.onOrderSourceSheetOpen(api: orderAPI)
|
|
await MainActor.run { [weak self] in
|
|
guard let self else { return }
|
|
let controller = OrderSourcePickerViewController(
|
|
orderNumber: order.orderNumber,
|
|
viewModel: self.orderListViewModel,
|
|
referralOrder: order.referralOrder,
|
|
loadLeads: { [weak self] person in
|
|
guard let self else { return }
|
|
await self.orderListViewModel.loadOrderSourceLeads(person: person, api: self.orderAPI)
|
|
},
|
|
onLeadBound: { [weak self] selection in
|
|
guard let self, let orderNumber = self.pendingOrderSourceNumber else { return }
|
|
self.dismiss(animated: true) {
|
|
self.orderSourcePicker = nil
|
|
}
|
|
Task {
|
|
await self.orderListViewModel.bindOrderSource(
|
|
orderNumber: orderNumber,
|
|
selection: selection,
|
|
api: self.orderAPI
|
|
)
|
|
}
|
|
},
|
|
onCallPhone: { [weak self] phone in
|
|
self?.callPhone(phone)
|
|
}
|
|
)
|
|
self.orderSourcePicker = controller
|
|
let nav = UINavigationController(rootViewController: controller)
|
|
if let sheet = nav.sheetPresentationController {
|
|
sheet.prefersGrabberVisible = true
|
|
if #available(iOS 16.0, *) {
|
|
sheet.detents = [
|
|
.custom(identifier: .init("orderSource")) { context in
|
|
context.maximumDetentValue * 0.82
|
|
},
|
|
.large(),
|
|
]
|
|
sheet.selectedDetentIdentifier = .init("orderSource")
|
|
} else {
|
|
sheet.detents = [.large()]
|
|
}
|
|
}
|
|
self.present(nav, animated: true)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func callPhone(_ phone: String) {
|
|
let trimmed = phone.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty, let url = URL(string: "tel://\(trimmed)") else { return }
|
|
UIApplication.shared.open(url)
|
|
}
|
|
}
|