Skip to content

Commit 5d75126

Browse files
committed
Add swipe actions to conversation selection rows
Implemented trailing swipe actions for duplicating and deleting conversations, and leading swipe actions for favoriting/unfavoriting conversations in ConversationSelectionView. These actions improve user interaction and management of conversations within the selection view.
1 parent 2d923f4 commit 5d75126

File tree

3 files changed

+115
-7
lines changed

3 files changed

+115
-7
lines changed

FlowDown/Interface/Components/ConversationSelectionView/ConversationSelectionView+Delegate.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,72 @@ extension ConversationSelectionView: UITableViewDelegate {
1515
ChatSelection.shared.select(identifier, options: [.collapseSidebar])
1616
}
1717

18+
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
19+
guard let identifier = dataSource.itemIdentifier(for: indexPath) else { return nil }
20+
21+
let duplicateAction = UIContextualAction(style: .normal, title: nil) { _, _, completion in
22+
if let duplicatedId = ConversationManager.shared.duplicateConversation(identifier: identifier) {
23+
ChatSelection.shared.select(duplicatedId, options: [.collapseSidebar])
24+
}
25+
completion(true)
26+
}
27+
duplicateAction.image = UIImage(systemName: "doc.on.doc")
28+
29+
let deleteAction = UIContextualAction(style: .destructive, title: nil) { [weak self] _, _, completion in
30+
guard let self else {
31+
completion(false)
32+
return
33+
}
34+
35+
let snapshot = dataSource.snapshot()
36+
let identifiers = snapshot.itemIdentifiers
37+
let currentSelectionIndex = identifiers.firstIndex(of: identifier)
38+
let isDeletingSelectedRow = tableView.indexPathForSelectedRow == indexPath
39+
40+
ConversationManager.shared.deleteConversation(identifier: identifier)
41+
42+
DispatchQueue.main.async {
43+
if isDeletingSelectedRow,
44+
let currentSelectionIndex,
45+
let nextIdentifier = (
46+
identifiers.dropFirst(currentSelectionIndex + 1).first
47+
?? identifiers.prefix(currentSelectionIndex).last
48+
)
49+
{
50+
ChatSelection.shared.select(nextIdentifier)
51+
}
52+
completion(true)
53+
}
54+
}
55+
deleteAction.image = UIImage(systemName: "trash")
56+
57+
let configuration = UISwipeActionsConfiguration(actions: [deleteAction, duplicateAction])
58+
configuration.performsFirstActionWithFullSwipe = true
59+
return configuration
60+
}
61+
62+
func tableView(_: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
63+
guard let identifier = dataSource.itemIdentifier(for: indexPath),
64+
let conversation = ConversationManager.shared.conversation(identifier: identifier)
65+
else { return nil }
66+
67+
let isFavorite = conversation.isFavorite
68+
let imageName = isFavorite ? "star.slash" : "star"
69+
70+
let favoriteAction = UIContextualAction(style: .normal, title: nil) { _, _, completion in
71+
ConversationManager.shared.editConversation(identifier: identifier) { conv in
72+
conv.update(\.isFavorite, to: !isFavorite)
73+
}
74+
completion(true)
75+
}
76+
favoriteAction.image = UIImage(systemName: imageName)
77+
favoriteAction.backgroundColor = .systemYellow
78+
79+
let configuration = UISwipeActionsConfiguration(actions: [favoriteAction])
80+
configuration.performsFirstActionWithFullSwipe = true
81+
return configuration
82+
}
83+
1884
func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
1985
guard dataSource.snapshot().numberOfSections > 1 else { return nil }
2086
let sectionIdentifier = dataSource.snapshot().sectionIdentifiers[section]

FlowDown/Interface/ViewController/MainController/MainController.swift

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,15 @@ class MainController: UIViewController {
316316
}
317317
Logger.app.debugFile("current conversation ID: \(currentConversationID)")
318318

319+
guard sdb.conversationWith(identifier: currentConversationID) != nil else {
320+
Logger.app.errorFile("conversation missing from database: id \(currentConversationID)")
321+
showErrorAlert(
322+
title: "Conversation Missing",
323+
message: "The selected conversation could not be found. Please choose another conversation or create a new one."
324+
)
325+
return
326+
}
327+
319328
// retrieve session
320329
let session = ConversationSessionManager.shared.session(for: currentConversationID)
321330
Logger.app.debugFile("session created/retrieved for conversation")
@@ -325,10 +334,7 @@ class MainController: UIViewController {
325334
Logger.app.errorFile("no default model configured")
326335
showErrorAlert(
327336
title: "No Model Available",
328-
message: String(
329-
localized:
330-
"Please add some models to use. You can choose to download models, or use cloud model from well known service providers."
331-
)
337+
message: "Please add some models to use. You can choose to download models, or use cloud model from well known service providers."
332338
) {
333339
let setting = SettingController()
334340
SettingController.setNextEntryPage(.modelManagement)
@@ -363,11 +369,15 @@ class MainController: UIViewController {
363369
}
364370
}
365371

366-
private func showErrorAlert(title: String, message: String, completion: @escaping () -> Void = {}) {
372+
private func showErrorAlert(
373+
title: String.LocalizationValue,
374+
message: String.LocalizationValue,
375+
completion: @escaping () -> Void = {}
376+
) {
367377
DispatchQueue.main.async {
368378
let alert = AlertViewController(
369-
title: "\(title)",
370-
message: "\(message)"
379+
title: title,
380+
message: message
371381
) { context in
372382
context.addAction(title: "OK") {
373383
context.dispose(completion)

FlowDown/Resources/Localizable.xcstrings

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,6 +2796,22 @@
27962796
}
27972797
}
27982798
},
2799+
"Conversation Missing" : {
2800+
"localizations" : {
2801+
"en" : {
2802+
"stringUnit" : {
2803+
"state" : "translated",
2804+
"value" : "Conversation Missing"
2805+
}
2806+
},
2807+
"zh-Hans" : {
2808+
"stringUnit" : {
2809+
"state" : "translated",
2810+
"value" : "对话丢失"
2811+
}
2812+
}
2813+
}
2814+
},
27992815
"Conversation: %@" : {
28002816
"localizations" : {
28012817
"en" : {
@@ -12864,6 +12880,22 @@
1286412880
}
1286512881
}
1286612882
},
12883+
"The selected conversation could not be found. Please choose another conversation or create a new one." : {
12884+
"localizations" : {
12885+
"en" : {
12886+
"stringUnit" : {
12887+
"state" : "translated",
12888+
"value" : "The selected conversation could not be found. Please choose another conversation or create a new one."
12889+
}
12890+
},
12891+
"zh-Hans" : {
12892+
"stringUnit" : {
12893+
"state" : "translated",
12894+
"value" : "未能找到选中的对话,请选择其他对话或创建新对话。"
12895+
}
12896+
}
12897+
}
12898+
},
1286712899
"The selected model does not support image inputs." : {
1286812900
"localizations" : {
1286912901
"en" : {

0 commit comments

Comments
 (0)