├── .github ├── ISSUE_TEMPLATE │ ├── 1.bug_report.yml │ ├── 2.feature_request.yml │ ├── 3.discussion.yml │ ├── 4.share.yml │ └── config.yml ├── labeler.yml └── workflows │ ├── Upgrade_Directly____No_need_to_uninstall_the_previous_version.yml │ ├── labeler.yml │ ├── latest.yml │ ├── new_release.yml │ ├── share.js │ ├── share.yml │ └── test.yml ├── .gitignore ├── Docs ├── CONTRIBUTING.md └── PRIVACY POLICY.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── spam │ │ └── blocker │ │ ├── service │ │ └── RuleTest.kt │ │ └── util │ │ └── ScheduleTest.kt │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── spam │ │ └── blocker │ │ ├── App.kt │ │ ├── GlobalEvents.kt │ │ ├── GlobalVariables.kt │ │ ├── config │ │ └── Config.kt │ │ ├── db │ │ ├── ApiTable.kt │ │ ├── BotTable.kt │ │ ├── Db.kt │ │ ├── HistoryTable.kt │ │ ├── RuleTable.kt │ │ └── SpamTable.kt │ │ ├── def │ │ └── Def.kt │ │ ├── service │ │ ├── CallScreeningService.kt │ │ ├── CallStateReceiver.kt │ │ ├── CopyToClipboardReceiver.kt │ │ ├── SmsReceiver.kt │ │ ├── Tile.kt │ │ ├── WapPushReceiver.kt │ │ ├── bot │ │ │ ├── Action.kt │ │ │ ├── Actions.kt │ │ │ ├── BotSerializersModule.kt │ │ │ ├── MyWorkManager.kt │ │ │ └── Schedule.kt │ │ ├── checker │ │ │ ├── CheckResult.kt │ │ │ └── Checker.kt │ │ └── reporting │ │ │ └── ReportSpam.kt │ │ ├── ui │ │ ├── NotificationTrampolineActivity.kt │ │ ├── crash │ │ │ └── CrashReportActivity.kt │ │ ├── history │ │ │ ├── HistoryCard.kt │ │ │ ├── HistoryContextMenu.kt │ │ │ ├── HistoryFabs.kt │ │ │ ├── HistoryIndicator.kt │ │ │ ├── HistoryList.kt │ │ │ ├── HistoryScreen.kt │ │ │ └── HistoryViewModel.kt │ │ ├── main │ │ │ ├── Debug.kt │ │ │ └── MainActivity.kt │ │ ├── setting │ │ │ ├── GloballyEnabled.kt │ │ │ ├── SettingScreen.kt │ │ │ ├── Testing.kt │ │ │ ├── api │ │ │ │ ├── ApiCard.kt │ │ │ │ ├── ApiHeader.kt │ │ │ │ ├── ApiList.kt │ │ │ │ ├── ApiPresets.kt │ │ │ │ ├── ApiViewModel.kt │ │ │ │ └── EditApiDialog.kt │ │ │ ├── bot │ │ │ │ ├── ActionCard.kt │ │ │ │ ├── ActionHeader.kt │ │ │ │ ├── ActionList.kt │ │ │ │ ├── BotCard.kt │ │ │ │ ├── BotHeader.kt │ │ │ │ ├── BotList.kt │ │ │ │ ├── BotPresets.kt │ │ │ │ ├── BotViewModel.kt │ │ │ │ ├── EditActionDialog.kt │ │ │ │ ├── EditBotDialog.kt │ │ │ │ └── EditScheduleDialog.kt │ │ │ ├── misc │ │ │ │ ├── About.kt │ │ │ │ ├── BackupRestore.kt │ │ │ │ ├── Languages.kt │ │ │ │ └── Theme.kt │ │ │ ├── quick │ │ │ │ ├── BlockType.kt │ │ │ │ ├── Contacts.kt │ │ │ │ ├── Dialed.kt │ │ │ │ ├── Emergency.kt │ │ │ │ ├── MeetingMode.kt │ │ │ │ ├── OffTime.kt │ │ │ │ ├── RecentApps.kt │ │ │ │ ├── RepeatedCall.kt │ │ │ │ ├── SpamDB.kt │ │ │ │ └── Stir.kt │ │ │ └── regex │ │ │ │ ├── CallAlert.kt │ │ │ │ ├── EditRuleDialog.kt │ │ │ │ ├── ImportRule.kt │ │ │ │ ├── RuleCard.kt │ │ │ │ ├── RuleHeader.kt │ │ │ │ ├── RuleList.kt │ │ │ │ ├── RuleSearchBox.kt │ │ │ │ ├── RuleViewModel.kt │ │ │ │ └── SmsBomb.kt │ │ ├── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ │ ├── util.kt │ │ └── widgets │ │ │ ├── Animation.kt │ │ │ ├── Balloon.kt │ │ │ ├── BottomNavView.kt │ │ │ ├── CheckBox.kt │ │ │ ├── ConfigImportExportDialog.kt │ │ │ ├── DrawableImage.kt │ │ │ ├── Dropdown.kt │ │ │ ├── Fab.kt │ │ │ ├── FileChooser.kt │ │ │ ├── Form.kt │ │ │ ├── GreenDot.kt │ │ │ ├── HtmlText.kt │ │ │ ├── InputBox.kt │ │ │ ├── Label.kt │ │ │ ├── NonLazyGrid.kt │ │ │ ├── OutlinedCard.kt │ │ │ ├── PopupDialog.kt │ │ │ ├── RadioGroup.kt │ │ │ ├── Resource.kt │ │ │ ├── RowCenter.kt │ │ │ ├── Scrollbar.kt │ │ │ ├── Section.kt │ │ │ ├── SnackBar.kt │ │ │ ├── StrokeButton.kt │ │ │ ├── Swipe.kt │ │ │ ├── SwitchBox.kt │ │ │ ├── TimePicker.kt │ │ │ ├── TooltiipPositionProvider.kt │ │ │ └── WeekdaysPicker.kt │ │ └── util │ │ ├── Algorithm.kt │ │ ├── AnnotatedFormatter.kt │ │ ├── AppInfo.kt │ │ ├── Clipboard.kt │ │ ├── Contact.kt │ │ ├── CountryCode.kt │ │ ├── Csv.kt │ │ ├── Flag.kt │ │ ├── Http.kt │ │ ├── Launcher.kt │ │ ├── Logcat.kt │ │ ├── Logger.kt │ │ ├── MockkWorkaround.kt │ │ ├── Notification.kt │ │ ├── PermissionChain.kt │ │ ├── Permissions.kt │ │ ├── PhoneNumber.kt │ │ ├── SharedPref.kt │ │ ├── StringTag.kt │ │ ├── TimeSchedule.kt │ │ ├── Util.kt │ │ ├── Xml.kt │ │ ├── pdu │ │ ├── ContentType.java │ │ ├── InvalidHeaderValueException.java │ │ ├── MmsException.java │ │ ├── pdu │ │ │ ├── AcknowledgeInd.java │ │ │ ├── Base64.java │ │ │ ├── CharacterSets.java │ │ │ ├── DeliveryInd.java │ │ │ ├── EncodedStringValue.java │ │ │ ├── GenericPdu.java │ │ │ ├── MultimediaMessagePdu.java │ │ │ ├── NotificationInd.java │ │ │ ├── NotifyRespInd.java │ │ │ ├── PduBody.java │ │ │ ├── PduComposer.java │ │ │ ├── PduContentTypes.java │ │ │ ├── PduHeaders.java │ │ │ ├── PduParser.java │ │ │ ├── PduPart.java │ │ │ ├── PduPersister.java │ │ │ ├── QuotedPrintable.java │ │ │ ├── ReadOrigInd.java │ │ │ ├── ReadRecInd.java │ │ │ ├── RetrieveConf.java │ │ │ ├── SendConf.java │ │ │ └── SendReq.java │ │ └── util │ │ │ ├── AbstractCache.java │ │ │ ├── DownloadDrmHelper.java │ │ │ ├── DrmConvertSession.java │ │ │ ├── PduCache.java │ │ │ ├── PduCacheEntry.java │ │ │ └── SqliteWrapper.java │ │ └── race.kt │ └── res │ ├── drawable │ ├── ic_add.xml │ ├── ic_airplane.xml │ ├── ic_alternative.xml │ ├── ic_backup_export.xml │ ├── ic_backup_import.xml │ ├── ic_bell_ringing.xml │ ├── ic_call.xml │ ├── ic_call_blocked.xml │ ├── ic_call_miss.xml │ ├── ic_call_pass.xml │ ├── ic_category.xml │ ├── ic_check_circle.xml │ ├── ic_check_green.xml │ ├── ic_clear.xml │ ├── ic_contact_circle.xml │ ├── ic_contact_square.xml │ ├── ic_contacts_square.xml │ ├── ic_contat_square.xml │ ├── ic_copy.xml │ ├── ic_csv.xml │ ├── ic_daily.xml │ ├── ic_db_add.xml │ ├── ic_db_and_config.xml │ ├── ic_db_delete.xml │ ├── ic_delay.xml │ ├── ic_dial_pad.xml │ ├── ic_display_filter.xml │ ├── ic_dropdown_arrow.xml │ ├── ic_duration.xml │ ├── ic_exclude.xml │ ├── ic_exit.xml │ ├── ic_fail_red.xml │ ├── ic_file_read.xml │ ├── ic_file_write.xml │ ├── ic_filter.xml │ ├── ic_find.xml │ ├── ic_find_check.xml │ ├── ic_flag_arabic.xml │ ├── ic_flag_catalan.xml │ ├── ic_flag_galician.xml │ ├── ic_flags.xml │ ├── ic_hang.xml │ ├── ic_heads_up.xml │ ├── ic_history_cleanup.xml │ ├── ic_hourglass.xml │ ├── ic_http.xml │ ├── ic_http_header.xml │ ├── ic_international.xml │ ├── ic_link.xml │ ├── ic_log.xml │ ├── ic_no.xml │ ├── ic_note.xml │ ├── ic_notification_off.xml │ ├── ic_number_sign.xml │ ├── ic_open_msg.xml │ ├── ic_post.xml │ ├── ic_priority.xml │ ├── ic_question.xml │ ├── ic_recycle_bin.xml │ ├── ic_regex.xml │ ├── ic_regex_capture.xml │ ├── ic_reorder.xml │ ├── ic_repeat.xml │ ├── ic_replace.xml │ ├── ic_right_arrow.xml │ ├── ic_settings.xml │ ├── ic_shade.xml │ ├── ic_sms.xml │ ├── ic_sms_blocked.xml │ ├── ic_sms_pass.xml │ ├── ic_statusbar_shade.xml │ ├── ic_tile.xml │ ├── ic_time_slot.xml │ ├── ic_toggle.xml │ ├── ic_tube.xml │ ├── ic_unknown_app_icon.xml │ ├── ic_warning.xml │ ├── ic_weekly.xml │ ├── ic_workflow.xml │ ├── ic_xml.xml │ ├── ic_yes.xml │ └── spinner_arrow.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.webp │ ├── values-ar │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-ca │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-de │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-es │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-fa │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-fr │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-gal │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-in │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-it │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-ja │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-pt-rBR │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-ru │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-tr │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-uk │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values-zh │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ └── strings_9.xml │ ├── values │ ├── strings_1.xml │ ├── strings_10.xml │ ├── strings_11.xml │ ├── strings_12.xml │ ├── strings_13.xml │ ├── strings_14.xml │ ├── strings_15.xml │ ├── strings_16.xml │ ├── strings_17.xml │ ├── strings_2.xml │ ├── strings_3.xml │ ├── strings_4.xml │ ├── strings_5.xml │ ├── strings_6.xml │ ├── strings_7.xml │ ├── strings_8.xml │ ├── strings_9.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── auto_translate ├── go.mod ├── retry.go └── translate.go ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties ├── metadata └── en-US │ ├── changelogs │ ├── 1.txt │ ├── 11.txt │ ├── 110.txt │ ├── 111.txt │ ├── 112.txt │ ├── 113.txt │ ├── 114.txt │ ├── 115.txt │ ├── 116.txt │ ├── 117.txt │ ├── 118.txt │ ├── 119.txt │ ├── 12.txt │ ├── 120.txt │ ├── 13.txt │ ├── 14.txt │ ├── 15.txt │ ├── 16.txt │ ├── 17.txt │ ├── 18.txt │ ├── 19.txt │ ├── 200.txt │ ├── 201.txt │ ├── 202.txt │ ├── 300.txt │ ├── 301.txt │ ├── 302.txt │ ├── 303.txt │ ├── 304.txt │ ├── 305.txt │ ├── 400.txt │ ├── 401.txt │ ├── 402.txt │ ├── 403.txt │ ├── 404.txt │ ├── 405.txt │ ├── 406.txt │ ├── 407.txt │ ├── 408.txt │ ├── 409.txt │ ├── 410.txt │ └── 411.txt │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.call_sms.png │ │ ├── 2.report_spam.png │ │ ├── 3.setting.png │ │ └── 4.notification.png │ ├── short_description.txt │ └── title.txt └── settings.gradle.kts /.github/ISSUE_TEMPLATE/1.bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report an issue 3 | labels: [bug] 4 | body: 5 | 6 | - type: input 7 | id: SpamBlocker-version 8 | attributes: 9 | label: SpamBlocker version 10 | description: | 11 | You can find the version in **About** section. 12 | placeholder: | 13 | Example: 3.2 14 | validations: 15 | required: false 16 | 17 | - type: input 18 | id: android-version 19 | attributes: 20 | label: Android version 21 | placeholder: | 22 | Example: 15 23 | validations: 24 | required: true 25 | 26 | 27 | - type: textarea 28 | id: detail 29 | attributes: 30 | label: The issue 31 | validations: 32 | required: true 33 | 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2.feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a new feature 3 | labels: [new feature] 4 | body: 5 | 6 | - type: textarea 7 | id: feature-description 8 | attributes: 9 | label: The feature 10 | validations: 11 | required: true 12 | 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3.discussion.yml: -------------------------------------------------------------------------------- 1 | name: Discussion 2 | description: Start a discussion 3 | labels: [discussion] 4 | body: 5 | 6 | - type: textarea 7 | id: detail 8 | attributes: 9 | label: Talk about anything 10 | validations: 11 | required: true 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/4.share.yml: -------------------------------------------------------------------------------- 1 | name: Share Regex/Workflow 2 | description: Share your regex rules or workflows 3 | title: "[Country] Description" 4 | labels: [share regex/workflow] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for sharing, the content will be synchronized to the wiki page within a minute. 10 | If you need to update the content later, just edit this issue. 11 | 12 | - type: textarea 13 | id: detail 14 | attributes: 15 | label: "The regex/workflow" 16 | validations: 17 | required: true 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Made with love 2 | # 3 | # This is the configuration file for the labeler action 4 | # for more example in setting this file or informations about 5 | # the labeler action, see: 6 | # https://github.com/actions/labeler 7 | # 8 | # If you want to use it in your project, you will simply need: 9 | # 1 - commit this config file to your repository into the '.github' folder. 10 | # 2 - commit the 'labeler.yml' action file into the '.github/workflows' folder. 11 | # 12 | # You will probably need to modify the labels, or their targets. 13 | # 14 | 15 | # Add 'assets' label to any assets file changes within the source dir 16 | Assets: 17 | - changed-files: 18 | - any-glob-to-any-file: 'app/src/main/assets/**/*' 19 | 20 | # Add 'Automation' label to any file changes within scripts or workflows dir 21 | Automation: 22 | - changed-files: 23 | - any-glob-to-any-file: '.github/scripts/**/*' 24 | - any-glob-to-any-file: '.github/workflows/**/*' 25 | 26 | # Add 'Github' label to any git file changes within the source dir 27 | Github: 28 | - changed-files: 29 | - any-glob-to-any-file: '.github/**' 30 | 31 | # Add 'Core' label to any change within the 'core' package 32 | Core: 33 | - changed-files: 34 | - any-glob-to-any-file: 'app/src/main/java/**/*' 35 | - any-glob-to-any-file: 'app/src/main/kotlin/**/*' 36 | 37 | # Add 'Store file' label to any change within the 'play' folder and subfolder(s) 38 | Store file: 39 | - changed-files: 40 | - any-glob-to-any-file: 'app/src/main/play/**/*' 41 | - any-glob-to-any-file: 'metadata/**/*' 42 | 43 | # Add 'Gradle wrapper' label to any change within the 'gradle' folder and subfolder 44 | Gradle wrapper: 45 | - changed-files: 46 | - any-glob-to-any-file: 'gradle/wrapper/**/*' 47 | 48 | # Add 'Translation` label to any change to strings.xml files as long as the English strings.xml file hasn't changed 49 | #Translation: 50 | #- changed-files: 51 | # - any-glob-to-any-file: 'app/src/**/strings_*.xml'] 52 | # - all: ['!app/src/main/res/values/strings_*.xml'] 53 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | # Made with love 2 | # 3 | # This workflow uses actions that are not certified by GitHub. 4 | # 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | # see https://github.com/actions/labeler 9 | # for more informations 10 | # 11 | # For most projects, this workflow file will not need changing; you simply need: 12 | # 1 - commit this file to your repository into the '.github/workflows' folder. 13 | # 2 - commit the 'labeler.yml' configuration file into the '.github' folder. 14 | # 15 | # You probably want to add, replace or delete labels, 16 | # remember to check this configuration file. 17 | # 18 | 19 | name: "Pull Request Labeler" 20 | on: 21 | - pull_request_target 22 | 23 | jobs: 24 | triage: 25 | permissions: 26 | contents: read 27 | pull-requests: write 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/labeler@v5 31 | with: 32 | # Check if you have enabled tokens for your repository 33 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 34 | # This removes the label if the change(s) are undone in the PR 35 | sync-labels: true 36 | 37 | -------------------------------------------------------------------------------- /.github/workflows/share.yml: -------------------------------------------------------------------------------- 1 | name: Share regex/workflow on wiki 2 | 3 | on: 4 | issues: 5 | types: [opened, edited, deleted] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | collect-issues: 12 | if: github.event.action == 'deleted' || contains(github.event.issue.labels.*.name, 'share regex/workflow') 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: '22' 22 | 23 | - name: Install Dependencies 24 | run: npm install @actions/core @actions/github 25 | 26 | - name: Run Collection Script 27 | id: collect 28 | run: node .github/workflows/share.js 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.SYNC_TOKEN }} 31 | 32 | - name: Log Results 33 | run: echo "${{ steps.collect.outputs.result }}" 34 | 35 | - name: Upload result 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: generated.md 39 | path: generated.md 40 | 41 | - name: Checkout wiki 42 | uses: actions/checkout@v4 43 | with: 44 | repository: ${{github.repository}}.wiki 45 | path: wiki_dir 46 | 47 | - name: Modify wiki 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.SYNC_TOKEN }} 50 | run: | 51 | cd wiki_dir 52 | mv ../generated.md ./Regex-Workflow-Templates.md 53 | git config --global user.email "actions@github.com" 54 | git config --global user.name "Github Action" 55 | git add . 56 | git diff-index --quiet HEAD -- || git commit -m "update template" 57 | git push 58 | 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | .kotlin/ 4 | *.db 5 | build/ 6 | app/release/ 7 | app/debug/ 8 | auto_translate/go.sum 9 | local.properties 10 | auto_translate/.vscode/ 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 aj3423 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class spam.blocker.config.* { *; } 24 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/service/CopyToClipboardReceiver.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.service 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import spam.blocker.util.Clipboard 7 | import spam.blocker.util.Notification 8 | 9 | class CopyToClipboardReceiver : BroadcastReceiver() { 10 | override fun onReceive(ctx: Context, intent: Intent) { 11 | 12 | // dismiss this notification, others remain 13 | val notifId = intent.getIntExtra("notificationId", 0) 14 | Notification.cancelById(ctx, notifId) 15 | 16 | // copy to clipboard 17 | val toCopy = intent.getStringExtra("toCopy") 18 | Clipboard.copy(ctx, toCopy) 19 | 20 | // val copied = ctx.getString(R.string.copied_to_clipboard) 21 | // Toast.makeText(ctx, copied.format(toCopy), Toast.LENGTH_SHORT).show(); 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/service/Tile.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.service 2 | 3 | import android.service.quicksettings.Tile.STATE_ACTIVE 4 | import android.service.quicksettings.Tile.STATE_INACTIVE 5 | import android.service.quicksettings.TileService 6 | import spam.blocker.G 7 | import spam.blocker.util.spf 8 | 9 | class Tile : TileService() { 10 | 11 | private fun update() { 12 | val spf = spf.Global(this) 13 | val enabled = spf.isGloballyEnabled() 14 | qsTile.state = if (enabled) STATE_ACTIVE else STATE_INACTIVE 15 | 16 | qsTile.updateTile() 17 | } 18 | override fun onStartListening() { 19 | update() 20 | } 21 | 22 | override fun onClick() { 23 | val spf = spf.Global(this) 24 | spf.toggleGloballyEnabled() 25 | 26 | update() 27 | 28 | G.globallyEnabled.value = spf.isGloballyEnabled() 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/service/bot/BotSerializersModule.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.service.bot 2 | 3 | import kotlinx.serialization.json.Json 4 | import kotlinx.serialization.modules.SerializersModule 5 | import kotlinx.serialization.modules.polymorphic 6 | import kotlinx.serialization.modules.subclass 7 | 8 | 9 | val botModule = SerializersModule { 10 | polymorphic(ISchedule::class) { 11 | subclass(Daily::class) 12 | subclass(Weekly::class) 13 | subclass(Periodically::class) 14 | subclass(Delay::class) 15 | } 16 | polymorphic(IAction::class) { 17 | subclass(CleanupHistory::class) 18 | subclass(HttpDownload::class) 19 | subclass(CleanupSpamDB::class) 20 | subclass(BackupExport::class) 21 | subclass(BackupImport::class) 22 | subclass(ReadFile::class) 23 | subclass(WriteFile::class) 24 | subclass(ParseCSV::class) 25 | subclass(ParseXML::class) 26 | subclass(RegexExtract::class) 27 | subclass(ImportToSpamDB::class) 28 | subclass(ImportAsRegexRule::class) 29 | subclass(ConvertNumber::class) 30 | subclass(FindRules::class) 31 | subclass(ModifyRules::class) 32 | subclass(EnableWorkflow::class) 33 | subclass(EnableApp::class) 34 | subclass(ParseQueryResult::class) 35 | subclass(FilterSpamResult::class) 36 | subclass(InterceptCall::class) 37 | subclass(InterceptSms::class) 38 | subclass(ReportNumber::class) 39 | subclass(CategoryConfig::class) 40 | } 41 | } 42 | 43 | // A json serializer that supports ISchedule and IAction 44 | val botJson = Json { 45 | serializersModule = botModule 46 | encodeDefaults = true 47 | classDiscriminator = "type" 48 | ignoreUnknownKeys = true 49 | } 50 | 51 | val botPrettyJson = Json { 52 | prettyPrint = true 53 | 54 | serializersModule = botModule 55 | encodeDefaults = true 56 | classDiscriminator = "type" 57 | ignoreUnknownKeys = true 58 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/NotificationTrampolineActivity.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import spam.blocker.util.Launcher 6 | import spam.blocker.util.Notification 7 | 8 | class NotificationTrampolineActivity : ComponentActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | 12 | val type = intent!!.getStringExtra("type") 13 | val blocked = intent.getBooleanExtra("blocked", false) 14 | 15 | // val action = intent.action 16 | // logi("notification clicked, type: $type, blocked: $blocked, action: $action") 17 | 18 | when (type) { 19 | "sms" -> { 20 | if (blocked) { // launch the default SMS app 21 | Launcher.launchSMSApp(this) 22 | } else { // open the conversation in SMS app 23 | val smsTo = intent.getStringExtra("rawNumber") 24 | Launcher.openSMSConversation(this, smsTo) 25 | } 26 | } 27 | "call" -> { // only blocked calls have notification 28 | Launcher.launchCallApp(this) 29 | } 30 | else -> { // this should never happen 31 | Launcher.launchThisApp(this) 32 | } 33 | } 34 | 35 | Notification.cancelAll(this) 36 | 37 | finish() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/history/HistoryViewModel.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.history 2 | 3 | import android.content.Context 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.lifecycle.ViewModel 6 | import spam.blocker.db.CallTable 7 | import spam.blocker.db.HistoryRecord 8 | import spam.blocker.db.HistoryTable 9 | import spam.blocker.db.SmsTable 10 | import spam.blocker.def.Def 11 | import spam.blocker.util.spf 12 | 13 | /* 14 | To simplify the code, this view model is used in GlobalVariables instead of viewModel<...>(). 15 | */ 16 | open class HistoryViewModel( 17 | val forType: Int, 18 | val table: HistoryTable, 19 | ) : ViewModel() { 20 | val records = mutableStateListOf() 21 | 22 | fun reload(ctx: Context) { 23 | records.clear() 24 | 25 | val spf = spf.HistoryOptions(ctx) 26 | val showPassed = spf.getShowPassed() 27 | val showBlocked = spf.getShowBlocked() 28 | 29 | records.addAll(table.listRecords(ctx).filter { 30 | (showPassed && it.isNotBlocked()) || (showBlocked && it.isBlocked()) 31 | }) 32 | } 33 | } 34 | 35 | class CallViewModel : HistoryViewModel(Def.ForNumber, CallTable()) 36 | 37 | class SmsViewModel : HistoryViewModel(Def.ForSms, SmsTable()) 38 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/main/Debug.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.main 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | 6 | @SuppressLint("Range") 7 | fun debug(ctx: Context) { 8 | 9 | 10 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/api/ApiCard.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.api 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.text.font.FontWeight 10 | import androidx.compose.ui.unit.dp 11 | import spam.blocker.db.Api 12 | import spam.blocker.ui.M 13 | import spam.blocker.ui.widgets.GreenDot 14 | import spam.blocker.ui.widgets.GreyLabel 15 | import spam.blocker.ui.widgets.NonLazyGrid 16 | import spam.blocker.ui.widgets.OutlineCard 17 | import spam.blocker.ui.widgets.RowVCenterSpaced 18 | 19 | 20 | 21 | @Composable 22 | fun ApiCard( 23 | api: Api, 24 | modifier: Modifier, 25 | ) { 26 | OutlineCard( 27 | containerBg = MaterialTheme.colorScheme.background 28 | ) { 29 | RowVCenterSpaced( 30 | space = 10, 31 | modifier = modifier 32 | .fillMaxWidth() 33 | .padding(horizontal = 10.dp, vertical = 8.dp) 34 | ) { 35 | Column( 36 | modifier = M.weight(1f) 37 | ) { 38 | RowVCenterSpaced(8) { 39 | // Green dot 40 | if (api.enabled) { 41 | GreenDot() 42 | } 43 | 44 | // Desc 45 | GreyLabel(text = api.summary(), fontWeight = FontWeight.SemiBold) 46 | } 47 | // first action summary 48 | // api.actions.firstOrNull()?.Summary() 49 | } 50 | 51 | // action icons 52 | NonLazyGrid( 53 | columns = 3, 54 | itemCount = api.actions.size, 55 | ) { 56 | api.actions[it].Icon() 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/api/ApiViewModel.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.api 2 | 3 | import android.content.Context 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.mutableStateOf 6 | import spam.blocker.db.Api 7 | import spam.blocker.db.ApiTable 8 | import spam.blocker.db.Db 9 | import spam.blocker.def.Def 10 | import spam.blocker.util.spf 11 | 12 | open class ApiViewModel( 13 | val table: ApiTable, 14 | val forType: Int, 15 | ) { 16 | val apis = mutableStateListOf() 17 | val listCollapsed = mutableStateOf(false) 18 | 19 | 20 | fun reloadDb(ctx: Context) { 21 | apis.clear() 22 | 23 | val all = table.listAll(ctx) 24 | apis.addAll(all) 25 | 26 | listCollapsed.value = if (forType == Def.ForApiQuery) 27 | spf.ApiQueryOptions(ctx).isListCollapsed() 28 | else 29 | spf.ApiReportOptions(ctx).isListCollapsed() 30 | } 31 | 32 | fun toggleCollapse(ctx: Context) { 33 | // don't collapse if it's empty 34 | if (apis.isEmpty() && !listCollapsed.value) { 35 | return 36 | } 37 | 38 | listCollapsed.value = !listCollapsed.value 39 | when(forType) { 40 | Def.ForApiQuery -> spf.ApiQueryOptions(ctx).setListCollapsed(listCollapsed.value) 41 | Def.ForApiReport -> spf.ApiReportOptions(ctx).setListCollapsed(listCollapsed.value) 42 | } 43 | } 44 | } 45 | 46 | class ApiQueryViewModel : ApiViewModel(ApiTable(Db.TABLE_API_QUERY), Def.ForApiQuery) 47 | class ApiReportViewModel : ApiViewModel(ApiTable(Db.TABLE_API_REPORT), Def.ForApiReport) 48 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/bot/BotViewModel.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.bot 2 | 3 | import android.content.Context 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.mutableStateOf 6 | import spam.blocker.db.Bot 7 | import spam.blocker.db.BotTable 8 | import spam.blocker.util.spf 9 | 10 | class BotViewModel { 11 | val bots = mutableStateListOf() 12 | val listCollapsed = mutableStateOf(false) 13 | 14 | 15 | fun reload(ctx: Context) { 16 | bots.clear() 17 | 18 | val all = BotTable.listAll(ctx) 19 | bots.addAll(all) 20 | 21 | listCollapsed.value = spf.BotOptions(ctx).isListCollapsed() 22 | } 23 | 24 | fun toggleCollapse(ctx: Context) { 25 | // don't collapse if it's empty 26 | if (bots.isEmpty() && !listCollapsed.value) { 27 | return 28 | } 29 | 30 | listCollapsed.value = !listCollapsed.value 31 | spf.BotOptions(ctx).setListCollapsed(listCollapsed.value) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/bot/EditActionDialog.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.bot 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.MutableState 5 | import androidx.compose.runtime.snapshots.SnapshotStateList 6 | import spam.blocker.service.bot.IAction 7 | import spam.blocker.service.bot.parseAction 8 | import spam.blocker.service.bot.serialize 9 | import spam.blocker.ui.widgets.PopupDialog 10 | 11 | // when this dialog closes, the actions[i] will be updated 12 | @Composable 13 | fun EditActionDialog( 14 | trigger: MutableState, 15 | actions: SnapshotStateList, 16 | index: Int, 17 | ) { 18 | if (!trigger.value) { 19 | return 20 | } 21 | PopupDialog( 22 | trigger = trigger, 23 | onDismiss = { 24 | // set to itself to refresh the UI 25 | val clone = actions[index].serialize().parseAction() 26 | actions[index] = clone 27 | } 28 | ) { 29 | actions[index].Options() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/bot/EditScheduleDialog.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.bot 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.MutableState 5 | import spam.blocker.service.bot.ISchedule 6 | import spam.blocker.service.bot.parseSchedule 7 | import spam.blocker.service.bot.serialize 8 | import spam.blocker.ui.widgets.PopupDialog 9 | 10 | @Composable 11 | fun EditScheduleDialog( 12 | trigger: MutableState, 13 | schedule: MutableState, 14 | ) { 15 | if (!trigger.value) { 16 | return 17 | } 18 | PopupDialog( 19 | trigger = trigger, 20 | onDismiss = { 21 | // set to itself to refresh the UI 22 | val clone = schedule.value!!.serialize().parseSchedule() 23 | schedule.value = clone 24 | } 25 | ) { 26 | schedule.value!!.Options() 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/misc/About.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.misc 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.saveable.rememberSaveable 6 | import androidx.compose.ui.platform.LocalContext 7 | import spam.blocker.BuildConfig 8 | import spam.blocker.R 9 | import spam.blocker.ui.setting.SettingRow 10 | import spam.blocker.ui.theme.SkyBlue 11 | import spam.blocker.ui.widgets.HtmlText 12 | import spam.blocker.ui.widgets.PopupDialog 13 | import spam.blocker.ui.widgets.Str 14 | import spam.blocker.ui.widgets.StrokeButton 15 | 16 | const val repo = "https://github.com/aj3423/SpamBlocker" 17 | 18 | @Composable 19 | fun About() { 20 | val ctx = LocalContext.current 21 | 22 | SettingRow { 23 | val popupTrigger = rememberSaveable { mutableStateOf(false) } 24 | 25 | if (popupTrigger.value) { 26 | PopupDialog( 27 | trigger = popupTrigger, 28 | content = { 29 | 30 | val msg = 31 | "${ctx.resources.getString(R.string.version)}:
 ${BuildConfig.VERSION_NAME}

