Skip to content

Commit a482447

Browse files
authored
Merge pull request #149 from Hirobreak/feedlist
Feeds List, Username and Contact Name Improvements
2 parents 85924f2 + 7f8db87 commit a482447

File tree

16 files changed

+180
-148
lines changed

16 files changed

+180
-148
lines changed

iOS-Email-Client.xcodeproj/project.pbxproj

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,17 @@
200200
7CC44BE220FD583A004BF759 /* CreateCustomJSONFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC44BE120FD583A004BF759 /* CreateCustomJSONFileTests.swift */; };
201201
7CC44BF120FEE1ED004BF759 /* AESCipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC44BF020FEE1ED004BF759 /* AESCipherTests.swift */; };
202202
7CCA060D210B687200BC4CEF /* Dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCA060C210B687200BC4CEF /* Dummy.swift */; };
203+
7CCC82E92125E83E00D33F84 /* FirebaseAnalytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A42D21111DCE00A40C74 /* FirebaseAnalytics.framework */; };
204+
7CCC82EA2125E83F00D33F84 /* FirebaseCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A42C21111DCD00A40C74 /* FirebaseCore.framework */; };
205+
7CCC82EB2125E83F00D33F84 /* FirebaseCoreDiagnostics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A43021111DCF00A40C74 /* FirebaseCoreDiagnostics.framework */; };
206+
7CCC82EC2125E83F00D33F84 /* FirebaseInstanceID.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A43121111DCF00A40C74 /* FirebaseInstanceID.framework */; };
207+
7CCC82ED2125E83F00D33F84 /* FirebaseNanoPB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A42E21111DCE00A40C74 /* FirebaseNanoPB.framework */; };
208+
7CCC82EE2125E83F00D33F84 /* GoogleToolboxForMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A42B21111DCD00A40C74 /* GoogleToolboxForMac.framework */; };
209+
7CCC82EF2125E83F00D33F84 /* nanopb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A42F21111DCF00A40C74 /* nanopb.framework */; };
210+
7CCC82F02125E83F00D33F84 /* FirebaseMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A43921111DE400A40C74 /* FirebaseMessaging.framework */; };
211+
7CCC82F12125E83F00D33F84 /* Protobuf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A43A21111DE400A40C74 /* Protobuf.framework */; };
212+
7CCC82F22125E83F00D33F84 /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41F3A277207C6470004BDF90 /* Fabric.framework */; };
213+
7CCC82F32125E83F00D33F84 /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41F3A275207C6466004BDF90 /* Crashlytics.framework */; };
203214
7CD4A43221111DCF00A40C74 /* GoogleToolboxForMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A42B21111DCD00A40C74 /* GoogleToolboxForMac.framework */; };
204215
7CD4A43321111DCF00A40C74 /* FirebaseCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A42C21111DCD00A40C74 /* FirebaseCore.framework */; };
205216
7CD4A43421111DCF00A40C74 /* FirebaseAnalytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CD4A42D21111DCE00A40C74 /* FirebaseAnalytics.framework */; };
@@ -528,7 +539,18 @@
528539
isa = PBXFrameworksBuildPhase;
529540
buildActionMask = 2147483647;
530541
files = (
542+
7CCC82EE2125E83F00D33F84 /* GoogleToolboxForMac.framework in Frameworks */,
543+
7CCC82E92125E83E00D33F84 /* FirebaseAnalytics.framework in Frameworks */,
544+
7CCC82EF2125E83F00D33F84 /* nanopb.framework in Frameworks */,
545+
7CCC82EB2125E83F00D33F84 /* FirebaseCoreDiagnostics.framework in Frameworks */,
546+
7CCC82EC2125E83F00D33F84 /* FirebaseInstanceID.framework in Frameworks */,
547+
7CCC82ED2125E83F00D33F84 /* FirebaseNanoPB.framework in Frameworks */,
548+
7CCC82F02125E83F00D33F84 /* FirebaseMessaging.framework in Frameworks */,
549+
7CCC82F12125E83F00D33F84 /* Protobuf.framework in Frameworks */,
531550
7C394E7E210BD5DB00AE3297 /* SignalProtocolFramework.framework in Frameworks */,
551+
7CCC82EA2125E83F00D33F84 /* FirebaseCore.framework in Frameworks */,
552+
7CCC82F32125E83F00D33F84 /* Crashlytics.framework in Frameworks */,
553+
7CCC82F22125E83F00D33F84 /* Fabric.framework in Frameworks */,
532554
);
533555
runOnlyForDeploymentPostprocessing = 0;
534556
};
@@ -1490,14 +1512,17 @@
14901512
FRAMEWORK_SEARCH_PATHS = (
14911513
"$(inherited)",
14921514
"$(PROJECT_DIR)/Carthage/Build/iOS",
1515+
"$(PROJECT_DIR)",
14931516
);
14941517
INFOPLIST_FILE = "iOS-Email-ClientTests/Info.plist";
14951518
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
1519+
OTHER_LDFLAGS = "-ObjC";
14961520
PRODUCT_BUNDLE_IDENTIFIER = "com.criptext.iOS-Email-ClientTests";
14971521
PRODUCT_NAME = "$(TARGET_NAME)";
14981522
SWIFT_VERSION = 4.0;
14991523
TARGETED_DEVICE_FAMILY = "1,2";
15001524
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOS-Email-Client.app/iOS-Email-Client";
1525+
USER_HEADER_SEARCH_PATHS = "${SRCROOT}/iOS-Email-Client";
15011526
};
15021527
name = Debug;
15031528
};
@@ -1512,14 +1537,17 @@
15121537
FRAMEWORK_SEARCH_PATHS = (
15131538
"$(inherited)",
15141539
"$(PROJECT_DIR)/Carthage/Build/iOS",
1540+
"$(PROJECT_DIR)",
15151541
);
15161542
INFOPLIST_FILE = "iOS-Email-ClientTests/Info.plist";
15171543
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
1544+
OTHER_LDFLAGS = "-ObjC";
15181545
PRODUCT_BUNDLE_IDENTIFIER = "com.criptext.iOS-Email-ClientTests";
15191546
PRODUCT_NAME = "$(TARGET_NAME)";
15201547
SWIFT_VERSION = 4.0;
15211548
TARGETED_DEVICE_FAMILY = "1,2";
15221549
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOS-Email-Client.app/iOS-Email-Client";
1550+
USER_HEADER_SEARCH_PATHS = "${SRCROOT}/iOS-Email-Client";
15231551
};
15241552
name = Release;
15251553
};

