Skip to content

Commit 04cf683

Browse files
dadachiclaude
andcommitted
Restore item-tag swipe actions on ShopDetailView; rename reset → idle
Reverts the upstream PR's removal of the complete/idle swipe actions on ShopDetailView so users can still mark items completed or send them back to idled without going through Settings. Renames the user-facing "reset" terminology to "idle" since the action just transitions a completed tag back to the idled state: - ItemTag UI/ViewModel/strings: resetTag → idleTag, isResetting → isIdling, itemTagReset[Error] → itemTagIdled[Error], button label "Reset" → "Idle". - ItemTag repository layer: ItemTagRepositoryProtocol.reset → idle, ItemTagRepository / DemoItemTagRepository / TestItemTagRepository updated. - ItemTag network layer: ResetItemTagRequest → IdleItemTagRequest, ItemTagsService.resetItemTag → idleItemTag, HTTP path /shopkeeper/item_tags/:id/reset → /shopkeeper/item_tags/:id/idle. Backend must expose PATCH /shopkeeper/item_tags/:id/idle. Shop repository / network layer left as `reset` (no UI surfaces it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cf8b706 commit 04cf683

11 files changed

Lines changed: 220 additions & 19 deletions

File tree

NativeAppTemplate/Constants.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ extension String {
204204
static let itemTagUpdated = "Tag updated successfully."
205205
static let itemTagDeleted = "Tag deleted successfully."
206206
static let itemTagDeletedError = "There was a problem deleting the tag."
207+
static let itemTagCompleted = "Tag completed successfully."
208+
static let itemTagCompletedError = "There was a problem completing the tag."
209+
static let itemTagIdled = "Tag idled successfully."
210+
static let itemTagIdledError = "There was a problem idling the tag."
207211

208212
static let shopkeeperCreated = "Account created successfully."
209213
static let shopkeeperCreatedError = "There was a problem creating the account."
@@ -298,7 +302,7 @@ extension String {
298302
static let backToStartScreen = "Back to Start Screen"
299303
static let fullName = "Full Name"
300304
static let fullNameIsRequired = "Full name is required."
301-
static let reset = "Reset"
305+
static let idle = "Idle"
302306
}
303307

304308
extension TimeInterval {

NativeAppTemplate/Data/Repositories/ItemTagRepository.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,15 @@ import SwiftUI
191191
}
192192
}
193193