" + 32 | "${ctx.resources.getString(R.string.source_code)}:
$repo
" 33 | 34 | HtmlText(html = msg) 35 | } 36 | ) 37 | } 38 | 39 | StrokeButton( 40 | label = Str(R.string.about), 41 | color = SkyBlue, 42 | ) { 43 | popupTrigger.value = true 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/misc/Theme.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.misc 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import androidx.compose.ui.platform.LocalContext 6 | import spam.blocker.G 7 | import spam.blocker.R 8 | import spam.blocker.ui.setting.LabeledRow 9 | import spam.blocker.ui.widgets.LabelItem 10 | import spam.blocker.ui.widgets.Spinner 11 | import spam.blocker.util.spf 12 | 13 | @Composable 14 | fun Theme() { 15 | val ctx = LocalContext.current 16 | val spf = spf.Global(ctx) 17 | 18 | val options = remember { 19 | 20 | val followSystem = ctx.resources.getString(R.string.follow_system) 21 | val themes = listOf(followSystem) + ctx.resources.getStringArray(R.array.theme_list).toList() 22 | 23 | themes.mapIndexed { index, label -> 24 | LabelItem( 25 | label = label, 26 | onClick = { 27 | spf.setThemeType(index) 28 | G.themeType.intValue = index 29 | } 30 | ) 31 | } 32 | } 33 | 34 | 35 | LabeledRow( 36 | R.string.theme, 37 | content = { 38 | Spinner( 39 | options, 40 | G.themeType.intValue, 41 | ) 42 | } 43 | ) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/quick/OffTime.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.quick 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableIntStateOf 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.saveable.rememberSaveable 9 | import androidx.compose.runtime.setValue 10 | import androidx.compose.ui.platform.LocalContext 11 | import spam.blocker.R 12 | import spam.blocker.ui.setting.LabeledRow 13 | import spam.blocker.ui.widgets.GreyButton 14 | import spam.blocker.ui.widgets.SwitchBox 15 | import spam.blocker.ui.widgets.TimeRangePicker 16 | import spam.blocker.util.Util 17 | import spam.blocker.util.spf 18 | 19 | @Composable 20 | fun OffTime() { 21 | val ctx = LocalContext.current 22 | val spf = spf.OffTime(ctx) 23 | 24 | var isEnabled by remember { mutableStateOf(spf.isEnabled()) } 25 | 26 | val popupTrigger = rememberSaveable { mutableStateOf(false) } 27 | 28 | var sHour by remember { mutableIntStateOf(spf.getStartHour()) } 29 | var sMin by remember { mutableIntStateOf(spf.getStartMin()) } 30 | var eHour by remember { mutableIntStateOf(spf.getEndHour()) } 31 | var eMin by remember { mutableIntStateOf(spf.getEndMin()) } 32 | 33 | if (popupTrigger.value) { 34 | TimeRangePicker( 35 | trigger = popupTrigger, 36 | sHour, sMin, eHour, eMin, 37 | ) { sH, sM, eH, eM -> 38 | spf.setStartHour(sH) 39 | spf.setStartMin(sM) 40 | spf.setEndHour(eH) 41 | spf.setEndMin(eM) 42 | sHour = sH 43 | sMin = sM 44 | eHour = eH 45 | eMin = eM 46 | } 47 | } 48 | LabeledRow( 49 | R.string.off_time, 50 | helpTooltipId = R.string.help_off_time, 51 | content = { 52 | if (isEnabled) { 53 | GreyButton( 54 | label = Util.timeRangeStr( 55 | ctx, sHour, sMin, eHour, eMin 56 | ), 57 | ) { 58 | popupTrigger.value = true 59 | } 60 | } 61 | SwitchBox(isEnabled) { isTurningOn -> 62 | spf.setEnabled(isTurningOn) 63 | isEnabled = isTurningOn 64 | } 65 | } 66 | ) 67 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/regex/RuleSearchBox.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.regex 2 | 3 | import androidx.compose.foundation.layout.Spacer 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.mutableStateOf 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.runtime.setValue 10 | import androidx.compose.ui.focus.FocusRequester 11 | import androidx.compose.ui.focus.focusRequester 12 | import androidx.compose.ui.focus.onFocusEvent 13 | import androidx.compose.ui.layout.onGloballyPositioned 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.unit.dp 16 | import spam.blocker.R 17 | import spam.blocker.ui.M 18 | import spam.blocker.ui.widgets.StrInputBox 19 | 20 | @Composable 21 | fun RuleSearchBox( 22 | vm: RuleViewModel, 23 | ) { 24 | val ctx = LocalContext.current 25 | 26 | if (vm.searchEnabled.value) { 27 | 28 | val focusRequester = remember { FocusRequester() } 29 | var textFieldLoaded by remember { mutableStateOf(false) } 30 | 31 | StrInputBox( 32 | text = "", 33 | leadingIconId = R.drawable.ic_find, 34 | onValueChange = { 35 | vm.filter = it 36 | vm.reloadDb(ctx) 37 | }, 38 | modifier = M 39 | // Auto focus, and force scroll to input box. 40 | .focusRequester(focusRequester) 41 | .onGloballyPositioned { 42 | if (!textFieldLoaded) { 43 | focusRequester.requestFocus() // IMPORTANT 44 | textFieldLoaded = true // stop cyclic recompositions 45 | } 46 | } 47 | 48 | // Hide itself when lose focus, the unfocused event is triggered in SettingScreen 49 | .onFocusEvent { focusState -> 50 | if (textFieldLoaded && !focusState.isFocused) { 51 | vm.searchEnabled.value = false 52 | vm.filter = "" 53 | vm.reloadDb(ctx) 54 | } 55 | } 56 | ) 57 | 58 | Spacer(M.height(8.dp)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/setting/regex/RuleViewModel.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.setting.regex 2 | 3 | import android.content.Context 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.mutableStateOf 6 | import spam.blocker.db.ContentRuleTable 7 | import spam.blocker.db.NumberRuleTable 8 | import spam.blocker.db.QuickCopyRuleTable 9 | import spam.blocker.db.RegexRule 10 | import spam.blocker.db.RuleTable 11 | import spam.blocker.def.Def 12 | import spam.blocker.util.spf 13 | 14 | open class RuleViewModel( 15 | val table: RuleTable, 16 | val forType: Int, 17 | ) { 18 | val rules = mutableStateListOf() 19 | val searchEnabled = mutableStateOf(false) 20 | val listCollapsed = mutableStateOf(false) 21 | 22 | var filter = "" 23 | 24 | fun reloadDb(ctx: Context) { 25 | rules.clear() 26 | var all = table.listAll(ctx) 27 | if (filter.isNotEmpty()) { 28 | all = all.filter { 29 | it.pattern.contains(filter) || it.description.contains(filter) 30 | } 31 | } 32 | rules.addAll(all) 33 | } 34 | 35 | fun toggleCollapse(ctx: Context) { 36 | // don't collapse if it's empty 37 | if (rules.size == 0 && !listCollapsed.value) { 38 | return 39 | } 40 | listCollapsed.value = !listCollapsed.value 41 | val spf = spf.RegexOptions(ctx) 42 | when (forType) { 43 | Def.ForNumber -> spf.setNumberCollapsed(listCollapsed.value) 44 | Def.ForSms -> spf.setContentCollapsed(listCollapsed.value) 45 | else -> spf.setQuickCopyCollapsed(listCollapsed.value) 46 | } 47 | } 48 | 49 | fun reloadOptions(ctx: Context) { 50 | val spf = spf.RegexOptions(ctx) 51 | 52 | listCollapsed.value = when (forType) { 53 | Def.ForNumber -> spf.isNumberCollapsed() 54 | Def.ForSms -> spf.isContentCollapsed() 55 | else -> spf.isQuickCopyCollapsed() 56 | } 57 | } 58 | 59 | fun reloadDbAndOptions(ctx: Context) { 60 | reloadOptions(ctx) 61 | reloadDb(ctx) 62 | } 63 | } 64 | 65 | class NumberRuleViewModel : RuleViewModel(NumberRuleTable(), Def.ForNumber) 66 | class ContentRuleViewModel : RuleViewModel(ContentRuleTable(), Def.ForSms) 67 | class QuickCopyRuleViewModel : RuleViewModel(QuickCopyRuleTable(), Def.ForQuickCopy) 68 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) 12 | 13 | val Purple200 = Color(0xFFBB86FC) 14 | val Purple500 = Color(0xFF6200EE) 15 | val Purple700 = Color(0xFF3700B3) 16 | val Teal200 = Color(0xFF03DAC5) 17 | val Teal700 = Color(0xFF018786) 18 | val Black = Color(0xff000000) 19 | val White = Color(0xffFFFFFF) 20 | val Grey = Color(0xffE4E4E4) 21 | val SwissCoffee = Color(0xffD1C8C8) 22 | val DarkGrey = Color(0xff404040) 23 | val MidGrey = Color(0xffb0b0b0) 24 | val ColdGrey = Color(0xffa0a0a0) 25 | val SilverGrey = Color(0xffC0C0C0) 26 | val DimGrey = Color(0xff696969) 27 | val LightGrey = Color(0xffcccccc) 28 | val Grey424242 = Color(0xff424242) 29 | val Grey383838 = Color(0xff383838) 30 | val Salmon = Color(0xfffa8072) 31 | val YellowGreen = Color(0xff9ACD32) 32 | val LightGreen = Color(0xff90cE90) 33 | val Emerald = Color(0xff50c878) 34 | val Orange = Color(0xffffa500) 35 | val OrangeRed = Color(0xffff4500) 36 | val DarkOrange = Color(0xffff8c00) 37 | val DodgeBlue = Color(0xff1E90FF) 38 | val SkyBlue = Color(0xff00BFFF) 39 | val MayaBlue = Color(0xff6ed0ff) 40 | val GhostWhite = Color(0xfffafaff) 41 | val EerieBlack = Color(0xff1d1d1d) 42 | val Black111111 = Color(0xff111111) 43 | val Black2d2d2d = Color(0xff2d2d2d) 44 | val RaisinBlack = Color(0xff201f25) 45 | val LightMagenta = Color(0xffea86ff) 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | 5 | // Set of Material typography styles to start with 6 | val Typography = Typography( 7 | // bodyLarge = TextStyle( 8 | // fontFamily = FontFamily.Default, 9 | // fontWeight = FontWeight.SemiBold, 10 | // fontSize = 24.sp, 11 | // lineHeight = 24.sp, 12 | // letterSpacing = 0.5.sp 13 | // ) 14 | /* Other default text styles to override 15 | titleLarge = TextStyle( 16 | fontFamily = FontFamily.Default, 17 | fontWeight = FontWeight.Normal, 18 | fontSize = 22.sp, 19 | lineHeight = 28.sp, 20 | letterSpacing = 0.sp 21 | ), 22 | labelSmall = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Medium, 25 | fontSize = 11.sp, 26 | lineHeight = 16.sp, 27 | letterSpacing = 0.5.sp 28 | ) 29 | */ 30 | ) -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/util.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui 2 | 3 | import androidx.compose.foundation.layout.heightIn 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.saveable.listSaver 6 | import androidx.compose.runtime.saveable.rememberSaveable 7 | import androidx.compose.runtime.snapshots.SnapshotStateList 8 | import androidx.compose.runtime.toMutableStateList 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.composed 11 | import androidx.compose.ui.platform.LocalContext 12 | import androidx.compose.ui.platform.LocalDensity 13 | import androidx.compose.ui.unit.dp 14 | 15 | typealias M = Modifier 16 | 17 | @Composable 18 | fun rememberSaveableMutableStateListOf(vararg elements: T): SnapshotStateList { 19 | return rememberSaveable(saver = snapshotStateListSaver()) { 20 | elements.toList().toMutableStateList() 21 | } 22 | } 23 | 24 | private fun snapshotStateListSaver() = listSaver, T>( 25 | save = { stateList -> stateList.toList() }, 26 | restore = { it.toMutableStateList() }, 27 | ) 28 | 29 | @Composable 30 | fun screenWidthDp() : Float { 31 | val ctx = LocalContext.current 32 | 33 | return LocalDensity.current.run { 34 | val screenWidthPx = ctx.resources.displayMetrics.widthPixels 35 | screenWidthPx.toDp().value 36 | } 37 | } 38 | @Composable 39 | fun screenHeightDp() : Float { 40 | val ctx = LocalContext.current 41 | 42 | return LocalDensity.current.run { 43 | val screenHeightPx = ctx.resources.displayMetrics.heightPixels 44 | screenHeightPx.toDp().value 45 | } 46 | } 47 | @Composable 48 | fun Modifier.maxScreenHeight(percentage: Float): Modifier = composed { 49 | this.then( 50 | Modifier.heightIn(max = (screenHeightDp() * percentage).dp) 51 | ) 52 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/Animation.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.expandVertically 5 | import androidx.compose.animation.shrinkVertically 6 | import androidx.compose.runtime.Composable 7 | 8 | //@Composable 9 | //fun AnimateVisibleH( 10 | // visible: Boolean, 11 | // content: @Composable ()->Unit 12 | //) { 13 | // AnimatedVisibility( 14 | // visible = visible, 15 | // enter = expandHorizontally( 16 | // animationSpec = tween(durationMillis = 300), 17 | // expandFrom = Alignment.Start 18 | // ) + fadeIn(), 19 | // exit = shrinkHorizontally( 20 | // animationSpec = tween(durationMillis = 300), 21 | // shrinkTowards = Alignment.Start 22 | // ) + fadeOut() 23 | // ) { 24 | // content() 25 | // } 26 | //} 27 | 28 | @Composable 29 | fun AnimatedVisibleV( 30 | visible: Boolean, 31 | content: @Composable () -> Unit, 32 | ) { 33 | AnimatedVisibility( 34 | visible = visible, 35 | enter = expandVertically(), 36 | exit = shrinkVertically() 37 | ) { 38 | content() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/CheckBox.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.interaction.MutableInteractionSource 5 | import androidx.compose.material3.Checkbox 6 | import androidx.compose.material3.CheckboxDefaults 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.ripple 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.draw.clip 13 | import spam.blocker.ui.theme.LocalPalette 14 | import spam.blocker.util.Lambda1 15 | 16 | 17 | @Composable 18 | fun CheckBox( 19 | checked: Boolean, 20 | onCheckChange: Lambda1, 21 | modifier: Modifier = Modifier, 22 | label: (@Composable ()->Unit)? = null, 23 | ) { 24 | RowVCenterSpaced( 25 | space = 4, 26 | modifier = modifier 27 | .clickable( 28 | indication = ripple(color = MaterialTheme.colorScheme.primary), 29 | interactionSource = remember { MutableInteractionSource() }, 30 | onClick = { 31 | onCheckChange(!checked) 32 | } 33 | ) 34 | .clip(MaterialTheme.shapes.small) 35 | ) { 36 | Checkbox( 37 | checked = checked, 38 | onCheckedChange = null, 39 | colors = CheckboxDefaults.colors( 40 | uncheckedColor = LocalPalette.current.textGrey, 41 | ), 42 | ) 43 | label?.let { 44 | label() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/Form.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | data class FormField(val label: String, var value: String = "") 6 | 7 | @Composable 8 | fun FormInputField(formField: FormField) { 9 | StrInputBox( 10 | text = formField.value, 11 | onValueChange = { formField.value = it }, 12 | label = { GreyLabel(formField.label) } 13 | ) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/GreenDot.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.foundation.Canvas 4 | import androidx.compose.foundation.layout.size 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.unit.dp 9 | 10 | // The green dot in BotCard and ApiCard, indicating the bot is scheduled or the api is enabled. 11 | @Composable 12 | fun GreenDot() { 13 | Canvas( 14 | modifier = Modifier.size(6.dp) 15 | ) { 16 | drawCircle(color = Color.Green, radius = size.minDimension / 2) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/HtmlText.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.text.Html 5 | import android.text.method.LinkMovementMethod 6 | import android.widget.TextView 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.graphics.toArgb 11 | import androidx.compose.ui.platform.LocalContext 12 | import androidx.compose.ui.viewinterop.AndroidView 13 | import androidx.core.text.HtmlCompat 14 | import spam.blocker.ui.theme.LocalPalette 15 | 16 | /* 17 | To embed images in the string, use tags like: 18 | 19 | world. 21 | ]]> 22 | 23 | */ 24 | @Composable 25 | fun HtmlText( 26 | html: String, 27 | modifier: Modifier = Modifier, 28 | color: Color = LocalPalette.current.textGrey, 29 | ) { 30 | val ctx = LocalContext.current 31 | 32 | AndroidView( 33 | modifier = modifier, 34 | factory = { context -> 35 | TextView(context).apply { 36 | setTextColor(color.toArgb()) 37 | textSize = 15f 38 | 39 | // make links clickable 40 | movementMethod = LinkMovementMethod.getInstance() 41 | } 42 | }, 43 | update = { textView -> 44 | textView.text = HtmlCompat.fromHtml( 45 | html, 46 | HtmlCompat.FROM_HTML_MODE_COMPACT, 47 | object : Html.ImageGetter { 48 | override fun getDrawable(source: String?): Drawable? { 49 | val resourceId = ctx.resources.getIdentifier(source, "drawable", ctx.packageName) 50 | return if (resourceId != 0) { 51 | ctx.resources.getDrawable(resourceId, null).apply { 52 | setBounds(0, 0, intrinsicWidth, intrinsicHeight) 53 | } 54 | } else { 55 | null 56 | } 57 | } 58 | }, 59 | null 60 | ) 61 | } 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/Label.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.material3.Text 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.text.style.TextOverflow 9 | import androidx.compose.ui.unit.TextUnit 10 | import androidx.compose.ui.unit.sp 11 | import spam.blocker.ui.theme.LocalPalette 12 | 13 | @Composable 14 | fun GreyLabel( 15 | text: String, 16 | modifier: Modifier = Modifier, 17 | color: Color = LocalPalette.current.textGrey, 18 | fontSize: TextUnit = TextUnit.Unspecified, 19 | fontWeight: FontWeight? = null, 20 | maxLines: Int = Int.MAX_VALUE, 21 | overflow: TextOverflow = TextOverflow.Clip, 22 | ) { 23 | Text( 24 | text = text, 25 | modifier = modifier, 26 | color = color, 27 | fontSize = fontSize, 28 | fontWeight = fontWeight, 29 | maxLines = maxLines, 30 | overflow = overflow, 31 | ) 32 | } 33 | @Composable 34 | fun SummaryLabel( 35 | text: String, 36 | ) { 37 | GreyLabel( 38 | text = text, 39 | fontSize = 13.sp, 40 | maxLines = 1, 41 | overflow = TextOverflow.Ellipsis, 42 | ) 43 | } 44 | 45 | // Used as input placeholder 46 | @Composable 47 | fun DimGreyLabel( 48 | text: String, 49 | modifier: Modifier = Modifier, 50 | color: Color = LocalPalette.current.textGrey.copy(alpha = 0.6f), 51 | fontSize: TextUnit = TextUnit.Unspecified, 52 | fontWeight: FontWeight? = null, 53 | ) { 54 | Text( 55 | text = text, 56 | modifier = modifier, 57 | color = color, 58 | fontSize = fontSize, 59 | fontWeight = fontWeight, 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/NonLazyGrid.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | 9 | 10 | // For the Bot icons. 11 | // LazyGrid can't be used in scrollable. 12 | // source: https://dev.to/maaxgr/jetpack-compose-grid-without-lazy-5gb8 13 | @Composable 14 | fun NonLazyGrid( 15 | columns: Int, 16 | itemCount: Int, 17 | modifier: Modifier = Modifier, 18 | content: @Composable() (Int) -> Unit 19 | ) { 20 | Column(modifier = modifier) { 21 | var rows = (itemCount / columns) 22 | if (itemCount.mod(columns) > 0) { 23 | rows += 1 24 | } 25 | 26 | for (rowId in 0 until rows) { 27 | val firstIndex = rowId * columns 28 | 29 | Row { 30 | for (columnId in 0 until columns) { 31 | val index = firstIndex + columnId 32 | Box { 33 | if (index < itemCount) { 34 | content(index) 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/OutlinedCard.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.shape.RoundedCornerShape 5 | import androidx.compose.material3.CardDefaults 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.OutlinedCard 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.unit.dp 12 | import spam.blocker.ui.theme.LocalPalette 13 | 14 | @Composable 15 | fun OutlineCard( 16 | modifier: Modifier = Modifier, 17 | containerBg: Color = MaterialTheme.colorScheme.background, 18 | content: @Composable () -> Unit, 19 | ) { 20 | val C = LocalPalette.current 21 | 22 | OutlinedCard( 23 | modifier = modifier, 24 | colors = CardDefaults.cardColors( 25 | containerColor = containerBg, 26 | ), 27 | border = BorderStroke(width = 1.dp, color = C.cardBorder), 28 | shape = RoundedCornerShape(6.dp), 29 | elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), 30 | ) { 31 | content() 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/RadioGroup.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.foundation.layout.ExperimentalLayoutApi 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.foundation.layout.width 6 | import androidx.compose.foundation.selection.selectable 7 | import androidx.compose.material3.RadioButton 8 | import androidx.compose.material3.RadioButtonDefaults 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.draw.scale 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.unit.dp 14 | import spam.blocker.ui.M 15 | import spam.blocker.ui.theme.LocalPalette 16 | import spam.blocker.util.Lambda1 17 | 18 | data class RadioItem( 19 | val text: String, 20 | val color: Color = Color.Unspecified 21 | ) 22 | 23 | @OptIn(ExperimentalLayoutApi::class) 24 | @Composable 25 | fun RadioGroup( 26 | items: List, 27 | selectedIndex: Int, 28 | onSelect: Lambda1, 29 | ) { 30 | val C = LocalPalette.current 31 | 32 | FlowRowSpaced(8) { 33 | items.forEachIndexed { idx, item -> 34 | RowVCenter( 35 | M.selectable( 36 | // make the text also clickable 37 | selected = idx == selectedIndex, 38 | onClick = { 39 | onSelect(idx) 40 | }, 41 | ), 42 | ) { 43 | RadioButton( 44 | selected = idx == selectedIndex, 45 | onClick = null, 46 | modifier = M 47 | .scale(0.7f) 48 | .width(24.dp)// reduce the gap between RadioButton and Text 49 | .height(10.dp), 50 | colors = RadioButtonDefaults.colors( 51 | unselectedColor = C.disabled, 52 | ) 53 | ) 54 | Text( 55 | text = item.text, 56 | color = item.color, 57 | ) 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/Section.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.border 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.offset 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.wrapContentHeight 10 | import androidx.compose.foundation.layout.wrapContentWidth 11 | import androidx.compose.foundation.shape.RoundedCornerShape 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.unit.dp 18 | import androidx.compose.ui.unit.sp 19 | import spam.blocker.ui.M 20 | import spam.blocker.ui.theme.LocalPalette 21 | import spam.blocker.ui.theme.SwissCoffee 22 | 23 | @Composable 24 | fun Section( 25 | title: String, 26 | horizontalPadding : Int = 0, 27 | bgColor: Color = MaterialTheme.colorScheme.background, 28 | content: @Composable ()->Unit, 29 | ) { 30 | val C = LocalPalette.current 31 | Box(modifier = M.padding(top = 8.dp, bottom = 8.dp)) { 32 | // the rectangle section border line 33 | Box( 34 | modifier = M 35 | .fillMaxWidth() 36 | .padding(horizontal = horizontalPadding.dp) 37 | .border(0.5.dp, C.cardBorder, shape = RoundedCornerShape(4.dp)) 38 | .padding(horizontal = 14.dp, vertical = 10.dp) 39 | .wrapContentHeight() 40 | ) { 41 | content() 42 | } 43 | 44 | // the section title 45 | Box( 46 | modifier = M 47 | .wrapContentWidth() 48 | .offset(20.dp, (-8).dp) 49 | .background(bgColor) 50 | ) { 51 | Text( 52 | text = title, 53 | fontSize = 13.sp, 54 | color = SwissCoffee, 55 | lineHeight = 13.sp, 56 | modifier = Modifier.padding(10.dp, 0.dp), 57 | ) 58 | } 59 | 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/ui/widgets/SnackBar.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.ui.widgets 2 | 3 | import androidx.compose.material3.SnackbarDuration 4 | import androidx.compose.material3.SnackbarHostState 5 | import androidx.compose.material3.SnackbarResult 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.Dispatchers.IO 8 | import kotlinx.coroutines.launch 9 | import kotlinx.coroutines.withContext 10 | import spam.blocker.util.Lambda 11 | 12 | object SnackBar { 13 | val state = SnackbarHostState() 14 | 15 | fun show( 16 | coroutine: CoroutineScope, 17 | content: String, 18 | actionLabel: String, 19 | onAction: Lambda 20 | ) { 21 | coroutine.launch { 22 | withContext(IO) { 23 | // cancel previous 24 | dismiss() 25 | 26 | val result = state.showSnackbar( 27 | if (content.length > 50) content.substring(0, 50) + "…" else content, 28 | actionLabel, 29 | withDismissAction = false, 30 | duration = SnackbarDuration.Short 31 | ) 32 | if (result == SnackbarResult.ActionPerformed) { 33 | onAction() 34 | } 35 | } 36 | } 37 | } 38 | fun dismiss() { 39 | state.currentSnackbarData?.dismiss() 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/Algorithm.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import android.util.Base64 4 | import java.io.BufferedReader 5 | import java.io.ByteArrayInputStream 6 | import java.io.ByteArrayOutputStream 7 | import java.io.InputStreamReader 8 | import java.security.MessageDigest 9 | import java.util.zip.GZIPInputStream 10 | import java.util.zip.GZIPOutputStream 11 | 12 | fun ByteArray.toHexString(): String { 13 | return joinToString("") { String.format("%02x", it) } 14 | } 15 | 16 | object Algorithm { 17 | 18 | fun compressString(data: String): ByteArray { 19 | val bos = ByteArrayOutputStream(data.length) 20 | val gzip = GZIPOutputStream(bos) 21 | gzip.write(data.toByteArray()) 22 | gzip.close() 23 | return bos.toByteArray() 24 | } 25 | 26 | fun decompressToString(compressed: ByteArray): String { 27 | val bis = ByteArrayInputStream(compressed) 28 | val gis = GZIPInputStream(bis) 29 | val br = BufferedReader(InputStreamReader(gis, "UTF-8")) 30 | val sb = StringBuilder() 31 | var line: String? 32 | while (br.readLine().also { line = it } != null) { 33 | sb.append(line) 34 | } 35 | br.close() 36 | gis.close() 37 | bis.close() 38 | return sb.toString() 39 | } 40 | 41 | fun b64Encode(raw: ByteArray): String { 42 | return Base64.encodeToString(raw, Base64.NO_WRAP) 43 | } 44 | fun b64Encode(raw: String): String { 45 | return b64Encode(raw.toByteArray()) 46 | } 47 | fun b64Decode(encoded: String): ByteArray { 48 | return Base64.decode(encoded, Base64.NO_WRAP) 49 | } 50 | 51 | fun sha1(raw: ByteArray): ByteArray { 52 | val digest = MessageDigest.getInstance("SHA-1") 53 | val hash = digest.digest(raw) 54 | return hash 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/AnnotatedFormatter.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.text.AnnotatedString 5 | import androidx.compose.ui.text.SpanStyle 6 | 7 | fun String.A(color: Color = Color.Unspecified): AnnotatedString = 8 | AnnotatedString(this, SpanStyle(color = color)) 9 | 10 | fun String.formatAnnotated(vararg args: AnnotatedString): AnnotatedString { 11 | // Split the format string by placeholders 12 | val parts = split("%s") 13 | val builder = AnnotatedString.Builder() 14 | 15 | // Match each part with annotations 16 | var annotationIndex = 0 17 | for (i in parts.indices) { 18 | builder.append(parts[i]) 19 | if (annotationIndex < args.size) { 20 | builder.append(args[annotationIndex]) 21 | annotationIndex++ 22 | } 23 | } 24 | 25 | // Handle case where there might be more placeholders than annotations provided 26 | while (annotationIndex < args.size) { 27 | builder.append(args[annotationIndex]) 28 | annotationIndex++ 29 | } 30 | 31 | return builder.toAnnotatedString() 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.graphics.drawable.Drawable 7 | import androidx.compose.runtime.Immutable 8 | import spam.blocker.R 9 | 10 | @Immutable 11 | class AppInfo { 12 | lateinit var pkgName: String 13 | lateinit var label: String 14 | lateinit var icon: Drawable 15 | 16 | companion object { 17 | @SuppressLint("UseCompatLoadingForDrawables") 18 | fun fromPackage(ctx: Context, packageName: String): AppInfo { 19 | val ret = AppInfo().apply { 20 | pkgName = packageName 21 | } 22 | 23 | val pm = ctx.packageManager 24 | 25 | val applicationInfo = try { 26 | pm.getApplicationInfo(packageName, 0) 27 | } catch (e: PackageManager.NameNotFoundException) { 28 | ret.label = "" 29 | ret.icon = ctx.getDrawable(R.drawable.ic_unknown_app_icon)!! 30 | return ret 31 | } 32 | 33 | ret.label = pm.getApplicationLabel(applicationInfo) as String 34 | 35 | ret.icon = pm.getApplicationIcon(packageName) 36 | 37 | return ret 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/Clipboard.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Context 6 | 7 | class Clipboard { 8 | companion object { 9 | fun copy(ctx: Context, toCopy: String?) { 10 | val clipboardManager = ctx.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 11 | val clipData = ClipData.newPlainText("", toCopy) 12 | clipboardManager.setPrimaryClip(clipData) 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/Flag.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import spam.blocker.def.Def 4 | 5 | // check if it has a flag 6 | fun Int.hasFlag(f: Int): Boolean { 7 | return this and f == f 8 | } 9 | 10 | // add or remove a flag 11 | fun Int.setFlag(f: Int, enabled: Boolean): Int { 12 | return if (enabled) { // add flag 13 | this or f 14 | } else { // clear flag 15 | this and f.inv() 16 | } 17 | } 18 | fun Int.addFlag(f: Int): Int { 19 | return setFlag(f, true) 20 | } 21 | fun Int.removeFlag(f: Int): Int { 22 | return setFlag(f, false) 23 | } 24 | 25 | // Generate string "imdlc" from flags 26 | // params: 27 | // attrMap - mapOf(IgnoreCase -> "i", DotMatchAll -> "d", ...) 28 | // inverse - invert the showing behavior of some flags, 29 | // by default it's: show when set 30 | // if it's inverted: show when not set 31 | fun Int.toFlagStr( 32 | attrMap: Map = Def.MAP_REGEX_FLAGS, 33 | inverse: List = Def.LIST_REGEX_FLAG_INVERSE, 34 | ): String { 35 | var ret = "" 36 | attrMap.forEach { (k, v) -> 37 | if (inverse.contains(k)) { 38 | if (!hasFlag(k)) 39 | ret += v 40 | } else { 41 | if (hasFlag(k)) 42 | ret += v 43 | } 44 | } 45 | return ret 46 | } 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/Launcher.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.provider.Telephony 7 | import spam.blocker.ui.main.MainActivity 8 | 9 | object Launcher { 10 | 11 | fun launchCallApp(ctx: Context) { 12 | ctx.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("content://call_log/calls"))) 13 | } 14 | 15 | fun launchSMSApp(ctx: Context) { 16 | val defaultSmsApp = Telephony.Sms.getDefaultSmsPackage(ctx) 17 | val intent = ctx.packageManager.getLaunchIntentForPackage(defaultSmsApp) 18 | intent?.let { ctx.startActivity(it) } 19 | } 20 | 21 | fun launchThisApp(ctx: Context) { 22 | val intent = Intent(ctx, MainActivity::class.java) 23 | ctx.startActivity(intent) 24 | } 25 | 26 | fun openCallConversation(ctx: Context, phoneNumber: String) { 27 | val intent = Intent(Intent.ACTION_DIAL).apply { 28 | data = Uri.parse("tel:$phoneNumber") 29 | } 30 | if (intent.resolveActivity(ctx.packageManager) != null) { 31 | ctx.startActivity(intent) 32 | } 33 | } 34 | 35 | fun openSMSConversation(ctx: Context, smsto: String?) { 36 | 37 | val smsUri = Uri.parse("smsto:$smsto") 38 | // val smsIntent = Intent(Intent.ACTION_VIEW, smsUri) // this popups dialog for choosing an app 39 | val smsIntent = Intent(Intent.ACTION_SENDTO, smsUri) // this doesn't popup that dialog 40 | smsIntent.addCategory(Intent.CATEGORY_DEFAULT) 41 | smsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 42 | ctx.startActivity(smsIntent) 43 | } 44 | 45 | fun selfRestart(ctx: Context) { 46 | val intent = Intent(ctx, MainActivity::class.java) 47 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) 48 | ctx.startActivity(intent) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/Logcat.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import android.util.Log 4 | 5 | const val TAG = "11111" 6 | 7 | fun logd(str: String) { 8 | Log.d(TAG, str) 9 | } 10 | fun logi(str: String) { 11 | Log.i(TAG, str) 12 | } 13 | fun logw(str: String) { 14 | Log.w(TAG, str) 15 | } 16 | fun loge(str: String) { 17 | Log.e(TAG, str) 18 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/MockkWorkaround.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import java.time.LocalDateTime 4 | 5 | // This file contains wrappers for testing, mockk doesn't support hooking 6 | // `System.currentTimeMillis()` or `LocalDateTime.now()` 7 | 8 | open class Now { 9 | companion object { 10 | 11 | fun currentMillis(): Long { 12 | return System.currentTimeMillis() 13 | } 14 | } 15 | } 16 | 17 | object LocalDateTimeMockk { 18 | fun now() : LocalDateTime { 19 | return LocalDateTime.now() 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/Xml.kt: -------------------------------------------------------------------------------- 1 | package spam.blocker.util 2 | 3 | import org.w3c.dom.Document 4 | import org.w3c.dom.NodeList 5 | import java.io.ByteArrayInputStream 6 | import javax.xml.parsers.DocumentBuilderFactory 7 | import javax.xml.xpath.XPathConstants 8 | import javax.xml.xpath.XPathFactory 9 | 10 | 11 | object Xml { 12 | fun parse( 13 | bytes: ByteArray, 14 | expression: String, 15 | ): List> { 16 | val document = parseXml(bytes) 17 | val xPath = XPathFactory.newInstance().newXPath() 18 | 19 | val nodes = 20 | xPath.evaluate(expression, document, XPathConstants.NODESET) as NodeList 21 | 22 | val list = mutableListOf>() 23 | 24 | for (i in 0 until nodes.length) { 25 | list.add( 26 | mapOf( 27 | "pattern" to nodes.item(i).textContent 28 | ) 29 | ) 30 | } 31 | return list 32 | } 33 | 34 | private fun parseXml(bytes: ByteArray): Document { 35 | val documentBuilderFactory = DocumentBuilderFactory.newInstance() 36 | val documentBuilder = documentBuilderFactory.newDocumentBuilder() 37 | val inputStream = ByteArrayInputStream(bytes) 38 | return documentBuilder.parse(inputStream) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/pdu/InvalidHeaderValueException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 Esmertec AG. 3 | * Copyright (C) 2007 The Android Open Source Project 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package spam.blocker.util.pdu; 19 | 20 | 21 | /** 22 | * Thrown when an invalid header value was set. 23 | */ 24 | public class InvalidHeaderValueException extends MmsException { 25 | private static final long serialVersionUID = -2053384496042052262L; 26 | 27 | /** 28 | * Constructs an InvalidHeaderValueException with no detailed message. 29 | */ 30 | public InvalidHeaderValueException() { 31 | super(); 32 | } 33 | 34 | /** 35 | * Constructs an InvalidHeaderValueException with the specified detailed message. 36 | * 37 | * @param message the detailed message. 38 | */ 39 | public InvalidHeaderValueException(String message) { 40 | super(message); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/pdu/MmsException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 Esmertec AG. 3 | * Copyright (C) 2007 The Android Open Source Project 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package spam.blocker.util.pdu; 19 | 20 | /** 21 | * A generic exception that is thrown by the Mms client. 22 | */ 23 | public class MmsException extends Exception { 24 | private static final long serialVersionUID = -7323249827281485390L; 25 | 26 | /** 27 | * Creates a new MmsException. 28 | */ 29 | public MmsException() { 30 | super(); 31 | } 32 | 33 | /** 34 | * Creates a new MmsException with the specified detail message. 35 | * 36 | * @param message the detail message. 37 | */ 38 | public MmsException(String message) { 39 | super(message); 40 | } 41 | 42 | /** 43 | * Creates a new MmsException with the specified cause. 44 | * 45 | * @param cause the cause. 46 | */ 47 | public MmsException(Throwable cause) { 48 | super(cause); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/spam/blocker/util/pdu/util/PduCacheEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 Esmertec AG. 3 | * Copyright (C) 2008 The Android Open Source Project 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package spam.blocker.util.pdu.util; 19 | 20 | 21 | import spam.blocker.util.pdu.pdu.GenericPdu; 22 | 23 | public final class PduCacheEntry { 24 | private final GenericPdu mPdu; 25 | private final int mMessageBox; 26 | private final long mThreadId; 27 | 28 | public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) { 29 | mPdu = pdu; 30 | mMessageBox = msgBox; 31 | mThreadId = threadId; 32 | } 33 | 34 | public GenericPdu getPdu() { 35 | return mPdu; 36 | } 37 | 38 | public int getMessageBox() { 39 | return mMessageBox; 40 | } 41 | 42 | public long getThreadId() { 43 | return mThreadId; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_airplane.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alternative.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_backup_export.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_backup_import.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bell_ringing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call_blocked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call_miss.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call_pass.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_category.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_circle.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_contact_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_contact_square.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_contacts_square.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_contat_square.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_csv.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_daily.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_db_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 20 | 21 | 28 | 29 | 36 | 37 | 44 | 45 | 52 | 53 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delay.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_display_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dropdown_arrow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_duration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fail_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file_read.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file_write.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_filter.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_find.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_find_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_flag_catalan.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_flags.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hang.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heads_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_history_cleanup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hourglass.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_http.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_http_header.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_international.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_link.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_no.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_note.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification_off.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_number_sign.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_open_msg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_post.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 28 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_priority.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_question.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_recycle_bin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_regex.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reorder.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_repeat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_replace.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_right_arrow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shade.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 15 | 16 | 19 | 20 | 23 | 24 | 27 | 28 | 31 | 32 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sms.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sms_blocked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sms_pass.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_statusbar_shade.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_time_slot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toggle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tube.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_unknown_app_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_warning.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_weekly.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_workflow.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_xml.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_yes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/spinner_arrow.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/SpamBlocker/686bd5f255a3ffe806ac76fe12c778a46ae78abd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-ar/strings_7.xml: -------------------------------------------------------------------------------- 1 | 2 | وقت البدء 3 | وقت الانتهاء 4 | تبديل الوضع 5 | مجموعة جهات الاتصال 6 | 7 | رقم الهاتف:
9 |   الوضع الافتراضي، ستطابق العبارة المنتظمة رقم الهاتف.
10 |
11 | جهات الاتصال:
12 |   ستطابق العبارة المنتظمة اسم جهة الاتصال المرتبطة بالرقم الوارد.
13 |   - حالة استخدام:
14 |   حظر جميع جهات الاتصال غير المحفوظة في نهاية الأسبوع باستخدام العبارة المنتظمة `.*`.
15 |
16 | مجموعة جهات الاتصال:
17 |   ستطابق العبارة المنتظمة اسم مجموعة جهات الاتصال المرتبطة بالرقم الوارد، 18 | مما يؤثر على جميع جهات الاتصال في تلك المجموعة، لذلك لا تحتاج إلى إضافتها بشكل فردي، 19 | ولا تحتاج إلى تحديثها حتى عند وجود تغييرات في الموظفين.
20 |   - حالة استخدام:
21 |   حظر جميع زملاء العمل في نهاية الأسبوع عن طريق حظر مجموعة جهات الاتصال "العمل". 22 | ]]> 23 |
24 | النص طويل جدًا، التحرير معطل 25 | متابعة النظام 26 | المظهر 27 | 28 | فاتح 29 | داكن 30 | 31 | 32 | بحث عن قاعدة 33 | استنساخ قاعدة 34 | حذف قاعدة 35 | حذف القواعد المكررة 36 | حذف الكل (%d) قواعد 37 | 38 | هل أنت متأكد من حذف %d قواعد مكررة؟ 39 | المدة 40 | 41 | 44 | 45 |
-------------------------------------------------------------------------------- /app/src/main/res/values-fa/strings_7.xml: -------------------------------------------------------------------------------- 1 | 2 | زمان شروع 3 | زمان پایان 4 | حالت سوئیچ 5 | گروه مخاطبین 6 | 7 | شماره تلفن:
9 |   حالت پیش فرض، عبارت باقاعده با شماره تلفن مطابقت خواهد داشت.
10 |
11 | مخاطبین:
12 |   عبارت باقاعده با نام مخاطب مرتبط با شماره ورودی مطابقت خواهد داشت.
13 |   - یک مورد استفاده:
14 |   مسدود کردن تمام غیر مخاطبین در آخر هفته با عبارت باقاعده `.*`.
15 |
16 | گروه مخاطبین:
17 |   عبارت باقاعده با نام گروه مخاطب مرتبط با شماره ورودی مطابقت خواهد داشت، 18 | که بر همه مخاطبین آن گروه تاثیر می‌گذارد، بنابراین نیازی به اضافه کردن آن‌ها به صورت جداگانه نیست، 19 | و حتی در صورت تغییرات پرسنلی نیازی به به‌روزرسانی آن نیست.
20 |   - یک مورد استفاده:
21 |   مسدود کردن تمام همکاران در آخر هفته با مسدود کردن گروه مخاطبین "کار". 22 | ]]> 23 |
24 | متن بیش از حد طولانی است، ویرایش غیرفعال است 25 | دنبال سیستم 26 | تم 27 | 28 | روشن 29 | تاریک 30 | 31 | 32 | جستجوی قانون 33 | کپی قانون 34 | حذف قانون 35 | حذف قوانین تکراری 36 | حذف همه (%d) قانون 37 | 38 | تایید حذف %d قانون تکراری؟ 39 | مدت زمان 40 | 41 | 44 | 45 |
-------------------------------------------------------------------------------- /app/src/main/res/values-in/strings_7.xml: -------------------------------------------------------------------------------- 1 | 2 | Waktu Mulai 3 | Waktu Akhir 4 | Mode Peralihan 5 | Grup Kontak 6 | 7 | Nomor Telepon:
9 |   Mode default, regex akan mencocokkan nomor telepon.
10 |
11 | Kontak:
12 |   Regex akan mencocokkan nama kontak yang terkait dengan nomor yang masuk.
13 |   - Kasus penggunaan:
14 |   Blokir semua non-kontak di akhir pekan dengan regex `.*`.
15 |
16 | Grup Kontak:
17 |   Regex akan mencocokkan nama grup kontak yang terkait dengan nomor yang masuk, 18 | berdampak pada semua kontak di grup tersebut, jadi Anda tidak perlu menambahkannya satu per satu, 19 | dan tidak perlu memperbaruinya meskipun ada perubahan anggota.
20 |   - Kasus penggunaan:
21 |   Blokir semua rekan kerja di akhir pekan dengan memblokir grup kontak "kerja". 22 | ]]> 23 |
24 | Teks terlalu panjang, pengubahan dinonaktifkan 25 | Ikuti Sistem 26 | Tema 27 | 28 | Terang 29 | Gelap 30 | 31 | 32 | Aturan Pencarian 33 | Aturan Penggandaan 34 | Aturan Penghapusan 35 | Hapus Aturan yang Diduplikasi 36 | Hapus Semua(%d) Aturan 37 | 38 | Konfirmasi untuk menghapus %d Aturan yang diduplikasi? 39 | Durasi 40 | 41 | 44 | 45 |
-------------------------------------------------------------------------------- /app/src/main/res/values-ja/strings_16.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 |
6 |

