diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index e4dfe68..239bee7 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ 7A4955822D58CCF900912E66 /* HistoryFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4955812D58CCF900912E66 /* HistoryFilter.swift */; }; 7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */; }; 7A54BFD32D43B95E00176D6D /* DbUpdatePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */; }; + 7A589E0F2D6B6E8E00EF3FBE /* NumberEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */; }; 7A5911EE2D63226F00EC51BA /* SearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5911ED2D63226F00EC51BA /* SearchScreen.swift */; }; 7A5911F02D63266B00EC51BA /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5911EF2D63266B00EC51BA /* SearchViewModel.swift */; }; 7A5911F22D63268400EC51BA /* SearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5911F12D63268400EC51BA /* SearchCoordinator.swift */; }; @@ -150,6 +151,8 @@ 7AABBE3B2CF9F85600346588 /* Binding+Map.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABBE3A2CF9F85600346588 /* Binding+Map.swift */; }; 7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; }; 7AB0EF812C5CC0FE00291EE6 /* SwiftLocationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */; }; + 7AB490292D6B1217002F39C6 /* ACKeyboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */; }; + 7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */; }; 7AB4E42C2D397D8E0006D052 /* VehicleCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4E42B2D397D8E0006D052 /* VehicleCellView.swift */; }; 7AB4E4332D3C21C00006D052 /* FileManagerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4E4322D3C21C00006D052 /* FileManagerExt.swift */; }; 7AB4E4382D3D0C5C0006D052 /* VehiclesArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4E4372D3D0C5C0006D052 /* VehiclesArchive.swift */; }; @@ -341,6 +344,7 @@ 7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCell.swift; sourceTree = ""; }; 7A530B7F2401803A00CBFE6E /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; 7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DbUpdatePolicy.swift; sourceTree = ""; }; + 7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberEditView.swift; sourceTree = ""; }; 7A5911ED2D63226F00EC51BA /* SearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchScreen.swift; sourceTree = ""; }; 7A5911EF2D63266B00EC51BA /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; 7A5911F12D63268400EC51BA /* SearchCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCoordinator.swift; sourceTree = ""; }; @@ -432,6 +436,8 @@ 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxSectionedDataSource.swift; sourceTree = ""; }; 7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = ""; }; 7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLocationProtocol.swift; sourceTree = ""; }; + 7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACKeyboardView.swift; sourceTree = ""; }; + 7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACKeyboardButton.swift; sourceTree = ""; }; 7AB4E42B2D397D8E0006D052 /* VehicleCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCellView.swift; sourceTree = ""; }; 7AB4E4322D3C21C00006D052 /* FileManagerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExt.swift; sourceTree = ""; }; 7AB4E4372D3D0C5C0006D052 /* VehiclesArchive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehiclesArchive.swift; sourceTree = ""; }; @@ -1143,6 +1149,9 @@ 7AB4E42B2D397D8E0006D052 /* VehicleCellView.swift */, 7A5912042D648A6000EC51BA /* AutoCancellable.swift */, 7AC8B2752D6A01C700190706 /* UISearchTextField+Dumb.swift */, + 7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */, + 7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */, + 7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */, ); path = SwiftUI; sourceTree = ""; @@ -1441,6 +1450,7 @@ 7AB9FE2A2D08CF35005DE374 /* EventsScreenMode.swift in Sources */, 7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */, 7A10227B2C557EE900B84627 /* LocationPickerCoordinator.swift in Sources */, + 7AB490292D6B1217002F39C6 /* ACKeyboardView.swift in Sources */, 7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */, 7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */, 7A6DD903242BF4A5009DE740 /* PlateView.swift in Sources */, @@ -1451,6 +1461,7 @@ 7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */, 7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */, 7A10226E2C551EE000B84627 /* LocationEditViewModel.swift in Sources */, + 7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */, 7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */, 7A14416C2C297F2100E79018 /* NotesCoordinator.swift in Sources */, 7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */, @@ -1483,6 +1494,7 @@ 7A5911F02D63266B00EC51BA /* SearchViewModel.swift in Sources */, 7A64AE752469DFB600ABE48E /* MediaBrowserViewController.swift in Sources */, 7ABD1B4B2D044A7D00B43213 /* GalleryCoordinator.swift in Sources */, + 7A589E0F2D6B6E8E00EF3FBE /* NumberEditView.swift in Sources */, 7A64AE732469DFB600ABE48E /* DismissAnimationController.swift in Sources */, 7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */, 7A1022702C551EFD00B84627 /* LocationEditCoordinator.swift in Sources */, diff --git a/AutoCat/SwiftUI/ACKeyboardButton.swift b/AutoCat/SwiftUI/ACKeyboardButton.swift new file mode 100644 index 0000000..0110531 --- /dev/null +++ b/AutoCat/SwiftUI/ACKeyboardButton.swift @@ -0,0 +1,81 @@ +// +// ACKeyboardButton.swift +// AutoCat +// +// Created by Selim Mustafaev on 23.02.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +struct ACKeyboardButton: View { + + let type: PNButtonType + let onTap: (PNButtonType) -> Void + + init(type: PNButtonType, onTap: @escaping (PNButtonType) -> Void) { + self.type = type + self.onTap = onTap + } + + var body: some View { + Button { + onTap(type) + } label: { + title + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + .foregroundColor(.primary) + .background { + RoundedRectangle(cornerRadius: 6) + .fill(backgroundColor) + .padding(4) + } + } + } + + @ViewBuilder + var title: some View { + switch type { + case .symbol(let s): + Text(s) + .font(.custom("RoadNumbers", size: 36 - 2*sizeDelta)) + .offset(y: sizeDelta/2) + case .backspace: + Image(systemName: "delete.left") + case .done: + Image(systemName: "arrow.turn.down.left") + } + } + + var backgroundColor: Color { + switch type { + case .symbol: Color("KeyBackground") + case .backspace: Color("DarkKeyBackground") + case .done: .blue + } + } + + var sizeDelta: CGFloat { + guard case let .symbol(title) = type else { + return 0 + } + + if let character = title.first { + return character.isNumber ? 3 : 0 + } else { + return 0 + } + } + +} + +#Preview { + + HStack(spacing: 0) { + ACKeyboardButton(type: .symbol("A")) { _ in } + ACKeyboardButton(type: .symbol("1")) { _ in } + ACKeyboardButton(type: .backspace) { _ in } + ACKeyboardButton(type: .done) { _ in } + } + .frame(height: 50) +} diff --git a/AutoCat/SwiftUI/ACKeyboardView.swift b/AutoCat/SwiftUI/ACKeyboardView.swift new file mode 100644 index 0000000..12c9fb5 --- /dev/null +++ b/AutoCat/SwiftUI/ACKeyboardView.swift @@ -0,0 +1,68 @@ +// +// ACKeyboardView.swift +// AutoCat +// +// Created by Selim Mustafaev on 23.02.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +struct ACKeyboardView: View { + + let buttonPressed: (PNButtonType) -> Void + + var body: some View { + HStack(spacing: 16) { + Grid(horizontalSpacing: 0, verticalSpacing: 0) { + GridRow { + ACKeyboardButton(type: .symbol("A"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("B"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("E"), onTap: buttonPressed) + } + GridRow { + ACKeyboardButton(type: .symbol("K"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("M"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("H"), onTap: buttonPressed) + } + GridRow { + ACKeyboardButton(type: .symbol("O"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("P"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("C"), onTap: buttonPressed) + } + GridRow { + ACKeyboardButton(type: .symbol("T"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("Y"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("X"), onTap: buttonPressed) + } + } + Grid(horizontalSpacing: 0, verticalSpacing: 0) { + GridRow { + ACKeyboardButton(type: .symbol("1"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("2"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("3"), onTap: buttonPressed) + } + GridRow { + ACKeyboardButton(type: .symbol("4"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("5"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("6"), onTap: buttonPressed) + } + GridRow { + ACKeyboardButton(type: .symbol("7"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("8"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("9"), onTap: buttonPressed) + } + GridRow { + ACKeyboardButton(type: .symbol("0"), onTap: buttonPressed) + ACKeyboardButton(type: .backspace, onTap: buttonPressed) + ACKeyboardButton(type: .done, onTap: buttonPressed) + } + } + } + } +} + +#Preview { + ACKeyboardView { _ in } + .frame(height: 240) +} diff --git a/AutoCat/SwiftUI/NumberEditView.swift b/AutoCat/SwiftUI/NumberEditView.swift new file mode 100644 index 0000000..9f33c2c --- /dev/null +++ b/AutoCat/SwiftUI/NumberEditView.swift @@ -0,0 +1,60 @@ +// +// NumberEditView.swift +// AutoCat +// +// Created by Selim Mustafaev on 23.02.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI +import AutoCatCore + +struct NumberEditView: View { + + let onCheck: (String) -> Void + + @State var number = PlateNumber("") + + var body: some View { + VStack(spacing: 16) { + HStack(spacing: 16) { + LicensePlateView(number: number, foreground: .primary) + .layoutPriority(1) + .frame(height: 50) + + Button { + + } label: { + Text("Check") + .foregroundColor(.primary) + .padding(16) + .background { + RoundedRectangle(cornerRadius: 6) + .fill(.blue) + } + } + .layoutPriority(0) + .frame(height: 50) + } + + ACKeyboardView(buttonPressed: buttonPressed) + .frame(height: 220) + } + .padding(16) + } + + func buttonPressed(type: PNButtonType) { + switch type { + case .symbol(let s): + number.insertText(s) + case .backspace: + number.deleteBackward() + case .done: + onCheck(number.asString()) + } + } +} + +#Preview { + NumberEditView() { _ in } +} diff --git a/AutoCatCore/Models/PlateNumber.swift b/AutoCatCore/Models/PlateNumber.swift index 3cc1d4f..f788ef4 100644 --- a/AutoCatCore/Models/PlateNumber.swift +++ b/AutoCatCore/Models/PlateNumber.swift @@ -44,6 +44,7 @@ public class PlateNumber { } // Returns true if number was changed + @discardableResult public func insertText(_ text: String) -> Bool { guard number.count < maxLength, text.count == 1 else { return false @@ -61,6 +62,7 @@ public class PlateNumber { return true } + @discardableResult public func deleteBackward() -> Bool { guard !number.isEmpty else { return false