1
+ //
2
+ // VehicleQuery.swift
3
+ // Basic-Car-Maintenance
4
+ //
5
+ // https://github.com/mikaelacaron/Basic-Car-Maintenance
6
+ // See LICENSE for license information.
7
+ //
8
+
9
+
10
+ import AppIntents
11
+
12
+ /// The query used to retrieve vehicles for adding odometer.
13
+ struct VehicleQuery : EntityQuery {
14
+
15
+ func entities( for identifiers: [ Vehicle . ID ] ) async throws -> [ Vehicle ] {
16
+ try await fetchVehicles ( )
17
+ }
18
+
19
+ func suggestedEntities( ) async throws -> [ Vehicle ] {
20
+ try await fetchVehicles ( )
21
+ }
22
+
23
+ private func fetchVehicles( ) async throws -> [ Vehicle ] {
24
+ let authViewModel = await AuthenticationViewModel ( )
25
+ let odometerVM = OdometerViewModel ( userUID: authViewModel. user? . uid)
26
+ await odometerVM. getVehicles ( )
27
+ guard !odometerVM. vehicles. isEmpty else {
28
+ throw OdometerReadingError . emptyVehicles
29
+ }
30
+ return odometerVM. vehicles
31
+ }
32
+ }
33
+
34
+ /// An enumeration representing the units of distance used for odometer readings.
35
+ ///
36
+ /// This enum conforms to `AppEnum` and `CaseIterable` to provide display representations
37
+ /// for the available distance units: miles and kilometers.
38
+ ///
39
+ /// - `mile`: Represents distance in miles.
40
+ /// - `kilometer`: Represents distance in kilometers.
41
+ enum DistanceUnit : String , AppEnum , CaseIterable {
42
+ static var typeDisplayRepresentation = TypeDisplayRepresentation ( name: " Distance Type " )
43
+ static var caseDisplayRepresentations : [ DistanceUnit : DisplayRepresentation ] {
44
+ [
45
+ . mile: " Miles " ,
46
+ . kilometer: " Kilometers "
47
+ ]
48
+ }
49
+
50
+ case mile
51
+ case kilometer
52
+ }
53
+
54
+ /// An `AppIntent` that allows the user to add an odometer reading for a specified vehicle.
55
+ ///
56
+ /// This intent accepts the distance traveled, the unit of distance (miles or kilometers),
57
+ /// the vehicle for which the odometer reading is being recorded, and the date of the reading.
58
+ ///
59
+ /// The intent validates the input, ensuring that the distance is a positive integer.
60
+ /// If the input is valid, the intent creates an `OdometerReading` and saves it using the `OdometerViewModel`.
61
+ /// Upon successful completion, a confirmation dialog is presented to the user.
62
+ struct AddOdometerReadingIntent : AppIntent {
63
+ @Parameter ( title: " Distance " )
64
+ var distance : Int
65
+
66
+ @Parameter (
67
+ title: LocalizedStringResource (
68
+ " Distance Unit " ,
69
+ comment: " The distance unit in miles or kilometers "
70
+ )
71
+ )
72
+ var distanceType : DistanceUnit
73
+
74
+ @Parameter ( title: " Vehicle " )
75
+ var vehicle : Vehicle
76
+
77
+ @Parameter ( title: " Date " )
78
+ var date : Date
79
+
80
+ static var title = LocalizedStringResource (
81
+ " Add Odometer Reading " ,
82
+ comment: " Title for the app intent when adding an odometer reading "
83
+ )
84
+
85
+ func perform( ) async throws -> some IntentResult & ProvidesDialog {
86
+ if distance < 1 {
87
+ throw OdometerReadingError . invalidDistance
88
+ }
89
+
90
+ let reading = OdometerReading (
91
+ date: date,
92
+ distance: distance,
93
+ isMetric: distanceType == . kilometer,
94
+ vehicleID: vehicle. id
95
+ )
96
+ let authViewModel = await AuthenticationViewModel ( )
97
+ let odometerVM = OdometerViewModel ( userUID: authViewModel. user? . uid)
98
+ try odometerVM. addReading ( reading)
99
+ return . result(
100
+ dialog: IntentDialog (
101
+ LocalizedStringResource (
102
+ " Added reading successfully " ,
103
+ comment: " The message shown when successfully adding an odometer reading using the app intent "
104
+ )
105
+ )
106
+ )
107
+ }
108
+ }
109
+
110
+ /// An enumeration representing errors that can occur when adding an odometer reading.
111
+ ///
112
+ /// This enum conforms to `Error` and `CustomLocalizedStringResourceConvertible` to provide
113
+ /// localized error messages for specific conditions:
114
+ ///
115
+ /// - `invalidDistance`: Triggered when a distance value less than 1 (either in kilometers or miles) is entered.
116
+ /// - `emptyVehicles`: Triggered when there are no vehicles available to select for the odometer reading.
117
+ ///
118
+ /// Each case provides a user-friendly localized string resource that describes the error.
119
+ enum OdometerReadingError : Error , CustomLocalizedStringResourceConvertible {
120
+ case invalidDistance
121
+ case emptyVehicles
122
+
123
+ var localizedStringResource : LocalizedStringResource {
124
+ switch self {
125
+ case . invalidDistance:
126
+ LocalizedStringResource (
127
+ " You can not select distance number less than 1 km or mi " ,
128
+ comment: " an error shown when entering a zero or negative value for distance "
129
+ )
130
+ case . emptyVehicles:
131
+ LocalizedStringResource (
132
+ " No vehicles available, please add a vehicle using the app and try again " ,
133
+ comment: " an error shown when attempting to add an odometer while there are no vehicles added "
134
+ )
135
+
136
+ }
137
+ }
138
+ }
0 commit comments