- プライバシー

7 | APIエンドポイントには、以下の情報が表示されます:
8 |
9 |   - IPアドレス
10 |   - TLSおよびTCPフィンガープリント(Androidのバージョンが明らかになる可能性があります)
11 |   - レポートされる番号(国番号を含む)
12 |
13 | それ以外の情報は報告されません。
14 |
15 |

- 手動レポート

16 | ここにAPIが有効になっている場合、通話履歴の番号をタップすると、レポートボタンが表示されます。
17 |
18 | その番号はすべてのAPIに報告されます。
19 |
20 |

- 自動レポート

21 | レポート遅延
22 | 通話がブロックされると、報告されるまでに1時間の猶予期間があります。 23 | この期間内に繰り返し許可された場合、またはかけ直された場合は、 24 | スパム番号ではないと見なされ、報告はキャンセルされます。
25 |
26 | レポートタイプ
27 | 1. 以下の場合は報告されません
28 |
29 | - SMSの番号またはコンテンツ
30 | - 許可された番号
31 | - グローバルテスト
32 | および、以下のブロックタイプ:
33 | - 連絡先Regex
34 | - 連絡先グループRegex
35 | - データベース
36 | - 会議モード
37 | - 即時クエリ(APIエンドポイントの番号が他の競合他社に漏洩するのを防ぐため)
38 |
39 | 2. 以下の方法でブロックされた番号は報告されます
40 |
41 | - 非連絡先(排他的)
42 | - STIR認証
43 | - 番号Regex
44 |
45 | 3. 例外ケース:
46 |
47 | - 即時クエリによってブロックされた場合、番号の評価スコアを上げるために、同じAPIに報告されます。 48 | たとえば、check.comからのAPIクエリによってブロックされた場合、check.comにのみ報告され、others.comには報告されません。
49 |
50 | - データベースによってブロックされ、そのレコードが元々インスタントAPIクエリによって追加された場合、 51 | 上記の理由と同じように、同じAPIにのみ報告されます。
52 | ]]> 53 |
54 |
-------------------------------------------------------------------------------- /app/src/main/res/values-ja/strings_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 開始時間 3 | 終了時間 4 | モード切り替え 5 | 連絡先グループ 6 | 7 | 電話番号:
9 |   デフォルトモードでは、正規表現は電話番号と一致します。
10 |
11 | 連絡先:
12 |   正規表現は、着信番号に関連付けられた連絡先名と一致します。
13 |   - ユースケース:
14 |   週末に連絡先以外のすべてを正規表現`.*`でブロックします。
15 |
16 | 連絡先グループ:
17 |   正規表現は、着信番号に関連付けられた連絡先グループ名と一致します。 18 | これにより、そのグループのすべての連絡先に影響が及ぶため、個別に追加する必要がなく、 19 | 人事異動があっても更新する必要はありません。
20 |   - ユースケース:
21 |   連絡先グループ「仕事」をブロックすることで、週末にすべての同僚をブロックします。 22 | ]]> 23 |
24 | テキストが長すぎるため、編集は無効になっています 25 | システムに従う 26 | テーマ 27 | 28 | ライト 29 | ダーク 30 | 31 | 32 | ルール検索 33 | ルール複製 34 | ルール削除 35 | 重複ルールの削除 36 | すべてのルール(%d)削除 37 | 38 | %dの重複したルールを削除しますか? 39 | 期間 40 | 41 | 44 | 45 |
-------------------------------------------------------------------------------- /app/src/main/res/values-tr/strings_7.xml: -------------------------------------------------------------------------------- 1 | 2 | Başlangıç Zaman 3 | Bitiş Zamanı 4 | Modu Değiştir 5 | İletişim Grubu 6 | 7 | Telefon Numarasır:
9 |   Varsayılan modda, düzenli ifade telefon numarasını eşleştirecektir.
10 |
11 | Kişiler:
12 |   Düzenli ifade, gelen numarayla ilişkili iletişim adını eşleştirecektir.
13 |   - Bir kullanım durumu:
14 |   Hafta sonu tüm iletişim dışı numaraları düzenli ifade ile engelle `.*`.
15 |
16 | İletişim Grubu:
17 |   Düzenli ifade, gelen numarayla ilişkili iletişim grubu adını eşleştirecektir, 18 | Bu gruptaki tüm iletişimleri etkileyerek, onları tek tek eklemenize gerek kalmaz, 19 | ve personel değişiklikleri olsa bile güncellemenize gerek kalmaz.
20 |   - Bir kullanım durumu:
21 |   Hafta sonu iletişim grubunu engelleyerek tüm iş arkadaşlarını engelle "iş". 22 | ]]> 23 |
24 | Metin çok uzun, düzenleme devre dışı 25 | Sistemi Takip Et 26 | Tema 27 | 28 | Aydınlık 29 | Karanlık 30 | 31 | 32 | Kural Ara 33 | Kuralı Kopyala 34 | Kuralı Sil 35 | Yinelenen Kuralları Sil 36 | Tüm (%d) Kuralları Sil 37 | 38 | Yenilenen %d kuralı silmek istediğinizi onaylıyor musunuz? 39 | Süre 40 | 41 | 44 | 45 |
-------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings_16.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 |
6 |

