NCAssistant.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. //
  2. // NCAssistant.swift
  3. // Nextcloud
  4. //
  5. // Created by Milen on 03.04.24.
  6. // Copyright © 2024 Marino Faggiana. All rights reserved.
  7. //
  8. import SwiftUI
  9. import NextcloudKit
  10. import PopupView
  11. struct NCAssistant: View {
  12. @EnvironmentObject var model: NCAssistantTask
  13. @State var presentNewTaskDialog = false
  14. @State var input = ""
  15. @Environment(\.presentationMode) var presentationMode
  16. var body: some View {
  17. NavigationView {
  18. ZStack {
  19. TaskList()
  20. if model.types.isEmpty, !model.isLoading {
  21. NCAssistantEmptyView(titleKey: "_no_types_", subtitleKey: "_no_types_subtitle_")
  22. } else if model.filteredTasks.isEmpty, !model.isLoading {
  23. NCAssistantEmptyView(titleKey: "_no_tasks_", subtitleKey: "_create_task_subtitle_")
  24. }
  25. }
  26. .toolbar {
  27. ToolbarItem(placement: .topBarLeading) {
  28. Button(NSLocalizedString("_close_", comment: "")) {
  29. presentationMode.wrappedValue.dismiss()
  30. }
  31. }
  32. ToolbarItem(placement: .topBarTrailing) {
  33. NavigationLink(destination: NCAssistantCreateNewTask()) {
  34. Image(systemName: "plus")
  35. .font(Font.system(.body).weight(.light))
  36. }
  37. .disabled(model.selectedType == nil)
  38. }
  39. }
  40. .navigationBarTitleDisplayMode(.inline)
  41. .navigationTitle(NSLocalizedString("_assistant_", comment: ""))
  42. .frame(maxWidth: .infinity, maxHeight: .infinity)
  43. .safeAreaInset(edge: .top, spacing: -10) {
  44. ScrollViewReader { scrollProxy in
  45. ScrollView(.horizontal, showsIndicators: false) {
  46. LazyHStack {
  47. ForEach(model.types, id: \.id) { type in
  48. TypeButton(taskType: type, scrollProxy: scrollProxy)
  49. }
  50. }
  51. .padding(20)
  52. .frame(height: 50)
  53. }
  54. }
  55. }
  56. }
  57. .navigationViewStyle(.stack)
  58. .popup(isPresented: $model.hasError) {
  59. Text(NSLocalizedString("_error_occurred_", comment: ""))
  60. .padding()
  61. .background(.red)
  62. .cornerRadius(30.0)
  63. } customize: {
  64. $0
  65. .type(.floater())
  66. .autohideIn(2)
  67. .position(.bottom)
  68. }
  69. .accentColor(Color(NCBrandColor.shared.iconImageColor))
  70. .environmentObject(model)
  71. }
  72. }
  73. #Preview {
  74. let model = NCAssistantTask()
  75. return NCAssistant()
  76. .environmentObject(model)
  77. .onAppear {
  78. model.loadDummyData()
  79. }
  80. }
  81. struct TaskList: View {
  82. @EnvironmentObject var model: NCAssistantTask
  83. var body: some View {
  84. List(model.filteredTasks, id: \.id) { task in
  85. TaskItem(task: task)
  86. }
  87. .if(!model.types.isEmpty) { view in
  88. view.refreshable {
  89. model.load()
  90. }
  91. }
  92. }
  93. }
  94. struct TypeButton: View {
  95. @EnvironmentObject var model: NCAssistantTask
  96. let taskType: NKTextProcessingTaskType?
  97. var scrollProxy: ScrollViewProxy
  98. var body: some View {
  99. Button {
  100. model.selectTaskType(taskType)
  101. withAnimation {
  102. scrollProxy.scrollTo(taskType?.id, anchor: .center)
  103. }
  104. } label: {
  105. Text(taskType?.name ?? "").font(.body)
  106. }
  107. .padding(.horizontal)
  108. .padding(.vertical, 7)
  109. .foregroundStyle(model.selectedType?.id == taskType?.id ? .white : .primary)
  110. .if(model.selectedType?.id == taskType?.id) { view in
  111. view.background(Color(NCBrandColor.shared.brandElement))
  112. }
  113. .if(model.selectedType?.id != taskType?.id) { view in
  114. view.background(.ultraThinMaterial)
  115. }
  116. .clipShape(.capsule)
  117. .overlay(
  118. RoundedRectangle(cornerRadius: 20, style: RoundedCornerStyle.continuous)
  119. .stroke(.tertiary.opacity(0.2), lineWidth: 1)
  120. )
  121. .id(taskType?.id)
  122. }
  123. }
  124. struct TaskItem: View {
  125. @EnvironmentObject var model: NCAssistantTask
  126. @State var showDeleteConfirmation = false
  127. let task: NKTextProcessingTask
  128. var body: some View {
  129. NavigationLink(destination: NCAssistantTaskDetail(task: task)) {
  130. VStack(alignment: .leading) {
  131. Text(task.input ?? "")
  132. .lineLimit(4)
  133. HStack {
  134. Label(
  135. title: {
  136. Text(NSLocalizedString(task.statusInfo.stringKey, comment: ""))
  137. },
  138. icon: {
  139. Image(systemName: task.statusInfo.imageSystemName)
  140. .renderingMode(.original)
  141. .font(Font.system(.body).weight(.light))
  142. }
  143. )
  144. .padding(.top, 1)
  145. .labelStyle(CustomLabelStyle())
  146. if let completionExpectedAt = task.completionExpectedAt {
  147. Text(NCUtility().dateDiff(.init(timeIntervalSince1970: TimeInterval(completionExpectedAt))))
  148. .frame(maxWidth: .infinity, alignment: .trailing)
  149. .foregroundStyle(.tertiary)
  150. }
  151. }
  152. }
  153. .swipeActions {
  154. Button(NSLocalizedString("_delete_", comment: "")) {
  155. showDeleteConfirmation = true
  156. }
  157. .tint(.red)
  158. }
  159. .confirmationDialog("", isPresented: $showDeleteConfirmation) {
  160. Button(NSLocalizedString("_delete_", comment: ""), role: .destructive) {
  161. withAnimation {
  162. model.deleteTask(task)
  163. }
  164. }
  165. }
  166. }
  167. }
  168. }
  169. private struct CustomLabelStyle: LabelStyle {
  170. var spacing: Double = 5
  171. func makeBody(configuration: Configuration) -> some View {
  172. HStack(spacing: spacing) {
  173. configuration.icon
  174. configuration.title
  175. }
  176. }
  177. }