dirk@30
|
1 |
//
|
dirk@30
|
2 |
// EmailListViewController.swift
|
dirk@30
|
3 |
// pEpForiOS
|
dirk@30
|
4 |
//
|
dirk@30
|
5 |
// Created by Dirk Zimmermann on 16/04/16.
|
dirk@30
|
6 |
// Copyright © 2016 p≡p Security S.A. All rights reserved.
|
dirk@30
|
7 |
//
|
dirk@30
|
8 |
|
dirk@30
|
9 |
import UIKit
|
dirk@810
|
10 |
import MessageModel
|
xavier@3368
|
11 |
import SwipeCellKit
|
dirk@810
|
12 |
|
xavier@3368
|
13 |
class EmailListViewController: BaseTableViewController, SwipeTableViewCellDelegate {
|
andreas@3223
|
14 |
var folderToShow: Folder?
|
andreas@3677
|
15 |
|
andreas@3677
|
16 |
func updateLastLookAt() {
|
andreas@3677
|
17 |
guard let saveFolder = folderToShow else {
|
andreas@3677
|
18 |
return
|
andreas@3677
|
19 |
}
|
andreas@3677
|
20 |
saveFolder.updateLastLookAt()
|
andreas@3677
|
21 |
}
|
andreas@3677
|
22 |
|
andreas@3151
|
23 |
private var model: EmailListViewModel?
|
andreas@3677
|
24 |
|
andreas@3151
|
25 |
private let queue: OperationQueue = {
|
andreas@3151
|
26 |
let createe = OperationQueue()
|
andreas@3151
|
27 |
createe.qualityOfService = .userInteractive
|
andreas@3678
|
28 |
createe.maxConcurrentOperationCount = 5
|
andreas@3151
|
29 |
return createe
|
andreas@3151
|
30 |
}()
|
andreas@3151
|
31 |
private var operations = [IndexPath:Operation]()
|
andreas@2838
|
32 |
public static let storyboardId = "EmailListViewController"
|
andreas@3151
|
33 |
fileprivate var lastSelectedIndexPath: IndexPath?
|
andreas@3208
|
34 |
|
igor@1301
|
35 |
let searchController = UISearchController(searchResultsController: nil)
|
xavier@3368
|
36 |
|
xavier@3368
|
37 |
//swipe acctions types
|
xavier@3368
|
38 |
var buttonDisplayMode: ButtonDisplayMode = .titleAndImage
|
xavier@3368
|
39 |
var buttonStyle: ButtonStyle = .backgroundColor
|
andreas@3678
|
40 |
|
andreas@3678
|
41 |
/// Indicates that we must not trigger reloadData.
|
andreas@3678
|
42 |
private var loadingBlocked = false
|
andreas@3208
|
43 |
|
andreas@3151
|
44 |
// MARK: - Outlets
|
andreas@3208
|
45 |
|
dirk@2067
|
46 |
@IBOutlet weak var enableFilterButton: UIBarButtonItem!
|
dirk@2067
|
47 |
@IBOutlet weak var textFilterButton: UIBarButtonItem!
|
andreas@3151
|
48 |
@IBOutlet var showFoldersButton: UIBarButtonItem!
|
andreas@3677
|
49 |
|
andreas@3151
|
50 |
// MARK: - Life Cycle
|
andreas@3208
|
51 |
|
dirk@275
|
52 |
override func viewDidLoad() {
|
ylandert@935
|
53 |
super.viewDidLoad()
|
dirk@2189
|
54 |
title = NSLocalizedString("Inbox", comment: "General name for (unified) inbox")
|
igor@1284
|
55 |
UIHelper.emailListTableHeight(self.tableView)
|
andreas@3163
|
56 |
self.textFilterButton.isEnabled = false
|
igor@1301
|
57 |
addSearchBar()
|
xavier@4348
|
58 |
|
xavier@4348
|
59 |
//some notifications to control when the app enter and recover from backgroud
|
xavier@4317
|
60 |
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
|
xavier@4317
|
61 |
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeInactive), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
|
xavier@4317
|
62 |
}
|
xavier@4317
|
63 |
|
xavier@4348
|
64 |
//observer clean
|
xavier@4347
|
65 |
deinit {
|
xavier@4347
|
66 |
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
|
xavier@4347
|
67 |
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
|
xavier@4347
|
68 |
}
|
xavier@4347
|
69 |
|
xavier@4317
|
70 |
@objc func didBecomeActive() {
|
xavier@4317
|
71 |
if tableView.tableHeaderView == nil {
|
xavier@4317
|
72 |
tableView.tableHeaderView = searchController.searchBar
|
xavier@4317
|
73 |
}
|
xavier@4317
|
74 |
}
|
xavier@4317
|
75 |
|
xavier@4317
|
76 |
@objc func didBecomeInactive() {
|
xavier@4317
|
77 |
tableView.tableHeaderView = nil
|
dirk@275
|
78 |
}
|
andreas@3208
|
79 |
|
dirk@784
|
80 |
override func viewWillAppear(_ animated: Bool) {
|
igor@1301
|
81 |
super.viewWillAppear(animated)
|
xavier@2325
|
82 |
self.navigationController?.setToolbarHidden(false, animated: true)
|
dirk@1344
|
83 |
if MiscUtil.isUnitTest() {
|
dirk@1344
|
84 |
return
|
dirk@1344
|
85 |
}
|
andreas@3223
|
86 |
|
dirk@1826
|
87 |
setDefaultColors()
|
andreas@3151
|
88 |
setup()
|
andreas@3208
|
89 |
|
dirk@2191
|
90 |
// Mark this folder as having been looked at by the user
|
andreas@3151
|
91 |
updateLastLookAt()
|
andreas@3151
|
92 |
setupFoldersBarButton()
|
xavier@3562
|
93 |
if model != nil {
|
xavier@3562
|
94 |
updateFilterButtonView()
|
xavier@3562
|
95 |
}
|
andreas@3151
|
96 |
}
|
andreas@3208
|
97 |
|
andreas@3151
|
98 |
// MARK: - NavigationBar
|
andreas@3208
|
99 |
|
andreas@3151
|
100 |
private func hideFoldersNavigationBarButton() {
|
andreas@3151
|
101 |
self.showFoldersButton.isEnabled = false
|
andreas@3151
|
102 |
self.showFoldersButton.tintColor = UIColor.clear
|
andreas@3151
|
103 |
}
|
andreas@3208
|
104 |
|
andreas@3151
|
105 |
private func showFoldersNavigationBarButton() {
|
andreas@3151
|
106 |
self.showFoldersButton.isEnabled = true
|
andreas@3151
|
107 |
self.showFoldersButton.tintColor = nil
|
andreas@3151
|
108 |
}
|
andreas@3208
|
109 |
|
andreas@3151
|
110 |
private func resetModel() {
|
andreas@3223
|
111 |
if folderToShow != nil {
|
andreas@3291
|
112 |
model = EmailListViewModel(delegate: self,
|
andreas@3291
|
113 |
messageSyncService: appConfig.messageSyncService,
|
andreas@3291
|
114 |
folderToShow: folderToShow)
|
xavier@2585
|
115 |
}
|
dirk@1348
|
116 |
}
|
andreas@3208
|
117 |
|
andreas@3151
|
118 |
private func setup() {
|
andreas@3289
|
119 |
if noAccountsExist() {
|
andreas@3272
|
120 |
// No account exists. Show account setup.
|
andreas@3272
|
121 |
performSegue(withIdentifier:.segueAddNewAccount, sender: self)
|
andreas@3289
|
122 |
} else if let vm = model {
|
andreas@3223
|
123 |
// We came back from e.g EmailView ...
|
andreas@3223
|
124 |
updateFilterText()
|
andreas@3223
|
125 |
// ... so we want to update "seen" status
|
andreas@3226
|
126 |
vm.reloadData()
|
andreas@3272
|
127 |
} else if folderToShow == nil {
|
andreas@3223
|
128 |
// We have not been created to show a specific folder, thus we show unified inbox
|
andreas@3151
|
129 |
folderToShow = UnifiedInbox()
|
andreas@3223
|
130 |
resetModel()
|
andreas@3272
|
131 |
} else if model == nil {
|
andreas@3223
|
132 |
// We still got no model, because:
|
andreas@3223
|
133 |
// - We are not coming back from a pushed view (for instance ComposeEmailView)
|
andreas@3223
|
134 |
// - We are not a UnifiedInbox
|
andreas@3226
|
135 |
// So we have been created to show a specific folder. Show it!
|
andreas@3223
|
136 |
resetModel()
|
andreas@3151
|
137 |
}
|
andreas@3272
|
138 |
|
andreas@3151
|
139 |
self.title = realNameOfFolderToShow()
|
andreas@2683
|
140 |
}
|
andreas@3223
|
141 |
|
andreas@3223
|
142 |
private func weCameBackFromAPushedView() -> Bool {
|
andreas@3223
|
143 |
return model != nil
|
andreas@3223
|
144 |
}
|
andreas@3208
|
145 |
|
andreas@3151
|
146 |
private func noAccountsExist() -> Bool {
|
andreas@3151
|
147 |
return Account.all().isEmpty
|
igor@1301
|
148 |
}
|
andreas@3208
|
149 |
|
andreas@3151
|
150 |
private func setupFoldersBarButton() {
|
andreas@3151
|
151 |
if let size = navigationController?.viewControllers.count, size > 1 {
|
andreas@3151
|
152 |
hideFoldersNavigationBarButton()
|
andreas@3151
|
153 |
} else {
|
andreas@3151
|
154 |
showFoldersNavigationBarButton()
|
xavier@2132
|
155 |
}
|
igor@1338
|
156 |
}
|
andreas@3208
|
157 |
|
andreas@3151
|
158 |
private func addSearchBar() {
|
igor@1301
|
159 |
searchController.searchResultsUpdater = self
|
igor@1301
|
160 |
searchController.dimsBackgroundDuringPresentation = false
|
igor@1301
|
161 |
searchController.delegate = self
|
igor@1301
|
162 |
definesPresentationContext = true
|
igor@1301
|
163 |
tableView.tableHeaderView = searchController.searchBar
|
xavier@3209
|
164 |
tableView.setContentOffset(CGPoint(x: 0.0, y: searchController.searchBar.frame.size.height), animated: false)
|
igor@1301
|
165 |
}
|
andreas@3208
|
166 |
|
andreas@3151
|
167 |
// MARK: - Other
|
andreas@3208
|
168 |
|
andreas@3151
|
169 |
private func realNameOfFolderToShow() -> String? {
|
andreas@3224
|
170 |
return folderToShow?.realName
|
andreas@2697
|
171 |
}
|
andreas@3522
|
172 |
|
andreas@3522
|
173 |
private func address(forRowAt indexPath: IndexPath) -> String {
|
andreas@3522
|
174 |
guard
|
andreas@3522
|
175 |
let saveModel = model,
|
andreas@3522
|
176 |
let row = saveModel.row(for: indexPath),
|
andreas@3522
|
177 |
let folder = folderToShow
|
andreas@3522
|
178 |
else {
|
andreas@3522
|
179 |
Log.shared.errorAndCrash(component: #function, errorString: "Invalid state")
|
andreas@3522
|
180 |
return ""
|
andreas@3522
|
181 |
}
|
andreas@3522
|
182 |
switch folder.folderType {
|
andreas@3522
|
183 |
case .all: fallthrough
|
andreas@3522
|
184 |
case .archive: fallthrough
|
andreas@3522
|
185 |
case .spam: fallthrough
|
andreas@3522
|
186 |
case .trash: fallthrough
|
andreas@3522
|
187 |
case .flagged: fallthrough
|
andreas@3522
|
188 |
case .inbox: fallthrough
|
andreas@3522
|
189 |
case .normal:
|
andreas@3522
|
190 |
return row.from
|
andreas@3522
|
191 |
case .drafts: fallthrough
|
andreas@3522
|
192 |
case .sent:
|
andreas@3522
|
193 |
return row.to
|
andreas@3522
|
194 |
}
|
andreas@3522
|
195 |
}
|
andreas@3522
|
196 |
|
andreas@3522
|
197 |
private func shouldShowAccessoryDisclosureIndicator() -> Bool {
|
andreas@3522
|
198 |
guard let folder = folderToShow else {
|
andreas@3522
|
199 |
Log.shared.errorAndCrash(component: #function, errorString: "No folder")
|
andreas@3522
|
200 |
return true
|
andreas@3522
|
201 |
}
|
andreas@3522
|
202 |
if folder.folderType == .drafts {
|
andreas@3522
|
203 |
// Mails in drafts folder can only be opened in compose mode, which is shown modally.
|
andreas@3522
|
204 |
return false
|
andreas@3522
|
205 |
}
|
andreas@3522
|
206 |
return true
|
andreas@3522
|
207 |
}
|
andreas@3208
|
208 |
|
andreas@3151
|
209 |
private func configure(cell: EmailListViewCell, for indexPath: IndexPath) {
|
andreas@3677
|
210 |
// Configure lightweight stuff on main thread ...
|
andreas@3677
|
211 |
guard let saveModel = model else {
|
andreas@3151
|
212 |
return
|
xavier@2967
|
213 |
}
|
andreas@3151
|
214 |
guard let row = saveModel.row(for: indexPath) else {
|
andreas@3151
|
215 |
Log.shared.errorAndCrash(component: #function, errorString: "We should have a row here")
|
andreas@3151
|
216 |
return
|
andreas@3151
|
217 |
}
|
andreas@3522
|
218 |
// Mails in drafts folder can only be opened in compose mode, which is shown modally.
|
andreas@3522
|
219 |
cell.accessoryDisclosureIndicator.isHidden = shouldShowAccessoryDisclosureIndicator()
|
andreas@3522
|
220 |
cell.addressLabel.text = address(forRowAt: indexPath)
|
andreas@3151
|
221 |
cell.subjectLabel.text = row.subject
|
andreas@3151
|
222 |
cell.summaryLabel.text = row.bodyPeek
|
andreas@3151
|
223 |
cell.isFlagged = row.isFlagged
|
andreas@3151
|
224 |
cell.isSeen = row.isSeen
|
andreas@3151
|
225 |
cell.hasAttachment = row.showAttchmentIcon
|
andreas@3151
|
226 |
cell.dateLabel.text = row.dateText
|
andreas@3151
|
227 |
// Set image from cache if any
|
andreas@3151
|
228 |
cell.setContactImage(image: row.senderContactImage)
|
andreas@3521
|
229 |
|
andreas@3151
|
230 |
let op = BlockOperation() { [weak self] in
|
andreas@3419
|
231 |
MessageModel.performAndWait {
|
andreas@3419
|
232 |
// ... and expensive computations in background
|
andreas@3419
|
233 |
guard let strongSelf = self else {
|
andreas@3419
|
234 |
// View is gone, nothing to do.
|
andreas@3419
|
235 |
return
|
andreas@3419
|
236 |
}
|
andreas@3419
|
237 |
|
andreas@3419
|
238 |
var senderImage: UIImage?
|
andreas@3419
|
239 |
if row.senderContactImage == nil {
|
andreas@3419
|
240 |
// image for identity has not been cached yet
|
andreas@3522
|
241 |
// Get (and cache) it here in the background ...
|
andreas@3419
|
242 |
senderImage = strongSelf.model?.senderImage(forCellAt: indexPath)
|
andreas@3419
|
243 |
|
andreas@3419
|
244 |
// ... and set it on the main queue
|
andreas@3419
|
245 |
DispatchQueue.main.async {
|
andreas@3419
|
246 |
if senderImage != nil && senderImage != cell.contactImageView.image {
|
andreas@3419
|
247 |
cell.contactImageView.image = senderImage
|
andreas@3419
|
248 |
}
|
andreas@3220
|
249 |
}
|
andreas@3220
|
250 |
}
|
andreas@3419
|
251 |
|
andreas@3419
|
252 |
let pEpRatingImage = strongSelf.model?.pEpRatingColorImage(forCellAt: indexPath)
|
andreas@3419
|
253 |
|
andreas@3419
|
254 |
// In theory we want to set all data in *one* async call. But as pEpRatingColorImage takes
|
andreas@3419
|
255 |
// very long, we are setting the sender image seperatelly.
|
andreas@3419
|
256 |
DispatchQueue.main.async {
|
andreas@3419
|
257 |
if pEpRatingImage != nil {
|
andreas@3419
|
258 |
cell.setPepRatingImage(image: pEpRatingImage)
|
andreas@3419
|
259 |
}
|
andreas@3151
|
260 |
}
|
andreas@3151
|
261 |
}
|
andreas@3151
|
262 |
}
|
andreas@3151
|
263 |
queue(operation: op, for: indexPath)
|
xavier@2967
|
264 |
}
|
andreas@3505
|
265 |
|
andreas@3505
|
266 |
private func showComposeView() {
|
andreas@3505
|
267 |
self.performSegue(withIdentifier: SegueIdentifier.segueEditDraft, sender: self)
|
andreas@3505
|
268 |
}
|
andreas@3505
|
269 |
|
andreas@3505
|
270 |
private func showEmail(forCellAt indexPath: IndexPath) {
|
andreas@3505
|
271 |
performSegue(withIdentifier: SegueIdentifier.segueShowEmail, sender: self)
|
andreas@3505
|
272 |
guard let vm = model else {
|
andreas@3505
|
273 |
Log.shared.errorAndCrash(component: #function, errorString: "No model.")
|
andreas@3505
|
274 |
return
|
andreas@3505
|
275 |
}
|
andreas@3505
|
276 |
vm.markRead(forIndexPath: indexPath)
|
andreas@3505
|
277 |
}
|
andreas@3208
|
278 |
|
andreas@3678
|
279 |
// MARK: - Action Filter Button
|
andreas@3208
|
280 |
|
andreas@3151
|
281 |
@IBAction func filterButtonHasBeenPressed(_ sender: UIBarButtonItem) {
|
andreas@3678
|
282 |
guard !loadingBlocked else {
|
andreas@3678
|
283 |
return
|
andreas@3678
|
284 |
}
|
andreas@3163
|
285 |
guard let vm = model else {
|
andreas@3163
|
286 |
Log.shared.errorAndCrash(component: #function, errorString: "We should have a model here")
|
andreas@3163
|
287 |
return
|
andreas@3163
|
288 |
}
|
andreas@3678
|
289 |
stopLoading()
|
andreas@3163
|
290 |
vm.isFilterEnabled = !vm.isFilterEnabled
|
xavier@3193
|
291 |
updateFilterButtonView()
|
xavier@1832
|
292 |
}
|
andreas@3208
|
293 |
|
andreas@3678
|
294 |
private func updateFilterButtonView() {
|
andreas@3163
|
295 |
guard let vm = model else {
|
andreas@3163
|
296 |
Log.shared.errorAndCrash(component: #function, errorString: "We should have a model here")
|
andreas@3163
|
297 |
return
|
andreas@3163
|
298 |
}
|
andreas@3208
|
299 |
|
andreas@3163
|
300 |
textFilterButton.isEnabled = vm.isFilterEnabled
|
andreas@3163
|
301 |
if textFilterButton.isEnabled {
|
andreas@3163
|
302 |
enableFilterButton.image = UIImage(named: "unread-icon-active")
|
andreas@3163
|
303 |
updateFilterText()
|
andreas@3163
|
304 |
} else {
|
andreas@3163
|
305 |
textFilterButton.title = ""
|
andreas@3163
|
306 |
enableFilterButton.image = UIImage(named: "unread-icon")
|
andreas@3163
|
307 |
}
|
andreas@3163
|
308 |
}
|
andreas@3208
|
309 |
|
andreas@3678
|
310 |
private func updateFilterText() {
|
xavier@3193
|
311 |
if let vm = model, let txt = vm.activeFilter?.title {
|
andreas@3163
|
312 |
textFilterButton.title = "Filter by: " + txt
|
andreas@3163
|
313 |
}
|
andreas@3151
|
314 |
}
|
andreas@3208
|
315 |
|
dirk@31
|
316 |
// MARK: - UITableViewDataSource
|
andreas@3208
|
317 |
|
dirk@784
|
318 |
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
andreas@3151
|
319 |
return model?.rowCount ?? 0
|
dirk@31
|
320 |
}
|
andreas@3208
|
321 |
|
dirk@784
|
322 |
override func tableView(_ tableView: UITableView,
|
dirk@784
|
323 |
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
andreas@3151
|
324 |
guard let cell = tableView.dequeueReusableCell(withIdentifier: EmailListViewCell.storyboardId,
|
andreas@3151
|
325 |
for: indexPath) as? EmailListViewCell
|
andreas@3151
|
326 |
else {
|
andreas@3151
|
327 |
Log.shared.errorAndCrash(component: #function, errorString: "Wrong cell!")
|
andreas@3151
|
328 |
return UITableViewCell()
|
andreas@3151
|
329 |
}
|
xavier@3368
|
330 |
cell.delegate = self
|
andreas@3151
|
331 |
configure(cell: cell, for: indexPath)
|
dirk@31
|
332 |
return cell
|
dirk@31
|
333 |
}
|
andreas@3208
|
334 |
|
andreas@3513
|
335 |
// MARK: - SwipeTableViewCellDelegate
|
xavier@3368
|
336 |
|
andreas@3911
|
337 |
func tableView(_ tableView: UITableView,
|
andreas@3911
|
338 |
editActionsForRowAt
|
andreas@3911
|
339 |
indexPath: IndexPath,
|
andreas@3911
|
340 |
for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
|
andreas@3917
|
341 |
|
andreas@3909
|
342 |
// Create swipe actions, taking the currently displayed folder into account
|
andreas@3514
|
343 |
var swipeActions = [SwipeAction]()
|
andreas@3911
|
344 |
|
andreas@3917
|
345 |
// Get messages parent folder
|
andreas@3917
|
346 |
let parentFolder: Folder
|
andreas@3917
|
347 |
if let folder = folderToShow, !(folder is UnifiedInbox) {
|
andreas@3917
|
348 |
// Do not bother our imperformant MessageModel if we already know the parent folder
|
andreas@3917
|
349 |
parentFolder = folder
|
andreas@3917
|
350 |
} else {
|
andreas@3917
|
351 |
// folderToShow is unified inbox, fetch parent folder from DB.
|
andreas@3917
|
352 |
guard let vm = model,
|
andreas@3917
|
353 |
let folder = vm.message(representedByRowAt: indexPath)?.parent else {
|
andreas@3917
|
354 |
Log.shared.errorAndCrash(component: #function, errorString: "Dangling Message")
|
andreas@3917
|
355 |
return nil
|
andreas@3917
|
356 |
}
|
andreas@3917
|
357 |
parentFolder = folder
|
andreas@3917
|
358 |
}
|
andreas@3917
|
359 |
|
andreas@3911
|
360 |
// Delete or Archive
|
andreas@3917
|
361 |
let defaultIsArchive = parentFolder.defaultDestructiveActionIsArchive
|
andreas@3917
|
362 |
let titleDestructive = defaultIsArchive ? "Archive" : "Delete"
|
andreas@3917
|
363 |
let descriptorDestructive: ActionDescriptor = defaultIsArchive ? .archive : .trash
|
andreas@3911
|
364 |
let archiveAction =
|
andreas@3911
|
365 |
SwipeAction(style: .destructive, title: titleDestructive) {action, indexPath in
|
andreas@3909
|
366 |
self.deleteAction(forCellAt: indexPath)
|
andreas@3514
|
367 |
}
|
andreas@3911
|
368 |
configure(action: archiveAction, with: descriptorDestructive)
|
andreas@3911
|
369 |
swipeActions.append(archiveAction)
|
andreas@3514
|
370 |
|
andreas@3909
|
371 |
// Flag
|
andreas@3514
|
372 |
let flagAction = SwipeAction(style: .default, title: "Flag") { action, indexPath in
|
andreas@3514
|
373 |
self.flagAction(forCellAt: indexPath)
|
andreas@3514
|
374 |
}
|
andreas@3514
|
375 |
flagAction.hidesWhenSelected = true
|
andreas@3514
|
376 |
configure(action: flagAction, with: .flag)
|
andreas@3514
|
377 |
swipeActions.append(flagAction)
|
andreas@3514
|
378 |
|
andreas@3909
|
379 |
// More (reply++)
|
andreas@3917
|
380 |
if parentFolder.folderType != .drafts {
|
andreas@3514
|
381 |
// Do not add "more" actions (reply...) to drafted mails.
|
andreas@3514
|
382 |
let moreAction = SwipeAction(style: .default, title: "More") { action, indexPath in
|
andreas@3514
|
383 |
self.moreAction(forCellAt: indexPath)
|
xavier@3368
|
384 |
}
|
andreas@3514
|
385 |
moreAction.hidesWhenSelected = true
|
andreas@3514
|
386 |
configure(action: moreAction, with: .more)
|
andreas@3514
|
387 |
swipeActions.append(moreAction)
|
xavier@3368
|
388 |
}
|
andreas@3514
|
389 |
return (orientation == .right ? swipeActions : nil)
|
xavier@3368
|
390 |
}
|
xavier@3368
|
391 |
|
xavier@3368
|
392 |
func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
|
xavier@3368
|
393 |
var options = SwipeTableOptions()
|
xavier@3368
|
394 |
options.expansionStyle = .destructive
|
xavier@3368
|
395 |
options.transitionStyle = .border
|
xavier@3368
|
396 |
options.buttonSpacing = 11
|
xavier@3368
|
397 |
return options
|
xavier@3368
|
398 |
}
|
xavier@3368
|
399 |
|
xavier@3368
|
400 |
func configure(action: SwipeAction, with descriptor: ActionDescriptor) {
|
xavier@3368
|
401 |
action.title = descriptor.title(forDisplayMode: buttonDisplayMode)
|
xavier@3368
|
402 |
action.image = descriptor.image(forStyle: buttonStyle, displayMode: buttonDisplayMode)
|
xavier@3368
|
403 |
|
xavier@3368
|
404 |
switch buttonStyle {
|
xavier@3368
|
405 |
case .backgroundColor:
|
xavier@3368
|
406 |
action.backgroundColor = descriptor.color
|
xavier@3368
|
407 |
case .circular:
|
xavier@3368
|
408 |
action.backgroundColor = .clear
|
xavier@3368
|
409 |
action.textColor = descriptor.color
|
xavier@3368
|
410 |
action.font = .systemFont(ofSize: 13)
|
xavier@3368
|
411 |
action.transitionDelegate = ScaleTransition.default
|
xavier@3368
|
412 |
}
|
xavier@3368
|
413 |
}
|
andreas@3208
|
414 |
|
andreas@3151
|
415 |
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
andreas@3151
|
416 |
cancelOperation(for: indexPath)
|
dirk@744
|
417 |
}
|
andreas@3208
|
418 |
|
andreas@3151
|
419 |
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
andreas@3505
|
420 |
guard let folder = folderToShow else {
|
andreas@3505
|
421 |
Log.shared.errorAndCrash(component: #function, errorString: "No folder")
|
andreas@3505
|
422 |
return
|
andreas@3505
|
423 |
}
|
andreas@3151
|
424 |
lastSelectedIndexPath = indexPath
|
andreas@3151
|
425 |
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
|
andreas@3505
|
426 |
|
andreas@3505
|
427 |
if folder.folderType == .drafts {
|
andreas@3505
|
428 |
showComposeView()
|
andreas@3505
|
429 |
} else {
|
andreas@3505
|
430 |
showEmail(forCellAt: indexPath)
|
andreas@3425
|
431 |
}
|
dirk@744
|
432 |
}
|
andreas@3278
|
433 |
|
andreas@3291
|
434 |
// Implemented to get informed about the scrolling position.
|
andreas@3291
|
435 |
// If the user has scrolled down (almost) to the end, we need to get older emails to display.
|
andreas@3291
|
436 |
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell,
|
andreas@3291
|
437 |
forRowAt indexPath: IndexPath) {
|
andreas@3278
|
438 |
guard let vm = model else {
|
andreas@3291
|
439 |
Log.shared.errorAndCrash(component: #function, errorString: "No model.")
|
andreas@3291
|
440 |
return
|
andreas@3278
|
441 |
}
|
andreas@3291
|
442 |
vm.fetchOlderMessagesIfRequired(forIndexPath: indexPath)
|
andreas@3278
|
443 |
}
|
andreas@3278
|
444 |
|
andreas@3151
|
445 |
// MARK: - Queue Handling
|
andreas@3678
|
446 |
|
andreas@3678
|
447 |
/// Cancels all operations and sets tableView.dataSource to nil.
|
andreas@3678
|
448 |
/// Used to avoid that an operation accesses an outdated view model
|
andreas@3678
|
449 |
private func stopLoading() {
|
andreas@3678
|
450 |
loadingBlocked = true
|
andreas@3678
|
451 |
tableView.dataSource = nil
|
andreas@3678
|
452 |
queue.cancelAllOperations()
|
andreas@3678
|
453 |
queue.waitUntilAllOperationsAreFinished()
|
andreas@3678
|
454 |
}
|
andreas@3208
|
455 |
|
andreas@3151
|
456 |
private func queue(operation op:Operation, for indexPath: IndexPath) {
|
andreas@3151
|
457 |
operations[indexPath] = op
|
andreas@3151
|
458 |
queue.addOperation(op)
|
dirk@1507
|
459 |
}
|
andreas@3208
|
460 |
|
andreas@3151
|
461 |
private func cancelOperation(for indexPath:IndexPath) {
|
andreas@3151
|
462 |
guard let op = operations.removeValue(forKey: indexPath) else {
|
andreas@3151
|
463 |
return
|
dirk@744
|
464 |
}
|
andreas@3151
|
465 |
if !op.isCancelled {
|
andreas@3151
|
466 |
op.cancel()
|
dirk@1507
|
467 |
}
|
dirk@744
|
468 |
}
|
andreas@3291
|
469 |
|
andreas@3291
|
470 |
// MARK: -
|
andreas@3291
|
471 |
|
andreas@3151
|
472 |
override func didReceiveMemoryWarning() {
|
andreas@3151
|
473 |
model?.freeMemory()
|
andreas@3151
|
474 |
}
|
andreas@3151
|
475 |
}
|
andreas@3151
|
476 |
|
andreas@3151
|
477 |
// MARK: - UISearchResultsUpdating, UISearchControllerDelegate
|
andreas@3151
|
478 |
|
andreas@3151
|
479 |
extension EmailListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
|
andreas@3151
|
480 |
public func updateSearchResults(for searchController: UISearchController) {
|
andreas@3151
|
481 |
guard let vm = model, let searchText = searchController.searchBar.text else {
|
andreas@3151
|
482 |
return
|
igor@1242
|
483 |
}
|
andreas@3163
|
484 |
vm.setSearchFilter(forSearchText: searchText)
|
igor@1242
|
485 |
}
|
andreas@3208
|
486 |
|
andreas@3151
|
487 |
func didDismissSearchController(_ searchController: UISearchController) {
|
andreas@3151
|
488 |
guard let vm = model else {
|
andreas@3151
|
489 |
return
|
andreas@3151
|
490 |
}
|
andreas@3151
|
491 |
vm.removeSearchFilter()
|
andreas@3151
|
492 |
}
|
andreas@3151
|
493 |
}
|
xavier@1623
|
494 |
|
andreas@3151
|
495 |
// MARK: - EmailListModelDelegate
|
andreas@3151
|
496 |
|
andreas@3151
|
497 |
extension EmailListViewController: EmailListViewModelDelegate {
|
andreas@3151
|
498 |
func emailListViewModel(viewModel: EmailListViewModel, didInsertDataAt indexPath: IndexPath) {
|
dirk@3866
|
499 |
Log.shared.info(component: #function, content: "\(model?.rowCount ?? 0)")
|
andreas@3166
|
500 |
tableView.beginUpdates()
|
andreas@3151
|
501 |
tableView.insertRows(at: [indexPath], with: .automatic)
|
andreas@3151
|
502 |
tableView.endUpdates()
|
andreas@3151
|
503 |
}
|
andreas@3208
|
504 |
|
andreas@3151
|
505 |
func emailListViewModel(viewModel: EmailListViewModel, didRemoveDataAt indexPath: IndexPath) {
|
dirk@3866
|
506 |
Log.shared.info(component: #function, content: "\(model?.rowCount ?? 0)")
|
andreas@3151
|
507 |
tableView.beginUpdates()
|
andreas@3151
|
508 |
tableView.deleteRows(at: [indexPath], with: .automatic)
|
andreas@3151
|
509 |
tableView.endUpdates()
|
andreas@3151
|
510 |
}
|
andreas@3208
|
511 |
|
andreas@3151
|
512 |
func emailListViewModel(viewModel: EmailListViewModel, didUpdateDataAt indexPath: IndexPath) {
|
dirk@3866
|
513 |
Log.shared.info(component: #function, content: "\(model?.rowCount ?? 0)")
|
andreas@3151
|
514 |
tableView.beginUpdates()
|
andreas@3151
|
515 |
tableView.reloadRows(at: [indexPath], with: .none)
|
andreas@3151
|
516 |
tableView.endUpdates()
|
andreas@3151
|
517 |
}
|
andreas@3677
|
518 |
|
andreas@3677
|
519 |
func updateView() {
|
andreas@3678
|
520 |
loadingBlocked = false
|
andreas@3678
|
521 |
tableView.dataSource = self
|
andreas@3678
|
522 |
tableView.reloadData()
|
andreas@3151
|
523 |
}
|
andreas@3151
|
524 |
}
|
andreas@3151
|
525 |
|
andreas@3151
|
526 |
// MARK: - ActionSheet & ActionSheet Actions
|
andreas@3151
|
527 |
|
andreas@3151
|
528 |
extension EmailListViewController {
|
andreas@3166
|
529 |
func showMoreActionSheet(forRowAt indexPath: IndexPath) {
|
andreas@3151
|
530 |
lastSelectedIndexPath = indexPath
|
igor@1242
|
531 |
let alertControler = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
igor@1438
|
532 |
alertControler.view.tintColor = .pEpGreen
|
igor@1242
|
533 |
let cancelAction = createCancelAction()
|
andreas@3151
|
534 |
let replyAction = createReplyAction()
|
andreas@3151
|
535 |
let replyAllAction = createReplyAllAction()
|
andreas@3151
|
536 |
let forwardAction = createForwardAction()
|
igor@1242
|
537 |
alertControler.addAction(cancelAction)
|
igor@1242
|
538 |
alertControler.addAction(replyAction)
|
xavier@1620
|
539 |
alertControler.addAction(replyAllAction)
|
igor@1242
|
540 |
alertControler.addAction(forwardAction)
|
igor@1414
|
541 |
if let popoverPresentationController = alertControler.popoverPresentationController {
|
andreas@3151
|
542 |
popoverPresentationController.sourceView = tableView
|
igor@1414
|
543 |
}
|
igor@1242
|
544 |
present(alertControler, animated: true, completion: nil)
|
igor@1242
|
545 |
}
|
andreas@3208
|
546 |
|
andreas@3151
|
547 |
// MARK: Action Sheet Actions
|
andreas@3208
|
548 |
|
igor@1242
|
549 |
func createCancelAction() -> UIAlertAction {
|
andreas@3151
|
550 |
return UIAlertAction(title: "Cancel", style: .cancel) { (action) in
|
andreas@3151
|
551 |
self.tableView.beginUpdates()
|
andreas@3151
|
552 |
self.tableView.setEditing(false, animated: true)
|
andreas@3151
|
553 |
self.tableView.endUpdates()
|
igor@1242
|
554 |
}
|
igor@1242
|
555 |
}
|
andreas@3208
|
556 |
|
andreas@3151
|
557 |
func createReplyAction() -> UIAlertAction {
|
andreas@3151
|
558 |
return UIAlertAction(title: "Reply", style: .default) { (action) in
|
andreas@3151
|
559 |
self.performSegue(withIdentifier: .segueReply, sender: self)
|
xavier@1620
|
560 |
}
|
xavier@1620
|
561 |
}
|
andreas@3208
|
562 |
|
andreas@3151
|
563 |
func createReplyAllAction() -> UIAlertAction {
|
andreas@3151
|
564 |
return UIAlertAction(title: "Reply All", style: .default) { (action) in
|
andreas@3151
|
565 |
self.performSegue(withIdentifier: .segueReplyAll, sender: self)
|
igor@1242
|
566 |
}
|
igor@1242
|
567 |
}
|
andreas@3208
|
568 |
|
andreas@3151
|
569 |
func createForwardAction() -> UIAlertAction {
|
andreas@3151
|
570 |
return UIAlertAction(title: "Forward", style: .default) { (action) in
|
andreas@3151
|
571 |
self.performSegue(withIdentifier: .segueForward, sender: self)
|
xavier@2369
|
572 |
}
|
igor@1301
|
573 |
}
|
igor@1301
|
574 |
}
|
igor@1338
|
575 |
|
andreas@3151
|
576 |
// MARK: - TableViewCell Actions
|
andreas@3151
|
577 |
|
andreas@3151
|
578 |
extension EmailListViewController {
|
andreas@3151
|
579 |
private func createRowAction(image: UIImage?,
|
xavier@3266
|
580 |
action: @escaping (UITableViewRowAction, IndexPath) -> Void
|
xavier@3266
|
581 |
) -> UITableViewRowAction {
|
xavier@3266
|
582 |
let rowAction = UITableViewRowAction(style: .normal, title: nil, handler: action)
|
andreas@3151
|
583 |
if let theImage = image {
|
andreas@3151
|
584 |
let iconColor = UIColor(patternImage: theImage)
|
andreas@3151
|
585 |
rowAction.backgroundColor = iconColor
|
andreas@3151
|
586 |
}
|
andreas@3151
|
587 |
return rowAction
|
andreas@3151
|
588 |
}
|
andreas@3208
|
589 |
|
xavier@3369
|
590 |
func flagAction(forCellAt indexPath: IndexPath) {
|
andreas@3151
|
591 |
guard let row = model?.row(for: indexPath) else {
|
andreas@3151
|
592 |
Log.shared.errorAndCrash(component: #function, errorString: "No data for indexPath!")
|
xavier@3369
|
593 |
return
|
andreas@3151
|
594 |
}
|
xavier@3369
|
595 |
if row.isFlagged {
|
xavier@3369
|
596 |
model?.unsetFlagged(forIndexPath: indexPath)
|
xavier@3369
|
597 |
} else {
|
xavier@3369
|
598 |
model?.setFlagged(forIndexPath: indexPath)
|
andreas@3151
|
599 |
}
|
andreas@3151
|
600 |
}
|
andreas@3208
|
601 |
|
xavier@3369
|
602 |
func deleteAction(forCellAt indexPath: IndexPath) {
|
andreas@3911
|
603 |
model?.delete(forIndexPath: indexPath)
|
andreas@3151
|
604 |
}
|
andreas@3208
|
605 |
|
xavier@3369
|
606 |
func moreAction(forCellAt indexPath: IndexPath) {
|
xavier@3369
|
607 |
self.showMoreActionSheet(forRowAt: indexPath)
|
andreas@3151
|
608 |
}
|
andreas@3151
|
609 |
}
|
andreas@3151
|
610 |
|
andreas@3151
|
611 |
// MARK: - SegueHandlerType
|
igor@1338
|
612 |
|
igor@1338
|
613 |
extension EmailListViewController: SegueHandlerType {
|
andreas@3208
|
614 |
|
igor@1338
|
615 |
enum SegueIdentifier: String {
|
igor@1338
|
616 |
case segueAddNewAccount
|
igor@1338
|
617 |
case segueShowEmail
|
igor@1338
|
618 |
case segueCompose
|
xavier@2715
|
619 |
case segueReply
|
xavier@1623
|
620 |
case segueReplyAll
|
xavier@1664
|
621 |
case segueForward
|
andreas@3505
|
622 |
case segueEditDraft
|
xavier@1865
|
623 |
case segueFilter
|
xavier@2248
|
624 |
case segueFolderViews
|
igor@1338
|
625 |
case noSegue
|
igor@1338
|
626 |
}
|
andreas@3208
|
627 |
|
igor@1338
|
628 |
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
andreas@3505
|
629 |
let segueId = segueIdentifier(for: segue)
|
andreas@3505
|
630 |
switch segueId {
|
andreas@3505
|
631 |
case .segueReply,
|
andreas@3505
|
632 |
.segueReplyAll,
|
andreas@3505
|
633 |
.segueForward,
|
andreas@3505
|
634 |
.segueCompose,
|
andreas@3505
|
635 |
.segueEditDraft:
|
andreas@3505
|
636 |
setupComposeViewController(for: segue)
|
andreas@3111
|
637 |
case .segueShowEmail:
|
andreas@2850
|
638 |
guard let vc = segue.destination as? EmailViewController,
|
andreas@3151
|
639 |
let indexPath = lastSelectedIndexPath,
|
andreas@3208
|
640 |
let message = model?.message(representedByRowAt: indexPath) else {
|
andreas@2850
|
641 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
642 |
return
|
igor@1338
|
643 |
}
|
andreas@2850
|
644 |
vc.appConfig = appConfig
|
andreas@3151
|
645 |
vc.message = message
|
andreas@3223
|
646 |
vc.folderShow = folderToShow
|
andreas@3166
|
647 |
vc.messageId = indexPath.row //that looks wrong
|
andreas@2850
|
648 |
case .segueFilter:
|
andreas@2850
|
649 |
guard let destiny = segue.destination as? FilterTableViewController else {
|
andreas@2850
|
650 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
651 |
return
|
andreas@2850
|
652 |
}
|
andreas@2850
|
653 |
destiny.appConfig = appConfig
|
andreas@3163
|
654 |
destiny.filterDelegate = model
|
andreas@2850
|
655 |
destiny.inFolder = false
|
andreas@3223
|
656 |
destiny.filterEnabled = folderToShow?.filter
|
andreas@2850
|
657 |
destiny.hidesBottomBarWhenPushed = true
|
andreas@2850
|
658 |
case .segueAddNewAccount:
|
andreas@2850
|
659 |
guard let vc = segue.destination as? LoginTableViewController else {
|
andreas@2850
|
660 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
661 |
return
|
andreas@2850
|
662 |
}
|
andreas@2850
|
663 |
vc.appConfig = appConfig
|
andreas@2850
|
664 |
vc.hidesBottomBarWhenPushed = true
|
xavier@1664
|
665 |
break
|
andreas@2850
|
666 |
case .segueFolderViews:
|
andreas@2850
|
667 |
guard let vC = segue.destination as? FolderTableViewController else {
|
andreas@2850
|
668 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
669 |
return
|
xavier@1865
|
670 |
}
|
andreas@2850
|
671 |
vC.appConfig = appConfig
|
andreas@2850
|
672 |
vC.hidesBottomBarWhenPushed = true
|
xavier@1865
|
673 |
break
|
andreas@2850
|
674 |
default:
|
andreas@2850
|
675 |
Log.shared.errorAndCrash(component: #function, errorString: "Unhandled segue")
|
dirk@1694
|
676 |
break
|
igor@1338
|
677 |
}
|
andreas@2838
|
678 |
}
|
andreas@3208
|
679 |
|
andreas@3166
|
680 |
@IBAction func segueUnwindAccountAdded(segue: UIStoryboardSegue) {
|
andreas@3151
|
681 |
// nothing to do.
|
andreas@3150
|
682 |
}
|
andreas@3505
|
683 |
|
andreas@3505
|
684 |
private func setupComposeViewController(for segue: UIStoryboardSegue) {
|
andreas@3505
|
685 |
let segueId = segueIdentifier(for: segue)
|
andreas@3505
|
686 |
guard
|
andreas@3505
|
687 |
let nav = segue.destination as? UINavigationController,
|
andreas@3505
|
688 |
let composeVc = nav.topViewController as? ComposeTableViewController,
|
andreas@3505
|
689 |
let composeMode = composeMode(for: segueId) else {
|
andreas@3505
|
690 |
Log.shared.errorAndCrash(component: #function,
|
andreas@3505
|
691 |
errorString: "composeViewController setup issue")
|
andreas@3505
|
692 |
return
|
andreas@3505
|
693 |
}
|
andreas@3505
|
694 |
composeVc.appConfig = appConfig
|
andreas@3505
|
695 |
composeVc.composeMode = composeMode
|
andreas@3505
|
696 |
composeVc.origin = folderToShow?.account.user
|
andreas@3505
|
697 |
if composeMode != .normal {
|
andreas@3505
|
698 |
// This is not a simple compose (but reply, forward or such),
|
andreas@3505
|
699 |
// thus we have to pass the original meaasge.
|
andreas@3505
|
700 |
guard
|
andreas@3505
|
701 |
let indexPath = lastSelectedIndexPath,
|
andreas@3505
|
702 |
let message = model?.message(representedByRowAt: indexPath) else {
|
andreas@3505
|
703 |
Log.shared.errorAndCrash(component: #function,
|
andreas@3505
|
704 |
errorString: "No original message")
|
andreas@3505
|
705 |
return
|
andreas@3505
|
706 |
}
|
andreas@3505
|
707 |
composeVc.originalMessage = message
|
andreas@3505
|
708 |
}
|
andreas@3505
|
709 |
}
|
andreas@3505
|
710 |
|
andreas@3505
|
711 |
private func composeMode(for segueId: SegueIdentifier) -> ComposeTableViewController.ComposeMode? {
|
andreas@3505
|
712 |
switch segueId {
|
andreas@3505
|
713 |
case .segueReply:
|
andreas@3505
|
714 |
return .replyFrom
|
andreas@3505
|
715 |
case .segueReplyAll:
|
andreas@3505
|
716 |
return .replyAll
|
andreas@3505
|
717 |
case .segueForward:
|
andreas@3505
|
718 |
return .forward
|
andreas@3505
|
719 |
case .segueCompose:
|
andreas@3505
|
720 |
return .normal
|
andreas@3505
|
721 |
case .segueEditDraft:
|
andreas@3505
|
722 |
return .draft
|
andreas@3505
|
723 |
default:
|
andreas@3505
|
724 |
return nil
|
andreas@3505
|
725 |
}
|
andreas@3505
|
726 |
}
|
dirk@1348
|
727 |
}
|
xavier@3368
|
728 |
|
xavier@3368
|
729 |
//enums to simplify configurations
|
xavier@3368
|
730 |
|
xavier@3368
|
731 |
enum ActionDescriptor {
|
andreas@3909
|
732 |
case read, more, flag, trash, archive
|
xavier@3368
|
733 |
|
xavier@3368
|
734 |
func title(forDisplayMode displayMode: ButtonDisplayMode) -> String? {
|
xavier@3368
|
735 |
guard displayMode != .imageOnly else { return nil }
|
xavier@3368
|
736 |
|
xavier@3368
|
737 |
switch self {
|
andreas@3909
|
738 |
case .read: return NSLocalizedString("Read", comment: "read button in slidw left menu")
|
andreas@3909
|
739 |
case .more: return NSLocalizedString("More", comment: "more button in slidw left menu")
|
andreas@3909
|
740 |
case .flag: return NSLocalizedString("Flag", comment: "read button in slidw left menu")
|
andreas@3909
|
741 |
case .trash: return NSLocalizedString("Trash", comment: "Trash button in slidw left menu")
|
andreas@3909
|
742 |
case .archive: return NSLocalizedString("Archive", comment: "Archive button in slidw left menu")
|
xavier@3368
|
743 |
}
|
xavier@3368
|
744 |
}
|
xavier@3368
|
745 |
|
xavier@3368
|
746 |
func image(forStyle style: ButtonStyle, displayMode: ButtonDisplayMode) -> UIImage? {
|
xavier@3368
|
747 |
guard displayMode != .titleOnly else { return nil }
|
xavier@3368
|
748 |
|
xavier@3368
|
749 |
let name: String
|
xavier@3368
|
750 |
switch self {
|
xavier@3368
|
751 |
case .read: name = "read"
|
xavier@3368
|
752 |
case .more: name = "more"
|
xavier@3368
|
753 |
case .flag: name = "flag"
|
xavier@3368
|
754 |
case .trash: name = "trash"
|
andreas@3909
|
755 |
case .archive: name = "trash"
|
xavier@3368
|
756 |
}
|
xavier@3368
|
757 |
|
xavier@3368
|
758 |
return UIImage(named: "swipe-" + name)
|
xavier@3368
|
759 |
}
|
xavier@3368
|
760 |
|
xavier@3368
|
761 |
var color: UIColor {
|
xavier@3368
|
762 |
switch self {
|
xavier@3368
|
763 |
case .read: return #colorLiteral(red: 0.2980392157, green: 0.8509803922, blue: 0.3921568627, alpha: 1)
|
xavier@3368
|
764 |
case .more: return #colorLiteral(red: 0.7803494334, green: 0.7761332393, blue: 0.7967314124, alpha: 1)
|
xavier@3368
|
765 |
case .flag: return #colorLiteral(red: 1, green: 0.5803921569, blue: 0, alpha: 1)
|
xavier@3368
|
766 |
case .trash: return #colorLiteral(red: 1, green: 0.2352941176, blue: 0.1882352941, alpha: 1)
|
andreas@3909
|
767 |
case .archive: return #colorLiteral(red: 0.2980392157, green: 0.8509803922, blue: 0.3921568627, alpha: 1)
|
xavier@3368
|
768 |
}
|
xavier@3368
|
769 |
}
|
xavier@3368
|
770 |
}
|
xavier@3368
|
771 |
|
xavier@3368
|
772 |
enum ButtonDisplayMode {
|
xavier@3368
|
773 |
case titleAndImage, titleOnly, imageOnly
|
xavier@3368
|
774 |
}
|
xavier@3368
|
775 |
|
xavier@3368
|
776 |
enum ButtonStyle {
|
xavier@3368
|
777 |
case backgroundColor, circular
|
xavier@3368
|
778 |
}
|