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@3223
|
15 |
|
andreas@3151
|
16 |
func updateLastLookAt() {
|
andreas@3224
|
17 |
guard let saveFolder = folderToShow else {
|
andreas@3151
|
18 |
return
|
andreas@3151
|
19 |
}
|
andreas@3151
|
20 |
saveFolder.updateLastLookAt()
|
andreas@3151
|
21 |
}
|
andreas@3208
|
22 |
|
andreas@3151
|
23 |
private var model: EmailListViewModel?
|
andreas@3208
|
24 |
|
andreas@3151
|
25 |
private let queue: OperationQueue = {
|
andreas@3151
|
26 |
let createe = OperationQueue()
|
andreas@3151
|
27 |
createe.qualityOfService = .userInteractive
|
andreas@3151
|
28 |
createe.maxConcurrentOperationCount = 10
|
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@3208
|
40 |
|
andreas@3151
|
41 |
// MARK: - Outlets
|
andreas@3208
|
42 |
|
dirk@2067
|
43 |
@IBOutlet weak var enableFilterButton: UIBarButtonItem!
|
dirk@2067
|
44 |
@IBOutlet weak var textFilterButton: UIBarButtonItem!
|
andreas@3151
|
45 |
@IBOutlet var showFoldersButton: UIBarButtonItem!
|
andreas@3208
|
46 |
|
andreas@3151
|
47 |
// MARK: - Life Cycle
|
andreas@3208
|
48 |
|
dirk@275
|
49 |
override func viewDidLoad() {
|
ylandert@935
|
50 |
super.viewDidLoad()
|
dirk@2189
|
51 |
title = NSLocalizedString("Inbox", comment: "General name for (unified) inbox")
|
igor@1284
|
52 |
UIHelper.emailListTableHeight(self.tableView)
|
andreas@3163
|
53 |
self.textFilterButton.isEnabled = false
|
igor@1301
|
54 |
addSearchBar()
|
dirk@275
|
55 |
}
|
andreas@3208
|
56 |
|
dirk@784
|
57 |
override func viewWillAppear(_ animated: Bool) {
|
igor@1301
|
58 |
super.viewWillAppear(animated)
|
xavier@2325
|
59 |
self.navigationController?.setToolbarHidden(false, animated: true)
|
dirk@1344
|
60 |
if MiscUtil.isUnitTest() {
|
dirk@1344
|
61 |
return
|
dirk@1344
|
62 |
}
|
andreas@3223
|
63 |
|
dirk@1826
|
64 |
setDefaultColors()
|
andreas@3151
|
65 |
setup()
|
andreas@3208
|
66 |
|
dirk@2191
|
67 |
// Mark this folder as having been looked at by the user
|
andreas@3151
|
68 |
updateLastLookAt()
|
andreas@3151
|
69 |
setupFoldersBarButton()
|
andreas@3151
|
70 |
}
|
andreas@3208
|
71 |
|
andreas@3151
|
72 |
// MARK: - NavigationBar
|
andreas@3208
|
73 |
|
andreas@3151
|
74 |
private func hideFoldersNavigationBarButton() {
|
andreas@3151
|
75 |
self.showFoldersButton.isEnabled = false
|
andreas@3151
|
76 |
self.showFoldersButton.tintColor = UIColor.clear
|
andreas@3151
|
77 |
}
|
andreas@3208
|
78 |
|
andreas@3151
|
79 |
private func showFoldersNavigationBarButton() {
|
andreas@3151
|
80 |
self.showFoldersButton.isEnabled = true
|
andreas@3151
|
81 |
self.showFoldersButton.tintColor = nil
|
andreas@3151
|
82 |
}
|
andreas@3208
|
83 |
|
andreas@3151
|
84 |
private func resetModel() {
|
andreas@3223
|
85 |
if folderToShow != nil {
|
andreas@3291
|
86 |
model = EmailListViewModel(delegate: self,
|
andreas@3291
|
87 |
messageSyncService: appConfig.messageSyncService,
|
andreas@3291
|
88 |
folderToShow: folderToShow)
|
xavier@2585
|
89 |
}
|
dirk@1348
|
90 |
}
|
andreas@3208
|
91 |
|
andreas@3151
|
92 |
private func setup() {
|
andreas@3289
|
93 |
if noAccountsExist() {
|
andreas@3272
|
94 |
// No account exists. Show account setup.
|
andreas@3272
|
95 |
performSegue(withIdentifier:.segueAddNewAccount, sender: self)
|
andreas@3289
|
96 |
} else if let vm = model {
|
andreas@3223
|
97 |
// We came back from e.g EmailView ...
|
andreas@3223
|
98 |
updateFilterText()
|
andreas@3223
|
99 |
// ... so we want to update "seen" status
|
andreas@3226
|
100 |
vm.reloadData()
|
andreas@3272
|
101 |
} else if folderToShow == nil {
|
andreas@3223
|
102 |
// We have not been created to show a specific folder, thus we show unified inbox
|
andreas@3151
|
103 |
folderToShow = UnifiedInbox()
|
andreas@3223
|
104 |
resetModel()
|
andreas@3272
|
105 |
} else if model == nil {
|
andreas@3223
|
106 |
// We still got no model, because:
|
andreas@3223
|
107 |
// - We are not coming back from a pushed view (for instance ComposeEmailView)
|
andreas@3223
|
108 |
// - We are not a UnifiedInbox
|
andreas@3226
|
109 |
// So we have been created to show a specific folder. Show it!
|
andreas@3223
|
110 |
resetModel()
|
andreas@3151
|
111 |
}
|
andreas@3272
|
112 |
|
andreas@3151
|
113 |
self.title = realNameOfFolderToShow()
|
andreas@2683
|
114 |
}
|
andreas@3223
|
115 |
|
andreas@3223
|
116 |
private func weCameBackFromAPushedView() -> Bool {
|
andreas@3223
|
117 |
return model != nil
|
andreas@3223
|
118 |
}
|
andreas@3208
|
119 |
|
andreas@3151
|
120 |
private func noAccountsExist() -> Bool {
|
andreas@3151
|
121 |
return Account.all().isEmpty
|
igor@1301
|
122 |
}
|
andreas@3208
|
123 |
|
andreas@3151
|
124 |
private func setupFoldersBarButton() {
|
andreas@3151
|
125 |
if let size = navigationController?.viewControllers.count, size > 1 {
|
andreas@3151
|
126 |
hideFoldersNavigationBarButton()
|
andreas@3151
|
127 |
} else {
|
andreas@3151
|
128 |
showFoldersNavigationBarButton()
|
xavier@2132
|
129 |
}
|
igor@1338
|
130 |
}
|
andreas@3208
|
131 |
|
andreas@3151
|
132 |
private func addSearchBar() {
|
igor@1301
|
133 |
searchController.searchResultsUpdater = self
|
igor@1301
|
134 |
searchController.dimsBackgroundDuringPresentation = false
|
igor@1301
|
135 |
searchController.delegate = self
|
igor@1301
|
136 |
definesPresentationContext = true
|
igor@1301
|
137 |
tableView.tableHeaderView = searchController.searchBar
|
xavier@3209
|
138 |
tableView.setContentOffset(CGPoint(x: 0.0, y: searchController.searchBar.frame.size.height), animated: false)
|
igor@1301
|
139 |
}
|
andreas@3208
|
140 |
|
andreas@3151
|
141 |
// MARK: - Other
|
andreas@3208
|
142 |
|
andreas@3151
|
143 |
private func realNameOfFolderToShow() -> String? {
|
andreas@3224
|
144 |
return folderToShow?.realName
|
andreas@2697
|
145 |
}
|
andreas@3208
|
146 |
|
andreas@3151
|
147 |
private func configure(cell: EmailListViewCell, for indexPath: IndexPath) {
|
andreas@3151
|
148 |
// Configure lightweight stuff on main thread ...
|
andreas@3151
|
149 |
guard let saveModel = model else {
|
andreas@3151
|
150 |
return
|
xavier@2967
|
151 |
}
|
andreas@3151
|
152 |
guard let row = saveModel.row(for: indexPath) else {
|
andreas@3151
|
153 |
Log.shared.errorAndCrash(component: #function, errorString: "We should have a row here")
|
andreas@3151
|
154 |
return
|
andreas@3151
|
155 |
}
|
andreas@3151
|
156 |
cell.senderLabel.text = row.from
|
andreas@3151
|
157 |
cell.subjectLabel.text = row.subject
|
andreas@3151
|
158 |
cell.summaryLabel.text = row.bodyPeek
|
andreas@3151
|
159 |
cell.isFlagged = row.isFlagged
|
andreas@3151
|
160 |
cell.isSeen = row.isSeen
|
andreas@3151
|
161 |
cell.hasAttachment = row.showAttchmentIcon
|
andreas@3151
|
162 |
cell.dateLabel.text = row.dateText
|
andreas@3151
|
163 |
// Set image from cache if any
|
andreas@3151
|
164 |
cell.setContactImage(image: row.senderContactImage)
|
andreas@3208
|
165 |
|
andreas@3151
|
166 |
let op = BlockOperation() { [weak self] in
|
andreas@3151
|
167 |
// ... and expensive computations in background
|
andreas@3151
|
168 |
guard let strongSelf = self else {
|
andreas@3151
|
169 |
// View is gone, nothing to do.
|
andreas@3151
|
170 |
return
|
andreas@3151
|
171 |
}
|
andreas@3208
|
172 |
|
andreas@3151
|
173 |
var senderImage: UIImage?
|
andreas@3151
|
174 |
if row.senderContactImage == nil {
|
andreas@3220
|
175 |
// image for identity has not been cached yet
|
andreas@3220
|
176 |
// Get and cache it here in the background ...
|
andreas@3151
|
177 |
senderImage = strongSelf.model?.senderImage(forCellAt: indexPath)
|
andreas@3220
|
178 |
|
andreas@3220
|
179 |
// ... and set it on the main queue
|
andreas@3220
|
180 |
DispatchQueue.main.async {
|
andreas@3220
|
181 |
if senderImage != nil && senderImage != cell.contactImageView.image {
|
andreas@3220
|
182 |
cell.contactImageView.image = senderImage
|
andreas@3220
|
183 |
}
|
andreas@3220
|
184 |
}
|
andreas@3151
|
185 |
}
|
andreas@3220
|
186 |
|
andreas@3220
|
187 |
let pEpRatingImage = strongSelf.model?.pEpRatingColorImage(forCellAt: indexPath)
|
andreas@3220
|
188 |
|
andreas@3151
|
189 |
// In theory we want to set all data in *one* async call. But as pEpRatingColorImage takes
|
andreas@3151
|
190 |
// very long, we are setting the sender image seperatelly.
|
andreas@3151
|
191 |
DispatchQueue.main.async {
|
andreas@3151
|
192 |
if pEpRatingImage != nil {
|
andreas@3151
|
193 |
cell.setPepRatingImage(image: pEpRatingImage)
|
andreas@3151
|
194 |
}
|
andreas@3151
|
195 |
}
|
andreas@3151
|
196 |
}
|
andreas@3151
|
197 |
queue(operation: op, for: indexPath)
|
xavier@2967
|
198 |
}
|
andreas@3208
|
199 |
|
andreas@3151
|
200 |
// MARK: - Actions
|
andreas@3208
|
201 |
|
andreas@3151
|
202 |
@IBAction func filterButtonHasBeenPressed(_ sender: UIBarButtonItem) {
|
andreas@3163
|
203 |
guard let vm = model else {
|
andreas@3163
|
204 |
Log.shared.errorAndCrash(component: #function, errorString: "We should have a model here")
|
andreas@3163
|
205 |
return
|
andreas@3163
|
206 |
}
|
andreas@3163
|
207 |
vm.isFilterEnabled = !vm.isFilterEnabled
|
xavier@3193
|
208 |
updateFilterButtonView()
|
xavier@1832
|
209 |
}
|
andreas@3208
|
210 |
|
xavier@3193
|
211 |
func updateFilterButtonView() {
|
andreas@3163
|
212 |
guard let vm = model else {
|
andreas@3163
|
213 |
Log.shared.errorAndCrash(component: #function, errorString: "We should have a model here")
|
andreas@3163
|
214 |
return
|
andreas@3163
|
215 |
}
|
andreas@3208
|
216 |
|
andreas@3163
|
217 |
textFilterButton.isEnabled = vm.isFilterEnabled
|
andreas@3163
|
218 |
if textFilterButton.isEnabled {
|
andreas@3163
|
219 |
enableFilterButton.image = UIImage(named: "unread-icon-active")
|
andreas@3163
|
220 |
updateFilterText()
|
andreas@3163
|
221 |
} else {
|
andreas@3163
|
222 |
textFilterButton.title = ""
|
andreas@3163
|
223 |
enableFilterButton.image = UIImage(named: "unread-icon")
|
andreas@3163
|
224 |
}
|
andreas@3163
|
225 |
}
|
andreas@3208
|
226 |
|
andreas@3163
|
227 |
func updateFilterText() {
|
xavier@3193
|
228 |
if let vm = model, let txt = vm.activeFilter?.title {
|
andreas@3163
|
229 |
textFilterButton.title = "Filter by: " + txt
|
andreas@3163
|
230 |
}
|
andreas@3151
|
231 |
}
|
andreas@3208
|
232 |
|
dirk@31
|
233 |
// MARK: - UITableViewDataSource
|
andreas@3208
|
234 |
|
dirk@784
|
235 |
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
andreas@3151
|
236 |
return model?.rowCount ?? 0
|
dirk@31
|
237 |
}
|
andreas@3208
|
238 |
|
dirk@784
|
239 |
override func tableView(_ tableView: UITableView,
|
dirk@784
|
240 |
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
andreas@3151
|
241 |
guard let cell = tableView.dequeueReusableCell(withIdentifier: EmailListViewCell.storyboardId,
|
andreas@3151
|
242 |
for: indexPath) as? EmailListViewCell
|
andreas@3151
|
243 |
else {
|
andreas@3151
|
244 |
Log.shared.errorAndCrash(component: #function, errorString: "Wrong cell!")
|
andreas@3151
|
245 |
return UITableViewCell()
|
andreas@3151
|
246 |
}
|
xavier@3368
|
247 |
cell.delegate = self
|
andreas@3151
|
248 |
configure(cell: cell, for: indexPath)
|
dirk@31
|
249 |
return cell
|
dirk@31
|
250 |
}
|
andreas@3208
|
251 |
|
dirk@719
|
252 |
// MARK: - UITableViewDelegate
|
xavier@3368
|
253 |
|
xavier@3368
|
254 |
|
xavier@3368
|
255 |
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
|
xavier@3368
|
256 |
if indexPath.section == 0 {
|
xavier@3368
|
257 |
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
|
xavier@3369
|
258 |
self.deleteAction(forCellAt: indexPath)
|
xavier@3368
|
259 |
}
|
xavier@3368
|
260 |
configure(action: deleteAction, with: .trash)
|
xavier@3368
|
261 |
|
xavier@3368
|
262 |
let flagAction = SwipeAction(style: .default, title: "Flag") { action, indexPath in
|
xavier@3369
|
263 |
self.flagAction(forCellAt: indexPath)
|
xavier@3368
|
264 |
}
|
xavier@3368
|
265 |
flagAction.hidesWhenSelected = true
|
xavier@3368
|
266 |
configure(action: flagAction, with: .flag)
|
xavier@3368
|
267 |
|
xavier@3368
|
268 |
let moreAction = SwipeAction(style: .default, title: "More") { action, indexPath in
|
xavier@3369
|
269 |
self.moreAction(forCellAt: indexPath)
|
xavier@3368
|
270 |
}
|
xavier@3368
|
271 |
moreAction.hidesWhenSelected = true
|
xavier@3368
|
272 |
configure(action: moreAction, with: .more)
|
xavier@3368
|
273 |
return (orientation == .right ? [deleteAction, flagAction, moreAction] : nil)
|
xavier@3368
|
274 |
}
|
xavier@3368
|
275 |
|
xavier@3368
|
276 |
return nil
|
xavier@3368
|
277 |
}
|
xavier@3368
|
278 |
|
xavier@3368
|
279 |
func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
|
xavier@3368
|
280 |
var options = SwipeTableOptions()
|
xavier@3368
|
281 |
options.expansionStyle = .destructive
|
xavier@3368
|
282 |
options.transitionStyle = .border
|
xavier@3368
|
283 |
options.buttonSpacing = 11
|
xavier@3368
|
284 |
return options
|
xavier@3368
|
285 |
}
|
xavier@3368
|
286 |
|
xavier@3368
|
287 |
func configure(action: SwipeAction, with descriptor: ActionDescriptor) {
|
xavier@3368
|
288 |
action.title = descriptor.title(forDisplayMode: buttonDisplayMode)
|
xavier@3368
|
289 |
action.image = descriptor.image(forStyle: buttonStyle, displayMode: buttonDisplayMode)
|
xavier@3368
|
290 |
|
xavier@3368
|
291 |
switch buttonStyle {
|
xavier@3368
|
292 |
case .backgroundColor:
|
xavier@3368
|
293 |
action.backgroundColor = descriptor.color
|
xavier@3368
|
294 |
case .circular:
|
xavier@3368
|
295 |
action.backgroundColor = .clear
|
xavier@3368
|
296 |
action.textColor = descriptor.color
|
xavier@3368
|
297 |
action.font = .systemFont(ofSize: 13)
|
xavier@3368
|
298 |
action.transitionDelegate = ScaleTransition.default
|
xavier@3368
|
299 |
}
|
xavier@3368
|
300 |
}
|
andreas@3208
|
301 |
|
andreas@3151
|
302 |
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
andreas@3151
|
303 |
cancelOperation(for: indexPath)
|
dirk@744
|
304 |
}
|
andreas@3208
|
305 |
|
andreas@3151
|
306 |
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
andreas@3151
|
307 |
lastSelectedIndexPath = indexPath
|
andreas@3151
|
308 |
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
|
andreas@3151
|
309 |
performSegue(withIdentifier: SegueIdentifier.segueShowEmail, sender: self)
|
dirk@744
|
310 |
}
|
andreas@3278
|
311 |
|
andreas@3291
|
312 |
// Implemented to get informed about the scrolling position.
|
andreas@3291
|
313 |
// If the user has scrolled down (almost) to the end, we need to get older emails to display.
|
andreas@3291
|
314 |
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell,
|
andreas@3291
|
315 |
forRowAt indexPath: IndexPath) {
|
andreas@3278
|
316 |
guard let vm = model else {
|
andreas@3291
|
317 |
Log.shared.errorAndCrash(component: #function, errorString: "No model.")
|
andreas@3291
|
318 |
return
|
andreas@3278
|
319 |
}
|
andreas@3291
|
320 |
vm.fetchOlderMessagesIfRequired(forIndexPath: indexPath)
|
andreas@3278
|
321 |
}
|
andreas@3278
|
322 |
|
andreas@3151
|
323 |
// MARK: - Queue Handling
|
andreas@3208
|
324 |
|
andreas@3151
|
325 |
private func queue(operation op:Operation, for indexPath: IndexPath) {
|
andreas@3151
|
326 |
operations[indexPath] = op
|
andreas@3151
|
327 |
queue.addOperation(op)
|
dirk@1507
|
328 |
}
|
andreas@3208
|
329 |
|
andreas@3151
|
330 |
private func cancelOperation(for indexPath:IndexPath) {
|
andreas@3151
|
331 |
guard let op = operations.removeValue(forKey: indexPath) else {
|
andreas@3151
|
332 |
return
|
dirk@744
|
333 |
}
|
andreas@3151
|
334 |
if !op.isCancelled {
|
andreas@3151
|
335 |
op.cancel()
|
dirk@1507
|
336 |
}
|
dirk@744
|
337 |
}
|
andreas@3291
|
338 |
|
andreas@3291
|
339 |
// MARK: -
|
andreas@3291
|
340 |
|
andreas@3151
|
341 |
override func didReceiveMemoryWarning() {
|
andreas@3151
|
342 |
model?.freeMemory()
|
andreas@3151
|
343 |
}
|
andreas@3151
|
344 |
}
|
andreas@3151
|
345 |
|
andreas@3151
|
346 |
// MARK: - UISearchResultsUpdating, UISearchControllerDelegate
|
andreas@3151
|
347 |
|
andreas@3151
|
348 |
extension EmailListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
|
andreas@3151
|
349 |
public func updateSearchResults(for searchController: UISearchController) {
|
andreas@3151
|
350 |
guard let vm = model, let searchText = searchController.searchBar.text else {
|
andreas@3151
|
351 |
return
|
igor@1242
|
352 |
}
|
andreas@3163
|
353 |
vm.setSearchFilter(forSearchText: searchText)
|
igor@1242
|
354 |
}
|
andreas@3208
|
355 |
|
andreas@3151
|
356 |
func didDismissSearchController(_ searchController: UISearchController) {
|
andreas@3151
|
357 |
guard let vm = model else {
|
andreas@3151
|
358 |
return
|
andreas@3151
|
359 |
}
|
andreas@3151
|
360 |
vm.removeSearchFilter()
|
andreas@3151
|
361 |
}
|
andreas@3151
|
362 |
}
|
xavier@1623
|
363 |
|
andreas@3151
|
364 |
// MARK: - EmailListModelDelegate
|
andreas@3151
|
365 |
|
andreas@3151
|
366 |
extension EmailListViewController: EmailListViewModelDelegate {
|
andreas@3151
|
367 |
func emailListViewModel(viewModel: EmailListViewModel, didInsertDataAt indexPath: IndexPath) {
|
andreas@3166
|
368 |
tableView.beginUpdates()
|
andreas@3151
|
369 |
tableView.insertRows(at: [indexPath], with: .automatic)
|
andreas@3151
|
370 |
tableView.endUpdates()
|
andreas@3151
|
371 |
}
|
andreas@3208
|
372 |
|
andreas@3151
|
373 |
func emailListViewModel(viewModel: EmailListViewModel, didRemoveDataAt indexPath: IndexPath) {
|
andreas@3151
|
374 |
tableView.beginUpdates()
|
andreas@3151
|
375 |
tableView.deleteRows(at: [indexPath], with: .automatic)
|
andreas@3151
|
376 |
tableView.endUpdates()
|
andreas@3151
|
377 |
}
|
andreas@3208
|
378 |
|
andreas@3151
|
379 |
func emailListViewModel(viewModel: EmailListViewModel, didUpdateDataAt indexPath: IndexPath) {
|
andreas@3151
|
380 |
tableView.beginUpdates()
|
andreas@3151
|
381 |
tableView.reloadRows(at: [indexPath], with: .none)
|
andreas@3151
|
382 |
tableView.endUpdates()
|
andreas@3151
|
383 |
}
|
andreas@3208
|
384 |
|
andreas@3151
|
385 |
func updateView() {
|
andreas@3151
|
386 |
self.tableView.reloadData()
|
andreas@3151
|
387 |
}
|
andreas@3151
|
388 |
}
|
andreas@3151
|
389 |
|
andreas@3151
|
390 |
// MARK: - ActionSheet & ActionSheet Actions
|
andreas@3151
|
391 |
|
andreas@3151
|
392 |
extension EmailListViewController {
|
andreas@3166
|
393 |
func showMoreActionSheet(forRowAt indexPath: IndexPath) {
|
andreas@3151
|
394 |
lastSelectedIndexPath = indexPath
|
igor@1242
|
395 |
let alertControler = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
igor@1438
|
396 |
alertControler.view.tintColor = .pEpGreen
|
igor@1242
|
397 |
let cancelAction = createCancelAction()
|
andreas@3151
|
398 |
let replyAction = createReplyAction()
|
andreas@3151
|
399 |
let replyAllAction = createReplyAllAction()
|
andreas@3151
|
400 |
let forwardAction = createForwardAction()
|
igor@1242
|
401 |
alertControler.addAction(cancelAction)
|
igor@1242
|
402 |
alertControler.addAction(replyAction)
|
xavier@1620
|
403 |
alertControler.addAction(replyAllAction)
|
igor@1242
|
404 |
alertControler.addAction(forwardAction)
|
igor@1414
|
405 |
if let popoverPresentationController = alertControler.popoverPresentationController {
|
andreas@3151
|
406 |
popoverPresentationController.sourceView = tableView
|
igor@1414
|
407 |
}
|
igor@1242
|
408 |
present(alertControler, animated: true, completion: nil)
|
igor@1242
|
409 |
}
|
andreas@3208
|
410 |
|
andreas@3151
|
411 |
// MARK: Action Sheet Actions
|
andreas@3208
|
412 |
|
igor@1242
|
413 |
func createCancelAction() -> UIAlertAction {
|
andreas@3151
|
414 |
return UIAlertAction(title: "Cancel", style: .cancel) { (action) in
|
andreas@3151
|
415 |
self.tableView.beginUpdates()
|
andreas@3151
|
416 |
self.tableView.setEditing(false, animated: true)
|
andreas@3151
|
417 |
self.tableView.endUpdates()
|
igor@1242
|
418 |
}
|
igor@1242
|
419 |
}
|
andreas@3208
|
420 |
|
andreas@3151
|
421 |
func createReplyAction() -> UIAlertAction {
|
andreas@3151
|
422 |
return UIAlertAction(title: "Reply", style: .default) { (action) in
|
andreas@3151
|
423 |
self.performSegue(withIdentifier: .segueReply, sender: self)
|
xavier@1620
|
424 |
}
|
xavier@1620
|
425 |
}
|
andreas@3208
|
426 |
|
andreas@3151
|
427 |
func createReplyAllAction() -> UIAlertAction {
|
andreas@3151
|
428 |
return UIAlertAction(title: "Reply All", style: .default) { (action) in
|
andreas@3151
|
429 |
self.performSegue(withIdentifier: .segueReplyAll, sender: self)
|
igor@1242
|
430 |
}
|
igor@1242
|
431 |
}
|
andreas@3208
|
432 |
|
andreas@3151
|
433 |
func createForwardAction() -> UIAlertAction {
|
andreas@3151
|
434 |
return UIAlertAction(title: "Forward", style: .default) { (action) in
|
andreas@3151
|
435 |
self.performSegue(withIdentifier: .segueForward, sender: self)
|
xavier@2369
|
436 |
}
|
igor@1301
|
437 |
}
|
igor@1301
|
438 |
}
|
igor@1338
|
439 |
|
andreas@3151
|
440 |
// MARK: - TableViewCell Actions
|
andreas@3151
|
441 |
|
andreas@3151
|
442 |
extension EmailListViewController {
|
andreas@3151
|
443 |
private func createRowAction(image: UIImage?,
|
xavier@3266
|
444 |
action: @escaping (UITableViewRowAction, IndexPath) -> Void
|
xavier@3266
|
445 |
) -> UITableViewRowAction {
|
xavier@3266
|
446 |
let rowAction = UITableViewRowAction(style: .normal, title: nil, handler: action)
|
andreas@3151
|
447 |
if let theImage = image {
|
andreas@3151
|
448 |
let iconColor = UIColor(patternImage: theImage)
|
andreas@3151
|
449 |
rowAction.backgroundColor = iconColor
|
andreas@3151
|
450 |
}
|
andreas@3151
|
451 |
return rowAction
|
andreas@3151
|
452 |
}
|
andreas@3208
|
453 |
|
xavier@3369
|
454 |
func flagAction(forCellAt indexPath: IndexPath) {
|
andreas@3151
|
455 |
guard let row = model?.row(for: indexPath) else {
|
andreas@3151
|
456 |
Log.shared.errorAndCrash(component: #function, errorString: "No data for indexPath!")
|
xavier@3369
|
457 |
return
|
andreas@3151
|
458 |
}
|
xavier@3369
|
459 |
if row.isFlagged {
|
xavier@3369
|
460 |
model?.unsetFlagged(forIndexPath: indexPath)
|
xavier@3369
|
461 |
} else {
|
xavier@3369
|
462 |
model?.setFlagged(forIndexPath: indexPath)
|
andreas@3151
|
463 |
}
|
xavier@3369
|
464 |
tableView.beginUpdates()
|
xavier@3369
|
465 |
tableView.setEditing(false, animated: true)
|
xavier@3369
|
466 |
tableView.reloadRows(at: [indexPath], with: .none)
|
xavier@3369
|
467 |
tableView.endUpdates()
|
andreas@3151
|
468 |
}
|
andreas@3208
|
469 |
|
xavier@3369
|
470 |
func deleteAction(forCellAt indexPath: IndexPath) {
|
xavier@3369
|
471 |
model?.delete(forIndexPath: indexPath) // mark for deletion/trash
|
andreas@3151
|
472 |
}
|
andreas@3208
|
473 |
|
xavier@3369
|
474 |
func moreAction(forCellAt indexPath: IndexPath) {
|
xavier@3369
|
475 |
self.showMoreActionSheet(forRowAt: indexPath)
|
andreas@3151
|
476 |
}
|
andreas@3151
|
477 |
}
|
andreas@3151
|
478 |
|
andreas@3151
|
479 |
// MARK: - SegueHandlerType
|
igor@1338
|
480 |
|
igor@1338
|
481 |
extension EmailListViewController: SegueHandlerType {
|
andreas@3208
|
482 |
|
igor@1338
|
483 |
enum SegueIdentifier: String {
|
igor@1338
|
484 |
case segueAddNewAccount
|
igor@1338
|
485 |
case segueShowEmail
|
igor@1338
|
486 |
case segueCompose
|
xavier@2715
|
487 |
case segueReply
|
xavier@1623
|
488 |
case segueReplyAll
|
xavier@1664
|
489 |
case segueForward
|
xavier@1865
|
490 |
case segueFilter
|
xavier@2248
|
491 |
case segueFolderViews
|
igor@1338
|
492 |
case noSegue
|
igor@1338
|
493 |
}
|
andreas@3208
|
494 |
|
andreas@3110
|
495 |
private func setup(composeViewController vc: ComposeTableViewController,
|
andreas@3110
|
496 |
composeMode: ComposeTableViewController.ComposeMode = .normal,
|
andreas@3110
|
497 |
originalMessage: Message? = nil) {
|
andreas@3110
|
498 |
vc.appConfig = appConfig
|
andreas@3110
|
499 |
vc.composeMode = composeMode
|
andreas@3110
|
500 |
vc.originalMessage = originalMessage
|
andreas@3223
|
501 |
vc.origin = folderToShow?.account.user
|
andreas@3110
|
502 |
}
|
andreas@3208
|
503 |
|
igor@1338
|
504 |
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
igor@1338
|
505 |
switch segueIdentifier(for: segue) {
|
xavier@2715
|
506 |
case .segueReply:
|
andreas@2850
|
507 |
guard let nav = segue.destination as? UINavigationController,
|
xavier@2715
|
508 |
let destination = nav.topViewController as? ComposeTableViewController,
|
andreas@3151
|
509 |
let indexPath = lastSelectedIndexPath,
|
andreas@3151
|
510 |
let message = model?.message(representedByRowAt: indexPath) else {
|
andreas@2850
|
511 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
512 |
return
|
xavier@2715
|
513 |
}
|
andreas@3110
|
514 |
setup(composeViewController: destination, composeMode: .replyFrom,
|
andreas@3151
|
515 |
originalMessage: message)
|
xavier@1623
|
516 |
case .segueReplyAll:
|
andreas@2850
|
517 |
guard let nav = segue.destination as? UINavigationController,
|
xavier@1623
|
518 |
let destination = nav.topViewController as? ComposeTableViewController,
|
andreas@3151
|
519 |
let indexPath = lastSelectedIndexPath,
|
andreas@3151
|
520 |
let message = model?.message(representedByRowAt: indexPath) else {
|
andreas@2850
|
521 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
522 |
return
|
xavier@1623
|
523 |
}
|
andreas@3110
|
524 |
setup(composeViewController: destination, composeMode: .replyAll,
|
andreas@3151
|
525 |
originalMessage: message)
|
andreas@3111
|
526 |
case .segueShowEmail:
|
andreas@2850
|
527 |
guard let vc = segue.destination as? EmailViewController,
|
andreas@3151
|
528 |
let indexPath = lastSelectedIndexPath,
|
andreas@3208
|
529 |
let message = model?.message(representedByRowAt: indexPath) else {
|
andreas@2850
|
530 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
531 |
return
|
igor@1338
|
532 |
}
|
andreas@2850
|
533 |
vc.appConfig = appConfig
|
andreas@3151
|
534 |
vc.message = message
|
andreas@3223
|
535 |
vc.folderShow = folderToShow
|
andreas@3166
|
536 |
vc.messageId = indexPath.row //that looks wrong
|
xavier@1664
|
537 |
case .segueForward:
|
andreas@2850
|
538 |
guard let nav = segue.destination as? UINavigationController,
|
xavier@1664
|
539 |
let destination = nav.topViewController as? ComposeTableViewController,
|
andreas@3151
|
540 |
let indexPath = lastSelectedIndexPath,
|
andreas@3151
|
541 |
let message = model?.message(representedByRowAt: indexPath) else {
|
andreas@2850
|
542 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
543 |
return
|
xavier@1664
|
544 |
}
|
andreas@3110
|
545 |
setup(composeViewController: destination, composeMode: .forward,
|
andreas@3151
|
546 |
originalMessage: message)
|
andreas@2850
|
547 |
case .segueFilter:
|
andreas@2850
|
548 |
guard let destiny = segue.destination as? FilterTableViewController else {
|
andreas@2850
|
549 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
550 |
return
|
andreas@2850
|
551 |
}
|
andreas@2850
|
552 |
destiny.appConfig = appConfig
|
andreas@3163
|
553 |
destiny.filterDelegate = model
|
andreas@2850
|
554 |
destiny.inFolder = false
|
andreas@3223
|
555 |
destiny.filterEnabled = folderToShow?.filter
|
andreas@2850
|
556 |
destiny.hidesBottomBarWhenPushed = true
|
andreas@2850
|
557 |
case .segueAddNewAccount:
|
andreas@2850
|
558 |
guard let vc = segue.destination as? LoginTableViewController else {
|
andreas@2850
|
559 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
560 |
return
|
andreas@2850
|
561 |
}
|
andreas@2850
|
562 |
vc.appConfig = appConfig
|
andreas@2850
|
563 |
vc.hidesBottomBarWhenPushed = true
|
xavier@1664
|
564 |
break
|
andreas@2850
|
565 |
case .segueFolderViews:
|
andreas@2850
|
566 |
guard let vC = segue.destination as? FolderTableViewController else {
|
andreas@2850
|
567 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
568 |
return
|
xavier@1865
|
569 |
}
|
andreas@2850
|
570 |
vC.appConfig = appConfig
|
andreas@2850
|
571 |
vC.hidesBottomBarWhenPushed = true
|
xavier@1865
|
572 |
break
|
andreas@2850
|
573 |
case .segueCompose:
|
andreas@2850
|
574 |
guard let nav = segue.destination as? UINavigationController,
|
dirk@2957
|
575 |
let destination = nav.rootViewController as? ComposeTableViewController else {
|
andreas@2850
|
576 |
Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
|
andreas@2850
|
577 |
return
|
dirk@2240
|
578 |
}
|
andreas@3110
|
579 |
setup(composeViewController: destination)
|
andreas@2850
|
580 |
default:
|
andreas@2850
|
581 |
Log.shared.errorAndCrash(component: #function, errorString: "Unhandled segue")
|
dirk@1694
|
582 |
break
|
igor@1338
|
583 |
}
|
andreas@2838
|
584 |
}
|
andreas@3208
|
585 |
|
andreas@3166
|
586 |
@IBAction func segueUnwindAccountAdded(segue: UIStoryboardSegue) {
|
andreas@3151
|
587 |
// nothing to do.
|
andreas@3150
|
588 |
}
|
dirk@1348
|
589 |
}
|
xavier@3368
|
590 |
|
xavier@3368
|
591 |
//enums to simplify configurations
|
xavier@3368
|
592 |
|
xavier@3368
|
593 |
enum ActionDescriptor {
|
xavier@3368
|
594 |
case read, more, flag, trash
|
xavier@3368
|
595 |
|
xavier@3368
|
596 |
func title(forDisplayMode displayMode: ButtonDisplayMode) -> String? {
|
xavier@3368
|
597 |
guard displayMode != .imageOnly else { return nil }
|
xavier@3368
|
598 |
|
xavier@3368
|
599 |
switch self {
|
xavier@3368
|
600 |
case .read: return NSLocalizedString("Read", comment: "read button")
|
xavier@3368
|
601 |
case .more: return NSLocalizedString("More", comment: "more button")
|
xavier@3368
|
602 |
case .flag: return NSLocalizedString("Flag", comment: "read button")
|
xavier@3368
|
603 |
case .trash: return NSLocalizedString("Trash", comment: "Trash button")
|
xavier@3368
|
604 |
}
|
xavier@3368
|
605 |
}
|
xavier@3368
|
606 |
|
xavier@3368
|
607 |
func image(forStyle style: ButtonStyle, displayMode: ButtonDisplayMode) -> UIImage? {
|
xavier@3368
|
608 |
guard displayMode != .titleOnly else { return nil }
|
xavier@3368
|
609 |
|
xavier@3368
|
610 |
let name: String
|
xavier@3368
|
611 |
switch self {
|
xavier@3368
|
612 |
case .read: name = "read"
|
xavier@3368
|
613 |
case .more: name = "more"
|
xavier@3368
|
614 |
case .flag: name = "flag"
|
xavier@3368
|
615 |
case .trash: name = "trash"
|
xavier@3368
|
616 |
}
|
xavier@3368
|
617 |
|
xavier@3368
|
618 |
return UIImage(named: "swipe-" + name)
|
xavier@3368
|
619 |
}
|
xavier@3368
|
620 |
|
xavier@3368
|
621 |
var color: UIColor {
|
xavier@3368
|
622 |
switch self {
|
xavier@3368
|
623 |
case .read: return #colorLiteral(red: 0.2980392157, green: 0.8509803922, blue: 0.3921568627, alpha: 1)
|
xavier@3368
|
624 |
case .more: return #colorLiteral(red: 0.7803494334, green: 0.7761332393, blue: 0.7967314124, alpha: 1)
|
xavier@3368
|
625 |
case .flag: return #colorLiteral(red: 1, green: 0.5803921569, blue: 0, alpha: 1)
|
xavier@3368
|
626 |
case .trash: return #colorLiteral(red: 1, green: 0.2352941176, blue: 0.1882352941, alpha: 1)
|
xavier@3368
|
627 |
}
|
xavier@3368
|
628 |
}
|
xavier@3368
|
629 |
}
|
xavier@3368
|
630 |
|
xavier@3368
|
631 |
enum ButtonDisplayMode {
|
xavier@3368
|
632 |
case titleAndImage, titleOnly, imageOnly
|
xavier@3368
|
633 |
}
|
xavier@3368
|
634 |
|
xavier@3368
|
635 |
enum ButtonStyle {
|
xavier@3368
|
636 |
case backgroundColor, circular
|
xavier@3368
|
637 |
}
|