diff --git a/Bouncer.xcodeproj/project.pbxproj b/Bouncer.xcodeproj/project.pbxproj index a6f8c53..9505379 100644 --- a/Bouncer.xcodeproj/project.pbxproj +++ b/Bouncer.xcodeproj/project.pbxproj @@ -56,7 +56,7 @@ D6CC124D24E4563F00388160 /* SettingsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CC124C24E4563F00388160 /* SettingsAction.swift */; }; D6CC124F24E47C3900388160 /* SettingsMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CC124E24E47C3900388160 /* SettingsMiddleware.swift */; }; D6CC125124E4837800388160 /* SettingsReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CC125024E4837800388160 /* SettingsReducer.swift */; }; - D6EA9A8224AD2201000135E7 /* SMSFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EA9A8124AD2201000135E7 /* SMSFilter.swift */; }; + D6EA9A8224AD2201000135E7 /* SMSFiltering.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EA9A8124AD2201000135E7 /* SMSFiltering.swift */; }; D6EA9A8424AD2244000135E7 /* FilterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EA9A8324AD2244000135E7 /* FilterStore.swift */; }; D6EA9A8524AD2319000135E7 /* FilterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EA9A8324AD2244000135E7 /* FilterStore.swift */; }; D6F0621124EC69F800F47BA2 /* FilterStoreFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0621024EC69F800F47BA2 /* FilterStoreFileTests.swift */; }; @@ -156,7 +156,7 @@ D6CC124E24E47C3900388160 /* SettingsMiddleware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMiddleware.swift; sourceTree = ""; }; D6CC125024E4837800388160 /* SettingsReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsReducer.swift; sourceTree = ""; }; D6DA734729F539B300C3A831 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = ""; }; - D6EA9A8124AD2201000135E7 /* SMSFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMSFilter.swift; sourceTree = ""; }; + D6EA9A8124AD2201000135E7 /* SMSFiltering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMSFiltering.swift; sourceTree = ""; }; D6EA9A8324AD2244000135E7 /* FilterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterStore.swift; sourceTree = ""; }; D6F0621024EC69F800F47BA2 /* FilterStoreFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterStoreFileTests.swift; sourceTree = ""; }; D6F14E8724E8A1780029A0DF /* ReviewMiddleware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewMiddleware.swift; sourceTree = ""; }; @@ -251,7 +251,7 @@ isa = PBXGroup; children = ( D60A19C624A69F1F0002E8BD /* SMSMessage.swift */, - D6EA9A8124AD2201000135E7 /* SMSFilter.swift */, + D6EA9A8124AD2201000135E7 /* SMSFiltering.swift */, D6233A7D24A651A200B37A6A /* SMSOfflineFilter.swift */, ); path = SMSFilter; @@ -640,7 +640,7 @@ D6529F7324A41371009AE3FF /* BouncerApp.swift in Sources */, D616A7F329F42EEC00D5523F /* FilterStoreFileMigrator.swift in Sources */, D6C90BF424CAB26700426B31 /* AppStore.swift in Sources */, - D6EA9A8224AD2201000135E7 /* SMSFilter.swift in Sources */, + D6EA9A8224AD2201000135E7 /* SMSFiltering.swift in Sources */, D61497EE24E4435100AF997E /* AppReducer.swift in Sources */, 09491AEF261D30A400CF8073 /* String+Empty.swift in Sources */, D67F582F24E4496300165365 /* AppSettingsStore.swift in Sources */, @@ -697,7 +697,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = SMSFilter/SMSFilter.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 514; + CURRENT_PROJECT_VERSION = 703; DEVELOPMENT_TEAM = JM9222EF99; INFOPLIST_FILE = SMSFilter/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -720,7 +720,7 @@ CODE_SIGN_ENTITLEMENTS = SMSFilter/SMSFilter.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 514; + CURRENT_PROJECT_VERSION = 703; DEVELOPMENT_TEAM = JM9222EF99; INFOPLIST_FILE = SMSFilter/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -866,7 +866,7 @@ CODE_SIGN_ENTITLEMENTS = Bouncer/Bouncer.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 514; + CURRENT_PROJECT_VERSION = 703; DEVELOPMENT_ASSET_PATHS = "\"Bouncer/Preview Content\""; DEVELOPMENT_TEAM = JM9222EF99; ENABLE_PREVIEWS = YES; @@ -897,7 +897,7 @@ CODE_SIGN_ENTITLEMENTS = Bouncer/Bouncer.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 514; + CURRENT_PROJECT_VERSION = 703; DEVELOPMENT_ASSET_PATHS = "\"Bouncer/Preview Content\""; DEVELOPMENT_TEAM = JM9222EF99; ENABLE_PREVIEWS = YES; diff --git a/Bouncer.xcodeproj/xcuserdata/afterxleep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Bouncer.xcodeproj/xcuserdata/afterxleep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index c236af1..50a88dd 100644 --- a/Bouncer.xcodeproj/xcuserdata/afterxleep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Bouncer.xcodeproj/xcuserdata/afterxleep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -7,112 +7,48 @@ - - - - - - - - - - - - - - - - diff --git a/Bouncer/Config/Constants.swift b/Bouncer/Config/Constants.swift index abbfba9..587c46a 100644 --- a/Bouncer/Config/Constants.swift +++ b/Bouncer/Config/Constants.swift @@ -27,13 +27,13 @@ struct SYSTEM_IMAGES { static let SENDER = SystemImage(image: "person", color: COLORS.DEFAULT_ICON_COLOR) static let MESSAGE_TEXT = SystemImage(image: "text.quote", color: COLORS.DEFAULT_ICON_COLOR) static let SPAM = SystemImage(image: "bin.xmark", color: COLORS.ALERT_COLOR) - static let TRANSACTION = SystemImage(image: "arrow.left.arrow.right", color: COLORS.OK_COLOR) + static let TRANSACTION = SystemImage(image: "ellipsis.circle", color: COLORS.OK_COLOR) static let TRANSACTION_ORDERS = SystemImage(image: "shippingbox", color: COLORS.OK_COLOR) - static let TRANSACTION_FINANCE = SystemImage(image: "banknote", color: COLORS.OK_COLOR) - static let TRANSACTION_REMINDERS = SystemImage(image: "alarm", color: COLORS.OK_COLOR) - static let PROMOTION = SystemImage(image: "megaphone", color: COLORS.WARNING_COLOR) + static let TRANSACTION_FINANCE = SystemImage(image: "creditcard", color: COLORS.OK_COLOR) + static let TRANSACTION_REMINDERS = SystemImage(image: "calendar.badge.clock", color: COLORS.OK_COLOR) + static let PROMOTION = SystemImage(image: "ellipsis.circle", color: COLORS.WARNING_COLOR) static let PROMOTION_OFFERS = SystemImage(image: "tag", color: COLORS.WARNING_COLOR) - static let PROMOTION_COUPONS = SystemImage(image: "gift", color: COLORS.WARNING_COLOR) + static let PROMOTION_COUPONS = SystemImage(image: "wallet.pass", color: COLORS.WARNING_COLOR) static let HELP = SystemImage(image: "questionmark.circle", color: COLORS.DEFAULT_COLOR) static let ADD = SystemImage(image: "plus.circle", color: COLORS.DEFAULT_COLOR) static let CLOSE = SystemImage(image: "multiply.circle", color: COLORS.ACCENT_COLOR) diff --git a/Bouncer/Models/FilterStore/FilterStore.swift b/Bouncer/Models/FilterStore/FilterStore.swift index da1e074..2df971e 100644 --- a/Bouncer/Models/FilterStore/FilterStore.swift +++ b/Bouncer/Models/FilterStore/FilterStore.swift @@ -19,12 +19,15 @@ enum FilterDestination: String, Codable, Equatable, CaseIterable { case transaction case promotion - // SubActions (Apple allows 5 max.) + // SubActions case transactionOrder case transactionFinance case transactionReminders + case transactionHealth + case transactionOther case promotionOffers case promotionCoupons + case promotionOther } @@ -34,14 +37,16 @@ struct Filter: Hashable, Identifiable, Equatable, Codable { var phrase: String var action: FilterDestination var subAction: FilterDestination - var useRegex: Bool? + var caseSensitive: Bool = false + var useRegex: Bool = false init(id: UUID, phrase: String, type: FilterType = .any, action: FilterDestination = .junk, subAction: FilterDestination = .none, - useRegex: Bool? = nil + useRegex: Bool = false, + caseSensitive: Bool = false ) { self.id = id self.type = type @@ -49,6 +54,7 @@ struct Filter: Hashable, Identifiable, Equatable, Codable { self.action = action self.subAction = subAction self.useRegex = useRegex + self.caseSensitive = caseSensitive } } diff --git a/Bouncer/Models/FilterStore/FilterStoreFile.swift b/Bouncer/Models/FilterStore/FilterStoreFile.swift index 31da119..4977cc0 100644 --- a/Bouncer/Models/FilterStore/FilterStoreFile.swift +++ b/Bouncer/Models/FilterStore/FilterStoreFile.swift @@ -134,8 +134,8 @@ extension FilterStoreFile { var newFilter = filter // If the filter is not a regular expression - if !(newFilter.useRegex ?? false) { - newFilter.phrase = newFilter.phrase.lowercased() + if !newFilter.useRegex { + newFilter.phrase = newFilter.phrase } filters.append(newFilter) filters = filters.sorted(by: { $1.phrase > $0.phrase }) diff --git a/Bouncer/Models/FilterStore/FilterStoreFileMigrator.swift b/Bouncer/Models/FilterStore/FilterStoreFileMigrator.swift index ffe6209..bbcbfa2 100644 --- a/Bouncer/Models/FilterStore/FilterStoreFileMigrator.swift +++ b/Bouncer/Models/FilterStore/FilterStoreFileMigrator.swift @@ -31,11 +31,23 @@ struct FilterStoreFileMigrator { // Update filters for filter in filters { + var subAction: FilterDestination + switch filter.action { + case .promotion: + subAction = .promotionOther + case .transaction: + subAction = .transactionOther + default: + subAction = .none + } let updatedFilter = Filter(id: filter.id, phrase: filter.phrase, type: filter.type, action: filter.action, - useRegex: filter.useRegex) + subAction: subAction, + useRegex: filter.useRegex, + caseSensitive: false) + print(updatedFilter) _ = store.add(filter: updatedFilter) } // Re-fetch data diff --git a/Bouncer/Models/FilterStore/LegacyModels/FilterV1.swift b/Bouncer/Models/FilterStore/LegacyModels/FilterV1.swift index 3fe9d7a..c098d64 100644 --- a/Bouncer/Models/FilterStore/LegacyModels/FilterV1.swift +++ b/Bouncer/Models/FilterStore/LegacyModels/FilterV1.swift @@ -12,13 +12,13 @@ struct FilterV1: Identifiable, Equatable, Codable { var type: FilterType var phrase: String var action: FilterDestination - var useRegex: Bool? + var useRegex: Bool = false init(id: UUID, phrase: String, type: FilterType = .any, action: FilterDestination = .junk, - useRegex: Bool? = nil + useRegex: Bool = false ) { self.id = id self.type = type diff --git a/Bouncer/Models/SMSFilter/SMSFilter.swift b/Bouncer/Models/SMSFilter/SMSFiltering.swift similarity index 90% rename from Bouncer/Models/SMSFilter/SMSFilter.swift rename to Bouncer/Models/SMSFilter/SMSFiltering.swift index 9bb0a91..b0f6e3a 100644 --- a/Bouncer/Models/SMSFilter/SMSFilter.swift +++ b/Bouncer/Models/SMSFilter/SMSFiltering.swift @@ -7,7 +7,7 @@ import Foundation import IdentityLookup -protocol SMSFilter { +protocol SMSFiltering { var filters: [Filter] { get } var filterListService: FilterStore { get } func filterMessage(message: SMSMessage) -> ILMessageFilterAction diff --git a/Bouncer/Models/SMSFilter/SMSOfflineFilter.swift b/Bouncer/Models/SMSFilter/SMSOfflineFilter.swift index 0225977..35df981 100644 --- a/Bouncer/Models/SMSFilter/SMSOfflineFilter.swift +++ b/Bouncer/Models/SMSFilter/SMSOfflineFilter.swift @@ -5,6 +5,7 @@ import Foundation import IdentityLookup +import OSLog typealias SMSOfflineFilterResponse = (action: ILMessageFilterAction, subaction: ILMessageFilterSubAction) @@ -19,6 +20,7 @@ struct SMSOfflineFilter { } private func applyFilter(filter: Filter, message: SMSMessage) -> Bool { + os_log("FILTEREXTENSION - Applying filter: %@", log: OSLog.messageFilterLog, type: .info, "\(filter.phrase)") var txt = "" switch (filter.type) { case .sender: @@ -28,30 +30,36 @@ struct SMSOfflineFilter { default: txt = "\(message.sender) \(message.text)" } - - // Filters initially used both matchText and regex, so if the value is not assigned, use both - guard let useRegex = filter.useRegex else { - return match(text: txt, filter: filter) || matchRegex(text: txt, filter: filter) - } - + // Use different filter strategies based on user selection - if useRegex { + if filter.useRegex { return matchRegex(text: txt, filter: filter) } else { - print("-- \(txt)") - print("\(filter.phrase): \(match(text: txt, filter: filter))") - print(match(text: txt, filter: filter)) return match(text: txt, filter: filter) } } private func match(text: String, filter: Filter) -> Bool { + var matchOptions: String.CompareOptions = [] + if !filter.caseSensitive { + matchOptions.insert(.caseInsensitive) + } + let result = text.range(of: filter.phrase, options: matchOptions) != nil + os_log("FILTEREXTENSION - -- Match: %@", log: OSLog.messageFilterLog, type: .info, "\(result)") + os_log("FILTEREXTENSION - -- Method: Text", log: OSLog.messageFilterLog, type: .info) return text.range(of: filter.phrase, options: .caseInsensitive) != nil } private func matchRegex(text: String, filter: Filter) -> Bool { - return (text.range(of: filter.phrase, options:[.regularExpression, .caseInsensitive]) != nil) + var matchOptions: String.CompareOptions = [.regularExpression] + if !filter.caseSensitive { + matchOptions.insert(.caseInsensitive) + } + let result = (text.range(of: filter.phrase, options: matchOptions) != nil) + os_log("FILTEREXTENSION - -- Match: %@", log: OSLog.messageFilterLog, type: .info, "\(result)") + os_log("FILTEREXTENSION - -- Method: Regex", log: OSLog.messageFilterLog, type: .info) + return result } private func getAction(_ filter: Filter) -> ILMessageFilterAction { @@ -80,11 +88,20 @@ struct SMSOfflineFilter { case .promotionCoupons: return .promotionalCoupons default: - return .none + // If no subaction pressent just return the base groups + switch filter.action { + case .promotion: + return .promotionalOthers + case .transaction: + return .transactionalOthers + default: + return .none + } } } func filterMessage(message: SMSMessage) -> SMSOfflineFilterResponse { + os_log("FILTEREXTENSION - Message Received: %@", log: OSLog.messageFilterLog, type: .info, "\(message)") for filter in filters { if(applyFilter(filter: filter, message: message)) { return (getAction(filter), getSubAction(filter)) diff --git a/Bouncer/Views/FilterDetail/FilterDetailContainerView.swift b/Bouncer/Views/FilterDetail/FilterDetailContainerView.swift index 24013b3..4935eb8 100644 --- a/Bouncer/Views/FilterDetail/FilterDetailContainerView.swift +++ b/Bouncer/Views/FilterDetail/FilterDetailContainerView.swift @@ -23,6 +23,7 @@ struct FilterDetailContainerView: View { @State private var filterTerm: String @State private var exactMatch: Bool @State private var useRegex: Bool + @State private var isCaseSensitive: Bool var body: some View { switch interactionType { @@ -34,7 +35,8 @@ struct FilterDetailContainerView: View { filterDestination: $filterDestination, filterTerm: $filterTerm, exactMatch: $exactMatch, - useRegex: $useRegex) + useRegex: $useRegex, + isCaseSensitive: $isCaseSensitive) case .update: FilterDetailView(isEmbedded: false, title: "FILTER_EDIT_VIEW_TITLE", @@ -44,7 +46,8 @@ struct FilterDetailContainerView: View { filterDestination: $filterDestination, filterTerm: $filterTerm, exactMatch: $exactMatch, - useRegex: $useRegex) + useRegex: $useRegex, + isCaseSensitive: $isCaseSensitive) } } @@ -57,9 +60,11 @@ extension FilterDetailContainerView { self.filterId = filter?.id self._filterType = .init(initialValue: filter?.type ?? .any) self._filterTerm = .init(initialValue: filter?.phrase ?? "") - self._filterDestination = .init(initialValue: filter?.action ?? .junk) + let action = filter?.subAction != FilterDestination.none ? filter?.subAction : filter?.action + self._filterDestination = .init(initialValue: action ?? .junk) self._exactMatch = .init(initialValue: false) - self._useRegex = .init(initialValue: filter?.useRegex ?? false) + self._useRegex = .init(initialValue: filter?.useRegex ?? false) + self._isCaseSensitive = .init(initialValue: filter?.caseSensitive ?? false) } private var cancelButton: some View { @@ -89,11 +94,30 @@ extension FilterDetailContainerView { private var filterToSave: Filter { - Filter(id: filterId ?? UUID(), - phrase: filterTerm.trimmed, - type: filterType, - action: filterDestination, - useRegex: useRegex) + var action: FilterDestination + var subAction: FilterDestination + switch filterDestination { + case .promotion, .promotionOffers, .promotionCoupons, .promotionOther: + action = .promotion + subAction = filterDestination + case .transaction, .transactionOrder, .transactionFinance, .transactionReminders, .transactionHealth, .transactionOther: + action = .transaction + subAction = filterDestination + case .junk: + action = .junk + subAction = .none + case .none: + action = .none + subAction = .none + } + let filter = Filter(id: filterId ?? UUID(), + phrase: filterTerm.trimmed, + type: filterType, + action: action, + subAction: subAction, + useRegex: useRegex, + caseSensitive: isCaseSensitive) + return filter } } diff --git a/Bouncer/Views/FilterDetail/FilterDetailView.swift b/Bouncer/Views/FilterDetail/FilterDetailView.swift index fece88c..467b2b3 100644 --- a/Bouncer/Views/FilterDetail/FilterDetailView.swift +++ b/Bouncer/Views/FilterDetail/FilterDetailView.swift @@ -17,6 +17,7 @@ struct FilterDetailView: View { @Binding var filterTerm: String @Binding var exactMatch: Bool @Binding var useRegex: Bool + @Binding var isCaseSensitive: Bool var body: some View { rootView @@ -52,9 +53,8 @@ extension FilterDetailView { Section(header: Text("FILTER_INFORMATION")) { Picker(selection: $filterType, label: Text("FILTER_TYPE_SELECTION_LABEL")) { ForEach(FilterType.allCases, id: \.self) { value in - HStack { + VStack { Text(value.formDescription.text) - Spacer() Image(systemName: value.formDescription.decoration.image) } } @@ -75,12 +75,12 @@ extension FilterDetailView { filterPickerSectionFor(.transactionOrder).tag(FilterDestination.transactionOrder) filterPickerSectionFor(.transactionFinance).tag(FilterDestination.transactionFinance) filterPickerSectionFor(.transactionReminders).tag(FilterDestination.transactionReminders) - filterPickerSectionFor(.transaction).tag(FilterDestination.transaction) + filterPickerSectionFor(.transactionOther).tag(FilterDestination.transactionOther) } Section(header: Text("PROMOTIONS")) { filterPickerSectionFor(.promotionOffers).tag(FilterDestination.promotionOffers) filterPickerSectionFor(.promotionCoupons).tag(FilterDestination.promotionCoupons) - filterPickerSectionFor(.promotion).tag(FilterDestination.promotion) + filterPickerSectionFor(.promotionOther).tag(FilterDestination.promotionOther) } } } @@ -89,11 +89,19 @@ extension FilterDetailView { VStack(alignment: .leading) { Text("USE_REGULAR_EXPRESSIONS") .padding(0) - Spacer() Text("USE_REGULAR_EXPRESSIONS_DETAIL") .font(.caption) .foregroundColor(Color("TextDefaultColor")) - }.padding(.init(top: 10, leading: 0, bottom: 10, trailing: 10)) + }.padding(.init(top: 5, leading: 0, bottom: 5, trailing: 10)) + } + Toggle(isOn: $isCaseSensitive) { + VStack(alignment: .leading) { + Text("IS_CASE_SENSITIVE") + .padding(0) + Text("IS_CASE_SENSITIVE_DETAIL") + .font(.caption) + .foregroundColor(Color("TextDefaultColor")) + }.padding(.init(top: 5, leading: 0, bottom: 5, trailing: 10)) } } } @@ -109,6 +117,7 @@ struct FilterDetailView_Previews: PreviewProvider { filterDestination: .constant(.transaction), filterTerm: .constant("Query Term"), exactMatch: .constant(false), - useRegex: .constant(false)) + useRegex: .constant(false), + isCaseSensitive: .constant(false)) } } diff --git a/Bouncer/Views/FilterList/FilterListView.swift b/Bouncer/Views/FilterList/FilterListView.swift index 418e243..3a59b3e 100644 --- a/Bouncer/Views/FilterList/FilterListView.swift +++ b/Bouncer/Views/FilterList/FilterListView.swift @@ -87,7 +87,6 @@ extension FilterListView { } }.onDelete(perform: onDelete) } - .listStyle(PlainListStyle()) } else { diff --git a/Bouncer/Views/FilterList/FilterRowView.swift b/Bouncer/Views/FilterList/FilterRowView.swift index 3379797..040bdc6 100644 --- a/Bouncer/Views/FilterList/FilterRowView.swift +++ b/Bouncer/Views/FilterList/FilterRowView.swift @@ -52,41 +52,49 @@ extension FilterRowView { } private func getFilterDestinationDecoration(filter: Filter) -> FilterDestinationDecoration { - var data: FilterDestinationDecoration switch (filter.action) { - case .junk, .none: - data = FilterDestination.junk.listDescription - case .promotionOffers: - data = FilterDestination.promotionOffers.listDescription - case .promotionCoupons: - data = FilterDestination.promotionCoupons.listDescription case .promotion: - data = FilterDestination.promotion.listDescription - case .transactionReminders: - data = FilterDestination.transactionReminders.listDescription - case .transactionFinance: - data = FilterDestination.transactionFinance.listDescription - case .transactionOrder: - data = FilterDestination.transactionOrder.listDescription + switch filter.subAction { + case .promotionOffers: + return FilterDestination.promotionOffers.listDescription + case .promotionCoupons: + return FilterDestination.promotionCoupons.listDescription + default: + return FilterDestination.promotionOther.listDescription + } case .transaction: - data = FilterDestination.transaction.listDescription + switch filter.subAction { + case .transactionReminders: + return FilterDestination.transactionReminders.listDescription + case .transactionFinance: + return FilterDestination.transactionFinance.listDescription + case .transactionOrder: + return FilterDestination.transactionOrder.listDescription + default: + return FilterDestination.transactionOther.listDescription + } + case .junk: + return FilterDestination.junk.listDescription + default: + return FilterDestination.junk.listDescription } - return data } private func getFilterTypeColor(filter: Filter) -> Color { switch (filter.action) { case .junk, .none: return COLORS.ALERT_COLOR - case .promotion, .promotionOffers, .promotionCoupons: + case .promotion, .promotionOther, .promotionOffers, .promotionCoupons: return COLORS.WARNING_COLOR - case .transaction, .transactionOrder, .transactionFinance, .transactionReminders: + case .transaction, .transactionOrder, .transactionOther, .transactionFinance, .transactionReminders: return COLORS.OK_COLOR + default: + return COLORS.ALERT_COLOR } } private func getFilterText(filter: Filter) -> Text { - if (filter.useRegex ?? false) { + if filter.useRegex { return Text("/\(filter.phrase)/") .bold() } diff --git a/Bouncer/Views/FilterList/FilterTypes.swift b/Bouncer/Views/FilterList/FilterTypes.swift index 7b0c87f..471c334 100644 --- a/Bouncer/Views/FilterList/FilterTypes.swift +++ b/Bouncer/Views/FilterList/FilterTypes.swift @@ -53,14 +53,17 @@ extension FilterDestination { return FilterDestinationDecoration(decoration: SYSTEM_IMAGES.TRANSACTION_FINANCE, text: "TRANSACTION_ACTION_FINANCE") case .transactionReminders: return FilterDestinationDecoration(decoration: SYSTEM_IMAGES.TRANSACTION_REMINDERS, text: "TRANSACTION_ACTION_REMINDERS") - case .transaction: - return FilterDestinationDecoration(decoration: SYSTEM_IMAGES.TRANSACTION, text: "TRANSACTION_ACTION") case .promotionOffers: return FilterDestinationDecoration(decoration: SYSTEM_IMAGES.PROMOTION_OFFERS, text: "PROMOTION_ACTION_OFFERS") case .promotionCoupons: return FilterDestinationDecoration(decoration: SYSTEM_IMAGES.PROMOTION_COUPONS, text: "PROMOTION_ACTION_COUPONS") - case .promotion: + case .transactionOther: + return FilterDestinationDecoration(decoration: SYSTEM_IMAGES.TRANSACTION, text: "TRANSACTION_ACTION") + case .promotionOther: return FilterDestinationDecoration(decoration: SYSTEM_IMAGES.PROMOTION, text: "PROMOTION_ACTION") + default: + return FilterDestinationDecoration(decoration: SYSTEM_IMAGES.SPAM, text: "JUNK_ACTION") } + } } diff --git a/Bouncer/Views/Tutorial/TutorialContainerView.swift b/Bouncer/Views/Tutorial/TutorialContainerView.swift index 431d96e..a8a4502 100644 --- a/Bouncer/Views/Tutorial/TutorialContainerView.swift +++ b/Bouncer/Views/Tutorial/TutorialContainerView.swift @@ -31,9 +31,8 @@ extension TutorialContainerView { } func openSettings() { - if let settingsURL = URL(string: UIApplication.openSettingsURLString), - UIApplication.shared.canOpenURL(settingsURL) { - UIApplication.shared.open(settingsURL, options: [:]) + if let url = URL(string: "App-Prefs:") { + UIApplication.shared.open(url, options: [:], completionHandler: nil) } setAppHasLaunched() } diff --git a/Bouncer/ar.lproj/Localizable.strings b/Bouncer/ar.lproj/Localizable.strings index 8cf3182..192c11a 100644 --- a/Bouncer/ar.lproj/Localizable.strings +++ b/Bouncer/ar.lproj/Localizable.strings @@ -74,3 +74,5 @@ Created by asaah18 on 7/13/21. "ASK_ANYTHING" = "اسأل أي شيء!"; "NO_EMAIL_CONFIGURED" = "يبدو أنه لم يتم تعيين حساب بريد إلكتروني على جهازك. يمكنك الكتابة لي مباشرة على danielbernal@hey.com"; "SETUP_INFO" = "تمكين تصفية الرسائل النصية"; +"IS_CASE_SENSITIVE" = "هل حساس لحالة الأحرف"; +"IS_CASE_SENSITIVE_DETAIL" = "تمكين هذا الخيار لمعاملة الأحرف الكبيرة والصغيرة بشكل مختلف"; diff --git a/Bouncer/de.lproj/Localizable.strings b/Bouncer/de.lproj/Localizable.strings index 46b4b08..674f948 100644 --- a/Bouncer/de.lproj/Localizable.strings +++ b/Bouncer/de.lproj/Localizable.strings @@ -74,3 +74,5 @@ Created by Daniel Bernal on 6/30/20. "ASK_ANYTHING" = "Frag alles!"; "NO_EMAIL_CONFIGURED" = "Es scheint, dass Sie kein E-Mail-Konto eingerichtet haben. Sie können mir direkt unter danielbernal@hey.com schreiben"; "SETUP_INFO" = "SMS-Filterung aktivieren"; +"IS_CASE_SENSITIVE" = "Groß- und Kleinschreibung beachten"; +"IS_CASE_SENSITIVE_DETAIL" = "Diese Option aktivieren, um Groß- und Kleinschreibung unterschiedlich zu behandeln."; diff --git a/Bouncer/en.lproj/Localizable.strings b/Bouncer/en.lproj/Localizable.strings index 79fe8b0..9c76733 100644 --- a/Bouncer/en.lproj/Localizable.strings +++ b/Bouncer/en.lproj/Localizable.strings @@ -74,3 +74,5 @@ "ASK_ANYTHING" = "Ask anything!"; "NO_EMAIL_CONFIGURED" = "It seems you don't have an email account set up. You can write me directly at danielbernal@hey.com"; "SETUP_INFO" = "Enable SMS filtering"; +"IS_CASE_SENSITIVE" = "Case sensitive"; +"IS_CASE_SENSITIVE_DETAIL" = "Enable this option to treat uppercase and lowercase characters differently."; diff --git a/Bouncer/es.lproj/Localizable.strings b/Bouncer/es.lproj/Localizable.strings index 50dfa76..8b8150e 100644 --- a/Bouncer/es.lproj/Localizable.strings +++ b/Bouncer/es.lproj/Localizable.strings @@ -73,4 +73,5 @@ "NEED_HELP" = "Necesitas ayuda?"; "ASK_ANYTHING" = "Preguntame!"; "NO_EMAIL_CONFIGURED" = "Parece que no tienes una cuenta de correo configurada en tu iPhone. Puedes escribirme a daniebernal@hey.com"; -"SETUP_INFO" = "Enable SMS filtering"; +"IS_CASE_SENSITIVE" = "Sensible a mayúsculas"; +"IS_CASE_SENSITIVE_DETAIL" = "Active esta opción para tratar los caracteres en mayúscula y minúscula de manera diferente."; diff --git a/Bouncer/fa.lproj/Localizable.strings b/Bouncer/fa.lproj/Localizable.strings index 3780490..3af6e9d 100644 --- a/Bouncer/fa.lproj/Localizable.strings +++ b/Bouncer/fa.lproj/Localizable.strings @@ -53,3 +53,5 @@ "4." = ".۴"; "USE_REGULAR_EXPRESSIONS" = "تطابق به عنوان Regex"; "USE_REGULAR_EXPRESSIONS_DETAIL" = "اگر فیلتر شما حاوی Regex است، این گزینه را فعال کنید. (Regex باید در قالب PCRE نوشته شود)."; +"IS_CASE_SENSITIVE" = "حساسیت به بزرگی و کوچکی حروف"; +"IS_CASE_SENSITIVE_DETAIL" = "فعال‌سازی این گزینه برای برخورد با حروف بزرگ و کوچک به صورت متفاوت"; diff --git a/Bouncer/fr.lproj/Localizable.strings b/Bouncer/fr.lproj/Localizable.strings index 91dcba4..1b65391 100644 --- a/Bouncer/fr.lproj/Localizable.strings +++ b/Bouncer/fr.lproj/Localizable.strings @@ -74,3 +74,5 @@ Créé par Daniel Bernal le 6/30/20. "ASK_ANYTHING" = "Posez-moi n'importe quelle question !"; "NO_EMAIL_CONFIGURED" = "Il semble que vous n'ayez pas configuré de compte e-mail. Vous pouvez m'écrire directement à danielbernal@hey.com."; "SETUP_INFO" = "Activer la filtration SMS"; +"IS_CASE_SENSITIVE" = "Sensible à la casse"; +"IS_CASE_SENSITIVE_DETAIL" = "Active cette option pour traiter les caractères majuscules et minuscules différemment."; diff --git a/BouncerTests/Models/AppSettingsDefaultsTests.swift.plist b/BouncerTests/Models/AppSettingsDefaultsTests.swift.plist index 7690b02..d2855c9 100644 Binary files a/BouncerTests/Models/AppSettingsDefaultsTests.swift.plist and b/BouncerTests/Models/AppSettingsDefaultsTests.swift.plist differ diff --git a/BouncerTests/Models/FilterStoreFileTests.swift b/BouncerTests/Models/FilterStoreFileTests.swift index c2827cc..e838988 100644 --- a/BouncerTests/Models/FilterStoreFileTests.swift +++ b/BouncerTests/Models/FilterStoreFileTests.swift @@ -150,15 +150,41 @@ class FilterStoreFileTests: XCTestCase { let filters: [Filter] = self.fetchFilters() XCTAssertEqual(filters.count, 5) - let regexFilter = filters[0] - XCTAssertEqual(regexFilter.phrase, "[b-chm-pP]at|ot") - XCTAssertEqual(regexFilter.subAction, .none) - XCTAssertEqual(regexFilter.useRegex, true) + var filter = filters[0] + XCTAssertEqual(filter.phrase, "YoUR COdE") + XCTAssertEqual(filter.action, .junk) + XCTAssertEqual(filter.subAction, .none) + XCTAssertEqual(filter.useRegex, false) + XCTAssertEqual(filter.caseSensitive, false) + + filter = filters[1] + XCTAssertEqual(filter.phrase, "[b-chm-pP]at|ot") + XCTAssertEqual(filter.action, .junk) + XCTAssertEqual(filter.subAction, .none) + XCTAssertEqual(filter.useRegex, true) + XCTAssertEqual(filter.caseSensitive, false) + + filter = filters[2] + XCTAssertEqual(filter.phrase, "etb") + XCTAssertEqual(filter.action, .promotion) + XCTAssertEqual(filter.subAction, .promotionOther) + XCTAssertEqual(filter.useRegex, false) + XCTAssertEqual(filter.caseSensitive, false) + + filter = filters[3] + XCTAssertEqual(filter.phrase, "rappi") + XCTAssertEqual(filter.action, .junk) + XCTAssertEqual(filter.subAction, .none) + XCTAssertEqual(filter.useRegex, false) + + filter = filters[4] + print(filter) + XCTAssertEqual(filter.phrase, "your code") + XCTAssertEqual(filter.action, .transaction) + XCTAssertEqual(filter.subAction, .transactionOther) + XCTAssertEqual(filter.useRegex, false) + - let wordFilter = filters[3] - XCTAssertEqual(wordFilter.phrase, "your code") - XCTAssertEqual(regexFilter.subAction, .none) - XCTAssertEqual(regexFilter.useRegex, true) } private func fetchFilters() -> [Filter] { diff --git a/BouncerTests/Models/SMSOfflineFilter.swift b/BouncerTests/Models/SMSOfflineFilter.swift index 498dc19..a07b8be 100644 --- a/BouncerTests/Models/SMSOfflineFilter.swift +++ b/BouncerTests/Models/SMSOfflineFilter.swift @@ -9,89 +9,115 @@ import IdentityLookup class SMSOfflineFilterTest: XCTestCase { + var messages = [ + SMSMessage(sender: "ETB Comunicaciones", text: "ETB compra 100 megas y recibe 200 por 6 meses. Incluye extensor de velocidAd mas promocion especial. Llama ya sin costo al 018000413807. Ver TyC. Hasta 31 ago 2020"), + SMSMessage(sender: "+16205261342", text: "ULANKA: Get 50% in shoes starting today!") + ] + override func setUp() { super.setUp() } - func testFiltering() { + func testV1Filters() { var smsFilter: SMSOfflineFilter - - let message = SMSMessage(sender: "ETB Comunicaciones", text: "ETB compra 100 megas y recibe 200 por 6 meses. Incluye extensor de velocidad mas promocion especial. Llama ya sin costo al 018000413807. Ver TyC. Hasta 31 ago 2020") - var filterResult: SMSOfflineFilterResponse // Old style filters (Text + Regex) smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "etb", type: .any, action: .junk)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .junk) smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "comunica", type: .sender, action: .transaction)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .transaction) smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "mega", type: .message, action: .promotion)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .promotion) - smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "[a-z]cidad", type: .message, action: .junk)]) - filterResult = smsFilter.filterMessage(message: message) + smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "[a-z]cidad", type: .message, action: .junk, useRegex: true)]) + filterResult = smsFilter.filterMessage(message: messages[0]) + XCTAssertEqual(filterResult.action, .junk) + + // Content filter test + smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "velocidad", type: .message, action: .junk, useRegex: false)]) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .junk) - + + } + + func testRegexFilter() { + var smsFilter: SMSOfflineFilter + var filterResult: SMSOfflineFilterResponse + // Regex filter test smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "[E].*[l][o]cidad", type: .message, action: .junk, useRegex: true)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .junk) - - // Content filter test - smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "velocidad", type: .message, action: .junk, useRegex: false)]) - filterResult = smsFilter.filterMessage(message: message) + + // Regex filter test (Case sensitive) + smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "[E].*[l][o]cidAd", type: .message, action: .junk, useRegex: true, caseSensitive: true)]) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .junk) - + } + + func testSubactionFilters() { + var smsFilter: SMSOfflineFilter + var filterResult: SMSOfflineFilterResponse + // SubAction filter tests smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "etb", type: .any, action: .transaction, subAction: .transactionOrder)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .transaction) XCTAssertEqual(filterResult.subaction, .transactionalOrders) - + smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "etb", type: .any, action: .transaction, subAction: .transactionFinance)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .transaction) XCTAssertEqual(filterResult.subaction, .transactionalFinance) - + smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "etb", type: .any, action: .transaction, subAction: .transactionReminders)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .transaction) XCTAssertEqual(filterResult.subaction, .transactionalReminders) - + smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "etb", type: .any, action: .promotion, subAction: .promotionOffers)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .promotion) XCTAssertEqual(filterResult.subaction, .promotionalOffers) - + smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), phrase: "etb", type: .any, action: .promotion, subAction: .promotionCoupons)]) - filterResult = smsFilter.filterMessage(message: message) + filterResult = smsFilter.filterMessage(message: messages[0]) XCTAssertEqual(filterResult.action, .promotion) XCTAssertEqual(filterResult.subaction, .promotionalCoupons) + smsFilter = SMSOfflineFilter(filterList: [Filter(id: UUID(), + phrase: "ULANKA", + type: .any, + action: .promotion, + subAction: .promotionOffers)]) + filterResult = smsFilter.filterMessage(message: messages[1]) + XCTAssertEqual(filterResult.action, .promotion) + XCTAssertEqual(filterResult.subaction, .promotionalOffers) } } diff --git a/smsfilter/MessageFilterExtension.swift b/smsfilter/MessageFilterExtension.swift index cdf0043..7306a03 100644 --- a/smsfilter/MessageFilterExtension.swift +++ b/smsfilter/MessageFilterExtension.swift @@ -16,32 +16,15 @@ final class MessageFilterExtension: ILMessageFilterExtension { override init() { os_log("FILTEREXTENSION - Message filtering Started.", log: OSLog.messageFilterLog, type: .info) super.init() - fetchFilters() } deinit { os_log("FILTEREXTENSION - Message filtering complete.", log: OSLog.messageFilterLog, type: .info) } - func fetchFilters() { - filterStore.fetch() - .receive(on: RunLoop.main) - .sink(receiveCompletion: {_ in - }, receiveValue: { [weak self] result in - os_log("FILTEREXTENSION - Filter list loaded", log: OSLog.messageFilterLog, type: .info) - self?.filters = result - }) - .store(in: &self.cancellables) - } - -} - -extension MessageFilterExtension: ILMessageFilterQueryHandling { - - func handle(_ queryRequest: ILMessageFilterQueryRequest, - context: ILMessageFilterExtensionContext, - completion: @escaping (ILMessageFilterQueryResponse) -> Void) { - + private func runFilters(_ queryRequest: ILMessageFilterQueryRequest, + context: ILMessageFilterExtensionContext, + completion: @escaping (ILMessageFilterQueryResponse) -> Void) { let response = ILMessageFilterQueryResponse() guard let sender = queryRequest.sender, let messageBody = queryRequest.messageBody else { return @@ -51,11 +34,32 @@ extension MessageFilterExtension: ILMessageFilterQueryHandling { text: messageBody)) response.action = filterOutput.action response.subAction = filterOutput.subaction - os_log("FILTEREXTENSION - Filtering action: %@", log: OSLog.messageFilterLog, type: .info, "\(response.action)") - os_log("FILTEREXTENSION - Filtering action: %@", log: OSLog.messageFilterLog, type: .info, "\(response.subAction)") + os_log("FILTEREXTENSION - Filtering action: %@", log: OSLog.messageFilterLog, type: .info, "\(response.action.rawValue)") + os_log("FILTEREXTENSION - Filtering sub-action: %@", log: OSLog.messageFilterLog, type: .info, "\(response.subAction.rawValue)") os_log("FILTEREXTENSION - Filtering done", log: OSLog.messageFilterLog, type: .info) completion(response) } + + +} + +extension MessageFilterExtension: ILMessageFilterQueryHandling { + + func handle(_ queryRequest: ILMessageFilterQueryRequest, + context: ILMessageFilterExtensionContext, + completion: @escaping (ILMessageFilterQueryResponse) -> Void) { + + filterStore.fetch() + .receive(on: RunLoop.main) + .sink(receiveCompletion: {_ in + }, receiveValue: { [weak self] result in + os_log("FILTEREXTENSION - Filter list loaded", log: OSLog.messageFilterLog, type: .info) + self?.filters = result + return self?.runFilters(queryRequest, context: context, completion: completion) ?? () + }) + .store(in: &self.cancellables) + } + } extension MessageFilterExtension: ILMessageFilterCapabilitiesQueryHandling { @@ -63,10 +67,12 @@ extension MessageFilterExtension: ILMessageFilterCapabilitiesQueryHandling { func handle(_ capabilitiesQueryRequest: ILMessageFilterCapabilitiesQueryRequest, context: ILMessageFilterExtensionContext, completion: @escaping (ILMessageFilterCapabilitiesQueryResponse) -> Void) { let response = ILMessageFilterCapabilitiesQueryResponse() response.transactionalSubActions = [.transactionalOrders, + .transactionalOthers, .transactionalFinance, .transactionalReminders] response.promotionalSubActions = [.promotionalOffers, - .promotionalCoupons] + .promotionalCoupons, + .promotionalOthers] completion(response) } }