- 隐私

7 | API端点将看到您的:
8 |
9 |   - IP地址
10 |   - TLS和TCP指纹(可能会泄露您的Android版本)
11 |   - 被举报的号码(包括您的国家代码)
12 |
13 | 不会举报任何其他信息。
14 |
15 |

- 手动举报

16 | 如果在此处启用了任何API,请在通话记录中点击一个号码,将出现一个举报按钮。
17 |
18 | 该号码将举报给所有API。
19 |
20 |

- 自动举报

21 | 举报延迟
22 | 当一个电话被阻止时,会有一个小时的时间缓冲期,然后才会进行举报。 23 | 如果该号码之后由于重复或在此时间缓冲期内回拨而被允许, 24 | 则它被认为是非垃圾号码,举报将被取消。
25 |
26 | 举报类型
27 | 1. 它不会举报:
28 |
29 | - 短信号码或内容
30 | - 允许的号码
31 | - 全局测试
32 | 以及以下阻止类型:
33 | - 联系人正则
34 | - 联系人组正则
35 | - 数据库
36 | - 会议模式
37 | - 即时查询(以防止API端点的号码泄露给其他竞争对手)
38 |
39 | 2. 它举报以下方式阻止的号码:
40 |
41 | - 非联系人(独占)
42 | - STIR认证
43 | - 号码正则
44 |
45 | 3. 例外情况:
46 |
47 | - 当它被即时查询阻止时,它将被报告回相同的API,以提高号码的评分。 48 | 例如,当它被check.com的API查询阻止时,它将只报告给check.com,而不是others.com。
49 |
50 | - 当它被数据库阻止,并且该记录最初是通过即时API查询添加的, 51 | 它将仅报告给相同的API,原因与上述相同。
52 | ]]> 53 |
54 |
-------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings_17.xml: -------------------------------------------------------------------------------- 1 | 2 | 紧急 3 | 紧急情况 4 | 5 | 7 |
8 |
- 时长
9 | 默认设置 120 分钟 表示在拨打紧急电话后的 120 分钟内,所有来电都将被允许。
10 |
11 |
- 检查STIR
12 | - 禁用时,允许所有来电。
13 | - 启用后,STIR验证失败的呼叫将不被允许,只允许“有效”和“未验证”的呼叫。
14 |
15 |
- 附加号码
16 | 拨打此列表中的任何号码也会触发此功能。
17 |
18 | 无需在此处显式添加 911 等号码,因为 Android 包含每个国家/地区的内置紧急号码列表(例如:警察、消防、救护车)。
19 |
20 | 用逗号分隔号码,例如:
21 |   111, 222, 3333
22 |
23 |
- 重置
24 | 如果因错误或在测试期间触发了此功能,请按此按钮重置此功能。
25 |
26 |
- 测试
27 | 模拟一个呼出电话,测试该号码是否为紧急号码。
28 |
29 | 优先级:最高 30 | ]]> 31 |
32 | 重置 33 | 状态 34 | 未激活 35 | 确认重置? 36 | 检查 STIR (?) 37 | 附加号码 38 | 39 | {sdcard}
41 | 外部存储目录,与以下目录相同:
42 | /storage/emulated/0
43 |
44 | {Download}
45 | 与以下目录相同:
46 | {sdcard}/Download
47 |
48 | {Documents}
49 | 与以下目录相同:
50 | {sdcard}/Documents 51 | ]]> 52 |
53 | 呼叫号码 54 | 呼叫至 55 |
-------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 联系人 3 | 非联系人 4 | 包含 5 | 排除 6 | 7 | 9 |
10 | - 包含:
11 |   非联系人号码将由其他规则检查。
12 |   优先级:10
13 |
14 | - 排除:
15 |   非联系人号码将被 阻止
16 |   优先级:0
17 |
18 | 它适用于呼叫和短信。 19 | ]]> 20 |
21 | STIR 22 | 包含未验证的 (*) 23 | 未验证 24 | 有效呼叫 25 | 伪造呼叫 26 | 27 | Android 11+ 和 运营商支持
29 |
30 | 有三种可能的认证结果:
31 | 有效
32 |   呼叫很可能是有效的,没有被伪造。
33 | 未验证
34 |   无法验证呼叫,或者不支持 STIR 认证。
35 | 伪造
36 |   很可能是一个伪造的呼叫。
37 |
38 |
39 | - 包含:
40 |   未通过此检查的呼叫仍将由其他规则检查。
41 |   当选中“包含未验证”时,如果呼叫未经验证,也会通过。
42 |   优先级:10
43 |
44 | - 排除:
45 |   未通过此检查的呼叫将被 阻止
46 |   当选中“包含未验证”时,如果呼叫未经验证,也会被阻止。
47 |   优先级:0 48 | ]]> 49 |
50 | 51 | pattern,请查看气球工具提示以获取详细说明。 53 | ]]> 54 | 55 |
-------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 |
6 | 如果开始时间大于结束时间,例如,20:00 -> 07:00,表示从晚上 20:00 到第二天早上 07:00 的时间段。
7 |
8 | 优先级:10 9 | ]]> 10 |
11 | 12 | 13 | 15 |
16 | 17 | 原始号码
18 |   启用后,号码将不会被优化,+ 19 | 和 前导 0 将保留,这对于区分国内号码很有用。
20 |
21 | 22 | 忽略国家代码
23 |   启用后,前导的 +国家代码 24 | 将从国际号码中删除。
25 |
26 | 例如,+3312345 和 27 | +4412345 都将变为 12345, 28 | 这允许您使用更简单的正则表达式 `123.*` 来代替 `(33|44)*123.*` 来匹配国际号码。
29 |
30 | 31 | 忽略大小写
32 |   它使模式匹配不区分大小写,a 33 | 将同时匹配 aA
34 |
35 | 36 | 点号匹配所有字符
37 |   启用后,. 也将匹配换行符 (\\n), 38 | 这允许点号匹配多行。
39 |   使用案例:
40 |   匹配多行短信:
41 |   领取您的奖品:http://
42 |   abc.com