194-
func reset(id: String) async throws -> ItemTag {
194+
func idle(id: String) async throws -> ItemTag {
195195
do {
196-
let resetItemTag = try await itemTagsService.resetItemTag(id: id)
197-
let itemTagIndex = (itemTags.firstIndex { $0.id == resetItemTag.id })
196+
let idledItemTag = try await itemTagsService.idleItemTag(id: id)
197+
let itemTagIndex = (itemTags.firstIndex { $0.id == idledItemTag.id })
198198
if itemTagIndex != nil {
199-
itemTags[itemTagIndex!] = resetItemTag
199+
itemTags[itemTagIndex!] = idledItemTag
200200
}
201201

202-
return resetItemTag
202+
return idledItemTag
203203
} catch {
204204
state = .failed
205205
Failure

NativeAppTemplate/Data/Repositories/ItemTagRepositoryProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ import SwiftUI
2424
func update(id: String, itemTag: ItemTag) async throws -> ItemTag
2525
func destroy(id: String) async throws
2626
func complete(id: String) async throws -> ItemTag
27-
func reset(id: String) async throws -> ItemTag
27+
func idle(id: String) async throws -> ItemTag
2828
}

NativeAppTemplate/Networking/Requests/ItemTagsRequest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ struct CompleteItemTagRequest: Request {
230230
}
231231
}
232232

233-
struct ResetItemTagRequest: Request {
233+
struct IdleItemTagRequest: Request {
234234
typealias Response = ItemTag
235235

236236
// MARK: - Properties
@@ -240,7 +240,7 @@ struct ResetItemTagRequest: Request {
240240
}
241241

242242
var path: String {
243-
"/shopkeeper/item_tags/\(id)/reset"
243+
"/shopkeeper/item_tags/\(id)/idle"
244244
}
245245

246246
var additionalHeaders: [String: String] = [:]

NativeAppTemplate/Networking/Services/ItemTagsService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ extension ItemTagsService {
3737
try await makeRequest(request: CompleteItemTagRequest(id: id))
3838
}
3939

40-
func resetItemTag(id: String) async throws -> ResetItemTagRequest.Response {
41-
try await makeRequest(request: ResetItemTagRequest(id: id))
40+
func idleItemTag(id: String) async throws -> IdleItemTagRequest.Response {
41+
try await makeRequest(request: IdleItemTagRequest(id: id))
4242
}
4343
}

NativeAppTemplate/UI/Shop Detail/ShopDetailView.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ private extension ShopDetailView {
6161
var cardsView: some View {
6262
ForEach(viewModel.itemTags, id: \.id) { itemTag in
6363
ShopDetailCardView(itemTag: itemTag)
64+
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
65+
if itemTag.state == ItemTagState.idled {
66+
Button { viewModel.completeTag(itemTagId: itemTag.id) } label: {
67+
Label(String.complete, systemImage: "bolt.fill")
68+
.labelStyle(.titleOnly)
69+
}
70+
.tint(.blue)
71+
} else {
72+
Button(role: .destructive) { viewModel.idleTag(itemTagId: itemTag.id) } label: {
73+
Label(String.idle, systemImage: "trash")
74+
.labelStyle(.titleOnly)
75+
}
76+
.tint(.validationError)
77+
}
78+
}
6479
.listRowBackground(Color.cardBackground.opacity(0.7))
6580
}
6681
}

NativeAppTemplate/UI/Shop Detail/ShopDetailViewModel.swift

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import SwiftUI
1010
@MainActor
1111
final class ShopDetailViewModel {
1212
var isFetching = true
13+
var isIdling = false
14+
var isCompleting = false
1315
var itemTags: [ItemTag] = []
1416
var shouldDismiss: Bool = false
1517
var shopId: String
@@ -41,7 +43,7 @@ final class ShopDetailViewModel {
4143
}
4244

4345
var isBusy: Bool {
44-
isFetching
46+
isFetching || isIdling || isCompleting
4547
}
4648

4749
var isLoggedIn: Bool {
@@ -68,6 +70,50 @@ final class ShopDetailViewModel {
6870
}
6971
}
7072

73+
func completeTag(itemTagId: String) {
74+
Task {
75+
isCompleting = true
76+
77+
do {
78+
_ = try await itemTagRepository.complete(id: itemTagId)
79+
messageBus.post(message: Message(level: .success, message: .itemTagCompleted))
80+
} catch {
81+
messageBus.post(
82+
message: Message(
83+
level: .error,
84+
message: "\(String.itemTagCompletedError) \(error.codedDescription)",
85+
autoDismiss: false
86+
)
87+
)
88+
}
89+
90+
isCompleting = false
91+
reload()
92+
}
93+
}
94+
95+
func idleTag(itemTagId: String) {
96+
Task {
97+
isIdling = true
98+
99+
do {
100+
_ = try await itemTagRepository.idle(id: itemTagId)
101+
messageBus.post(message: Message(level: .success, message: .itemTagIdled))
102+
} catch {
103+
messageBus.post(
104+
message: Message(
105+
level: .error,
106+
message: "\(String.itemTagIdledError) \(error.codedDescription)",
107+
autoDismiss: false
108+
)
109+
)
110+
}
111+
112+
isIdling = false
113+
reload()
114+
}
115+
}
116+
71117
func setTabViewModelShowingDetailViewToTrue() {
72118
tabViewModel.showingDetailView[mainTab] = true
73119
}

NativeAppTemplateTests/Demo/Data/Repositories/DemoItemTagRepository.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ final class DemoItemTagRepository: ItemTagRepositoryProtocol {
7777
return itemTag
7878
}
7979

80-
func reset(id: String) async throws -> ItemTag {
80+
func idle(id: String) async throws -> ItemTag {
8181
var itemTag = itemTags.first { $0.id == id }!
8282
itemTag.state = .idled
8383
itemTag.completedAt = nil

NativeAppTemplateTests/Demo/Data/Repositories/DemoItemTagRepositoryTest.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,17 @@ struct DemoItemTagRepositoryTest {
9898
}
9999

100100
@Test
101-
func reset() async throws {
101+
func idle() async throws {
102102
repository.reload(shopId: "1")
103103

104104
var itemTag = repository.findBy(id: "1")
105105
itemTag.state = .completed
106106
itemTag.completedAt = .now
107107
_ = try await repository.update(id: "1", itemTag: itemTag)
108108

109-
let resetItemTag = try await repository.reset(id: "1")
110-
#expect(resetItemTag.state == .idled)
111-
#expect(resetItemTag.completedAt == nil)
109+
let idledItemTag = try await repository.idle(id: "1")
110+
#expect(idledItemTag.state == .idled)
111+
#expect(idledItemTag.completedAt == nil)
112112
}
113113
}
114114
}

NativeAppTemplateTests/Testing/Repositories/TestItemTagRepository.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ final class TestItemTagRepository: ItemTagRepositoryProtocol {
126126
return itemTag
127127
}
128128

129-
func reset(id: String) async throws -> ItemTag {
129+
func idle(id: String) async throws -> ItemTag {
130130
guard error == nil else {
131131
state = .failed
132132
throw error!

0 commit comments

Comments
 (0)