diff --git a/Message Filter Extension/MessageFilterExtension.swift b/Message Filter Extension/MessageFilterExtension.swift index c6158bb..412ceaf 100644 --- a/Message Filter Extension/MessageFilterExtension.swift +++ b/Message Filter Extension/MessageFilterExtension.swift @@ -39,6 +39,6 @@ extension MessageFilterExtension: ILMessageFilterQueryHandling { let body = queryRequest.messageBody ?? "" let sender = queryRequest.sender ?? "" - return self.extensionManager.evaluateMessage(body: body, sender: sender) + return self.extensionManager.evaluateMessage(body: body, sender: sender).action } } diff --git a/Simply Filter SMS.xcodeproj/project.pbxproj b/Simply Filter SMS.xcodeproj/project.pbxproj index 6d464e0..9e72603 100644 --- a/Simply Filter SMS.xcodeproj/project.pbxproj +++ b/Simply Filter SMS.xcodeproj/project.pbxproj @@ -867,7 +867,7 @@ CODE_SIGN_ENTITLEMENTS = "Simply Filter SMS/Resources/Simply Filter SMS.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 202204121903; + CURRENT_PROJECT_VERSION = 202204271814; DEVELOPMENT_ASSET_PATHS = "\"Simply Filter SMS/Resources/Preview Content\""; DEVELOPMENT_TEAM = AL28LDR9PU; ENABLE_PREVIEWS = YES; @@ -883,10 +883,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.grizz.apps.dev.Simply-Filter-SMS"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "Development - Offline 2"; + PROVISIONING_PROFILE_SPECIFIER = "May 22: Simply Filter SMS - Dev"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; @@ -902,7 +902,7 @@ CODE_SIGN_ENTITLEMENTS = "Simply Filter SMS/Resources/Simply Filter SMS.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202204121903; + CURRENT_PROJECT_VERSION = 202204271814; DEVELOPMENT_ASSET_PATHS = "\"Simply Filter SMS/Resources/Preview Content\""; DEVELOPMENT_TEAM = AL28LDR9PU; ENABLE_PREVIEWS = YES; @@ -918,7 +918,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.grizz.apps.dev.Simply-Filter-SMS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -933,7 +933,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = "Message Filter Extension/Simply Filter SMS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202204121903; + CURRENT_PROJECT_VERSION = 202204271814; DEVELOPMENT_TEAM = AL28LDR9PU; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Message Filter Extension/Info.plist"; @@ -944,7 +944,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.grizz.apps.dev.Simply-Filter-SMS.Simply-Filter-SMS-Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -959,7 +959,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = "Message Filter Extension/Simply Filter SMS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202204121903; + CURRENT_PROJECT_VERSION = 202204271814; DEVELOPMENT_TEAM = AL28LDR9PU; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Message Filter Extension/Info.plist"; @@ -970,7 +970,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.grizz.apps.dev.Simply-Filter-SMS.Simply-Filter-SMS-Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/Simply Filter SMS/Framework Layer/Managers/AppManager.swift b/Simply Filter SMS/Framework Layer/Managers/AppManager.swift index 1ad621d..baed824 100644 --- a/Simply Filter SMS/Framework Layer/Managers/AppManager.swift +++ b/Simply Filter SMS/Framework Layer/Managers/AppManager.swift @@ -36,11 +36,27 @@ class AppManager: AppManagerProtocol { self.messageEvaluationManager = messageEvaluationManager self.networkSyncManager = networkSyncManager self.amazonS3Service = amazonS3Service + } + + func onAppLaunch() { + let _ = self.defaultsManager.appAge // make sure it's initialized - let isOnline = networkSyncManager.networkStatus == .online + if let sessionAge = self.defaultsManager.sessionAge { + if sessionAge.daysBetween(date: Date()) != 0 { + self.onNewUserSession() + } + } + else { + self.onNewUserSession() + } + } + + func onNewUserSession() { + self.defaultsManager.sessionCounter += 1 + self.defaultsManager.sessionAge = Date() - if !inMemory && isOnline { - automaticFilterManager.updateAutomaticFiltersIfNeeded() + if self.networkSyncManager.networkStatus == .online { + self.automaticFilterManager.updateAutomaticFiltersIfNeeded() } } diff --git a/Simply Filter SMS/Framework Layer/Managers/MessageEvaluationManager.swift b/Simply Filter SMS/Framework Layer/Managers/MessageEvaluationManager.swift index 0420637..81e7298 100644 --- a/Simply Filter SMS/Framework Layer/Managers/MessageEvaluationManager.swift +++ b/Simply Filter SMS/Framework Layer/Managers/MessageEvaluationManager.swift @@ -46,52 +46,53 @@ class MessageEvaluationManager: MessageEvaluationManagerProtocol { } //MARK: Public API (MessageEvaluationManagerProtocol) - func evaluateMessage(body: String, sender: String) -> ILMessageFilterAction { - var action = ILMessageFilterAction.none + func evaluateMessage(body: String, sender: String) -> MessageEvaluationResult { + + var result = MessageEvaluationResult(action: .none) // Priority #1 - Allow - action = self.runUserFilters(type: .allow, body: body, sender: sender) - guard !action.isFiltered else { return action } + result = self.runUserFilters(type: .allow, body: body, sender: sender) + guard !result.action.isFiltered else { return result } // Priority #2 - Deny - action = self.runUserFilters(type: .deny, body: body, sender: sender) - guard !action.isFiltered else { return action } + result = self.runUserFilters(type: .deny, body: body, sender: sender) + guard !result.action.isFiltered else { return result } // Priority #3 - Deny Language - action = self.runUserFilters(type: .denyLanguage, body: body, sender: sender) - guard !action.isFiltered else { return action } + result = self.runUserFilters(type: .denyLanguage, body: body, sender: sender) + guard !result.action.isFiltered else { return result } // Priority #4 - Automatic Filtering - action = self.runAutomaticFilters(body: body, sender: sender) - guard !action.isFiltered else { return action } + result = self.runAutomaticFilters(body: body, sender: sender) + guard !result.action.isFiltered else { return result } // Priority #5 - Rules - action = self.runFilterRules(body: body, sender: sender) + result = self.runFilterRules(body: body, sender: sender) - if !action.isFiltered { - action = .allow + if !result.action.isFiltered { + result = MessageEvaluationResult(action: .allow, reason: "testFilters_resultReason_noMatch"~) } - return action + return result } func setLogger(_ logger: Logger) { self.logger = logger } - + //MARK: - Private - private var logger: Logger? private var persistentContainer: NSPersistentContainer? private(set) var context: NSManagedObjectContext - private func runUserFilters(type: FilterType, body: String, sender: String) -> ILMessageFilterAction { - var action = ILMessageFilterAction.none + private func runUserFilters(type: FilterType, body: String, sender: String) -> MessageEvaluationResult { + var result = MessageEvaluationResult(action: .none) let fetchRequest = NSFetchRequest(entityName: "Filter") fetchRequest.predicate = NSPredicate(format: "type == %ld", type.rawValue) guard let filters = try? self.context.fetch(fetchRequest) else { self.logger?.error("ERROR! While loading filters on MessageEvaluationManager.runUserFilters") - return action + return result } switch type { @@ -100,7 +101,7 @@ class MessageEvaluationManager: MessageEvaluationManagerProtocol { guard let filter = filter as? Filter, self.isMataching(filter: filter, body: body, sender: sender) else { continue } - action = .allow + result = MessageEvaluationResult(action: .allow, reason: filter.text) break } @@ -109,7 +110,7 @@ class MessageEvaluationManager: MessageEvaluationManagerProtocol { guard let filter = filter as? Filter, self.isMataching(filter: filter, body: body, sender: sender) else { continue } - action = filter.denyFolderType.action + result = MessageEvaluationResult(action: filter.denyFolderType.action, reason: filter.text) break } @@ -122,17 +123,17 @@ class MessageEvaluationManager: MessageEvaluationManagerProtocol { if language != .undetermined, NLLanguage.dominantLanguage(for: body) == language { - action = filter.denyFolderType.action + result = MessageEvaluationResult(action: filter.denyFolderType.action, reason: language.localizedName) break } } } - return action + return result } - private func runAutomaticFilters(body: String, sender: String) -> ILMessageFilterAction { - var action = ILMessageFilterAction.none + private func runAutomaticFilters(body: String, sender: String) -> MessageEvaluationResult { + var result = MessageEvaluationResult(action: .none) let lowercasedBody = body.lowercased() let lowercasedSender = sender.lowercased() let languageRequest: NSFetchRequest = AutomaticFiltersLanguage.fetchRequest() @@ -144,100 +145,108 @@ class MessageEvaluationManager: MessageEvaluationManagerProtocol { let automaticFilterList = AutomaticFilterListsResponse(base64String: filtersData) else { self.logger?.error("ERROR! While loading cache on MessageEvaluationManager.runAutomaticFilters") - return action + return result } for automaticFiltersLanguageRecord in automaticFiltersLanguageRecords { - guard action == .none else { break } + guard result.action == .none else { break } if automaticFiltersLanguageRecord.isActive, let langRawValue = automaticFiltersLanguageRecord.lang, let languageResponse = automaticFilterList.filterLists[langRawValue] { + let lang = NLLanguage(rawValue: langRawValue) + for allowedSender in languageResponse.allowSenders { if lowercasedSender == allowedSender.lowercased() { - action = .allow + result = MessageEvaluationResult(action: .allow, reason: "\("autoFilter_title"~) (\(lang.localizedName ?? langRawValue))") break } } - guard !action.isFiltered else { break } + guard !result.action.isFiltered else { break } for allowedBody in languageResponse.allowBody { if lowercasedBody.contains(allowedBody.lowercased()) { - action = .allow + result = MessageEvaluationResult(action: .allow, reason: "\("autoFilter_title"~) (\(lang.localizedName ?? langRawValue))") break } } - guard !action.isFiltered else { break } + guard !result.action.isFiltered else { break } for deniedSender in languageResponse.denySender { if lowercasedSender == deniedSender.lowercased() { - action = .junk + result = MessageEvaluationResult(action: .junk, reason: "\("autoFilter_title"~) (\(lang.localizedName ?? langRawValue))") break } } - guard !action.isFiltered else { break } + guard !result.action.isFiltered else { break } for deniedBody in languageResponse.denyBody { if lowercasedBody.contains(deniedBody.lowercased()) { - action = .junk + result = MessageEvaluationResult(action: .junk, reason: "\("autoFilter_title"~) (\(lang.localizedName ?? langRawValue))") break } } } } - return action + return result } - private func runFilterRules(body: String, sender: String) -> ILMessageFilterAction { - var action = ILMessageFilterAction.none + private func runFilterRules(body: String, sender: String) -> MessageEvaluationResult { + var result = MessageEvaluationResult(action: .none) let ruleRequest: NSFetchRequest = AutomaticFiltersRule.fetchRequest() ruleRequest.predicate = NSPredicate(format: "isActive == %@", NSNumber(value: true)) guard let activeAutomaticFiltersRuleRecords = try? self.context.fetch(ruleRequest) else { self.logger?.error("ERROR! While loading rules on MessageEvaluationManager.runFilterRules") - return action + return result } - + for activeRule in activeAutomaticFiltersRuleRecords { if let ruleType = activeRule.ruleType { switch ruleType { case .allUnknown: - action = .junk + result = MessageEvaluationResult(action: .junk, reason: "testFilters_resultReason_unknownSender"~) break case .links: if body.containsLink { - action = .junk + result = MessageEvaluationResult(action: .junk, reason: "autoFilter_links_shortTitle"~) break } case .numbersOnly: if let _ = sender.rangeOfCharacter(from: NSCharacterSet.letters) { - action = .junk + result = MessageEvaluationResult(action: .junk, reason: "autoFilter_numbersOnly_shortTitle"~) break } case .shortSender: if sender.count <= Int(activeRule.selectedChoice) { - action = .junk + result = MessageEvaluationResult(action: .junk, reason: "autoFilter_shortSender_shortTitle"~) break } case .email: if sender.containsEmail { - action = .junk + result = MessageEvaluationResult(action: .junk, reason: "autoFilter_email_shortTitle"~) + break + } + + case .emojis: + if body.containsEmoji { + result = MessageEvaluationResult(action: .junk, reason: "autoFilter_emojis_shortTitle"~) break } } } } - return action + return result } private func isMataching(filter: Filter, body: String, sender: String) -> Bool { diff --git a/Simply Filter SMS/Framework Layer/Managers/Protocols/AppManagerProtocol.swift b/Simply Filter SMS/Framework Layer/Managers/Protocols/AppManagerProtocol.swift index 0ab30b5..37b9ad2 100644 --- a/Simply Filter SMS/Framework Layer/Managers/Protocols/AppManagerProtocol.swift +++ b/Simply Filter SMS/Framework Layer/Managers/Protocols/AppManagerProtocol.swift @@ -18,5 +18,7 @@ protocol AppManagerProtocol { var networkSyncManager: NetworkSyncManagerProtocol { get } var amazonS3Service: AmazonS3ServiceProtocol { get } + func onAppLaunch() + func onNewUserSession() func getFrequentlyAskedQuestions() -> [QuestionView.ViewModel] } diff --git a/Simply Filter SMS/Framework Layer/Managers/Protocols/MessageEvaluationManagerProtocol.swift b/Simply Filter SMS/Framework Layer/Managers/Protocols/MessageEvaluationManagerProtocol.swift index ce12472..2ea79fe 100644 --- a/Simply Filter SMS/Framework Layer/Managers/Protocols/MessageEvaluationManagerProtocol.swift +++ b/Simply Filter SMS/Framework Layer/Managers/Protocols/MessageEvaluationManagerProtocol.swift @@ -10,9 +10,15 @@ import CoreData import IdentityLookup import OSLog + +struct MessageEvaluationResult { + var action: ILMessageFilterAction + var reason: String? +} + protocol MessageEvaluationManagerProtocol { var context: NSManagedObjectContext { get } - func evaluateMessage(body: String, sender: String) -> ILMessageFilterAction + func evaluateMessage(body: String, sender: String) -> MessageEvaluationResult func setLogger(_ logger: Logger) } diff --git a/Simply Filter SMS/Framework Layer/Shared with Extension/Constsants.swift b/Simply Filter SMS/Framework Layer/Shared with Extension/Constsants.swift index 2abc3f6..f3d6b7a 100644 --- a/Simply Filter SMS/Framework Layer/Shared with Extension/Constsants.swift +++ b/Simply Filter SMS/Framework Layer/Shared with Extension/Constsants.swift @@ -258,7 +258,7 @@ enum FilterCase: Int64, CaseIterable, Identifiable { } enum RuleType: Int64, CaseIterable, Equatable, Identifiable { - case allUnknown=0, links, numbersOnly, shortSender, email + case allUnknown=0, links, numbersOnly, shortSender, email, emojis var id: Self { self } @@ -274,6 +274,8 @@ enum RuleType: Int64, CaseIterable, Equatable, Identifiable { return "autoFilter_shortSender"~ case .email: return "autoFilter_email"~ + case .emojis: + return "autoFilter_emojis"~ } } @@ -289,6 +291,8 @@ enum RuleType: Int64, CaseIterable, Equatable, Identifiable { return 3 case .numbersOnly: return 4 + case .emojis: + return 5 } } @@ -304,9 +308,15 @@ enum RuleType: Int64, CaseIterable, Equatable, Identifiable { return "textformat.123" case .email: return "envelope.fill" + case .emojis: + return "๐Ÿ™„" } } + var isTextIcon: Bool { + return self == .emojis + } + var iconColor: Color { switch self { case .allUnknown: @@ -319,6 +329,8 @@ enum RuleType: Int64, CaseIterable, Equatable, Identifiable { return .orange case .email: return .brown + case .emojis: + return .orange } } @@ -361,6 +373,8 @@ enum RuleType: Int64, CaseIterable, Equatable, Identifiable { return "autoFilter_shortSender_shortTitle"~ case .email: return "autoFilter_email_shortTitle"~ + case .emojis: + return "autoFilter_emojis_shortTitle"~ } } diff --git a/Simply Filter SMS/Framework Layer/Shared with Extension/SharedExtensions.swift b/Simply Filter SMS/Framework Layer/Shared with Extension/SharedExtensions.swift index d4a4846..85d0f6b 100644 --- a/Simply Filter SMS/Framework Layer/Shared with Extension/SharedExtensions.swift +++ b/Simply Filter SMS/Framework Layer/Shared with Extension/SharedExtensions.swift @@ -188,6 +188,8 @@ extension String { let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" return NSPredicate(format: "SELF MATCHES %@", emailRegex).evaluate(with: self) } + + var containsEmoji: Bool { self.contains { $0.isEmoji } } } extension URL { @@ -195,3 +197,17 @@ extension URL { return self.scheme?.lowercased() == "mailto" } } + +extension Character { + + var isSimpleEmoji: Bool { + guard let firstScalar = self.unicodeScalars.first else { return false } + return firstScalar.properties.isEmoji && firstScalar.value > 0x238C + } + + var isCombinedIntoEmoji: Bool { + return self.unicodeScalars.count > 1 && self.unicodeScalars.first?.properties.isEmoji ?? false + } + + var isEmoji: Bool { self.isSimpleEmoji || self.isCombinedIntoEmoji } +} diff --git a/Simply Filter SMS/Resources/en.lproj/Localizable.strings b/Simply Filter SMS/Resources/en.lproj/Localizable.strings index b43176b..519dd29 100644 --- a/Simply Filter SMS/Resources/en.lproj/Localizable.strings +++ b/Simply Filter SMS/Resources/en.lproj/Localizable.strings @@ -133,6 +133,8 @@ "autoFilter_email" = "Block Email Senders"; "autoFilter_empty" = "Go online while the app is open at least once to download the Automatic Filtering lists."; "autoFilter_error" = "It seems there is a problem downloading the Automatic Filtering lists. Please try again later."; +"autoFilter_emojis" = "Block Emojis"; +"autoFilter_emojis_shortTitle" = "Emojis"; // MARK: Test Filters "testFilters_title" = "Test Your Filters"; @@ -143,6 +145,9 @@ "testFilters_resultJunk" = "It's Junk! ๐Ÿ˜ˆ"; "testFilters_resultPromotion" = "It's a Promotion! ๐Ÿ“ข"; "testFilters_resultTransaction" = "It's a Transaction! ๐Ÿงพ"; +"testFilters_resultReason_noMatch" = "No match"; +"testFilters_resultReason_unknownSender" = "Unknown sender"; +"testFilters_resultReason" = "Reason:"; "notification_offline_title" = "iCloud sync is paused"; "notification_offline_subtitle" = "Your device has no network"; diff --git a/Simply Filter SMS/Resources/he.lproj/Localizable.strings b/Simply Filter SMS/Resources/he.lproj/Localizable.strings index 3dcdc06..e71d8b1 100644 --- a/Simply Filter SMS/Resources/he.lproj/Localizable.strings +++ b/Simply Filter SMS/Resources/he.lproj/Localizable.strings @@ -247,3 +247,13 @@ "aboutView_review" = "ื“ืจื’ื• ืื•ืชื ื• ื‘ื‘ืงืฉื”!"; "autoFilter_error" = "ื ืจืื” ืฉื™ืฉ ื‘ืขื™ื” ื‘ื”ื•ืจื“ืช ืจืฉื™ืžืช ื”ืคื™ืœื˜ืจื™ื ื”ืื•ื˜ื•ืžื˜ื™ื. ืื ื ื ืกื• ืฉื•ื‘ ื‘ืžื•ืขื“ ืžืื•ื—ืจ ื™ื•ืชืจ."; + +"testFilters_resultReason_noMatch" = "ืœื ื ืžืฆืื” ื”ืชืืžื”"; + +"testFilters_resultReason_unknownSender" = "ืฉื•ืœื— ืœื ืžื•ื›ืจ"; + +"testFilters_resultReason" = "ืกื™ื‘ื”:"; + +"autoFilter_emojis" = "ื—ืกื•ื ืื™ืžื•ื’'ื™ื"; + +"autoFilter_emojis_shortTitle" = "ืื™ืžื•ื’'ื™ื"; diff --git a/Simply Filter SMS/Simply_Filter_SMSApp.swift b/Simply Filter SMS/Simply_Filter_SMSApp.swift index 53348f8..65a4540 100644 --- a/Simply Filter SMS/Simply_Filter_SMSApp.swift +++ b/Simply Filter SMS/Simply_Filter_SMSApp.swift @@ -21,26 +21,15 @@ struct Simply_Filter_SMSApp: App { class AppDelegate: NSObject, UIApplicationDelegate { var didRegisterForRemoteNotifications = false - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + if !self.didRegisterForRemoteNotifications { application.registerForRemoteNotifications() self.didRegisterForRemoteNotifications = true } - var defaultsManager = AppManager.shared.defaultsManager - let _ = defaultsManager.appAge // make sure it's initialized - - if let sessionAge = defaultsManager.sessionAge { - if sessionAge.daysBetween(date: Date()) != 0 { - defaultsManager.sessionCounter += 1 - defaultsManager.sessionAge = Date() - } - } - else { - defaultsManager.sessionCounter += 1 - defaultsManager.sessionAge = Date() - } - + AppManager.shared.onAppLaunch() return true } } diff --git a/Simply Filter SMS/View Layer/Screens/AppHomeView.swift b/Simply Filter SMS/View Layer/Screens/AppHomeView.swift index 27fc92e..609dacd 100644 --- a/Simply Filter SMS/View Layer/Screens/AppHomeView.swift +++ b/Simply Filter SMS/View Layer/Screens/AppHomeView.swift @@ -87,10 +87,18 @@ struct AppHomeView: View { Toggle(isOn: $model.rules[index].state) { HStack { - Image(systemName: rule.icon) - .foregroundColor(rule.iconColor.opacity(isDisabled ? 0.5 : 1)) - .frame(maxWidth: 20, maxHeight: .infinity, alignment: .center) - .font(rule.isDestructive ? Font.body.bold() : .body) + if rule.isTextIcon { + Text(rule.icon) + .opacity(isDisabled ? 0.5 : 1) + .frame(maxWidth: 20, maxHeight: .infinity, alignment: .center) + .font(.system(size: 16)) + } + else { + Image(systemName: rule.icon) + .foregroundColor(rule.iconColor.opacity(isDisabled ? 0.5 : 1)) + .frame(maxWidth: 20, maxHeight: .infinity, alignment: .center) + .font(rule.isDestructive ? Font.body.bold() : .body) + } VStack (alignment: .leading, spacing: 0) { let color = rule.isDestructive && model.rules[index].state ? Color.red : .primary diff --git a/Simply Filter SMS/View Layer/Screens/TestFiltersView.swift b/Simply Filter SMS/View Layer/Screens/TestFiltersView.swift index 2382d25..182f85a 100644 --- a/Simply Filter SMS/View Layer/Screens/TestFiltersView.swift +++ b/Simply Filter SMS/View Layer/Screens/TestFiltersView.swift @@ -42,18 +42,17 @@ struct TestFiltersView: View { .foregroundColor(.secondary) TextEditor(text: $model.text) - .frame(height: 100, alignment: .top) + .frame(height: 80, alignment: .top) .focused($focusedField, equals: .text) .multilineTextAlignment(.leading) .padding(.top, 15) - } .listRowInsets(EdgeInsets(top: 20, leading: 20, bottom: 0, trailing: 20)) FadingTextView(model: self.model.fadeTextModel) - .font(.title3.bold()) - .frame(maxWidth: .infinity, maxHeight: 50, alignment: .center) + .multilineTextAlignment(.leading) + .frame(minHeight: 45, alignment: .top) Button { self.model.evaluateMessage() @@ -117,8 +116,14 @@ extension TestFiltersView { func evaluateMessage() { let sender = self.sender.isEmpty ? "1234567" : self.sender - let action = self.appManager.messageEvaluationManager.evaluateMessage(body: self.text, sender: sender) - self.fadeTextModel.text = action.testResult + let result = self.appManager.messageEvaluationManager.evaluateMessage(body: self.text, sender: sender) + + if let reason = result.reason { + self.fadeTextModel.text = "\(result.action.testResult)\n\("testFilters_resultReason"~) \(reason)" + } + else { + self.fadeTextModel.text = result.action.testResult + } } } } diff --git a/Simply-Filter-SMS-Info.plist b/Simply-Filter-SMS-Info.plist index ca9a074..793300d 100644 --- a/Simply-Filter-SMS-Info.plist +++ b/Simply-Filter-SMS-Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + UIBackgroundModes remote-notification diff --git a/Tests/MessageEvaluationManagerTests.swift b/Tests/MessageEvaluationManagerTests.swift index de99fba..d500845 100644 --- a/Tests/MessageEvaluationManagerTests.swift +++ b/Tests/MessageEvaluationManagerTests.swift @@ -67,7 +67,7 @@ class MessageEvaluationManagerTests: XCTestCase { for testCase in testCases { - let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender) + let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender).action XCTAssert(testCase.expectedAction == actualAction, "sender \"\(testCase.sender)\", body \"\(testCase.body)\": \(testCase.expectedAction.debugName) != \(actualAction.debugName).") @@ -87,7 +87,7 @@ class MessageEvaluationManagerTests: XCTestCase { for testCase in testCases { - let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender) + let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender).action XCTAssert(testCase.expectedAction == actualAction, "sender \"\(testCase.sender)\", body \"\(testCase.body)\": \(testCase.expectedAction.debugName) != \(actualAction.debugName).") @@ -135,7 +135,7 @@ class MessageEvaluationManagerTests: XCTestCase { for testCase in testCases { - let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender) + let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender).action XCTAssert(testCase.expectedAction == actualAction, "sender \"\(testCase.sender)\", body \"\(testCase.body)\": \(testCase.expectedAction.debugName) != \(actualAction.debugName).") @@ -168,6 +168,11 @@ class MessageEvaluationManagerTests: XCTestCase { numbersOnly.ruleType = .numbersOnly numbersOnly.selectedChoice = 0 + let noEmojis = AutomaticFiltersRule(context: self.testSubject.context) + noEmojis.isActive = true + noEmojis.ruleType = .emojis + noEmojis.selectedChoice = 0 + let automaticFiltersLanguageHE = AutomaticFiltersLanguage(context: self.testSubject.context) automaticFiltersLanguageHE.lang = NLLanguage.hebrew.rawValue automaticFiltersLanguageHE.isActive = true @@ -186,13 +191,33 @@ class MessageEvaluationManagerTests: XCTestCase { MessageTestCase(sender: "054123465", body: "bla bla adi@gmail.com bla", expectedAction: .allow), MessageTestCase(sender: "054123465", body: "bla bla 054-123456 bla", expectedAction: .allow), MessageTestCase(sender: "Taasuka", body: "bla bla btl.gov.il/asdasdf", expectedAction: .allow), - MessageTestCase(sender: "Ontopo", body: "ืื ื ืืฉืจื• ื”ื–ืžื ืชื›ื ืœืฉื™ืœื” ื‘ืงื™ืฉื•ืจ. tinyurl.com/ycq952f ืœื—ืฆื• ืœืฆืคื™ื™ื”", expectedAction: .allow) + MessageTestCase(sender: "Ontopo", body: "ืื ื ืืฉืจื• ื”ื–ืžื ืชื›ื ืœืฉื™ืœื” ื‘ืงื™ืฉื•ืจ. tinyurl.com/ycq952f ืœื—ืฆื• ืœืฆืคื™ื™ื”", expectedAction: .allow), + MessageTestCase(sender: "054123465", body: "bla bla ๐Ÿ’€ bla", expectedAction: .junk) ] for testCase in testCases { - let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender) + let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender).action + + XCTAssert(testCase.expectedAction == actualAction, + "sender \"\(testCase.sender)\", body \"\(testCase.body)\": \(testCase.expectedAction.debugName) != \(actualAction.debugName).") + } + + numbersOnly.isActive = false + noEmojis.isActive = false + + try? self.testSubject.context.save() + + let secondTestCases: [MessageTestCase] = [ + MessageTestCase(sender: "not a number", body: "ืื ื ืืฉืจื• ื”ื–ืžื ืชื›ื ืœืฉื™ืœื” ื‘ืงื™ืฉื•ืจ.ืœื—ืฆื• ืœืฆืคื™ื™ื”", expectedAction: .allow), + MessageTestCase(sender: "054123465", body: "bla bla ๐Ÿ’€ bla", expectedAction: .allow) + ] + + + for testCase in secondTestCases { + + let actualAction = self.testSubject.evaluateMessage(body: testCase.body, sender: testCase.sender).action XCTAssert(testCase.expectedAction == actualAction, "sender \"\(testCase.sender)\", body \"\(testCase.body)\": \(testCase.expectedAction.debugName) != \(actualAction.debugName).") diff --git a/Tests/Mocks/mock_AppManager.swift b/Tests/Mocks/mock_AppManager.swift index c9c439a..be2d601 100644 --- a/Tests/Mocks/mock_AppManager.swift +++ b/Tests/Mocks/mock_AppManager.swift @@ -22,7 +22,22 @@ class mock_AppManager: AppManagerProtocol { var amazonS3Service: AmazonS3ServiceProtocol = mock_AmazonS3Service() var getFrequentlyAskedQuestionsCounter = 0 + var onAppLaunchCounter = 0 + var onNewUserSessionCounter = 0 + var getFrequentlyAskedQuestionsClosuer: (() -> ([QuestionView.ViewModel]))? + var onAppLaunchClosuer: (() -> ())? + var onNewUserSessionClosuer: (() -> ())? + + func onAppLaunch() { + self.onAppLaunchCounter += 1 + self.onAppLaunchClosuer?() + } + + func onNewUserSession() { + self.onNewUserSessionCounter += 1 + self.onNewUserSessionClosuer?() + } func getFrequentlyAskedQuestions() -> [QuestionView.ViewModel] { return self.getFrequentlyAskedQuestionsClosuer?() ?? [] diff --git a/Tests/Mocks/mock_MessageEvaluationManager.swift b/Tests/Mocks/mock_MessageEvaluationManager.swift index f4f1eb3..a3178fa 100644 --- a/Tests/Mocks/mock_MessageEvaluationManager.swift +++ b/Tests/Mocks/mock_MessageEvaluationManager.swift @@ -17,12 +17,12 @@ class mock_MessageEvaluationManager: MessageEvaluationManagerProtocol { var evaluateMessageCounter = 0 var setLoggerCounter = 0 - var evaluateMessageClosure: ((String, String) -> (ILMessageFilterAction))? + var evaluateMessageClosure: ((String, String) -> (MessageEvaluationResult))? var setLoggerClosure: ((Logger) -> ())? - func evaluateMessage(body: String, sender: String) -> ILMessageFilterAction { + func evaluateMessage(body: String, sender: String) -> MessageEvaluationResult { self.evaluateMessageCounter += 1 - return self.evaluateMessageClosure?(body, sender) ?? .none + return self.evaluateMessageClosure?(body, sender) ?? MessageEvaluationResult(action: .none) } func setLogger(_ logger: Logger) {