43 |   使用规则:
44 |   *http.*com.*
45 | 46 | ]]> 47 |
48 | 49 | 50 | 原始号码 51 | 忽略国家代码 52 | 忽略大小写 53 | 点匹配所有 54 | 55 | 可以通过正则表达式标志“原始号码”来禁用此优化。 56 |
-------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 开始时间 3 | 结束时间 4 | 切换模式 5 | 联系人组 6 | 7 | 电话号码:
9 |   默认模式,正则表达式将匹配电话号码。
10 |
11 | 联系人:
12 |   正则表达式将匹配与来电号码关联的联系人姓名。
13 |   - 用例:
14 |   使用正则表达式 `.*` 在周末屏蔽所有非联系人。
15 |
16 | 联系人组:
17 |   正则表达式将匹配与来电号码关联的联系人组名称, 18 | 影响该组中的所有联系人,因此您无需单独添加它们, 19 | 即使有人员变动也无需更新。
20 |   - 用例:
21 |   通过屏蔽“工作”联系人组来屏蔽所有周末的同事。 22 | ]]> 23 |
24 | 文本过长,编辑功能已禁用 25 | 跟随系统 26 | 主题 27 | 28 | 浅色 29 | 深色 30 | 31 | 32 | 搜索规则 33 | 复制规则 34 | 删除规则 35 | 删除重复规则 36 | 删除所有(%d)规则 37 | 38 | 确认删除%d条重复规则吗? 39 | 持续时间 40 | 41 | 44 | 45 |
-------------------------------------------------------------------------------- /app/src/main/res/values/strings_7.xml: -------------------------------------------------------------------------------- 1 | 2 | Start Time 3 | End Time 4 | Switch Mode 5 | Contact Group 6 | 7 | Phone Number:
9 |   The default mode, the regex will match the phone number.
10 |
11 | Contacts:
12 |   The regex will match the contact name associated with the incoming number.
13 |   - A use case:
14 |   Block all non-contacts at weekend with regex `.*`.
15 |
16 | Contact Group:
17 |   The regex will match the contact group name associated with the incoming number, 18 | impacting all the contacts in that group, so you don\'t need to add them individually, 19 | and no need to update it even when there are personnel changes.
20 |   - A use case:
21 |   Block all coworkers at weekend by blocking the contact group "work". 22 | ]]> 23 |
24 | Text too long, editing is disabled 25 | Follow System 26 | Theme 27 | 28 | Light 29 | Dark 30 | 31 | 32 | Search Rule 33 | Clone Rule 34 | Delete Rule 35 | Delete Duplicated Rules 36 | Delete All(%d) Rules 37 | 38 | Confirm to delete %d duplicated Rules? 39 | Duration 40 | 41 | 44 | 45 |
-------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 |