iOS-Email-Client/AppDelegate.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
3232
UIApplication.shared.statusBarStyle = .lightContent
3333

3434
let config = Realm.Configuration(
35-
schemaVersion: 2,
35+
schemaVersion: 3,
3636
migrationBlock: { migration, oldSchemaVersion in
37-
if (oldSchemaVersion == 1) {
38-
37+
if (oldSchemaVersion < 3) {
38+
migration.enumerateObjects(ofType: FeedItem.className()){ oldObject, newObject in
39+
if oldObject?["email"] == nil,
40+
let newFeed = newObject {
41+
migration.delete(newFeed)
42+
}
43+
}
3944
}
4045
})
4146

iOS-Email-Client/Controllers/FeedViewController.swift

Lines changed: 34 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@
88

99
import Foundation
1010
import SignalProtocolFramework
11+
import RealmSwift
1112

1213
class FeedViewController: UIViewController{
1314
let HEADER_HEIGHT : CGFloat = 42.0
1415
var feedsData = FeedsData()
1516
@IBOutlet weak var noFeedsView: UIView!
1617
@IBOutlet weak var feedsTableView: UITableView!
18+
var newFeedsToken: NotificationToken?
19+
var oldFeedsToken: NotificationToken?
20+
1721
var mailboxVC : InboxViewController! {
1822
get {
1923
return self.navigationDrawerController?.rootViewController.childViewControllers.first as? InboxViewController
@@ -40,35 +44,48 @@ class FeedViewController: UIViewController{
4044
}
4145
}
4246

43-
func loadFeeds(clear: Bool = false){
44-
let date = clear ? Date() : feedsData.oldFeeds.last?.date ?? (feedsData.newFeeds.last?.date ?? Date())
45-
let feeds = DBManager.getFeeds(since: date, limit: 20, lastSeen: lastSeen)
46-
if(clear){
47-
feedsData.newFeeds = feeds.0
48-
feedsData.oldFeeds = feeds.1
49-
} else {
50-
feedsData.newFeeds.append(contentsOf: feeds.0)
51-
feedsData.oldFeeds.append(contentsOf: feeds.1)
47+
func loadFeeds(){
48+
let feeds = DBManager.getFeeds(since: Date() , limit: 20, lastSeen: lastSeen)
49+
feedsData.newFeeds = feeds.0
50+
feedsData.oldFeeds = feeds.1
51+
newFeedsToken = feedsData.newFeeds.observe { [weak self] changes in
52+
guard let tableView = self?.feedsTableView else {
53+
return
54+
}
55+
switch(changes){
56+
case .initial:
57+
tableView.reloadData()
58+
case .update(_, let deletions, let insertions, let modifications):
59+
tableView.applyChanges(section: 0, deletions: deletions, insertions: insertions, updates: modifications)
60+
default:
61+
break
62+
}
5263
}
53-
self.feedsData.loadingFeeds = false
54-
if(feeds.0.isEmpty && feeds.1.isEmpty){
55-
self.feedsData.reachedEnd = true
64+
oldFeedsToken = feedsData.oldFeeds.observe { [weak self] changes in
65+
guard let tableView = self?.feedsTableView else {
66+
return
67+
}
68+
switch(changes){
69+
case .initial:
70+
tableView.reloadData()
71+
case .update(_, let deletions, let insertions, let modifications):
72+
tableView.applyChanges(section: 1, deletions: deletions, insertions: insertions, updates: modifications)
73+
default:
74+
break
75+
}
5676
}
5777
checkIfFeedsEmpty()
5878
feedsTableView.reloadData()
5979
}
6080

6181
func viewClosed() {
62-
loadFeeds(clear: true)
82+
loadFeeds()
6383
}
6484
}
6585

6686
extension FeedViewController: UITableViewDelegate, UITableViewDataSource{
6787

6888
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
69-
if(indexPath.section > 0 && indexPath.row == feedsData.oldFeeds.count){
70-
return buildLastRow()
71-
}
7289
let cell = tableView.dequeueReusableCell(withIdentifier: "feedTableCellView", for: indexPath) as! FeedTableViewCell
7390
let feed = (indexPath.section == 0 ? feedsData.newFeeds[indexPath.row] : feedsData.oldFeeds[indexPath.row])
7491
cell.setLabels(feed.header, feed.subject, feed.formattedDate)
@@ -81,7 +98,7 @@ extension FeedViewController: UITableViewDelegate, UITableViewDataSource{
8198
if(section == 0){
8299
return feedsData.newFeeds.count
83100
}
84-
return feedsData.oldFeeds.count + 1
101+
return feedsData.oldFeeds.count
85102
}
86103

87104
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
@@ -101,17 +118,10 @@ extension FeedViewController: UITableViewDelegate, UITableViewDataSource{
101118
}
102119

103120
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
104-
guard !isLoaderRow(indexPath) else {
105-
return nil
106-
}
107121
let delete = deleteAction(tableView, indexPath: indexPath)
108122
return UISwipeActionsConfiguration(actions: [delete])
109123
}
110124

111-
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
112-
return !isLoaderRow(indexPath)
113-
}
114-
115125
func muteAction(_ tableView: UITableView, indexPath: IndexPath) -> UIContextualAction{
116126
let feed = (indexPath.section == 0 ? feedsData.newFeeds[indexPath.row] : feedsData.oldFeeds[indexPath.row])
117127
let action = UIContextualAction(style: .normal, title: feed.isMuted ? "Unmute" : "Mute" ){
@@ -125,21 +135,7 @@ extension FeedViewController: UITableViewDelegate, UITableViewDataSource{
125135
return action
126136
}
127137

128-
func buildLastRow() -> UITableViewCell{
129-
let footerView = feedsTableView.dequeueReusableCell(withIdentifier: "EndCell") as! TableEndViewCell
130-
if(feedsData.reachedEnd){
131-
footerView.displayMessage("")
132-
}else{
133-
footerView.displayLoader()
134-
}
135-
return footerView
136-
}
137-
138138
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
139-
guard !isLoaderRow(indexPath) else {
140-
tableView.deselectRow(at: indexPath, animated: false)
141-
return
142-
}
143139
let feed = (indexPath.section == 0 ? feedsData.newFeeds[indexPath.row] : feedsData.oldFeeds[indexPath.row])
144140
let workingLabel = feed.email.isSpam ? SystemLabel.spam.id : (feed.email.isTrash ? SystemLabel.trash.id : SystemLabel.sent.id)
145141
guard let selectedThread = DBManager.getThread(threadId: feed.email.threadId, label: workingLabel) else {
@@ -154,32 +150,14 @@ extension FeedViewController: UITableViewDelegate, UITableViewDataSource{
154150
let feed: FeedItem
155151
if(indexPath.section == 0){
156152
feed = self.feedsData.newFeeds[indexPath.row]
157-
self.feedsData.newFeeds.remove(at: indexPath.row)
158153
}else{
159154
feed = self.feedsData.oldFeeds[indexPath.row]
160-
self.feedsData.oldFeeds.remove(at: indexPath.row)
161155
}
162-
tableView.deleteRows(at: [indexPath], with: .automatic)
163156
DBManager.delete(feed: feed)
164157
completion(true)
165158
}
166159
action.image = #imageLiteral(resourceName: "delete-icon")
167160
action.backgroundColor = UIColor(red: 220/255, green: 77/255, blue: 72/255, alpha: 1)
168161
return action
169162
}
170-
171-
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
172-
guard !feedsData.loadingFeeds && !feedsData.reachedEnd && isLoaderRow(indexPath) else {
173-
return
174-
}
175-
feedsData.loadingFeeds = true
176-
tableView.reloadRows(at: [indexPath], with: .automatic)
177-
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)){
178-
self.loadFeeds()
179-
}
180-
}
181-
182-
func isLoaderRow(_ indexPath: IndexPath) -> Bool {
183-
return indexPath.section == 1 && indexPath.row == feedsData.oldFeeds.count
184-
}
185163
}

iOS-Email-Client/Controllers/InboxViewController.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ extension InboxViewController: EventHandlerDelegate {
283283
let feedVC = self.navigationDrawerController?.rightViewController as? FeedViewController else {
284284
return
285285
}
286-
feedVC.loadFeeds(clear: true)
287286
let badgeCounter = feedVC.feedsData.newFeeds.count
288287
updateFeedsBadge(counter: badgeCounter)
289288
}

iOS-Email-Client/Controllers/Login/SignUpViewController.swift

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class SignUpViewController: UIViewController{
4040
let tap : UIGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
4141
view.addGestureRecognizer(tap)
4242

43-
usernameTextField.delegate = self
4443
usernameTextField.autocorrectionType = .no
4544
usernameTextField.autocapitalizationType = .none
4645
usernameTextField.markView = usernameMark
@@ -107,14 +106,10 @@ class SignUpViewController: UIViewController{
107106
}
108107

109108
func checkUsername(){
110-
guard !usernameTextField.isEmpty,
111-
let username = usernameTextField.text else {
112-
let inputError = "please enter your username"
113-
usernameTextField.setStatus(.invalid, inputError)
114-
return
115-
}
116-
guard username.count >= MIN_USERNAME_LENGTH else {
117-
let inputError = "use more than 2 characters"
109+
usernameTextField.text = usernameTextField.text?.lowercased()
110+
guard let username = usernameTextField.text,
111+
isValidUsername(username) else {
112+
let inputError = "min 3 letters, start/end with a-z, valid 0-9, . _ -"
118113
usernameTextField.setStatus(.invalid, inputError)
119114
return
120115
}
@@ -269,6 +264,13 @@ class SignUpViewController: UIViewController{
269264
return emailTest.evaluate(with: testStr)
270265
}
271266

267+
func isValidUsername(_ testStr:String) -> Bool {
268+
let emailRegEx = "^[a-z][.a-z0-9_-]+[a-z0-9]$"
269+
270+
let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
271+
return emailTest.evaluate(with: testStr)
272+
}
273+
272274
func checkToEnableDisableCreateButton(){
273275
createAccountButton.isEnabled = (usernameTextField.isValid && fullnameTextField.isValid && passwordTextField.isValid && confirmPasswordTextField.isValid && emailTextField.isNotInvalid)
274276
if(createAccountButton.isEnabled){
@@ -285,15 +287,3 @@ class SignUpViewController: UIViewController{
285287
}
286288
}
287289
}
288-
289-
extension SignUpViewController: UITextFieldDelegate {
290-
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
291-
guard !string.isEmpty else {
292-
return true
293-
}
294-
guard range.location > 0 else {
295-
return string.range(of: "[a-z]", options: .regularExpression) != nil
296-
}
297-
return string.range(of: "^[.a-z0-9_-]*$", options: .regularExpression) != nil
298-
}
299-
}

iOS-Email-Client/Extensions.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,16 @@ extension Formatter {
596596
}()
597597
}
598598

599+
extension UITableView {
600+
func applyChanges(section: Int = 0, deletions: [Int], insertions: [Int], updates: [Int]){
601+
beginUpdates()
602+
deleteRows(at: deletions.map({IndexPath(row: $0, section: section)}), with: .automatic)
603+
insertRows(at: insertions.map({IndexPath(row: $0, section: section)}), with: .automatic)
604+
reloadRows(at: updates.map({IndexPath(row: $0, section: section)}), with: .automatic)
605+
endUpdates()
606+
}
607+
}
608+
599609
enum MessageType: Int {
600610
case none = 0
601611
case cipherText = 1

iOS-Email-Client/Managers/ContactUtils.swift

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,17 @@ class ContactUtils {
1313
static let store = CNContactStore()
1414

1515
private class func parseContact(_ contactString: String) -> Contact {
16-
guard !contactString.starts(with: "<") else {
17-
let cString = contactString.replacingOccurrences(of: "<", with: "").replacingOccurrences(of: ">", with: "")
18-
guard let existingContact = DBManager.getContact(cString) else {
19-
return Contact(value: ["displayName": cString.split(separator: "@")[0], "email": cString])
20-
}
21-
return existingContact
22-
}
23-
let splittedContact = contactString.split(separator: "<")
24-
guard splittedContact.count > 1 else {
25-
guard let existingContact = DBManager.getContact(contactString) else {
26-
return Contact(value: ["displayName": contactString.split(separator: "@")[0], "email": contactString])
27-
}
28-
return existingContact
29-
}
30-
let contactName = splittedContact[0].prefix((splittedContact[0].count - 1))
31-
let email = splittedContact[1].prefix((splittedContact[1].count - 1)).replacingOccurrences(of: ">", with: "")
32-
guard let existingContact = DBManager.getContact(email) else {
33-
let newContact = Contact(value: ["displayName": contactName, "email": email])
16+
let contactMetadata = self.getStringEmailName(contact: contactString);
17+
guard let existingContact = DBManager.getContact(contactMetadata.0) else {
18+
let newContact = Contact(value: ["displayName": contactMetadata.1, "email": contactMetadata.0])
3419
DBManager.store([newContact])
3520
return newContact
3621
}
37-
DBManager.update(contact: existingContact, name: String(contactName))
22+
let isNameFromEmail = existingContact.email.starts(with: existingContact.displayName)
23+
let isNewNameFromEmail = contactMetadata.0.starts(with: contactMetadata.1)
24+
if (!isNameFromEmail && !isNewNameFromEmail && contactMetadata.1 != existingContact.displayName) {
25+
DBManager.update(contact: existingContact, name: contactMetadata.1)
26+
}
3827
return existingContact
3928
}
4029

@@ -49,4 +38,14 @@ class ContactUtils {
4938
DBManager.store([emailContact])
5039
}
5140
}
41+
42+
class func getStringEmailName(contact: String) -> (String, String) {
43+
let myContact = NSString(string: contact.replacingOccurrences(of: "\"", with: ""))
44+
let pattern = "<(.*)>"
45+
let regex = try! NSRegularExpression(pattern: pattern, options: [])
46+
let matches = regex.matches(in: contact, options: [], range: NSRange(location: 0, length: myContact.length))
47+
let email = (matches.first != nil ? myContact.substring(with: matches.first!.range(at: 1)) : String(myContact)).lowercased()
48+
let name = matches.first != nil && contact.split(separator: "<").count > 1 ? contact.split(separator: "<")[0] : email.split(separator: "@")[0]
49+
return (email, String(name.trimmingCharacters(in: .whitespacesAndNewlines)))
50+
}
5251
}

0 commit comments

Comments
 (0)