NCAssistant.swift 6.6 KB

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