diff --git a/.DS_Store b/.DS_Store index 0435604d..35d64bee 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index 24b79169..b082c916 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -1,170 +1,422 @@ { "configVersion": 2, "packages": [ + { + "name": "_fe_analyzer_shared", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/_fe_analyzer_shared-59.0.0", + "packageUri": "lib/", + "languageVersion": "2.17" + }, + { + "name": "analyzer", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/analyzer-5.11.1", + "packageUri": "lib/", + "languageVersion": "2.19" + }, + { + "name": "args", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/args-2.4.0", + "packageUri": "lib/", + "languageVersion": "2.18" + }, { "name": "async", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.9.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/async-2.11.0", "packageUri": "lib/", - "languageVersion": "2.14" + "languageVersion": "2.18" }, { "name": "boolean_selector", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/boolean_selector-2.1.1", + "packageUri": "lib/", + "languageVersion": "2.17" + }, + { + "name": "build", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/build-2.3.1", + "packageUri": "lib/", + "languageVersion": "2.17" + }, + { + "name": "built_collection", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/built_collection-5.1.1", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "built_value", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/built_value-8.4.4", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "characters", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.2.1", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/characters-1.3.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "clock", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.1", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/clock-1.1.1", "packageUri": "lib/", "languageVersion": "2.12" }, + { + "name": "code_builder", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/code_builder-4.6.0", + "packageUri": "lib/", + "languageVersion": "2.19" + }, { "name": "collection", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/collection-1.17.2", "packageUri": "lib/", - "languageVersion": "2.12" + "languageVersion": "2.18" + }, + { + "name": "convert", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/convert-3.1.1", + "packageUri": "lib/", + "languageVersion": "2.18" + }, + { + "name": "coverage", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/coverage-1.6.3", + "packageUri": "lib/", + "languageVersion": "2.18" + }, + { + "name": "crypto", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/crypto-3.0.2", + "packageUri": "lib/", + "languageVersion": "2.14" + }, + { + "name": "dart_style", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/dart_style-2.3.0", + "packageUri": "lib/", + "languageVersion": "2.19" }, { "name": "fake_async", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.3.1", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/fake_async-1.3.1", "packageUri": "lib/", "languageVersion": "2.12" }, + { + "name": "file", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/file-6.1.4", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "fixnum", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/fixnum-1.1.0", + "packageUri": "lib/", + "languageVersion": "2.19" + }, { "name": "flutter", "rootUri": "file:///Users/rafaelsetragni/Development/flutter/packages/flutter", "packageUri": "lib/", - "languageVersion": "2.17" + "languageVersion": "3.0" }, { "name": "flutter_lints", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_lints-2.0.1", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/flutter_lints-2.0.3", "packageUri": "lib/", - "languageVersion": "2.17" + "languageVersion": "2.19" }, { "name": "flutter_test", "rootUri": "file:///Users/rafaelsetragni/Development/flutter/packages/flutter_test", "packageUri": "lib/", - "languageVersion": "2.17" + "languageVersion": "3.0" }, { "name": "flutter_web_plugins", "rootUri": "file:///Users/rafaelsetragni/Development/flutter/packages/flutter_web_plugins", "packageUri": "lib/", - "languageVersion": "2.17" + "languageVersion": "3.0" + }, + { + "name": "frontend_server_client", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/frontend_server_client-3.2.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "glob", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/glob-2.1.1", + "packageUri": "lib/", + "languageVersion": "2.15" + }, + { + "name": "http_multi_server", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/http_multi_server-3.2.1", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "http_parser", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/http_parser-4.0.2", + "packageUri": "lib/", + "languageVersion": "2.12" }, { "name": "intl", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/intl-0.17.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/intl-0.18.1", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "io", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/io-1.0.4", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "js", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/js-0.6.4", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/js-0.6.7", "packageUri": "lib/", - "languageVersion": "2.16" + "languageVersion": "2.19" }, { "name": "lints", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/lints-2.0.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/lints-2.0.1", "packageUri": "lib/", "languageVersion": "2.17" }, + { + "name": "logging", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/logging-1.1.1", + "packageUri": "lib/", + "languageVersion": "2.18" + }, { "name": "matcher", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.12", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/matcher-0.12.16", "packageUri": "lib/", - "languageVersion": "2.12" + "languageVersion": "2.18" }, { "name": "material_color_utilities", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/material_color_utilities-0.1.5", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/material_color_utilities-0.5.0", "packageUri": "lib/", - "languageVersion": "2.13" + "languageVersion": "2.17" }, { "name": "meta", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.8.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/meta-1.9.1", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "mime", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mime-1.0.4", + "packageUri": "lib/", + "languageVersion": "2.18" + }, + { + "name": "mockito", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mockito-5.4.2", + "packageUri": "lib/", + "languageVersion": "2.19" + }, + { + "name": "mocktail", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mocktail-1.0.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "node_preamble", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/node_preamble-2.0.2", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "package_config", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/package_config-2.1.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "path", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.2", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/path-1.8.3", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "plugin_platform_interface", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-2.1.3", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.6", + "packageUri": "lib/", + "languageVersion": "2.19" + }, + { + "name": "pool", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/pool-1.5.1", "packageUri": "lib/", "languageVersion": "2.12" }, + { + "name": "pub_semver", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/pub_semver-2.1.3", + "packageUri": "lib/", + "languageVersion": "2.17" + }, + { + "name": "shelf", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf-1.4.0", + "packageUri": "lib/", + "languageVersion": "2.17" + }, + { + "name": "shelf_packages_handler", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_packages_handler-3.0.1", + "packageUri": "lib/", + "languageVersion": "2.14" + }, + { + "name": "shelf_static", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_static-1.1.1", + "packageUri": "lib/", + "languageVersion": "2.14" + }, + { + "name": "shelf_web_socket", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_web_socket-1.0.3", + "packageUri": "lib/", + "languageVersion": "2.17" + }, { "name": "sky_engine", "rootUri": "file:///Users/rafaelsetragni/Development/flutter/bin/cache/pkg/sky_engine", "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "source_gen", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_gen-1.2.7", + "packageUri": "lib/", + "languageVersion": "2.18" + }, + { + "name": "source_map_stack_trace", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_map_stack_trace-2.1.1", + "packageUri": "lib/", "languageVersion": "2.12" }, + { + "name": "source_maps", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_maps-0.10.12", + "packageUri": "lib/", + "languageVersion": "2.18" + }, { "name": "source_span", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.9.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_span-1.10.0", "packageUri": "lib/", - "languageVersion": "2.14" + "languageVersion": "2.18" }, { "name": "stack_trace", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/stack_trace-1.11.0", "packageUri": "lib/", - "languageVersion": "2.12" + "languageVersion": "2.18" }, { "name": "stream_channel", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/stream_channel-2.1.1", "packageUri": "lib/", - "languageVersion": "2.12" + "languageVersion": "2.14" }, { "name": "string_scanner", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.1", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/string_scanner-1.2.0", "packageUri": "lib/", - "languageVersion": "2.12" + "languageVersion": "2.18" }, { "name": "term_glyph", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.1", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/term_glyph-1.2.1", "packageUri": "lib/", "languageVersion": "2.12" }, + { + "name": "test", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test-1.24.3", + "packageUri": "lib/", + "languageVersion": "2.18" + }, { "name": "test_api", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.12", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test_api-0.6.0", + "packageUri": "lib/", + "languageVersion": "2.18" + }, + { + "name": "test_core", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test_core-0.5.3", + "packageUri": "lib/", + "languageVersion": "2.18" + }, + { + "name": "typed_data", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/typed_data-1.3.1", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "vector_math", - "rootUri": "file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.2", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/vector_math-2.1.4", + "packageUri": "lib/", + "languageVersion": "2.14" + }, + { + "name": "vm_service", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/vm_service-9.4.0", + "packageUri": "lib/", + "languageVersion": "2.15" + }, + { + "name": "watcher", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/watcher-1.0.2", "packageUri": "lib/", "languageVersion": "2.14" }, + { + "name": "web", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/web-0.1.4-beta", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "web_socket_channel", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.0", + "packageUri": "lib/", + "languageVersion": "2.15" + }, + { + "name": "webkit_inspection_protocol", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/webkit_inspection_protocol-1.2.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "yaml", + "rootUri": "file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/yaml-3.1.1", + "packageUri": "lib/", + "languageVersion": "2.12" + }, { "name": "awesome_notifications", "rootUri": "../", "packageUri": "lib/", - "languageVersion": "2.14" + "languageVersion": "2.19" } ], - "generated": "2022-10-12T11:23:17.491156Z", + "generated": "2023-09-22T23:21:52.866203Z", "generator": "pub", - "generatorVersion": "2.18.2" + "generatorVersion": "3.1.1" } diff --git a/.dart_tool/package_config_subset b/.dart_tool/package_config_subset index 232cc22a..5eb995a4 100644 --- a/.dart_tool/package_config_subset +++ b/.dart_tool/package_config_subset @@ -1,109 +1,277 @@ +_fe_analyzer_shared +2.17 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/_fe_analyzer_shared-59.0.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/_fe_analyzer_shared-59.0.0/lib/ +analyzer +2.19 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/analyzer-5.11.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/analyzer-5.11.1/lib/ +args +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/args-2.4.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/args-2.4.0/lib/ async -2.14 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.9.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.9.0/lib/ +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/async-2.11.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/async-2.11.0/lib/ boolean_selector +2.17 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/boolean_selector-2.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/boolean_selector-2.1.1/lib/ +build +2.17 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/build-2.3.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/build-2.3.1/lib/ +built_collection +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/built_collection-5.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/built_collection-5.1.1/lib/ +built_value 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/built_value-8.4.4/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/built_value-8.4.4/lib/ characters 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.2.1/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/characters-1.2.1/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/characters-1.3.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/characters-1.3.0/lib/ clock 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.1/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/clock-1.1.1/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/clock-1.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/clock-1.1.1/lib/ +code_builder +2.19 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/code_builder-4.6.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/code_builder-4.6.0/lib/ collection -2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0/lib/ +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/collection-1.17.2/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/collection-1.17.2/lib/ +convert +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/convert-3.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/ +coverage +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/coverage-1.6.3/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/coverage-1.6.3/lib/ +crypto +2.14 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/crypto-3.0.2/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/crypto-3.0.2/lib/ +dart_style +2.19 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/dart_style-2.3.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/dart_style-2.3.0/lib/ fake_async 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.3.1/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.3.1/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/fake_async-1.3.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/fake_async-1.3.1/lib/ +file +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/file-6.1.4/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/file-6.1.4/lib/ +fixnum +2.19 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/fixnum-1.1.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/fixnum-1.1.0/lib/ flutter_lints -2.17 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_lints-2.0.1/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_lints-2.0.1/lib/ +2.19 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/flutter_lints-2.0.3/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/flutter_lints-2.0.3/lib/ +frontend_server_client +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/frontend_server_client-3.2.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/frontend_server_client-3.2.0/lib/ +glob +2.15 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/glob-2.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/glob-2.1.1/lib/ +http_multi_server +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/http_multi_server-3.2.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/http_multi_server-3.2.1/lib/ +http_parser +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/http_parser-4.0.2/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/http_parser-4.0.2/lib/ intl 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/intl-0.17.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/intl-0.17.0/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/intl-0.18.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/intl-0.18.1/lib/ +io +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/io-1.0.4/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/io-1.0.4/lib/ js -2.16 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/js-0.6.4/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/js-0.6.4/lib/ +2.19 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/js-0.6.7/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/js-0.6.7/lib/ lints 2.17 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/lints-2.0.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/lints-2.0.0/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/lints-2.0.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/lints-2.0.1/lib/ +logging +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/logging-1.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/logging-1.1.1/lib/ matcher -2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.12/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.12/lib/ +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/matcher-0.12.16/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/matcher-0.12.16/lib/ material_color_utilities -2.13 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/material_color_utilities-0.1.5/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/material_color_utilities-0.1.5/lib/ +2.17 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/material_color_utilities-0.5.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/material_color_utilities-0.5.0/lib/ meta 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.8.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.8.0/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/meta-1.9.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/meta-1.9.1/lib/ +mime +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mime-1.0.4/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mime-1.0.4/lib/ +mockito +2.19 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mockito-5.4.2/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mockito-5.4.2/lib/ +mocktail +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mocktail-1.0.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/mocktail-1.0.0/lib/ +node_preamble +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/node_preamble-2.0.2/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/node_preamble-2.0.2/lib/ +package_config +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/package_config-2.1.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/package_config-2.1.0/lib/ path 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.2/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.8.2/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/path-1.8.3/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/path-1.8.3/lib/ plugin_platform_interface +2.19 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.6/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.6/lib/ +pool 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-2.1.3/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-2.1.3/lib/ -source_span +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/pool-1.5.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/pool-1.5.1/lib/ +pub_semver +2.17 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/pub_semver-2.1.3/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/pub_semver-2.1.3/lib/ +shelf +2.17 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf-1.4.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf-1.4.0/lib/ +shelf_packages_handler 2.14 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.9.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.9.0/lib/ -stack_trace +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_packages_handler-3.0.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_packages_handler-3.0.1/lib/ +shelf_static +2.14 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_static-1.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_static-1.1.1/lib/ +shelf_web_socket +2.17 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_web_socket-1.0.3/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/shelf_web_socket-1.0.3/lib/ +source_gen +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_gen-1.2.7/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_gen-1.2.7/lib/ +source_map_stack_trace 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_map_stack_trace-2.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_map_stack_trace-2.1.1/lib/ +source_maps +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_maps-0.10.12/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_maps-0.10.12/lib/ +source_span +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_span-1.10.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/source_span-1.10.0/lib/ +stack_trace +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/stack_trace-1.11.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/stack_trace-1.11.0/lib/ stream_channel -2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib/ +2.14 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/stream_channel-2.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/stream_channel-2.1.1/lib/ string_scanner -2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.1/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.1/lib/ +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/string_scanner-1.2.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/string_scanner-1.2.0/lib/ term_glyph 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.1/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.1/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/term_glyph-1.2.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/term_glyph-1.2.1/lib/ +test +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test-1.24.3/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test-1.24.3/lib/ test_api +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test_api-0.6.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test_api-0.6.0/lib/ +test_core +2.18 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test_core-0.5.3/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/test_core-0.5.3/lib/ +typed_data 2.12 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.12/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.12/lib/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/typed_data-1.3.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/typed_data-1.3.1/lib/ vector_math 2.14 -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.2/ -file:///Users/rafaelsetragni/Development/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.2/lib/ -sky_engine +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/vector_math-2.1.4/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/ +vm_service +2.15 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/vm_service-9.4.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/vm_service-9.4.0/lib/ +watcher +2.14 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/watcher-1.0.2/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/watcher-1.0.2/lib/ +web +3.1 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/web-0.1.4-beta/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/web-0.1.4-beta/lib/ +web_socket_channel +2.15 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.0/lib/ +webkit_inspection_protocol 2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/webkit_inspection_protocol-1.2.0/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/webkit_inspection_protocol-1.2.0/lib/ +yaml +2.12 +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/yaml-3.1.1/ +file:///Users/rafaelsetragni/.pub-cache/hosted/pub.dev/yaml-3.1.1/lib/ +sky_engine +3.0 file:///Users/rafaelsetragni/Development/flutter/bin/cache/pkg/sky_engine/ file:///Users/rafaelsetragni/Development/flutter/bin/cache/pkg/sky_engine/lib/ flutter -2.17 +3.0 file:///Users/rafaelsetragni/Development/flutter/packages/flutter/ file:///Users/rafaelsetragni/Development/flutter/packages/flutter/lib/ flutter_test -2.17 +3.0 file:///Users/rafaelsetragni/Development/flutter/packages/flutter_test/ file:///Users/rafaelsetragni/Development/flutter/packages/flutter_test/lib/ flutter_web_plugins -2.17 +3.0 file:///Users/rafaelsetragni/Development/flutter/packages/flutter_web_plugins/ file:///Users/rafaelsetragni/Development/flutter/packages/flutter_web_plugins/lib/ awesome_notifications -2.14 +2.19 file:///Users/rafaelsetragni/Documents/GitHub/plugins/awesome_notifications/ file:///Users/rafaelsetragni/Documents/GitHub/plugins/awesome_notifications/lib/ 2 diff --git a/.dart_tool/version b/.dart_tool/version index 2c6109e5..faedad41 100644 --- a/.dart_tool/version +++ b/.dart_tool/version @@ -1 +1 @@ -3.3.4 \ No newline at end of file +3.13.3 \ No newline at end of file diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 6f5a9a1f..d61f413c 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -7,9 +7,9 @@ name: Dart on: push: - branches: [ "master", "hotfix", "develop" ] + branches: [ "master", "hotfix", "development" ] pull_request: - branches: [ "master", "hotfix", "develop" ] + branches: [ "master", "hotfix", "development" ] jobs: build: @@ -17,29 +17,40 @@ jobs: steps: - uses: actions/checkout@v3 - - # Note: This workflow uses the latest stable version of the Dart SDK. - # You can specify other versions if desired, see documentation here: - # https://github.com/dart-lang/setup-dart/blob/main/README.md - # - uses: dart-lang/setup-dart@v1 - - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 + - id: flutter-action + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - name: Print Flutter configurations + run: | + echo CACHE-PATH=${{ steps.flutter-action.outputs.CACHE-PATH }} + echo CACHE-KEY=${{ steps.flutter-action.outputs.CACHE-KEY }} + echo CHANNEL=${{ steps.flutter-action.outputs.CHANNEL }} + echo VERSION=${{ steps.flutter-action.outputs.VERSION }} + echo ARCHITECTURE=${{ steps.flutter-action.outputs.ARCHITECTURE }} + + - name: Clean cache configurations + run: flutter clean - name: Install dependencies - run: dart pub get + run: flutter pub get - # Uncomment this step to verify the use of 'dart format' on each commit. - - name: Verify formatting - run: dart format --output=none --set-exit-if-changed . + # Disabled due inconsistencies between local and remote execution. + # - name: Verify formatting + # run: dart format --output=none --set-exit-if-changed . - # Consider passing '--fatal-infos' for slightly stricter analysis. - name: Analyze project source - run: dart analyze + run: flutter analyze --no-fatal-infos - # Your project will need to have tests in test/ and a dependency on - # package:test for this step to succeed. Note that Flutter projects will - # want to change this to 'flutter test'. - name: Run tests - run: dart test + run: flutter test - - name: Run tests - run: dart test --coverage + - name: Run tests coverage + run: flutter test --coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false + directory: coverage \ No newline at end of file diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml deleted file mode 100644 index 25984e78..00000000 --- a/.idea/libraries/Dart_Packages.xml +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index b0f69711..53449dae 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -1,6 +1,8 @@ - + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index 639900d1..374f537c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,8 @@ - + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 631dbb4c..042a88f2 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -10,13 +10,16 @@ - + + + - - - - - - - - - - - - - - - + { + "keyToString": { + "DEBUGGABLE_DEVICE": "motorola-moto_g_play-0029216329", + "DEBUGGABLE_PROCESS": "me.carda.awesome_notifications_example", + "DEBUGGER_ID": "Java", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.cidr.known.project.marker": "true", + "SHARE_PROJECT_CONFIGURATION_FILES": "true", + "SHOW_ALL_PROCESSES": "false", + "cidr.known.project.marker": "true", + "com.intellij.openapi.externalSystem.service.settings.ExternalSystemGroupConfigurable": "ALL", + "dart.analysis.tool.window.visible": "false", + "io.flutter.reload.alreadyRun": "true", + "last_opened_file_path": "D:/GitHub/plugins/awesome_notifications_fcm", + "run.code.analysis.last.selected.profile": "pProject Default", + "settings.editor.selected.configurable": "project.propVCSSupport.Mappings", + "show.migrate.to.gradle.popup": "false" + } +} @@ -59,8 +88,12 @@ + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -157,11 +241,6 @@ - - file://$PROJECT_DIR$/lib/src/models/notification_content.dart - 133 - file://$PROJECT_DIR$/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationContentModel.java 140 @@ -169,7 +248,7 @@ file://$PROJECT_DIR$/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/builders/NotificationBuilder.java - 566 + 580 @@ -177,7 +256,29 @@ 205 + + file://$PROJECT_DIR$/android/src/main/java/me/carda/awesome_notifications/AwesomeNotificationsPlugin.java + 1379 + + + file://$PROJECT_DIR$/lib/src/helpers/bitmap_helper.dart + 44 + + + + + + + + + + + + + \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 81d01f35..c3345e17 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,21 +25,33 @@ "name": "Complete Example", "cwd": "example", "request": "launch", - "type": "dart" + "type": "dart", + "program": "lib/main_complete.dart", }, { "name": "Complete Example (profile mode)", "cwd": "example", "request": "launch", "type": "dart", - "flutterMode": "profile" + "flutterMode": "profile", + "program": "lib/main_complete.dart", + } }, { "name": "Complete Example (release mode)", "cwd": "example", "request": "launch", "type": "dart", - "flutterMode": "release" - } + "flutterMode": "release", + "program": "lib/main_complete.dart", + }, + { + "name": "All tests coverage", + "request": "launch", + "type": "dart", + "toolArgs": [ + "--coverage", + ], + }, ] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ae6cad2d..ea517283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,220 +1,276 @@ +## [0.7.5] +### Added +* Full support for Flutter 3.13 +* Support for iOS 17 +* Compatibility with AGP 8 +* Automated tests for Dart code in master, with over 90% coverage +### Fixed +* New image provider for native resource media +### Improved +* Project dependencies upgraded to the latest version available +* Documentation updated + +## [0.7.5-dev.3] +### Added +* Chronometer indicator for Android notifications +### Fixed +* Default app small icon fixed on Android +* New field timeoutAfter exposed on NotificationContentModel constructor +* Notification events changed to fire also for negative address +* Filters to separate meta data from payload on iOS pushes changed to ignore local notifications +* Order of recreation notification events changed on iOS to preserve expired lost events +### Improved +* iOS minimal deployment target decreased to 11 +* TimeoutAfter modified to use Duration, instead of integer values to represent seconds + +## [0.7.5-dev.2+1] +### Improved +* Android core dependencies moved to new repository +* Added new native module switcher to avoid the folder copy at example/android folder. +* Added new test cases for actionType property + +## [0.7.5-dev.2] +### Fixed +* Replaces local references from development repositories to online versions +* Updates and improves documentation due new pub.dev requirements + +## [0.7.5-dev.1] +### Added +* Added translation feature to notifications based on app configurations or custom configurations +* Added "AwnAppGroupName" property to "Info.plist" to support custom App Group names on iOS +* Added support for inexact schedules, allowing notifications to be scheduled at approximate times rather than exact times +* Added "delayTolerance" parameter to schedules to provide a tolerance for delayed notifications (in seconds) +* Added methods to API to list all active notification IDs and check if a notification ID is * * currently active +* Achieved 97.7% test coverage with new test cases for all relevant classes + +### Improved +* SQLite database to replace SharedPreferences on Android +* Ability for displayed notification events to fire in the exact amount that they had displayed to the user, even with the same ID +* Example app with ReceivePort and SendPort + +### Fixed +* schedule map conversions for interval notifications with repeat option on Android +* onCreated and onDisplayed calling events for Notification Target Extensions on iOS + +## [0.7.4+1] +* Updates Discord markdown links on pub.dev ## [0.7.4] -* Added single page example on pub.dev -* Fixed background action definition labels for iOS -* Fixed getInitialNotificationAction for larger projects with intense initial loading -* Notifications with reply buttons now do not close the status bar after send text messages -* Documentation updated and improved +* Adds single page example on pub.dev +* Fixes background action definition labels for iOS +* Fixes getInitialNotificationAction for larger projects with intense initial loading +* Ensures notifications with reply buttons do not close the status bar after sending text messages +* Updates and improves documentation ## [0.7.3] -* Added badge parameter into notification's content to set the app badge value through notifications -* Added deep merge method into MapUtils to allow notification content combinations -* Flutter minimal version was increased to 2.14 due DecoderCallback deprecation -* Documentation updated and improved +* Adds badge parameter into notification's content to set the app badge value through notifications +* Adds deep merge method into MapUtils to allow notification content combinations +* Increases Flutter minimal version to 2.14 due to DecoderCallback deprecation +* Updates and improves documentation ## [0.7.2] -* Media Style dependencies upgraded +* Upgrades Media Style dependencies ## [0.7.1] -* Flutter sdk dependency decreased from 2.18 to 2.12 to support old flutter applications -* Added switch imports for Dart:io and Dart:html to improve web support -* Awesome web portal urls updated +* Decreases Flutter SDK dependency from 2.18 to 2.12 to support old Flutter applications +* Adds switch imports for Dart:io and Dart:html to improve web support +* Updates Awesome web portal URLs ## [0.7.0+1] -* iOS core dependency updated to version 0.7.1 to match FCM add-on plugin +* Updates iOS core dependency to version 0.7.1 to match FCM add-on plugin ## [0.7.0] * Initial release of Awesome Notifications version 0.7.0 ## [0.7.0-beta.7+6] -* Android core dependency updated to 0.7.0-alpha.7+6 -* Added getInitialNotificationAction method +* Updates Android core dependency to 0.7.0-alpha.7+6 +* Adds getInitialNotificationAction method ## [0.7.0-beta.7+5] -* Android core dependency updated to 0.7.0-alpha.7+6 +* Updates Android core dependency to 0.7.0-alpha.7+6 ## [0.7.0-beta.7+4] -* Run dart format over Lib to increase pub.dev score +* Runs dart format over Lib to increase pub.dev score ## [0.7.0-beta.7+3] -* Dart support decreased to support version 2.18.0. -* DecoderCallback replaced by DecoderBufferCallback due flutter deprecation for resource images +* Decreases Dart support to support version 2.18.0. +* Replaces DecoderCallback with DecoderBufferCallback due to Flutter deprecation for resource images ## [0.7.0-beta.7+2] -* Modifications made according lint warnings to increase pub.dev score +* Makes modifications according to lint warnings to increase pub.dev score ## [0.7.0-beta.7+1] -* Fix to change the plugin domain name +* Fixes to change the plugin domain name ## [0.7.0-beta.7] -* Added new Flutter plugin architecture to make it compatible with all available platforms -* Android and iOS core successfully extracted to be reused in remote repositories and other languages +* Adds new Flutter plugin architecture to make it compatible with all available platforms +* Extracts Android and iOS core to be reused in remote repositories and other languages ## [0.7.0-beta.6+1] -* Inclusion of the Donate with PayPal section +* Includes the Donate with PayPal section ## [0.7.0-beta.6] -* Fixed out of sync definition values between Flutter, Android and iOS -* Coverage extended to Android 13 (SDK 33), without the new request dialog (temporary) +* Fixes out-of-sync definition values between Flutter, Android, and iOS +* Extends coverage to Android 13 (SDK 33), without the new request dialog (temporary) ## [0.7.0-beta.5] -* Fixed zero being saved instead of the actual handles for silent background actions -* Added getLifeCycle function to return the current life cycle state of awesome notifications -* Added optional value for payload data +* Fixes zero being saved instead of the actual handles for silent background actions +* Adds getLifeCycle function to return the current life cycle state of awesome notifications +* Adds optional value for payload data ## [0.7.0-beta.4] * iOS notification actions improved to gain more performance and ensure the fast first opening * iOS notification builder refactored to ensure the ios category creation before the notification being displayed * iOS notification builder refactored to ensure the completion handle order for push notifications -* Fixed displayOnForeground e displayOnBackground for iOS +* Fixes displayOnForeground e displayOnBackground for iOS ## [0.7.0-beta.3+2] -* Fixed empty title and body for messaging layout +* Fixes empty title and body for messaging layout * Removed Java cast warnings for cases where's a previous type checking. ## [0.7.0-beta.3+1] -* Fixed NotificationCalendar's inverted error "The time conditions are invalid" +* Fixes NotificationCalendar's inverted error "The time conditions are invalid" * Documentation improved with new imports and observations with common found issues during beta phase * Dart source code cleaned to improve pub points ## [0.7.0-beta.3] * Coverage extended to Android 12L (SDK 32) * All exceptions have been standardized with distinct exception codes to improve native error handling via PlatformException * New exception catcher implemented to handle all native exceptions and provide better integration with Firebase Analytics. -* StopForeground method by id was fixed on Android -* Message layout grouping fixed (https://github.com/rafaelsetragni/awesome_notifications/pull/466) +* StopForeground method by id was Fixes on Android +* Message layout grouping Fixes (https://github.com/rafaelsetragni/awesome_notifications/pull/466) * Background actions improved on iOS to hold long tasks * Background actions improved on iOS to increase UI performance through background threads -* Added console performance measures for iOS -* Added warning messages for non implemented layouts on iOS +* Adds console performance measures for iOS +* Adds warning messages for non implemented layouts on iOS * Notification's payload attribute changed to support null values * awesome_notifications_core package renamed to core to reduce import's path length * Documentation improved -### [0.7.0-beta.2] +## [0.7.0-beta.2] * AsyncTask replaced by Handler/Looper due deprecation in Android 12 * FULL_WAKE_LOCK replaced by SCREEN_BRIGHT_WAKE_LOCK to improve battery life in Android * Implemented research by Android alarm intents to optimize the reschedule process -* Added id helpers to improve the performance of ScheduleManger's cancellations process in almost 100 times +* Adds id helpers to improve the performance of ScheduleManger's cancellations process in almost 100 times ## [0.7.0-beta.1] -* Added dart isolates to allow receiving background notifications without bring the app to foreground -* Added silentBackground, silentBackgroundAction, disableAction and dismissAction action types for notifications and buttons +* Adds dart isolates to allow receiving background notifications without bring the app to foreground +* Adds silentBackground, silentBackgroundAction, disableAction and dismissAction action types for notifications and buttons * InputField type deprecated, as now is possible to combine input buttons with all other action types. Now, to use InputField, please use the property requireInputText * Internal architecture rewrote to decrease O.S. interventions, allowing to increase the performance while create and schedules notifications in almost 10 times * Date objects replaced by Calendar type to enable real time zone operations in native layer -* Added test unit cases to increase test coverage (55% coverage) +* Adds test unit cases to increase test coverage (55% coverage) * Network images for foreground services are reactivated * Upgraded external dependencies to become compatible with Flutter 2.10 ## [0.6.21] -* Added customSound feature for Android (only applicable for versions older than Android 8 (Oreo)) +* Adds customSound feature for Android (only applicable for versions older than Android 8 (Oreo)) * Type parameter T removed from AwesomeAssertUtils to allows it to be compatible with Flutter web parser. ## [0.6.20] -* Added rounded images for large icon and big picture -* Added bool value extraction for Map objects in dart -* Fixed immutable error for input buttons in Android 12 +* Adds rounded images for large icon and big picture +* Adds bool value extraction for Map objects in dart +* Fixes immutable error for input buttons in Android 12 * Network images for foreground services are temporarily disabled (https://github.com/rafaelsetragni/awesome_notifications/issues/369) ## [0.6.19] -* Added sound extension for notifications with categories Alarm and Call -* Added call notification behavior (stay floating on screen and keep playing the sound in loop) for Call category -* Added CancellationManager to reorganize all dismiss and cancellation methods in a single place +* Adds sound extension for notifications with categories Alarm and Call +* Adds call notification behavior (stay floating on screen and keep playing the sound in loop) for Call category +* Adds CancellationManager to reorganize all dismiss and cancellation methods in a single place * Created StatusBarManager to manage which notifications are currently active, improving performance and extending support to Android 5.0 Lollipop (SDK 21) * Notification layouts BigPicture, Messaging and MessagingGroup improved to be more performative and to reuses network connection data -* Added history box to notification's reply buttons on Android 8.0 to 12.0 +* Adds history box to notification's reply buttons on Android 8.0 to 12.0 * iOS swift completion handlers modified to allow display notifications from another plugins ## [0.6.18+2] * Update readme file and PermissionManager to be compatible with Android 12 and Java 7 ## [0.6.18+1] * Java lambda expressions removed to turn Android source compatible with old Java 7 ## [0.6.18] -* Added Channel's Group feature for Android -* Added notification's category feature for Android -* Added fullScreenIntent permission and content option to allow to show notifications in full screen mode. -* Added PreciseAlarms permission and schedule option to allow to show scheduled notifications with more precision. -* Added showPage methods to provide shortcuts to channel permissions page and alarm permission page. -* Added shouldShowRationale method for android to check if the requested permissions require user intervention to be enable. -* Added request permissions methods to demand the users permissions considered dangerous by the system. +* Adds Channel's Group feature for Android +* Adds notification's category feature for Android +* Adds fullScreenIntent permission and content option to allow to show notifications in full screen mode. +* Adds PreciseAlarms permission and schedule option to allow to show scheduled notifications with more precision. +* Adds showPage methods to provide shortcuts to channel permissions page and alarm permission page. +* Adds shouldShowRationale method for android to check if the requested permissions require user intervention to be enable. +* Adds request permissions methods to demand the users permissions considered dangerous by the system. * Permission's request methods refactored to enable a more modular and scalable evolution to comport future permissions for Android * Documentation has been improved with the new permissions methods, channel's group, notification categories, fullScreenIntent and PreciseAlarms permissions ## [0.6.17] -* Added wakeUpScreen option in notification content to wake up screen when a notification is displayed (Requires to add WAKE_LOCK permission into AndroidManifest.xml). -* Added custom permissions for method requestPermissionToSendNotifications (has no effect on Android). +* Adds wakeUpScreen option in notification content to wake up screen when a notification is displayed (Requires to add WAKE_LOCK permission into AndroidManifest.xml). +* Adds custom permissions for method requestPermissionToSendNotifications (has no effect on Android). * Documentation has been improved with wakeUpScreen option ## [0.6.16] * Media button receiver removed from AndroidManifest.xml due incompatibility with some Galaxy models and another plugins (#81 and #320) * Documentation on scheduling notifications has been improved ## [0.6.15] * PushNotification class deprecated, as all push features are being moved to the new companion plugin. Instead, use NotificationModel. -* Added isDangerousOption for action buttons, to color the text in red to indicate a dangerous option for the user. -* Added color option for action buttons, to color the text in Android 8.0 Oreo and beyond (has no effect on iOS). -* Fix for issue #321, when a new notification is erroneously created when the user taps on notification action button. +* Adds isDangerousOption for action buttons, to color the text in red to indicate a dangerous option for the user. +* Adds color option for action buttons, to color the text in Android 8.0 Oreo and beyond (has no effect on iOS). +* Fixes for issue #321, when a new notification is erroneously created when the user taps on notification action button. ## [0.6.14] -* Added validation to prevent scheduling with repeating intervals smaller than 60 seconds (iOS restriction) -* Added crontab schedule to allow complex schedules based on initial and expiration date, a list of precise dates, or a crontab expression, and all four options can be combined together (only for Android) +* Adds validation to prevent scheduling with repeating intervals smaller than 60 seconds (iOS restriction) +* Adds crontab schedule to allow complex schedules based on initial and expiration date, a list of precise dates, or a crontab expression, and all four options can be combined together (only for Android) * Defined the final standard to replace negative IDs by random values * Minimum Android requirements increased to SDK 23 (Android 6.0 Marshmallow) due to new cancellation methods with unsecure procedures on API prior 23 ## [0.6.13] -* Added messaging layout and messaging group layout -* Added method showNotificationPage to programmatically redirect the user to O.S. notifications permission page +* Adds messaging layout and messaging group layout +* Adds method showNotificationPage to programmatically redirect the user to O.S. notifications permission page * Minimum Android requirements increased to SDK 21 (Android 5.0 Lollipop) due to new cancellation methods -* Added new cancellation methods (dismiss, cancel schedules and cancel all (dismiss and schedules at same time))) based on group key or channel key +* Adds new cancellation methods (dismiss, cancel schedules and cancel all (dismiss and schedules at same time))) based on group key or channel key * Property "autoCancel" changed to "autoDismissable", to match the new cancellation methods naming principles -* Added internal group key based on ID, to prevent Android auto grouping with 4+ notification from same channel when group key was not specified +* Adds internal group key based on ID, to prevent Android auto grouping with 4+ notification from same channel when group key was not specified * Android channels refactored to keep the original channel key at maximum as possible, maximizing the compatibility with another plugins. * Models refactored to follow the native standards and transformations from map data * Calendar millisecond precision has been deprecated, due devices do not provide or ignore such precision. -* Added error handling for image assets (iOS) -* Added video tutorial into README file +* Adds error handling for image assets (iOS) +* Adds video tutorial into README file * Version numbering has changed to better translate the stage of development for future releases. ## [0.0.6+12] -* Added showInCompactView property for MediaPlayer buttons -* Added support to multiple subscriptions on created, displayed, action and dismissed stream +* Adds showInCompactView property for MediaPlayer buttons +* Adds support to multiple subscriptions on created, displayed, action and dismissed stream * Removed channel key from Android Badge methods, because the segregation in channel keys was never used (now is all global) -* Added increment and decrement badge methods (more performative) +* Adds increment and decrement badge methods (more performative) ## [0.0.6+11] * Fix Android reschedules on startup process (issue #285) * Improved Android channels to manage another package channels and convert then to the new standard, using channelKey as hashKey produced from digest channel content ## [0.0.6+10] -* Added foreground services for Android -* Fixed android reference for guava package +* Adds foreground services for Android +* Fixes android reference for guava package ## [0.0.6+9] -* Fixed null reference for main class inside NotificationBuilder.java +* Fixes null reference for main class inside NotificationBuilder.java ## [0.0.6+8] -* Fixed null reference for main class +* Fixes null reference for main class ## [0.0.6+7] -* Documentation improved -* Push notifications from example app updated +* Improves documentation +* Updates push notifications in the example app ## [0.0.6+6] -* Added time zones for scheduled notifications -* Added foreground behavior for input button -* Added dismiss methods to dismiss the notifications without cancel their respective schedules +* Adds time zones for scheduled notifications +* Adds foreground behavior for input button +* Adds dismiss methods to dismiss the notifications without cancel their respective schedules ## [0.0.6+5] -* Added the behavior of bringing to the foreground for action buttons on iOS -* Added debug option on initialize method to lower the debug verbose if not necessary -* Leveled error messages and error handling for iOS and Android platforms +* Adds behavior of bringing action buttons to foreground on iOS +* Adds debug option on initialize method to lower debug verbosity if not necessary +* Improves error messages and error handling for iOS and Android platforms ## [0.0.6+4] -* Added native firebase handling for will present notification method -* Added fixedDate to getNextDate on iOS -* Added .aiff example files with more quality +* Adds native firebase handling for will present notification method +* Adds fixedDate to getNextDate on iOS +* Adds .aiff example files with more quality * Adjust weekday to work with ISO 8601 ## [0.0.6+3] -* Fixed Android canceling for a grouped notification set as summary behaviour -* Added color hexadecimal representation for json content +* Fixes Android canceling for a grouped notification set as summary behaviour +* Adds color hexadecimal representation for json content ## [0.0.6+2] -* Fixed Android first grouped message as summary behaviour. +* Fixes Android first grouped message as summary behaviour. ## [0.0.6+1] -* Flutter version fixed, according to pub.dev warnings +* Flutter version Fixes, according to pub.dev warnings ## [0.0.6] * Plugin upgraded to support dart Null Safety * FCM service native support removed for iOS and Android. * iOS awesome extensions removed. * Schedule feature downgraded due to iOS official developer's incapacity and SO limitations (could be reactivated manually). -* Added an example to how to integrate awesome_notification with firebase_messaging plugin. +* Adds an example to how to integrate awesome_notification with firebase_messaging plugin. * Documentation improved ## [0.0.5+8] * FCM service with comments removed for iOS. ## [0.0.5+7] -* Added removeChannel method for iOS and error messages for Android removeChannel method. +* Adds removeChannel method for iOS and error messages for Android removeChannel method. * ios project and example project recovered. ## [0.0.5+6] * Releasing of final version with push notifications enabled. -* Added forceUpdate option on setChannel method, to allows to full update an channel on Android Oreo and above without need to reinstall the app, with the downside to close all current notifications. +* Adds forceUpdate option on setChannel method, to allows to full update an channel on Android Oreo and above without need to reinstall the app, with the downside to close all current notifications. * Flutter version updated to 3.0, with null safety support. -* Fixed privacy bugs -* Fixed grouping functionality and added sort option (sort only works for Android) -* Fixed media button errors for Android 10 and above -* Fixed media path errors for iOS -* Added default sound options for Ringtone, Alarm and Notifications. +* Fixes privacy bugs +* Fixes grouping functionality and Adds sort option (sort only works for Android) +* Fixes media button errors for Android 10 and above +* Fixes media path errors for iOS +* Adds default sound options for Ringtone, Alarm and Notifications. * Documentation improved ## [0.0.5+5] -* Added the link to allows the community to remove or not the push notification functionality from Awesome Notifications plugin. +* Adds the link to allows the community to remove or not the push notification functionality from Awesome Notifications plugin. ## [0.0.5+4] -* Added the icon field inside notification content package to allow to change the small icon without need to use another channel +* Adds the icon field inside notification content package to allow to change the small icon without need to use another channel * Included the example for locked notifications for Android and improved the locked priority behaviour -* Added importance level for notifications (Android 8 and above) +* Adds importance level for notifications (Android 8 and above) * Documentation improved ## [0.0.5+3] * Internal firebase packages updated to the last Android and iOS version -* Fixed auto cancel off for schedule notifications (Android) -* Fixed action buttons for push notifications (iOS) +* Fixes auto cancel off for schedule notifications (Android) +* Fixes action buttons for push notifications (iOS) * Solution for DateUtils class conflict with the new Material "DateUtils" included on documentation * Documentation improved ## [0.0.5+2] @@ -226,7 +282,7 @@ ## [0.0.5] * Finished FCM push messages for iOS 10 or higher * Decreased the implementation complexity to use NotificationServiceExtension and NotificationContentExtension targets (iOS) -* Added two app examples on documentation as tutorials +* Adds two app examples on documentation as tutorials * Improved the native resource decoder to work outside of main thread (Android) * Included protect mode to native resources against obfuscation (Android) * Improved object storage to work correctly with minification @@ -239,19 +295,19 @@ ## [0.0.4+2] * Cleared log messages to decrease visual pollution. * Replaced the native Java Log package by the Flutter's one. -* Fixed wrong "Invalid push notification content", wrongly showed when notification is successfully created. +* Fixes wrong "Invalid push notification content", wrongly showed when notification is successfully created. ## [0.0.4+1] * Simplifying the iOS setup process for the developer (it's still a bit complex) ## [0.0.4] -* Fixed complex schedules for iOS apps running on Foreground +* Fixes complex schedules for iOS apps running on Foreground * Included Global Badge indicators for iOS and some Android distributions * Included request permission and check permission methods * Included Firebase support to send push notifications on iOS (work in progress) ## [0.0.3+3] -* Fixed Shader's render problems on iOS devices +* Fixes Shader's render problems on iOS devices ## [0.0.3+2] -* Fixed UTC Dates on iOS devices +* Fixes UTC Dates on iOS devices ## [0.0.3+1] * Adjusting the plugin content to pub.dev patterns ## [0.0.3] @@ -265,7 +321,7 @@ ## [0.0.2+1] * Documentation updated ## [0.0.2] -* Added precise schedules option to schedule a notification multiple times with precisely date and time +* Adds precise schedules option to schedule a notification multiple times with precisely date and time ## [0.0.1+7] ## [0.0.1+6] diff --git a/README.md b/README.md index e12bcee3..fd91a1f8 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,64 @@ # Awesome Notifications for Flutter ![](https://raw.githubusercontent.com/rafaelsetragni/awesome_notifications/master/example/assets/readme/awesome-notifications.jpg) -
[![Flutter](https://img.shields.io/badge/Flutter-%2302569B.svg?style=for-the-badge&logo=Flutter&logoColor=white)](#) [![Firebase](https://img.shields.io/badge/firebase-%23039BE5.svg?style=for-the-badge&logo=firebase)](#) -[![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](#) +[![Discord](https://img.shields.io/discord/888523488376279050.svg?style=for-the-badge&colorA=7289da&label=Chat%20on%20Discord)](https://discord.awesome-notifications.carda.me) -[![Discord Shield](https://discordapp.com/api/guilds/888523488376279050/widget.png?style=shield)](https://discord.awesome-notifications.carda.me) +[![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](#) [![pub package](https://img.shields.io/pub/v/awesome_notifications.svg)](https://pub.dev/packages/awesome_notifications) +![Full tests workflow](https://github.com/rafaelsetragni/awesome_notifications/actions/workflows/dart.yml/badge.svg?branch=master) +![codecov badge](https://codecov.io/gh/rafaelsetragni/awesome_notifications/branch/master/graph/badge.svg) + + +Engage your users with custom local and push notifications on Flutter. Get real-time events and never miss a user interaction with Awesome Notifications. -### Features +
+
-- Create **Local Notifications** on Android, iOS and Web using Flutter. -- Send **Push Notifications** using add-on plugins, as [awesome_notifications_fcm]() -- Add **images**, **sounds**, **emoticons**, **buttons** and different layouts on your notifications. -- Easy to use and highly customizable. -- Notifications could be created at **any moment** (on Foreground, Background or even when the application is terminated/killed). -- **High trustworthy** on receive notifications in any Application lifecycle. -- Notifications events are received on **Flutter level code** when they are created, displayed, dismissed or even tapped by the user. -- Notifications could be **scheduled** repeatedly or not, with second precision. +### **Key Features:** + +* **Create custom notifications:** Use Awesome Notifications to easily create and customize local and push notifications for your Flutter app. +* **Engage your users:** Keep your users engaged with your app by sending notifications with images, sounds, emoticons, buttons, and many different layouts. +* **Real-time events:** Receive real-time events on the Flutter level code for notifications that are created, displayed, dismissed, or tapped by the user. +* **Highly customizable:** With a range of customizable options, including translations, you can tailor notifications to fit your specific needs. +* **Scheduled notifications:** Schedule notifications repeatedly or at specific times with second precision to keep your users up-to-date. +* **Trusted performance:** Receive notifications with confidence and trust in any application lifecycle. +* **Easy to use:** With an easy-to-use interface, you can start creating custom notifications in minutes. +

+***Android** notification examples:* + ![](https://raw.githubusercontent.com/rafaelsetragni/awesome_notifications/master/example/assets/readme/awesome-notifications-android-examples.jpg) -*Some **android** notification examples:*
+***iOS** notification examples:* + ![](https://raw.githubusercontent.com/rafaelsetragni/awesome_notifications/master/example/assets/readme/awesome-notifications-ios-examples.jpg) -*Some **iOS** notification examples **(work in progress)**:*
## Notification Types Available +Here are some of the notification types available with Awesome Notifications: + - Basic notification - Big picture notification - Media notification -- Big Text notification +- Big text notification - Inbox notification - Messaging notification -- Messaging Group notification +- Messaging group notification - Notifications with action buttons - Grouped notifications - Progress bar notifications (only for Android) -All notifications could be created locally or via Firebase services, with all the features. +All notification types can be created locally or via remote push services, with all the features and customizations available.
@@ -56,62 +67,65 @@ All notifications could be created locally or via Firebase services, with all th ![image](https://user-images.githubusercontent.com/40064496/155188371-48e22104-8bb8-4f38-ba1a-1795eeb7b81b.png) -*Working progress percentages of awesome notifications plugin* +*Working Progress Percentages of Awesome Notifications Plugin* + +OBS: Please note that these progress percentages are estimates and are subject to change. We are continually working to improve the Awesome Notifications plugin and add support for new platforms.

-# 🛑 ATTENTION
Users from `flutter_local_notifications` plugin - -`awesome_notifications` plugin is incompatible with `flutter_local_notifications`, as both plugins will compete each other to accquire global notification resources to send notifications and to receive notification actions. - -So, you **MUST not use** `flutter_local_notifications` with `awesome_notifications`. Awesome Notifications contains all features available in `flutter_local_notifications` plugin and more, so you can replace totally `flutter_local_notifications` in your project. +# 🛑 ATTENTION
Users from flutter_local_notifications plugin +awesome_notifications plugin is incompatible with `flutter_local_notifications`, as both plugins will compete each other to accquire global notification resources to send notifications and to receive notification actions. +So, you MUST not use `flutter_local_notifications` with `awesome_notifications`. Awesome Notifications contains all features available in flutter_local_notifications plugin and more, so you can replace totally flutter_local_notifications in your project. + +

-# 🛑 ATTENTION
Users from `firebase_messaging` plugin - -The support for `firebase_messaging` plugin is now deprecated, but all other firebase plugins are still being supported. And this happened by the same reason as `flutter_local_notifications`, as both plugins will compete each other to accquire global notification resources. +# 🛑 ATTENTION
Users from firebase_messaging plugin +The support for `firebase_messaging` plugin is now deprecated, but all other firebase plugins are still being supported. This is because firebase_messaging plugin and awesome_notifications plugin will compete with each other to acquire global notification resources. -To use FCM services with Awesome Notifications, you need use the [Awesome Notifications FCM add-on plugin](https://pub.dev/packages/awesome_notifications_fcm). +To use FCM services with Awesome Notifications, you need to use the Awesome Notifications FCM add-on plugin. -Only using [awesome_notifications_fcm](https://pub.dev/packages/awesome_notifications_fcm) you will be capable to achieve all Firebase Cloud Messaging features + all Awesome Notifications features. To keep using firebase_messaging, you gonna need to do workarounds with silent push notifications, and this is disrecommended to display visual notifications and will result in several background penalities to your application. - -So, you **MUST not use** `firebase_messaging` with `awesome_notifications`. Use `awesome_notifications` with `awesome_notifications_fcm` instead. +Only by using `awesome_notifications_fcm` will you be capable of achieving all Firebase Cloud Messaging features + all Awesome Notifications features. To continue using `firebase_messaging`, you will need to implement workarounds with silent push notifications, which is not recommended for displaying visual notifications and will result in several background penalties for your application. + +So, you MUST not use `firebase_messaging` with `awesome_notifications`. Use `awesome_notifications` with `awesome_notifications_fcm` instead.

# ✅ Next steps -- Include Web support -- Finish the add-on plugin to enable Firebase Cloud Message with all the awesome features available. (accomplished) -- Add an option to choose if a notification action should bring the app to foreground or not. (accomplished) -- Include support for another push notification services (Wonderpush, One Signal, IBM, AWS, Azure, etc) +- Include `Web support` +- Include `Desktop support` +- Include `Live Activities notifications` +- Include `Time Sensitive notifications` +- Include `Communication notifications` +- Include full `Media Player notifications` +- Implement test cases for native libraries to achieve +90% test coverage in each one +- Include support for other push notification services (Wonderpush, One Signal, IBM, AWS, Azure, etc) - Replicate all Android layouts for iOS (almost accomplished) - Custom layouts for notifications - + +We are constantly working to improve Awesome Notifications and provide support for new platforms and services. Stay tuned for more updates! +

-# 💰 Donate via PayPal or BuyMeACoffee +# 💰 Donate via Stripe or BuyMeACoffee -Help us to improve and maintain our work with donations of any amount, via Paypal. -Your donation will be mainly used to purchase new devices and equipments, which we will use to test and ensure that our plugins works correctly on all platforms and their respective versions. +Help us improve and maintain our work with donations of any amount via Stripe or BuyMeACoffee. Your donation will mainly be used to purchase new devices and equipment, which we will use to test and ensure that our plugins work correctly on all platforms and their respective versions. - - Donate with PayPal - - -Buy Me A Coffee +[*![Donate With Stripe](https://github.com/rafaelsetragni/awesome_notifications/blob/68c963206885290f8a44eee4bfc7e7b223610e4a/example/assets/readme/stripe.png?raw=true)*](https://donate.stripe.com/3cs14Yf79dQcbU4001) +[*![Donate With Buy Me A Coffee](https://github.com/rafaelsetragni/awesome_notifications/blob/95ee986af0aa59ccf9a80959bbf3dd60b5a4f048/example/assets/readme/buy-me-a-coffee.jpeg?raw=true)*](https://www.buymeacoffee.com/rafaelsetragni)

# 💬 Discord Chat Server -To stay tuned with new updates and get our community support, please subscribe into our Discord Chat Server: +Stay up to date with new updates and get community support by subscribing to our Discord chat server: [![Discord Banner 3](https://discordapp.com/api/guilds/888523488376279050/widget.png?style=banner3)](https://discord.awesome-notifications.carda.me) @@ -122,23 +136,23 @@ To stay tuned with new updates and get our community support, please subscribe i # 📙 Table of Contents - [Awesome Notifications for Flutter](#awesome-notifications-for-flutter) - - [Features](#features) + - [**Key Features:**](#key-features) - [Notification Types Available](#notification-types-available) - [🛑 ATTENTION - PLUGIN UNDER DEVELOPMENT](#-attention---plugin-under-development) -- [🛑 ATTENTION
Users from `flutter_local_notifications` plugin](#-attention--users-from-flutter_local_notifications-plugin) -- [🛑 ATTENTION
Users from `firebase_messaging` plugin](#-attention--users-from-firebase_messaging-plugin) +- [🛑 ATTENTION Users from flutter\_local\_notifications plugin](#-attention--users-from-flutter_local_notifications-plugin) +- [🛑 ATTENTION Users from firebase\_messaging plugin](#-attention--users-from-firebase_messaging-plugin) - [✅ Next steps](#-next-steps) -- [💰 Donate via PayPal or BuyMeACoffee](#-donate-via-paypal-or-buymeacoffee) +- [💰 Donate via Stripe or BuyMeACoffee](#-donate-via-stripe-or-buymeacoffee) - [💬 Discord Chat Server](#-discord-chat-server) - [📙 Table of Contents](#-table-of-contents) - [🔶 Main Philosophy](#-main-philosophy) -- [🚚 Migrating from version 0.6.X to 0.7.X
Breaking changes](#-migrating-from-version-06x-to-07xbreaking-changes) +- [🚚 Migrating from version 0.6.X to 0.7.XBreaking changes](#-migrating-from-version-06x-to-07xbreaking-changes) - [🛠 Getting Started](#-getting-started) - [Initial Configurations](#initial-configurations) - - [🤖 Configuring Android](#-configuring-android) - - [🍎 Configuring iOS](#-configuring-ios) + - [🤖 Configuring Android:](#-configuring-android) + - [🍎 Configuring iOS:](#-configuring-ios) - [📨 How to show Local Notifications](#-how-to-show-local-notifications) - - [📝 Important notes](#-important-notes) + - [📝 Getting started - Important notes](#-getting-started---important-notes) - [🍎⁺ Extra iOS Setup for Background Actions](#-extra-ios-setup-for-background-actions) - [📱 Example Apps](#-example-apps) - [🔷 Awesome Notification's Flowchart](#-awesome-notifications-flowchart) @@ -149,27 +163,38 @@ To stay tuned with new updates and get our community support, please subscribe i - [Notification's Permissions:](#notifications-permissions) - [Notification's Permission Level](#notifications-permission-level) - [Full example on how to request permissions](#full-example-on-how-to-request-permissions) +- [📡 Notification channels](#-notification-channels) + - [Notification Channel Attributes](#notification-channel-attributes) + - [📝 Notification Channel's Important Notes:](#-notification-channels-important-notes) - [📅 Scheduling a Notification](#-scheduling-a-notification) - [⏰ Schedule Precision](#-schedule-precision) - - [📝 Important Notes:](#-important-notes-1) - - [Old schedule Cron rules (For versions older than 0.0.6)](#old-schedule-cron-rules-for-versions-older-than-006) + - [📝 Schedule Notification's Important Notes:](#-schedule-notifications-important-notes) + - [Deprecated Schedule Class for Cron Rules (Versions Prior to 0.0.6)](#deprecated-schedule-class-for-cron-rules-versions-prior-to-006) +- [🌎 Translation of Notification Content](#-translation-of-notification-content) +- [⏱ Chronometer and Timeout (Expiration)](#-chronometer-and-timeout-expiration) - [⌛️ Progress Bar Notifications (Only for Android)](#️-progress-bar-notifications-only-for-android) - [😃 Emojis (Emoticons)](#-emojis-emoticons) +- [🎨 Notification Layout Types](#-notification-layout-types) +- [📷 Media Source Types](#-media-source-types) +- [⬆️ Notification Importance](#️-notification-importance) - [🔆 Wake Up Screen Notifications](#-wake-up-screen-notifications) - [🖥 Full Screen Notifications (only for Android)](#-full-screen-notifications-only-for-android) -- [📡 Notification channels](#-notification-channels) - - [📝 Important Notes:](#-important-notes-2) - [🏗 Notification Structures](#-notification-structures) - [NotificationContent ("content" in Push data) - (required)](#notificationcontent-content-in-push-data---required) + - [📝 Notification Content's Important Notes:](#-notification-contents-important-notes) - [NotificationActionButton ("actionButtons" in Push data) - (optional)](#notificationactionbutton-actionbuttons-in-push-data---optional) - [Schedules](#schedules) - [NotificationInterval ("schedule" in Push data) - (optional)](#notificationinterval-schedule-in-push-data---optional) - [NotificationCalendar ("schedule" in Push data) - (optional)](#notificationcalendar-schedule-in-push-data---optional) - [NotificationAndroidCrontab (Only for Android)("schedule" in Push data) - (optional)](#notificationandroidcrontab-only-for-androidschedule-in-push-data---optional) - - [Notification Layout Types](#notification-layout-types) - - [Media Source Types](#media-source-types) - - [Notification Importance (Android's channel)](#notification-importance-androids-channel) - - [Common Known Issues](#common-known-issues) +- [Common Known Issues](#common-known-issues) + - [***Issue***: Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present](#issue-targeting-s-version-31-and-above-requires-that-an-explicit-value-for-androidexported-be-defined-when-intent-filters-are-present) + - [***Issue***: Notification is not showing up or is showing up inconsistently.](#issue-notification-is-not-showing-up-or-is-showing-up-inconsistently) + - [***Issue:*** My schedules are only displayed immediately after I open my app](#issue-my-schedules-are-only-displayed-immediately-after-i-open-my-app) + - [***Issue***: DecoderBufferCallback not found / Uint8List not found](#issue-decoderbuffercallback-not-found--uint8list-not-found) + - [***Issue***: Using bridging headers with module interfaces is unsupported](#issue-using-bridging-headers-with-module-interfaces-is-unsupported) + - [***Issue***: Invalid notification content](#issue-invalid-notification-content) + - [***Issue***: Undefined symbol: OBJC\_CLASS$\_FlutterStandardTypedData / OBJC\_CLASS$\_FlutterError / OBJC\_CLASS$\_FlutterMethodChannel](#issue-undefined-symbol-objc_class_flutterstandardtypeddata--objc_class_fluttererror--objc_class_fluttermethodchannel) - [Android Foreground Services (Optional)](#android-foreground-services-optional) - [IMPORTANT](#important) - [Foreground Services behaviour on platforms other than Android](#foreground-services-behaviour-on-platforms-other-than-android) @@ -180,28 +205,34 @@ To stay tuned with new updates and get our community support, please subscribe i # 🔶 Main Philosophy -Considering all the many different devices available, with different hardware and software resources, this plugin ALWAYS shows the notification, trying to use all features available. If the feature is not available, the notification ignores that specific feature, but shows the rest of notification anyway. +At Awesome Notifications, we believe that notifications should be transparent to developers with all available features, simplifying the many different hardware and software resources available on different devices. This way, developers can focus on `what to do` instead of `how to do`. To achieve this, we adopt the philosophy of showing notifications with all available features requested, but ignoring any features that are not available on the device. -**Example**: If the device has LED colored lights, use it. Otherwise, ignore the lights, but shows the notification with all another features available. In last case, shows at least the most basic notification. +For example, if a device has LED colored lights, we will use them for notifications. If it doesn't have LED lights, we will ignore that feature but still show the notification with all the other available features. We follow the same philosophy for Notification Channels: if there is no channel segregation of notifications, we use the channel configuration as default. If the device has channels, we use them as expected. -Also, the Notification Channels follows the same rule. If there is no channel segregation of notifications, use the channel configuration as defaults configuration. If the device has channels, use it as expected to be. +All notifications sent while the app was killed are registered and delivered as soon as possible to the Application after the plugin initialization and the event listeners being set, respecting the delivery order. This way, your application will receive all notification events at the Flutter level code. -And all notifications sent while the app was killed are registered and delivered as soon as possible to the Application, after the plugin initialization, respecting the delivery order. +For the app badge number manipulation, we consider as expected behavior how iOS handles it. For all other platforms, we try to mimic its behavior as much close as possible. -This way, your Application will receive **all notifications events at Flutter level code**. +By following our main philosophy, we ensure that Awesome Notifications delivers notifications that work consistently across different devices and provide the best user experience possible for your users.

# 🚚 Migrating from version 0.6.X to 0.7.X
Breaking changes -* Now it's possible to receive action events without bring the app to foreground. Check our action type's topic to know more. -* All streams (createdStream, displayedStream, actionStream and dismissedStream) was replaced by `global static methods`. You must replace your old stream methods by static and global methods, in other words, they must be `static Future` and use `async`/`await` and you MUST use `@pragma("vm:entry-point")` to preserve dart addressing. -
(To use context and redirect the user to another page inside static methods, please use flutter navigatorKey or another third party library, such as GetX. Check our [How to show Local Notifications](#-how-to-show-local-notifications) topic to know more). -* Now all the notification events are delivered only after the first setListeners being called. -* The ButtonType class was renamed to ActionType. -* The action type `InputField` is deprecated. Now you just need to set the property `requireInputText` to true to achieve the same, but now it works combined with all another action types. -* The support for `firebase_messaging` plugin is now deprecated, but all other firebase plugins still being supported. You need use the [Awesome's FCM add-on plugin](https://pub.dev/packages/awesome_notifications_fcm) to achieve all firebase messaging features, without violate the platform rules. This is the only way to fully integrated with awesome notifications, running all in native level. +We have made some changes to Awesome Notifications in version 0.7.X that may require you to update your code. Here are the main breaking changes: + +- ***Action events:*** Now it's possible to receive action events without bringing the app to the foreground. Please refer to the action type's topic for more information on how to implement this. + +- ***Streams replaced by global static methods:*** All streams (createdStream, displayedStream, actionStream, and dismissedStream) have been replaced by global static methods. You must replace your old stream methods with static and global methods. These must be static Future and use async/await. Make sure to use @pragma("vm:entry-point") to preserve dart addressing. To use context and redirect the user to another page inside static methods, please use Flutter navigatorKey or another third-party library, such as GetX. Check our How to show Local Notifications topic to know more. + +- ***Delayed notification events:*** All notification events are now delivered only after the first setListeners being called. Please make sure to update your code accordingly. + +- ***Renamed ButtonType class:*** The ButtonType class has been renamed to ActionType. Please update your code to use the new class name. + +- ***Deprecated InputField action type:*** The action type InputField is now deprecated. You just need to set the property requireInputText to true to achieve the same result, but it now works combined with all other action types. + +- ***Deprecated support for firebase_messaging plugin:*** The support for firebase_messaging plugin is now deprecated. You need to use the [Awesome's FCM add-on plugin](https://pub.dev/packages/awesome_notifications_fcm) to achieve all Firebase Cloud Messaging features without violating the platform rules. This is the only way to fully integrate with Awesome Notifications, running all in native level.
@@ -217,7 +248,7 @@ Bellow are the obligatory configurations that your app must meet to use awesome_
-### 🤖 Configuring Android +### 🤖 Configuring Android: 1 - Is required the minimum android SDK to 21 (Android 5.0 Lollipop) and Java compiled SDK Version to 33 (Android 13). You can change the `minSdkVersion` to 21 and the `compileSdkVersion` and `targetSdkVersion` to 33, inside the file `build.gradle`, located inside "android/app/" folder. ```Gradle @@ -233,7 +264,20 @@ android { } ``` -Also, to turn your app fully compatible with Android 13 (SDK 33), you need to add the attribute `android:exported="true"` to any \, \, \, or \ components that have \ declared inside in the app’s AndroidManifest.xml file, and that's turns required for every other flutter packages that you're using. +2 - In the app’s `AndroidManifest.xml` file (which can be found in the android/app/src/main directory of your Flutter project), add the following permissions: + +```xml + + + ... + + + + +``` + +3 - If you're using any ``, ``, ``, or `` components with `` declared inside, add the attribute `android:exported="true"` to make them accessible from outside the app's context.: ```xml ``` +

-### 🍎 Configuring iOS +### 🍎 Configuring iOS: To use Awesome Notifications and build your app correctly, you need to ensure to set some `build settings` options for your app targets. In your project view, click on *Runner -> Target Runner -> Build settings*... @@ -269,6 +314,7 @@ In *Runner* Target: In *all other* Targets: * Build libraries for distribution => NO * Only safe API extensions => YES +* iOS Deployment Target => 11 or greater

@@ -289,7 +335,7 @@ awesome_notifications: any # Any attribute updates automatically your source to import 'package:awesome_notifications/awesome_notifications.dart'; ``` -3. Initialize the plugin on main.dart, before MaterialApp widget (preferentially inside main() method), with at least one native icon and one channel +3. Initialize the plugin on main.dart, before MaterialApp widget (preferentially inside `main()` method), with at least one native icon and one channel ```dart AwesomeNotifications().initialize( @@ -454,31 +500,33 @@ AwesomeNotifications().isNotificationAllowed().then((isAllowed) { }); ``` -7. In any place of your app, create a new notification +7. In any place of your app, create a new notification with: ```dart AwesomeNotifications().createNotification( content: NotificationContent( id: 10, channelKey: 'basic_channel', - title: 'Simple Notification', - body: 'Simple body', actionType: ActionType.Default + title: 'Hello World!', + body: 'This is my first notification!', ) ); ```
+This will create a new notification with ID `10`, using the previously defined notification channel `basic_channel` and the default action type that brings the app to foreground. The notification will have a title of "Hello World!" and a body of "This is my first notification!". + 🎉🎉🎉 **THATS IT! CONGRATZ MY FRIEND!!!** 🎉🎉🎉

-## 📝 Important notes +## 📝 Getting started - Important notes
-1 . You must initialize all Awesome Notifications plugins, even if your app does not have permissions to send notifications. +1 . You MUST initialize all Awesome Notifications plugins, even if your app does not have permissions to send notifications. 2 . In case you need to capture the user notification action before calling the method `setListeners`, you can call the method `getInitialNotificationAction` at any moment. In case your app was started by an user notification action, `getInitialNotificationAction` will return the respective `ActionReceived` object. Otherwise will return null. @@ -495,32 +543,42 @@ void main() async { } ``` -3 . In case you need to redirect the user after a `silentAction` or `silentBackgroundAction` event, you may face the situation where you are running inside an dart Isolate with no valid Context to redirect the user. -For these cases, you need to use `ReceivePort` and `SendPort` to switch execution between the isolates. Just create a `ReceivePort` inside your initialization process (which only occurs in main isolated), and then, inside your `onActionReceivedMethod`, use `SendPort` to send the execution to the listening `ReceivePort`. +3 . In case you need to redirect the user after a `silentAction` or `silentBackgroundAction` event, you may face the situation where you are running inside a dart Isolate with no valid `BuildContext` to redirect the user. For such cases, you can use `SendPort` and `ReceivePort` to switch execution between isolates. +First, create a `ReceivePort` inside your initialization process (which only occurs in the main isolate). Then, inside your `onActionReceivedMethod`, check if you are running inside the main isolate first. If not, use a `SendPort` to send the execution to the listening `ReceivePort`. Inside the `ReceivePort` listener, you can then call the appropriate method to handle the background action. -In the initialization of your notification_controller.dart: +Here is an example: + +In the initialization of your `notification_controller.dart`: ```Dart + // Create a receive port ReceivePort port = ReceivePort(); + + // Register the receive port with a unique name IsolateNameServer.registerPortWithName( port, - 'background_notification_action', + 'notification_actions', ); + // Listen for messages on the receive port port.listen((var received) async { - _handleBackgroundAction(received); + print('Action running on main isolate'); + _handleActionReceived(received); }); + // Set the initialization flag _initialized = true; ``` -In your backgroundActionMethod: +In your `onActionReceivedMethod` method: ```Dart - static Future onSilentActionHandle(ReceivedAction received) async { - print('On new background action received: ${received.toMap()}'); + static Future onActionReceivedMethod(ReceivedAction received) async { + print('New action received: ${received.toMap()}'); + // If the controller was not initialized or the function is not running in the main isolate, + // try to retrieve the ReceivePort at main isolate and them send the execution to it if (!_initialized) { - SendPort? uiSendPort = IsolateNameServer.lookupPortByName('background_notification_action'); + SendPort? uiSendPort = IsolateNameServer.lookupPortByName('notification_actions'); if (uiSendPort != null) { print('Background action running on parallel isolate without valid context. Redirecting execution'); uiSendPort.send(received); @@ -528,18 +586,26 @@ In your backgroundActionMethod: } } - print('Background action running on main isolate'); - await _handleBackgroundAction(received); + print('Action running on background isolate'); + await _handleActionReceived(received); } - static Future _handleBackgroundAction(ReceivedAction received) async { - // Your background action handle + static Future _handleActionReceived(ReceivedAction received) async { + // Here you handle your notification actions + + // Navigate into pages, avoiding to open the notification details page twice + // In case youre using some state management, such as GetX or get_route, use them to get the valid context instead + // of using the Flutter's navigator key + MyApp.navigatorKey.currentState?.pushNamedAndRemoveUntil('/notification-page', + (route) => (route.settings.name != '/notification-page') || route.isFirst, + arguments: receivedAction); } ```
-4. On Android, if you press the back button until leave the app and then reopen it using the "Recent apps list" *`THE LAST APP INITIALIZATION WILL BE REPEATED`*. So, if your app was started up by a notification, in this exclusive case the notification action will be repeated. As you know the business logic of your app, you need to decide if that notification action can be repeated or if it must be ignored. +4. On Android, if you press the back button until leaves the app and then reopen it using the "Recent apps list" *`THE LAST APP INITIALIZATION WILL BE REPEATED`*. +So, in the case where the app was started up by a notification, in this exclusive case the notification action will be repeated. If this is not desirable behavior for your app, you will need to handle this case specifically in your app's logic.

@@ -549,7 +615,7 @@ In your backgroundActionMethod:
On iOS, to use any plugin inside background actions, you will need to manually register each plugin you want. Otherwise, you will face the `MissingPluginException` exception. -To avoid this, you need to add the following lines to the `didFinishLaunchingWithOptions` method in your iOS project's AppDelegate.m/AppDelegate.swift file: +To avoid this, you need to add the following lines to the `AppDelegate.swift` file in your iOS project folder: ```Swift import Flutter @@ -575,7 +641,7 @@ override func application( } ``` -And you can check how to correctly call each plugin opening the file `GeneratedPluginRegistrant.m` +You can also check the `GeneratedPluginRegistrant.m` file to see the correct plugin names to use. (Note that the plugin names may change over time)

@@ -588,8 +654,8 @@ With the examples bellow, you can check all the features and how to use the Awes To run and debug the Simple Example App, follow the steps bellow: -1. Create a new Flutter project with at least Android and iOS -2. Copy the example content at https://pub.dev/packages/awesome_notifications/example +1. Create a new Flutter project with at least Android or iOS +2. Copy the example code at https://pub.dev/packages/awesome_notifications/example 3. Paste the content inside the `main.dart` file 4. Debug the application with a real device or emulator @@ -628,7 +694,7 @@ The awesome notifications event methods available to track your notifications ar ... and these are the delivery conditions: -| Platform | App in Foreground | App in Background | App Terminated (Killed) | +| Platform | App in Foreground | App in Background | App Terminated (Force Quit) | | ----------- | ----------------- | ----------------- | ----------------------- | | **Android** | Fires all events immediately after occurs | Fires all events immediately after occurs | Store events to be fired when app is on Foreground or Background | | **iOS** | Fires all events immediately after occurs | Store events to be fired when app is on Foreground | Store events to be fired when app is on Foreground | @@ -641,43 +707,46 @@ Exception: **onActionReceivedMethod** fires all events immediately after occurs # 👊 Notification Action Types -The notification action type defines how awesome notifications should handle the user actions. -OBS: For silent types, its necessary to hold the execution with await keyword, to prevent the isolates to shutdown itself before all work is done. +There are several types of notification actions that you can use in Awesome Notifications: + +* **Default:** This is the default action type. It forces the app to go to the foreground when the user taps the notification. +* **SilentAction:** This type of action does not force the app to go to the foreground, but it runs on the main thread and can accept visual elements. It can be interrupted if the main app is terminated. +* **SilentBackgroundAction:** This type of action does not force the app to the foreground and runs in the background. It does not accept any visual element and the execution is done on an exclusive Dart isolate. +* **KeepOnTop:** This type of action fires the respective action without closing the notification status bar and does not bring the app to the foreground. +* **DisabledAction:** When the user taps this type of action, the notification simply closes itself on the tray without firing any action event. +* **DismissAction:** This type of action behaves the same way as a user dismissing action, but it dismisses the respective notification and fires the onDismissActionReceivedMethod. It ignores the autoDismissible property. +* **InputField:** (Deprecated) When the user taps this type of action, it opens a dialog box that allows them to send a text response. Now you can use the requireInputText property instead. - * Default: Is the default action type, forcing the app to goes foreground. - * SilentAction: Do not forces the app to go foreground, but runs on main thread, accept visual elements and can be interrupt if main app gets terminated. - * SilentBackgroundAction: Do not forces the app to go foreground and runs on background, not accepting any visual element. The execution is done on an exclusive dart isolate. - * KeepOnTop: Fires the respective action without close the notification status bar and don't bring the app to foreground. - * DisabledAction: When pressed, the notification just close itself on the tray, without fires any action event. - * DismissAction: Behaves as the same way as a user dismissing action, but dismissing the respective notification and firing the onDismissActionReceivedMethod. Ignores autoDismissible property. - * InputField: (Deprecated) When the button is pressed, it opens a dialog shortcut to send an text response. Use the property `requireInputText` instead. +Remember that for silent types, it is necessary to use the await keyword to prevent the isolates from shutting down before all the work is done. Consider using these different types of actions to customize the behavior of your notifications to suit your needs.

# 🟦 Notification's Category -The notification category is a group of predefined categories that best describe the nature of the notification and may be used by some systems for ranking, delay or filter the notifications. Its highly recommended to correctly categorize your notifications. +The notification category is a group of predefined categories that best describe the nature of the notification and may be used by some systems to rank, delay or filter the notifications. - * Alarm: Alarm or timer. - * Call: incoming call (voice or video) or similar synchronous communication request - * Email: asynchronous bulk message (email). - * Error: error in background operation or authentication status. - * Event: calendar event. - * LocalSharing: temporarily sharing location. - * Message: incoming direct message (SMS, instant message, etc.). - * MissedCall: incoming call (voice or video) or similar synchronous communication request - * Navigation: map turn-by-turn navigation. - * Progress: progress of a long-running background operation. - * Promo: promotion or advertisement. - * Recommendation: a specific, timely recommendation for a single thing. For example, a news app might want to recommend a news story it believes the user will want to read next. - * Reminder: user-scheduled reminder. - * Service: indication of running background service. - * Social: social network or sharing update. - * Status: ongoing information about device or contextual status. - * StopWatch: running stopwatch. - * Transport: media transport control for playback. - * Workout: tracking a user's workout. +***It's highly recommended to always correctly categorize your notifications***. + + * **Alarm:** Alarm or timer. + * **Call:** incoming call (voice or video) or similar synchronous communication request + * **Email:** asynchronous bulk message (email). + * **Error:** error in background operation or authentication status. + * **Event:** calendar event. + * **LocalSharing:** temporarily sharing location. + * **Message:** incoming direct message (SMS, instant message, etc.). + * **MissedCall:** incoming call (voice or video) or similar synchronous communication request + * **Navigation:** map turn-by-turn navigation. + * **Progress:** progress of a long-running background operation. + * **Promo:** promotion or advertisement. + * **Recommendation:** a specific, timely recommendation for a single thing. For example, a news app might want to recommend a news story it believes the user will want to read next. + * **Reminder:** user-scheduled reminder. + * **Service:** indication of running background service. + * **Social:** social network or sharing update. + * **Status:** ongoing information about device or contextual status. + * **StopWatch:** running stopwatch. + * **Transport:** media transport control for playback. + * **Workout:** tracking a user's workout.

@@ -685,15 +754,18 @@ The notification category is a group of predefined categories that best describe # 👮‍♀️ Requesting Permissions -Permissions give transparency to the user of what you pretend to do with your app while its in use. To show any notification on device, you must obtain the user consent and keep in mind that this consent can be revoke at any time, in any platform. On Android, the basic permissions are always conceived to any new installed app, but for iOS, even the basic permission must be requested to the user. +Permissions give transparency to the user about what you intend to do with your app while it's in use. To show any notification on a device, you must obtain the user's consent. Keep in mind that this consent can be revoked at any time, on any platform. On Android 12 and below, the basic permissions are always granted to any newly installed app. But for iOS and Android 13 and beyond, even the basic permission must be requested from the user. + +> **Disclaimer:** +> On Android, revoke certain permissions, including notification permissions, may cause the system to automatically restart the app to ensure the new permission setting is respected. Handle this scenario in your code by saving any necessary state before requesting or changing permissions, and restoring that state when the app is restarted. Inform your users about this behavior to avoid confusion and ensure a smoother user experience. -The permissions can be defined in 3 types: +Permissions can be defined in three types: -- Normal permissions: Are permissions not considered dangerous and do not require the explicit user consent to be enabled. -- Execution permissions: Are permissions considered more sensible to the user and you must obtain his explicit consent to use. -- Special/Dangerous permissions: Are permissions that can harm the user experience or his privacy and you must obtain his explicit consent and, depending of what platform are you running, you must obtain permission from the manufacture itself to use it. +- **Normal permissions:** These permissions are not considered dangerous and do not require explicit user consent to be enabled. +- **Execution permissions:** These permissions are considered more sensitive to the user, and you must obtain their explicit consent to use them. +- **Special/Dangerous permissions:** These permissions can harm the user experience or their privacy, and you must obtain their explicit consent. Depending on the platform, you may need permission from the manufacturer to use them. -As a good pratice, consider always to check if the permissions that you're desiring are conceived before create any new notification, independent of platform. To check if the permissions needs the explicity user consent, call the method shouldShowRationaleToRequest. The list of permissions that needs a rationale to the user can be different between platforms and O.S. versions. And if you app does not require extremely the permission to execute what you need, consider to not request the user permission and respect his will. +As a good practice, always check if the permissions you desire are granted before creating any new notification, regardless of the platform. To check if a permission requires explicit user consent, call the method `shouldShowRationaleToRequest`. The list of permissions that require a rationale to the user can differ between platforms and OS versions. If your app does not require a permission to execute what you need, consider not requesting it, respecting the user's will.
@@ -701,7 +773,7 @@ As a good pratice, consider always to check if the permissions that you're desir - Alert: Alerts are notifications with high priority that pops up on the user screen. Notifications with normal priority only shows the icon on status bar. -- Sound: Sound allows the ability to play sounds for new displayed notifications. The notification sounds are limited to a few seconds and if you pretend to play a sound for more time, you must consider to play a background sound to do it simultaneously with the notification. +- Sound: Sound allows for the ability to play sounds for new displayed notifications. The notification sounds are limited to a few seconds and if you plan to play a sound for more time, you must consider to play a background sound to do it simultaneously with the notification. - Badge: Badge is the ability to display a badge alert over the app icon to alert the user about updates. The badges can be displayed on numbers or small dots, depending of platform or what the user defined in the device settings. Both Android and iOS can show numbers on badge, depending of its version and distribution. @@ -751,7 +823,7 @@ Below is a complete example of how to check if the desired permission is enabled required List permissionList} ) async { - // Check if the basic permission was conceived by the user + // Check if the basic permission was granted by the user if(!await requestBasicPermissionToSendNotifications(context)) return []; @@ -864,33 +936,94 @@ Below is a complete example of how to check if the desired permission is enabled } ``` +
+
+ +# 📡 Notification channels + +
+ +Notification channels are a way to group notifications that share common characteristics, such as the channel name, description, sound, vibration, LED light, and importance level. You can create and delete notification channels at any time in your app. However, at least one notification channel must exist during the initialization of the Awesome Notifications plugin. If you create a notification using an invalid channel key, the notification will be discarded. + +In Android 8 (SDK 26) and later versions, you cannot update notification channels after they are created, except for the name and description attributes. However, for exceptional cases where you need to change your channels, you can set the `forceUpdate` property to true in the `setChannel` method. This option will delete the original channel and recreate it with a different native channel key. But use it only in cases of extreme need because this method deviates from the standard defined by the Android team. Note that this operation has the negative effect of automatically closing all active notifications on that channel. + +For iOS, there is no native notification channel concept. However, Awesome Notifications will handle your notification channels in the same way as Android, so you only need to write your code once and it will work on both platforms. + +You can also organize your notification channels visually in your Android app by using `NotificationChannelGroup` in the `AwesomeNotifications().initialize` method and the `channelGroupKey` property in the respective channels. You can update the channel group name at any time, but a channel can only be defined in a group when it is created. + +The main methods to manipulate notification channels are: + +* `AwesomeNotifications().setChannel`: Creates or updates a notification channel. +* `AwesomeNotifications().removeChannel`: Removes a notification channel, closing all current notifications on that channel. +You can use the following attributes to configure your notification channels: + +
+ +## Notification Channel Attributes + +| Attribute | Required | Description | Type | Updatable | Default Value | +| ----------------------| -------- | -------------------------------------------------------------------------------------------- | ---------------------- | --------- | ------------- | +| `channelKey` | Yes | A string key that identifies a channel where notifications are sent. | String | No | basic_channel | +| `channelName` | Yes | The name of the channel, which is visible to users on Android. | String | Yes | None | +| `channelDescription` | Yes | A brief description of the channel, which is visible to users on Android. | String | Yes | None | +| `channelShowBadge` | No | Whether the notification should automatically increment the app icon badge counter. | Boolean | Yes | `false` | +| `importance` | No | The importance level of the notification. | NotificationImportance | No | `Normal` | +| `playSound` | No | Whether the notification should play a sound. | Boolean | No | `true` | +| `soundSource` | No | The path of a custom sound file to be played with the notification. | String | No | None | +| `defaultRingtoneType` | No | The type of default sound to be played with the notification (only for Android). | DefaultRingtoneType | Yes | `Notification`| +| `enableVibration` | No | Whether the device should vibrate when the notification is received. | Boolean | No | `true` | +| `enableLights` | No | Whether the device should display a blinking LED when the notification is received. | Boolean | No | `true` | +| `ledColor` | No | The color of the LED to display when the notification is received. | Color | No | `Colors.white`| +| `ledOnMs` | No | The duration in milliseconds that the LED should remain on when displaying the notification. | Integer | No | None | +| `ledOffMs` | No | The duration in milliseconds that the LED should remain off when displaying the notification.| Integer | No | None | +| `groupKey` | No | The string key used to group notifications together. | String | No | None | +| `groupSort` | No | The order in which notifications within a group should be sorted. | GroupSort | No | `Desc` | +| `groupAlertBehavior` | No | The alert behavior to use for notifications within a group. | GroupAlertBehavior | No | `All` | +| `defaultPrivacy` | No | The level of privacy to apply to the notification when the device is locked. | NotificationPrivacy | No | `Private` | +| `icon` | No | The name of the notification icon to display in the status bar. | String | No | None | +| `defaultColor` | No | The color to use for the notification on Android. | Color | No | `Color.black` | +| `locked` | No | Whether the notification should be prevented from being dismissed by the user. | Boolean | No | `false` | +| `onlyAlertOnce` | No | Whether the notification should only alert the user once. | Boolean | No | `false` | + + +
+ +## 📝 Notification Channel's Important Notes: + +1 - Notification channels cannot be modified after being created on devices running Android 8 (SDK 26) or later, unless the app is reinstalled or installed for the first time after the changes. + +2 - In exceptional cases where modification is necessary, you can set the `forceUpdate` property to true in the `setChannel` method to delete the original channel and recreate it with a different native channel key. However, this method should only be used when absolutely necessary as it deviates from the standard defined by the Android team.. + +3 - Keep in mind that using `forceUpdate` will also close all active notifications on the channel. + +

# 📅 Scheduling a Notification -Schedules could be created from a UTC or local time zone, and specifying a time interval or setting a calendar filter. Notifications could be scheduled even remotely. -Attention: for iOS, is not possible to define the correct `displayedDate`, because is not possible to run exactly at same time with the notification schedules when it arrives in the user status bar. +Notifications can be scheduled either from a UTC or local time zone, and can be configured with a time interval or by setting a calendar filter. Schedules can also be created remotely using silent push notifications. +Note for iOS users: It is not possible to define the exact `displayedDate` for a notification on iOS, as it is not possible due the impossibility to execute anything at same time as the scheduled time when it arrives in the user's status bar. -To send notifications schedules, you need to instantiate one of the classes bellow in the notification property 'schedule': +To schedule a notification, instantiate one of the classes below in the `schedule` property of the notification: -- NotificationCalendar: Creates a notification scheduled to be displayed when the set date components matches the current date. If a time component is set to null, so any value is considered valid to produce the next valid date. Only one value is allowed by each component. -- NotificationInterval: Creates a notification scheduled to be displayed at each interval time, starting from the next valid interval. -- NotificationAndroidCrontab: Creates a notification scheduled to be displayed based on a list of precise dates or a crontab rule, with seconds precision. To know more about how to create a valid crontab rule, take a look at [this article](https://www.baeldung.com/cron-expressions). +- `NotificationCalendar`: Creates a notification that is scheduled to be displayed when the set date components match the current date. If a time component is set to `null`, then any value is considered valid to produce the next valid date. Only one value is allowed for each component. +- `NotificationInterval`: Creates a notification that is scheduled to be displayed at each interval time, starting from the next valid interval. +- `NotificationAndroidCrontab`: Creates a notification that is scheduled to be displayed based on a list of precise dates or a crontab rule, with seconds precision. To learn more about how to create a valid crontab rule, check out [this article](https://www.baeldung.com/cron-expressions). -Also, all of then could be configured using: +All of these classes can be configured with the following properties: -- timeZone: describe which time zone that schedule is based (valid examples: America/Sao_Paulo, America/Los_Angeles, GMT+01:00, Europe/London, UTC) -- allowWhileIdle: Determines if notification will send, even when the device is in critical situation, such as low battery. -- repeats: Determines if the schedule should be repeat after be displayed. If there is no more valid date compatible with the schedule rules, the notification is automatically canceled. +- `timeZone`: describes the time zone on which the schedule is based (valid examples include "America/Sao_Paulo", "America/Los_Angeles", "GMT+01:00", "Europe/London", "UTC"). +- `allowWhileIdle`: determines whether the notification will be sent even when the device is in a critical situation, such as low battery. +- `repeats`: determines whether the schedule should repeat after the notification is displayed. If there are no more valid dates compatible with the schedule rules, the notification is automatically canceled. -For time zones, please note that: +Please note the following about time zones: -- Dates with UTC time zones are triggered at the same time in all parts of the planet and are not affected by daylight rules. -- Dates with local time zones, defined such "GMT-07: 00", are not affected by daylight rules. -- Dates with local time zones, defined such "Europe / Lisbon", are affected by daylight rules, especially when scheduled based on a calendar filter. +* Dates with UTC time zones are triggered at the same time in all parts of the planet and are not affected by daylight rules. +* Dates with local time zones, defined such as "GMT-07:00", are not affected by daylight rules. +* Dates with local time zones, defined such as "Europe/Lisbon", are affected by daylight rules, especially when scheduled based on a calendar filter. -Here are some practical examples of how to create a notification scheduled: +Here are some practical examples of how to create a scheduled notification: ```Dart String localTimeZone = await AwesomeNotifications().getLocalTimeZoneIdentifier(); @@ -900,9 +1033,9 @@ Here are some practical examples of how to create a notification scheduled: content: NotificationContent( id: id, channelKey: 'scheduled', - title: 'Notification at every single minute', + title: 'Notification every single minute', body: - 'This notification was schedule to repeat at every single minute.', + 'This notification was scheduled to repeat every minute.', notificationLayout: NotificationLayout.BigPicture, bigPicture: 'asset://assets/images/melted-clock.png'), schedule: NotificationInterval(interval: 60, timeZone: localTimeZone, repeats: true)); @@ -913,8 +1046,8 @@ await AwesomeNotifications().createNotification( content: NotificationContent( id: id, channelKey: 'scheduled', - title: 'wait 5 seconds to show', - body: 'now is 5 seconds later', + title: 'Wait 5 seconds to show', + body: 'Now it is 5 seconds later.', wakeUpScreen: true, category: NotificationCategory.Alarm, ), @@ -944,7 +1077,7 @@ await AwesomeNotifications().createNotification( id: id, channelKey: 'scheduled', title: 'Just in time!', - body: 'This notification was schedule to shows at ' + + body: 'This notification was scheduled to shows at ' + (Utils.DateUtils.parseDateToString(scheduleTime.toLocal()) ?? '?') + ' $timeZoneIdentifier (' + (Utils.DateUtils.parseDateToString(scheduleTime.toUtc()) ?? '?') + @@ -964,16 +1097,16 @@ await AwesomeNotifications().createNotification( ## ⏰ Schedule Precision -It's important to keep in mind that some Android distributions could ignore or delay the schedule execution, if their algorithms judge it necessary to save the battery life, etc, and this intervention is even more common for repeating schedules. Im most cases this behavior is recommended, since as a battery-hungry app can denigrate the app and the manufacturer's image. Therefore, you need to consider this fact in your business logic. +It's important to keep in mind that schedules can be ignored or delayed, especially for repeating schedules, due to system algorithms designed to save battery life and prevent abuse of resources. While this behavior is recommended to protect the app and the manufacturer's image, it's important to consider this fact in your business logic. -But, for some cases where the schedules precision is a MUST requirement, you can use some features to ensure the execution in the correct time: +However, for cases where precise schedule execution is a MUST requirement, there are some features you can use to ensure the execution at the correct time: -- Set the notification's category to a critical category, such as Alarm, Reminder or Call. -- Set the `preciseAlarm` property to true. For Android versions greater or equal than 12, you need to explicitly request the user consent to enable this feature. You can request the permission with `requestPermissionToSendNotifications` or take the user to the permission page calling `showAlarmPage`. -- Set `criticalAlerts` channel property and notification content property to true. This feature allows you to show notification and play sounds even when the device is on silent / Do not Disturb mode. Because of it, this feature is considered highly sensitive and you must request Apple a special authorization to use it. On Android, for versions greater or equal than 11, you need to explicitly request the user consent to enable this feature. You can request the permission with `requestPermissionToSendNotifications`. +- Set the notification's category to a critical category, such as Alarm, Reminder, or Call. +- Set the `preciseAlarm` property to true. This feature allows the system to schedule notifications to be sent at an exact time, even if the device is in low-power mode. For Android versions greater than or equal to 12, you need to explicitly request user consent to enable this feature. You can request the permission with `requestPermissionToSendNotifications` or take the user to the permission page calling `showAlarmPage`. +- Set criticalAlerts channel property and notification content property to true. This feature allows you to show notifications and play sounds even when the device is on silent or Do Not Disturb mode. Due to its sensitivity, this feature requires special authorization from Apple on iOS and explicit user consent on Android versions greater than or equal to 11. On iOS, you must submit a request authorization to Apple to enable it, as described in [this post](https://medium.com/@shashidharyamsani/implementing-ios-critical-alerts-7d82b4bb5026). -To enable precise alarms, you need to add the `SCHEDULE_EXACT_ALARM` permission into your `AndroidManifest.xml` file, inside the `Android/app/src/main/` folder +To enable precise alarms, you need to add the `SCHEDULE_EXACT_ALARM` permission to your app's `AndroidManifest.xml` file, which is located in the ***Android/app/src/main/*** folder: ```xml ``` -To enable critical alerts, you need to add the `ACCESS_NOTIFICATION_POLICY` permission into your `AndroidManifest.xml` file, inside the `Android/app/src/main/` folder +To enable critical alerts, you need to add the `ACCESS_NOTIFICATION_POLICY` permission to your app's `AndroidManifest.xml` file: ```xml ``` -For iOS, you must submit a request authorization to Apple to enable it, as described [in this post](https://medium.com/@shashidharyamsani/implementing-ios-critical-alerts-7d82b4bb5026). +In summary, if you need to ensure precise execution of scheduled notifications, make sure to use the appropriate categories and properties for your notifications, and enable the necessary permissions in your app's manifest file. + +Additionally, you can ask your users to whitelist your app from any battery optimization feature that the device may have. This can be done by adding your app to the "unmonitored apps" or "battery optimization exceptions" list, depending on the device. + +You can also try to use the [flutter_background_fetch](https://pub.dev/packages/flutter_background_fetch) package to help schedule background tasks. This package allows you to schedule tasks that will run even when the app is not open, and it has some built-in features to help handle battery optimization. + +To know more about it, please visit [flutter_background_fetch documentation](https://pub.dev/packages/flutter_background_fetch) and [Optimizing for Doze and App Standby](https://developer.android.com/training/monitoring-device-state/doze-standby) for Android devices.
-## 📝 Important Notes: +## 📝 Schedule Notification's Important Notes: -1. Schedules may be severaly delayed or denied if the device/application is in battery saver mode or locked to perform background tasks. Teach your users with a good rationale to not set these modes and tell them the consequences of doing so. Some battery saving modes may differ between manufacturers, for example Samsung and Xiaomi (the last one sets the battery saving mode automatically for each new app installed). -2. If you're running your app in debug mode, right after close it all schedules may be erased by Android OS. Thats happen to ensure the same execution in debug mode for each debug startup. To make schedule tests on Android while terminated, remember to open your app without debug it. -3. If your app doesn't need to be as accurate to display schedule notifications, don't request for exact notifications. Be reasonable. -4. Remember to categorize your notifications correctly to avoid scheduling delays. -5. Critical alerts are still under development and should not be used in production mode. +1. Schedules may be delayed or denied if the device/application is in battery saver mode or locked to perform background tasks. Educate your users on why it's important to avoid these modes and the potential consequences of using them. Also, some battery saving modes may differ between manufacturers, such as Samsung and Xiaomi, which automatically enable battery saving mode for newly installed apps. +2. On iOS, you can only schedule up to 64 notifications per app. On Android, you can schedule up to 500 notifications per app. +3. If you're running your app in debug mode, all schedules may be erased by the Android OS when you close the app. This ensures consistent behavior when testing in debug mode. To test schedule notifications on Android when the app is not running, make sure to open the app without debugging. +4. If your app doesn't require precise scheduling of notifications, avoid requesting exact notifications to conserve battery life. +5. Categorize your notifications correctly to avoid scheduling delays. +6. Note that critical alerts are still under development and should not be used in production mode.
-## Old schedule Cron rules (For versions older than 0.0.6) +## Deprecated Schedule Class for Cron Rules (Versions Prior to 0.0.6) -Due to the way that background task and notification schedules works on iOS, wasn't possible yet to enable officially all the old Cron features on iOS while the app is in Background and even when the app is terminated (Killed). -Thanks to this, the complex schedules based on cron tab rules are only available on Android by the class `NotificationAndroidCrontab`. +Before version 0.0.6, Awesome Notifications included the Schedule class, which allowed users to schedule notifications based on cron tab rules. However, due to limitations with how background tasks and notification schedules work on iOS, it was not possible to fully support cron-based schedules on iOS devices while the app is in the background or terminated. -A support ticket was opened for Apple in order to resolve this issue, but they don't even care about. You can follow the progress of the process [here](https://github.com/rafaelsetragni/awesome_notifications/issues/16). +As a result, the `NotificationAndroidCrontab` class was introduced as an alternative for Android users to create complex schedules based on cron tab rules. Unfortunately, Apple has not yet resolved the limitations with cron-based schedules on iOS, and there are no plans to support the deprecated Schedule class in future versions of Awesome Notifications. + +A support ticket was opened for Apple in order to resolve this issue, but they don't even care about. For more information and updates on this issue, you can follow the progress of the support ticket [here](https://github.com/rafaelsetragni/awesome_notifications/issues/16).
+ +# 🌎 Translation of Notification Content + +The new NotificationLocalization class allows you to create a set of localized strings for a notification, including the title, body, summary, large icon, big picture, and button labels. This feature makes it easy to provide localized content for your users, which is essential for global applications. + +To set the desired localization for notifications, use the setLocalization method. This method takes a required languageCode parameter, which is an optional, case-insensitive string that represents the language code for the desired localization. For example, you can set the language code to "en" for English, "pt-br" for Brazilian Portuguese, "es" for Spanish, and so on. If the localization was never set or redefined, the default localization will be loaded from the device system. + +```Dart +await AwesomeNotifications().setLocalization(languageCode: 'pt-br'); +``` + +To get the current localization code used by the plugin for notification content, use the getLocalization method. This method returns a string representing the current localization code, which is a two-letter language code or a language code combined with a region code. If no localization has been set, this method will return the system's default language code. + +```Dart +String currentLanguageCode = await AwesomeNotifications().getLocalization(); +``` + +Here's an example of how to use the localizations parameter to translate notification content into several languages: + +```Dart +await AwesomeNotifications().createNotification( + content: NotificationContent( + id: id, + channelKey: 'basic_channel', + title: 'This title is written in english', + body: 'Now it is really easy to translate a notification content, ' + 'including images and buttons!', + summary: 'Awesome Notifications Translations', + notificationLayout: NotificationLayout.BigPicture, + bigPicture: 'asset://assets/images/awn-rocks-en.jpg', + largeIcon: 'asset://assets/images/american.jpg', + payload: {'uuid': 'user-profile-uuid'}), + actionButtons: [ + NotificationActionButton( + key: 'AGREED1', label: 'I agree', autoDismissible: true), + NotificationActionButton( + key: 'AGREED2', label: 'I agree too', autoDismissible: true), + ], + localizations: { + 'pt-br' : NotificationLocalization( + title: 'Este título está escrito em português do Brasil!', + body: 'Agora é muito fácil traduzir o conteúdo das notificações, ' + 'incluindo imagens e botões!', + summary: 'Traduções Awesome Notifications', + bigPicture: 'asset://assets/images/awn-rocks-pt-br.jpg', + largeIcon: 'asset://assets/images/brazilian.jpg', + buttonLabels: { + 'AGREED1': 'Eu concordo!', + 'AGREED2': 'Eu concordo também!' + } + ), + 'zh': NotificationLocalization( + title: '这个标题是用中文写的', + body: '现在,轻松翻译通知内容,包括图像和按钮!', + summary: '', + bigPicture: 'asset://assets/images/awn-rocks-zh.jpg', + largeIcon: 'asset://assets/images/chinese.jpg', + buttonLabels: { + 'AGREED1': '我同意', + 'AGREED2': '我也同意' + } + ), + 'ko': NotificationLocalization( + title: '이 타이틀은 한국어로 작성되었습니다', + body: '이제 이미지 및 버튼을 포함한 알림 콘텐츠를 쉽게 번역할 수 있습니다!', + summary: '', + bigPicture: 'asset://assets/images/awn-rocks-ko.jpg', + largeIcon: 'asset://assets/images/korean.jpg', + buttonLabels: { + 'AGREED1': '동의합니다', + 'AGREED2': '저도 동의합니다' + } + ), + } +); +``` + +
+
+ +# ⏱ Chronometer and Timeout (Expiration) + +With Awesome Notifications, you can now set a chronometer and a timeout (expiration time) for your notifications. + +The `chronometer` field is a `Duration` type that sets the `showWhen` attribute of Android notifications to the amount of seconds to start. The `timeoutAfter` field, also a `Duration` type, determines an expiration time limit for the notification to stay in the system tray. After this period, the notification will automatically dismiss itself. + +Both fields are optional and when used with JSON data, should be positive integers representing the amount of seconds. + +Here is how you can set the `chronometer` and `timeoutAfter` in your notifications: + +```dart + await AwesomeNotifications().createNotification( + content: NotificationContent( + id: id, + channelKey: 'basic_channel', + title: 'Notification with Chronometer and Timeout', + body: 'This notification will start with a chronometer and dismiss after 20 seconds', + chronometer: Duration.zero, // Chronometer starts to count at 0 seconds + timeoutAfter: Duration(seconds: 20) // Notification dismisses after 20 seconds + ) + ); +``` + +
+
+ # ⌛️ Progress Bar Notifications (Only for Android) -To show progress bar using local notifications, you need to create a notification with Layout `ProgressBar` and set a progress ammount between 0 and 100, or set it's progress as indeterminated. +On Android, you can display a progress bar notification to show the progress of an ongoing task. To create a progress bar notification, you need to set the notification layout to ProgressBar and specify the progress value (between 0 and 100) or set it to indeterminate. -To update your notificaiton progress you need to create a new notification with same id and you MUST not exceed 1 second between each update, otherwise your notifications will be randomly blocked by O.S. +To update the progress of your notification, you can create a new notification with the same ID. However, you should not update the notification more frequently than once per second, as doing so may cause the notifications to be blocked by the operating system. -Below is an example of how to update your progress notification: +Here is an example of how to create a progress bar notification and update its progress: ```Dart int currentStep = 0; @@ -1091,15 +1338,17 @@ void _updateCurrentProgressBar({ } } ``` + +Note that in this example, the showProgressNotification function creates a loop to simulate progress by delaying a fixed amount of time between each simulated step. The _updateCurrentProgressBar function is called at a frequency of one call per second and updates the progress value of the notification. The locked parameter is set to true to prevent the user from dismissing the notification while the progress bar is active. +

# 😃 Emojis (Emoticons) -To send emojis in your local notifications, concatenate the class `Emoji` with your text. -For push notifications, copy the emoji (unicode text) from http://www.unicode.org/emoji/charts/full-emoji-list.html and send it or use the format \u\{1f6f8}. +You can use Emojis in your local notifications by concatenating the Emoji class with your text. For push notifications, you can use the Unicode text of the Emoji, which can be found on [http://www.unicode.org/emoji/charts/full-emoji-list.html](https://www.unicode.org/emoji/charts/full-emoji-list.html), and use the format \u{1f6f8}. -OBS: not all emojis work with all platforms. Please, test the specific emoji before using it in production. +Please note that not all Emojis work on all platforms. You should test the specific Emoji you want to use before using it in production. ```dart await AwesomeNotifications().createNotification( @@ -1112,13 +1361,76 @@ OBS: not all emojis work with all platforms. Please, test the specific emoji bef notificationLayout: NotificationLayout.BigPicture, )); ``` + +You can find more than 3000 Emojis available in the Emoji class, which includes most of the popular Emojis. + +
+
+ +# 🎨 Notification Layout Types + +The appearance of a notification can be customized using different layouts. Each layout type can be specified by including a respective source prefix before the path. The available layout types are: + +* `Default`: The default notification layout. This layout will be used if no other layout is specified or if there is an error while loading the specified layout. +* `BigPicture`: This layout displays a large picture along with a small image attached to the notification. +* `BigText`: This layout can display more than two lines of text. +* `Inbox`: This layout can be used to list messages or items separated by lines. +* `ProgressBar`: This layout displays a progress bar, such as a download progress bar. +* `Messaging`: This layout displays each notification as a chat conversation with one person. +* `Messaging Group`: This layout displays each notification as a chat conversation with more than one person (groups). +* `MediaPlayer`: This layout displays a media controller with action buttons, allowing the user to send commands without bringing the application to the foreground. + +
+
+ +# 📷 Media Source Types + +To display images in notifications, you need to include the respective source prefix before the path. + +Images can be defined using the following prefix types: + +* `Asset`: images accessed through the Flutter asset method. Example: asset://path/to/image-asset.png +* `Network`: images accessed through an internet connection. Example: http(s)://url.com/to/image-asset.png +* `File`: images accessed through files stored on the device. Example: file://path/to/image-asset.png +* `Resource`: images accessed through drawable native resources. On Android, these files are stored inside [project]/android/app/src/main/drawable folder. Example: resource://drawable/res_image-asset.png + +Note that icons and sounds can only be resource media types. + +Unfortunately, to protect your native resources on Android against minification, please include the prefix `res_` in your resource file names. The use of the tag `shrinkResources` to false inside build.gradle or the command flutter build apk `--no-shrink` ***is not recommended***. + +For more information, please visit [Shrink, obfuscate, and optimize your app](https://developer.android.com/studio/build/shrink-code) + +
+
+ + +# ⬆️ Notification Importance + +Defines the notification's importance level as a hierarchy, with Max being the most important and None being the least important. Depending on the importance level, the notification may have different behaviors, such as making a sound, appearing as a heads-up notification, or not showing at all. + +The possible importance levels are as follows: + +* `Max`: Makes a sound and appears as a heads-up notification. +* `Higher`: Shows everywhere, makes noise and peeks. May use full-screen intents. +* `Default`: Shows everywhere, makes noise, but does not visually intrude. +* `Low`: Shows in the shade, and potentially in the status bar (see shouldHideSilentStatusBarIcons()), but is not audibly intrusive. +* `Min`: Only shows in the shade, below the fold. +* `None`: Disables the respective channel. + +Note that higher importance levels should only be used when necessary, such as for critical or time-sensitive notifications. Abusing higher importance levels can be intrusive to the user and negatively impact their experience. + +OBS: Unfortunately, the channel's importance can only be defined on the first time. After that, it cannot be changed. +

# 🔆 Wake Up Screen Notifications -To send notifications that wake up the device screen even when it is locked, you can set the `wakeUpScreen` property to true. -To enable this property on Android, you need to add the `WAKE_LOCK` permission and the property `android:turnScreenOn` into your `AndroidManifest.xml` file, inside the `Android/app/src/main/` folder. +To send notifications that wake up the device screen even when it is locked, you can set the wakeUpScreen property to true when creating a notification. + +However, on Android devices, you will need to add the `WAKE_LOCK` permission to your app's `AndroidManifest.xml` file in order to use this feature. Additionally, you will need to include the `android:turnScreenOn` property in the activity tag of your app's `AndroidManifest.xml` file. + +Here's an example of how to add these properties to your `AndroidManifest.xml` file: ```xml + android:windowSoftInputMode="adjustResize" + android:turnScreenOn="true"> ...
... @@ -1145,16 +1457,18 @@ To enable this property on Android, you need to add the `WAKE_LOCK` permission a ``` +Note that the `android:turnScreenOn` property will only work if the device's screen is off. If the device's screen is already on, the property will have no effect. +

# 🖥 Full Screen Notifications (only for Android) -To send notifications in full screen mode, even when it is locked, you can set the `fullScreenIntent` property to true. +Full screen notifications can be sent on Android by setting the `fullScreenIntent` property to `true`. These notifications are displayed in full screen mode, even when the device is locked. -Sometimes when your notification is displayed, your app is automatically triggered by the Android system, similar to when the user taps on it. That way, you can display your page in full screen and customize it as you like. There is no way to control when your full screen will be called. +When the notification is displayed, the Android system may automatically trigger your app, similar to when the user taps on it. This allows you to display your page in full screen and customize it as desired. However, you cannot control when your full screen notification will be called. -To enable this property, you need to add the property `android:showOnLockScreen="true"` and the `USE_FULL_SCREEN_INTENT` permission to your `AndroidManifest.xml` file, inside the `Android/app/src/main/` folder +To enable the `fullScreenIntent` property, you must add the `android:showOnLockScreen="true"` property and the `USE_FULL_SCREEN_INTENT` permission to your `AndroidManifest.xml` file, inside the `Android/app/src/main/` folder. ```xml ``` -On Android, for versions greater or equal than 11, you need to explicitly request the user consent to enable this feature. You can request the permission with `requestPermissionToSendNotifications`. - -
-
- -# 📡 Notification channels - -
- -Notification channels are means by which notifications are send, defining the characteristics that will be common among all notifications on that same channel. - -A notification channel can be created and deleted at any time in the application, however it is required that at least one exists during the initialization of this plugin. If a notification is created using a invalid channel key, the notification is discarded. - -For Android greater than 8 (SDK 26) its not possible to update notification channels after being created, except for name and description attributes. - -On iOS there is no native channels, but awesome will handle your notification channels by the same way than Android. This way your app doesn't need to do workarounds to get the closest possible results in both platforms. You gonna write once and run anywhere. - -Also, its possible to organize visualy the channel's in you Android's app channel page using `NotificationChannelGroup` in the `AwesomeNotifications().initialize` method and the property `channelGroupKey` in the respective channels. The channel group name can be updated at any time, but an channel only can be defined into a group when is created. - -The main methods to manipulate notification channels are: - -* AwesomeNotifications().setChannel: Create or update a notification channel. -* AwesomeNotifications().removeChannel: Remove a notification channel, closing all current notifications on that same channel. - -
- -| Attribute | Required | Description | Type | Updatable without force mode | Value Limits | Default value | -| --------------------- | -------- | ------------------------------------------------------------------------ | ---------------------- | ---------------------------- | ------------------------ | ------------------------- | -| channelKey | YES | String key that identifies a channel where not | String | NOT AT ALL | channel must be enabled | basic_channel | -| channelName | YES | The title of the channel (is visible for the user on Android) | String | YES | unlimited | | -| channelDescription | YES | The channel description (is visible for the user on Android) | String | YES | unlimited | | -| channelShowBadge | NO | The notification should automatically increment the badge counter | bool | YES | true or false | false | -| importance | NO | The notification should automatically increment the badge counter | NotificationImportance | NO | Enumerator | Normal | -| playSound | NO | Determines if the notification should play sound | bool | NO | true or false | true | -| soundSource | NO | Specify a custom sound to be played (must be a native resource file) | String | NO | unlimited | | -| defaultRingtoneType | NO | Determines what default sound type should be played (only for Android) | DefaultRingtoneType | YES | Enumerator | Notification | -| enableVibration | NO | Activate / deactivate the vibration functionality | bool | NO | true or false | true | -| enableLights | NO | Determines that the LED lights should be on in notifications | bool | NO | true or false | true | -| ledColor | NO | Determines the LED lights color to be played on notifications | Color | NO | unlimited | Colors.white | -| ledOnMs | NO | Determines the time, in milliseconds, that the LED lights must be on | int | NO | 1 - 2.147.483.647 | | -| ledOffMs | NO | Determines the time, in milliseconds, that the LED lights must be off | int | NO | 1 - 2.147.483.647 | | -| groupKey | NO | Determines the common key used to group notifications in a compact form | String | NO | unlimited | | -| groupSort | NO | Determines the notifications sort order inside the grouping | GroupSort | NO | Enumerator | Desc | -| groupAlertBehavior | NO | Determines the alert type for notifications in same grouping | GroupAlertBehavior | NO | Enumerator | All | -| defaultPrivacy | NO | Determines the privacy level to be applied when the device is locked | NotificationPrivacy | NO | Enumerator | Private | -| icon | NO | Determines the notification small top icon on status bar | String | NO | unlimited | | -| defaultColor | NO | Determines the notification global color (only for Android) | Color | NO | unlimited | Color.black | -| locked | NO | Determines if the user cannot manually dismiss the notification | bool | NO | true or false | false | -| onlyAlertOnce | NO | Determines if the notification should alert once, when created | bool | NO | true or false | false | - - -
- -## 📝 Important Notes: - -Notification channels after Android 8 (SDK 26) cannot be changed after creation, unless the app is reinstalled or installed for the first time after the changes. - -For exceptional cases, where you definitely need to change your channels, you can set the `forceUpdate` property to true in the `setChannel` method. This option will delete the original channel and recreate it with a different native channel key. But only use it in cases of extreme need, as this method deviates from the standard defined by the Android team. - -Also, this operation has the negative effect of automatically closing all active notifications on that channel. - +For Android versions 11 and above, you must request the user's consent to enable this feature using `requestPermissionToSendNotifications`.

@@ -1243,31 +1497,71 @@ Also, this operation has the negative effect of automatically closing all active ## NotificationContent ("content" in Push data) - (required)
-| Attribute | Required | Description | Type | Value Limits | Default value | -| --------------------- | -------- | ------------------------------------------------------------------------ | --------------------- | ------------------------ | ------------------------- | -| id | YES | Number that identifies a unique notification | int | 1 - 2.147.483.647 | | -| channelKey | YES | String key that identifies a channel where not. will be displayed | String | channel must be enabled | basic_channel | -| title | NO | The title of notification | String | unlimited | | -| body | NO | The body content of notification | String | unlimited | | -| summary | NO | A summary to be displayed when the content is protected by privacy | String | unlimited | | -| category | NO | The notification category that best describes the nature of the notification for O.S. | Enumerator | NotificationCategory | | -| badge | NO | Set a badge value over app icon | int | 0 - 999.999 | | -| showWhen | NO | Hide/show the time elapsed since notification was displayed | bool | true or false | true | -| displayOnForeground | NO | Hide/show the notification if the app is in the Foreground (streams are preserved ) | bool | true or false | true | -| displayOnBackground | NO | Hide/show the notification if the app is in the Background (streams are preserved ). OBS: Only available for iOS with background special permissions | bool | true or false | true | -| icon | NO | Small icon to be displayed on the top of notification (Android only) | String | must be a resource image | | -| largeIcon | NO | Large icon displayed at right middle of compact notification | String | unlimited | | -| bigPicture | NO | Big image displayed on expanded notification | String | unlimited | | -| autoDismissible | NO | Notification should auto dismiss when gets tapped by the user (has no effect for reply actions on Android) | bool | true or false | true | -| color | NO | Notification text color | Color | 0x000000 to 0xFFFFFF | 0x000000 (Colors.black) | -| backgroundColor | NO | Notification background color | Color | 0x000000 to 0xFFFFFF | 0xFFFFFF (Colors.white) | -| payload | NO | Hidden payload content | Map | Only String for values | null | -| notificationLayout | NO | Layout type of notification | Enumerator | NotificationLayout | Default | -| hideLargeIconOnExpand | NO | Hide/show the large icon when notification gets expanded | bool | true or false | false | -| locked | NO | Blocks the user to dismiss the notification | bool | true or false | false | -| progress | NO | Current value of progress bar (percentage). Null for indeterminate. | int | 0 - 100 | null | -| ticker | NO | Text to be displayed on top of the screen when a notification arrives | String | unlimited | | -| actionType (Only for Android) | NO | Notification action response type | Enumerator | ActionType | Default | +```Dart +NotificationContent ( + id: int, + channelKey: String, + title: String?, + body: String?, + summary: String?, + category: NotificationCategory?, + badge: int?, + showWhen: bool?, + displayOnForeground: bool?, + displayOnBackground: bool?, + icon: String?, + largeIcon: String?, + bigPicture: String?, + autoDismissible: bool?, + chronometer: Duration?, + timeoutAfter: Duration?, + color: Color?, + backgroundColor: Color?, + payload: Map?, + notificationLayout: NotificationLayout?, + hideLargeIconOnExpand: bool?, + locked: bool?, + progress: int?, + ticker: String?, + actionType: ActionType? +) +``` + +| Attribute | Required | Description | Type | Value Limits | Default value | +| --------------------- | -------- | -------------------------------------------------------------------------------------------------------- | --------------------- | ------------------------- | ------------- | +| id | YES | A unique identifier for the notification | int | 1 - 2,147,483,647 | - | +| channelKey | YES | The identifier of the notification channel where the notification will be displayed | String | Channel must be enabled | basic_channel | +| title | NO | The title of the notification | String | Unlimited | - | +| body | NO | The body text of the notification | String | Unlimited | - | +| summary | NO | A summary to be displayed when the notification content is protected by privacy | String | Unlimited | - | +| category | NO | The notification category that best describes the nature of the notification (Android only) | NotificationCategory | - | - | +| badge | NO | The value to display as the app's badge | int | 0 - 999,999 | - | +| chronometer | NO | A duration to set the showWhen attribute of Android notifications to the amount of seconds to start | Duration | Positive integers | - | +| timeoutAfter | NO | A duration to determine an expiration time limit for the notification to stay in the system tray | Duration | Positive integers | - | +| showWhen | NO | Whether to show the time elapsed since the notification was posted | bool | True or false | true | +| chronometer | NO | Display how many seconds has | bool | True or false | true | +| displayOnForeground | NO | Whether to display the notification while the app is in the foreground (preserves streams) | bool | True or false | true | +| displayOnBackground | NO | Whether to display the notification while the app is in the background (preserves streams, Android only) | bool | True or false | true | +| icon | NO | The name of the small icon to display with the notification (Android only) | String | A resource image | - | +| largeIcon | NO | The name of the large icon to display with the notification | String | Unlimited | - | +| bigPicture | NO | The name of the image to display when the notification is expanded (Android only) | String | Unlimited | - | +| autoDismissible | NO | Whether to automatically dismiss the notification when the user taps it (Android only) | bool | True or false | true | +| color | NO | The text color for the notification | Color | 0x000000 to 0xFFFFFF | Colors.black | +| backgroundColor | NO | The background color for the notification | Color | 0x000000 to 0xFFFFFF | Colors.white | +| payload | NO | A hidden payload for the notification | Map | Only string values | - | +| notificationLayout | NO | The layout type for the notification | NotificationLayout | - | Default | +| hideLargeIconOnExpand | NO | Whether to hide the large icon when the notification is expanded (Android only) | bool | True or false | false | +| locked | NO | Whether to prevent the user from dismissing the notification (Android only) | bool | True or false | false | +| progress | NO | The current value for the notification's progress bar (Android only) | int | 0 - 100 | - | +| ticker | NO | The text to display in the ticker when the notification arrives | String | Unlimited | - | +| actionType (Only for Android) | NO | Specifies the type of action that should be taken when the user taps on the body of the notification. | Enumerator | NotificationActionType | NotificationActionType.Default | + +
+ +## 📝 Notification Content's Important Notes: + +1. Custom vibrations are only available for Android devices. +2. ProgressBar and Inbox layouts are only available for Android devices.

@@ -1275,19 +1569,19 @@ Also, this operation has the negative effect of automatically closing all active ## NotificationActionButton ("actionButtons" in Push data) - (optional)
-* Is necessary at least one *required attribute +* At least one *required attribute is necessary -| Attribute | Required | Description | Type | Value Limits | Default value | -| ------------- | -------- | ----------------------------------------------------------------------------- | --------------------- | ----------------------- | ----------------------- | -| key | YES | Text key to identifies what action the user took when tapped the notification | String | unlimited | | -| label | *YES | Text to be displayed over the action button | String | unlimited | | -| icon | *YES | Icon to be displayed inside the button | String | must be a resource image | | -| color | NO | Label text color (only for Android) | Color | 0x000000 to 0xFFFFFF | | -| enabled | NO | On Android, deactivates the button. On iOS, the button disappear | bool | true or false | true | -| autoDismissible | NO | Notification should auto cancel when gets tapped by the user | bool | true or false | true | -| showInCompactView | NO | For MediaPlayer notifications on Android, sets the button as visible in compact view | bool | true or false | true | -| isDangerousOption | NO | Mark the button as a dangerous option, displaying the text in red | bool | true or false | false | -| actionType | NO | Notification action response type | Enumerator | ActionType | Default | +| Attribute | Required | Description | Type | Value Limits | Default value | +| ------------------ | -------- | --------------------------------------------------------------------------------------- | --------------------- |------------------------------| ----------------------- | +| key | YES | A text key that identifies what action the user took when they tapped the notification | String | unlimited | | +| label | *YES | The text to be displayed on the action button | String | unlimited | | +| icon | *YES | The icon to be displayed inside the button (only available for few layouts) | String | Must be a resource image | | +| color | NO | The label text color (only for Android) | Color | 0x000000 to 0xFFFFFF | | +| enabled | NO | On Android, deactivates the button. On iOS, the button disappears | bool | true or false | true | +| autoDismissible | NO | Whether the notification should be auto-cancelled when the user taps the button | bool | true or false | true | +| showInCompactView | NO | For MediaPlayer notifications on Android, sets the button as visible in compact view | bool | true or false | true | +| isDangerousOption | NO | Whether the button is marked as a dangerous option, displaying the text in red | bool | true or false | false | +| actionType | NO | The notification action response type | Enumerator | ActionType (Default) | |

@@ -1299,12 +1593,15 @@ Also, this operation has the negative effect of automatically closing all active ### NotificationInterval ("schedule" in Push data) - (optional)
-| Attribute | Required | Description | Type | Value Limits / Format | Default value | -| --------------- | -------- | -------------------------------------------------------------------------- | --------------- | --------------------- | --------------- | -| interval | YES | Time interval between each notification (minimum of 60 sec case repeating) | Int (seconds) | Positive unlimited | | -| allowWhileIdle | NO | Displays the notification, even when the device is low battery | bool | true or false | false | -| repeats | NO | Defines if the notification should play only once or keeps repeating | bool | true or false | false | -| timeZone | NO | Time zone identifier (ISO 8601) | String | "America/Sao_Paulo", "GMT-08:00" or "UTC" | "UTC" | +| Attribute | Required | Description | Type | Value Limits / Format | Default value | +|----------------| -------- |----------------------------------------------------------------------------------------------------------------------------- | ------------- | --------------------------- | --------------- | +| interval | YES | The time interval between each notification (minimum of 60 seconds for repeating notifications) | Int (seconds) | Positive integers | | +| allowWhileIdle | NO | Displays the notification even when the device is in a low-power idle mode | bool | true or false | false | +| repeats | NO | Determines whether the notification should be played once or repeatedly | bool | true or false | false | +| preciseAlarm | NO | Requires the notification to be displayed at the precise scheduled time, even when the device is in a low-power idle mode. Requires explicit permission on Android 12 and beyond. | bool | true or false | false | +| delayTolerance | NO | Sets the acceptable delay tolerance for inexact notifications | int (seconds) | 600000 or greater | 600000 | +| timeZone | NO | Specifies the time zone identifier (ISO 8601) for the notification | String | "America/Sao_Paulo", "GMT-08:00", or "UTC" | "UTC" | +| preciseAlarm | NO | Requires the notification to be displayed at the precise scheduled time, even when the device is in a low-power idle mode. This attribute requires explicit permission on Android 12 and beyond. | bool | true or false | false |
@@ -1314,105 +1611,61 @@ Also, this operation has the negative effect of automatically closing all active * Is necessary at least one *required attribute * If the calendar time condition is not defined, then any value is considered valid in the filtering process for the respective time component -| Attribute | Required | Description | Type | Value Limits / Format | Default value | -| ------------------ | -------- | -------------------------------------------------------------------- | ---------- | --------------------- | --------------- | -| era, | *YES | Schedule era condition | Integer | 0 - 99999 | | -| year, | *YES | Schedule year condition | Integer | 0 - 99999 | | -| month, | *YES | Schedule month condition | Integer | 1 - 12 | | -| day, | *YES | Schedule day condition | Integer | 1 - 31 | | -| hour, | *YES | Schedule hour condition | Integer | 0 - 23 | | -| minute, | *YES | Schedule minute condition | Integer | 0 - 59 | | -| second, | *YES | Schedule second condition | Integer | 0 - 59 | | -| weekday, | *YES | Schedule weekday condition | Integer | 1 - 7 | | -| weekOfMonth, | *YES | Schedule weekOfMonth condition | Integer | 1 - 6 | | -| weekOfYear, | *YES | Schedule weekOfYear condition | Integer | 1 - 53 | | -| allowWhileIdle | NO | Displays the notification, even when the device is low battery | bool | true or false | false | -| repeats | NO | Defines if the notification should play only once or keeps repeating | bool | true or false | false | -| timeZone | NO | Time zone identifier (ISO 8601) | String | "America/Sao_Paulo", "GMT-08:00" or "UTC" | "UTC" | - -
- -### NotificationAndroidCrontab (Only for Android)("schedule" in Push data) - (optional) -
- -* Is necessary at least one *required attribute -* Cron expression must respect the format (with seconds precision) as described in [this article](https://www.baeldung.com/cron-expressions) - -| Attribute | Required | Description | Type | Value Limits / Format | Default value | -| ------------------ | -------- | -------------------------------------------------------------------- | ---------------- | --------------------- | --------------- | -| initialDateTime | NO | Initial limit date of valid dates (does not fire any notification) | String | YYYY-MM-DD hh:mm:ss | | -| expirationDateTime | NO | Final limit date of valid dates (does not fire any notification) | String | YYYY-MM-DD hh:mm:ss | | -| crontabExpression | *YES | Crontab rule to generate new valid dates (with seconds precision) | bool | true or false | false | -| preciseSchedules | *YES | List of precise valid dates to fire | bool | true or false | false | -| allowWhileIdle | NO | Displays the notification, even when the device is low battery | bool | true or false | false | -| repeats | NO | Defines if the notification should play only once or keeps repeating | bool | true or false | false | -| timeZone | NO | Time zone identifier (ISO 8601) | String | "America/Sao_Paulo", "GMT-08:00" or "UTC" | "UTC" | +| Attribute | Required | Description | Type | Value Limits / Format | Default value | +| --------------- | -------- | --------------------------------------------------------------------- | -------- | --------------------- | ------------- | +| era | *YES | The era of the calendar. Example: 1 for AD, 0 for BC | Integer | 0 - 99999 | | +| year | *YES | The year in the calendar. | Integer | 0 - 99999 | | +| month | *YES | The month in the calendar. | Integer | 1 - 12 | | +| day | *YES | The day of the month in the calendar. | Integer | 1 - 31 | | +| hour | *YES | The hour of the day in the calendar. | Integer | 0 - 23 | | +| minute | *YES | The minute of the hour in the calendar. | Integer | 0 - 59 | | +| second | *YES | The second of the minute in the calendar. | Integer | 0 - 59 | | +| weekday | *YES | The day of the week in the calendar. | Integer | 1 - 7 | | +| weekOfMonth | *YES | The week of the month in the calendar. | Integer | 1 - 6 | | +| weekOfYear | *YES | The week of the year in the calendar. | Integer | 1 - 53 | | +| allowWhileIdle | NO | Displays the notification, even when the device is low battery. | bool | true or false | false | +| delayTolerance | NO | Set the delay tolerance for inexact schedules. | bool | 600000 or greater | 600000 | +| preciseAlarm | NO | Require schedules to be precise, even when the device is low battery. | bool | true or false | false | +| repeats | NO | Defines if the notification should play only once or keeps repeating. | bool | true or false | false | +| timeZone | NO | Time zone identifier (ISO 8601). | String | "America/Sao_Paulo", "GMT-08:00" or "UTC" | "UTC" |
-
- -## Notification Layout Types - -To show any images on notification, at any place, you need to include the respective source prefix before the path. -Layouts can be defined using 4 prefix types: - -- Default: The default notification layout. Also, is the layout chosen in case of any failure found on other layouts -- BigPicture: Shows a big picture and/or a small image attached to the notification. -- BigText: Shows more than 2 lines of text. -- Inbox: Lists messages or items separated by lines -- ProgressBar: Shows an progress bar, such as download progress bar -- Messaging: Shows each notification as an chat conversation with one person -- Messaging Group: Shows each notification as an chat conversation with more than one person (Groups) -- MediaPlayer: Shows an media controller with action buttons, that allows the user to send commands without brings the application to foreground. - -
+### NotificationAndroidCrontab (Only for Android)("schedule" in Push data) - (optional)
-## Media Source Types +* At least one *required attribute is necessary for scheduling the notification using a Cron expression. +* The Cron expression must respect the format described in [this article](https://www.baeldung.com/cron-expressions), including seconds precision. -To show any images on notification, at any place, you need to include the respective source prefix before the path. +| Attribute | Required | Description | Type | Value Limits / Format | Default value | +| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------ | ---------------------------------------- | ------------- | +| initialDateTime | NO | The initial limit date of valid dates, which does not fire any notifications | String | YYYY-MM-DD hh:mm:ss | | +| expirationDateTime | NO | The final limit date of valid dates, which does not fire any notifications | String | YYYY-MM-DD hh:mm:ss | | +| crontabExpression | *YES | The crontab rule to generate new valid dates, with seconds precision | String | crontab expression format | | +| preciseSchedules | *YES | A list of precise valid dates to fire. Each item in the list should be a string in the format "YYYY-MM-DD hh:mm:ss", with seconds. | Array | array of strings in the specified format | | +| allowWhileIdle | NO | Displays the notification, even when the device is low on battery | bool | true or false | false | +| delayTolerance | NO | Sets the delay tolerance for inexact schedules | bool | 600000 or greater | 600000 | +| preciseAlarm | NO | Requires schedules to be precise, even when the device is low on battery. Requires explicit permission in Android 12 and beyond. | bool | true or false | false | +| repeats | NO | Defines if the notification should play only once or keep repeating | bool | true or false | false | +| timeZone | NO | The time zone identifier in the ISO 8601 format | String | "America/Sao_Paulo", "GMT-08:00", "UTC" | "UTC" | -Images can be defined using 4 prefix types: -- Asset: images access through Flutter asset method. **Example**: asset://path/to/image-asset.png -- Network: images access through internet connection. **Example**: http(s)://url.com/to/image-asset.png -- File: images access through files stored on device. **Example**: file://path/to/image-asset.png -- Resource: images access through drawable native resources. On Android, those files are stored inside [project]/android/app/src/main/drawable folder. **Example**: resource://drawable/res_image-asset.png - -OBS: Unfortunately, icons and sounds can be only resource media types. -
-OBS 2: To protect your native resources on Android against minification, please include the prefix "res_" in your resource file names. The use of the tag `shrinkResources false` inside build.gradle or the command `flutter build apk --no-shrink` is not recommended. -To know more about it, please visit [Shrink, obfuscate, and optimize your app](https://developer.android.com/studio/build/shrink-code)

+ +# Common Known Issues -## Notification Importance (Android's channel) - -Defines the notification's importance level and how it should be displayed to the user. -The possible importance levels are the following: -- Max: Makes a sound and appears as a heads-up notification. -- Higher: shows everywhere, makes noise and peeks. May use full screen intents. -- Default: shows everywhere, makes noise, but does not visually intrude. -- Low: Shows in the shade, and potentially in the status bar (see shouldHideSilentStatusBarIcons()), but is not audibly intrusive. -- Min: only shows in the shade, below the fold. -- None: disable the respective channel. +## ***Issue***: Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present -OBS: Unfortunately, the channel's importance can only be defined on first time. After that, it cannot be changed. +***Fix***: You need to add the attribute android:exported="true" to any , , , or components that have declared inside in the app’s AndroidManifest.xml file. This is necessary to comply with Android 12's new security requirements. However, manually adding this attribute to your plugin's local files can be risky as they can be modified or even erased by some flutter commands, such as "Pub clear cache". -
-
- +To fix this issue, it's recommended to request the changes to be made in the plugin repository instead and upgrade it in your pubspec.yaml to the latest version. This ensures that the necessary changes are made without compromising the integrity of the local files. -## Common Known Issues - -**Issue:** Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present - -**Fix:** You need to add the attribute `android:exported="true"` to any \, \, \, or \ components that have \ declared inside in the app’s AndroidManifest.xml file, and thats turns valid for every other flutter packages that youre using. +For example, you can add the following line of code to your AndroidManifest.xml file: ```xml ``` - -But you need to remember that your plugin local files can be modified or even erased by some flutter commands, such as "Pub clear cache". So, do not add the attribute exported manually. Instead, request this changes to your plugin repository instead and upgrate it in your pubspec.yaml to the last version. - -To know more about it, please visit [Android 12 - Safer component exporting](https://developer.android.com/about/versions/12/behavior-changes-12?hl=pt-br#exported) +To learn more about this issue and how to fix it, please visit [Android 12 - Safer component exporting](https://developer.android.com/about/versions/12/behavior-changes-12?hl=pt-br#exported) +
-## +--- -**Issue:** awesome_notifications is not displaying images, playing custom sounds or showing icons on release mode +## ***Issue***: Notification is not showing up or is showing up inconsistently. -**Fix:** You need to protect your Android resource files from being minimized and obfuscated. You can achieve this in two ways: - -1 - Please include the prefix "res_" in your native resource file names. The use of the tag `shrinkResources false` inside build.gradle or the command `flutter build apk --no-shrink` is not recommended. To know more about it, please visit [Shrink, obfuscate, and optimize your app](https://developer.android.com/studio/build/shrink-code) +***Fix***: This can happen due to various reasons such as channel not being registered properly, notification not being triggered at the right time due device battery optimization settings, and other ones. -2 - Create a keep.xml file and add the following content: +- First, make sure that you have registered your notification channels properly and that your app is targeting at least API level 26 (Android 8.0) or higher. +- Check if the notification is triggered at the right time. You may need to verify that the correct date and time have been set in the notification. +- Check the device battery optimization settings, as it can interfere with the scheduled notifications. You can disable battery optimization for your app in the device settings. -``` - - -``` +If none of the above solutions work, you can also try clearing the cache and data of your app, uninstalling and reinstalling the app, or checking for any conflicts with other third-party apps that might be causing the issue. To know more about it, please visit [Customize which resources to keep](https://developer.android.com/studio/build/shrink-code#keep-resources) +
+ +--- -## +## ***Issue:*** My schedules are only displayed immediately after I open my app -**Issue:** My schedules are only displayed immediately after I open my app +***Fix:*** Your app or device is under battery saving mode restrictions. This may be different on some platforms, for example Xiaomi already sets this feature for every new app installed. You should educate your users about the need to disable battery saving modes and allow you to run background tasks. -**Fix:** Your app or device is under battery saving mode restrictions. This may be different on some platforms, for example Xiaomi already sets this feature for every new app installed. You should educate your users about the need to disable battery saving modes and allow you to run background tasks. +Additionally, you can ask your users to whitelist your app from any battery optimization feature that the device may have. This can be done by adding your app to the "unmonitored apps" or "battery optimization exceptions" list, depending on the device. + +You can also try to use the [flutter_background_fetch](https://pub.dev/packages/flutter_background_fetch) package to help schedule background tasks. This package allows you to schedule tasks that will run even when the app is not open, and it has some built-in features to help handle battery optimization. + +To know more about it, please visit [flutter_background_fetch documentation](https://pub.dev/packages/flutter_background_fetch) and [Optimizing for Doze and App Standby](https://developer.android.com/training/monitoring-device-state/doze-standby) for Android devices.
-## +--- -**Issue:** DecoderBufferCallback not found / Uint8List not found +## ***Issue***: DecoderBufferCallback not found / Uint8List not found -**Fix:** You need to update your Flutter version running `flutter upgrade`. These methods was added/deprecated sice version 2.12. +***Fix***: You need to update your Flutter version running `flutter upgrade`. + +These methods were added/deprecated since version 2.12. If you are already on the latest Flutter version and still encountering the issue, make sure to also update your awesome_notifications package to the latest version.
-**Issue:** Using bridging headers with module interfaces is unsupported +--- + +## ***Issue***: Using bridging headers with module interfaces is unsupported -**Fix:** You need to set `build settings` options below in your Runner target: +***Fix***: You need to set `build settings` options below in your Runner target: * Build libraries for distribution => NO * Only safe API extensions => NO @@ -1486,22 +1744,24 @@ To know more about it, please visit [Customize which resources to keep](https://
-## +--- -**Issue:** Invalid notification content +## ***Issue***: Invalid notification content -**Fix:** The notification sent via FCM services *MUST* respect the types of the respective Notification elements. Otherwise, your notification will be discarded as invalid one. +***Fix***: The notification sent via FCM services *MUST* respect the types of the respective Notification elements. Otherwise, your notification will be discarded as invalid one. Also, all the payload elements *MUST* be a String, as the same way as you do in Local Notifications using dart code. To see more information about each type, please go to https://github.com/rafaelsetragni/awesome_notifications#notification-types-values-and-defaults +
-## +--- -**Issue:** Undefined symbol: OBJC_CLASS$_FlutterStandardTypedData / OBJC_CLASS$_FlutterError / OBJC_CLASS$_FlutterMethodChannel +## ***Issue***: Undefined symbol: OBJC_CLASS$_FlutterStandardTypedData / OBJC_CLASS$_FlutterError / OBJC_CLASS$_FlutterMethodChannel -**Fix:** Please, remove the old target extensions and update your awesome_notification plugin to the last version available +***Fix***: This error happens when the flutter dependecies are not copied to another target extensions. Please, remove the old target extensions and update your awesome_notification plugin to the last version available, modifying your pod file according and running `pod install` after it. +

# Android Foreground Services (Optional) @@ -1559,3 +1819,4 @@ If the icon of the notification is not set or not valid, the notification will a ### Foreground Services behaviour on platforms other than Android On any platform other then Android, all foreground service methods are no-ops (they do nothing when called), so you don't need to do a platform check before calling them. + diff --git a/analysis_options.yaml b/analysis_options.yaml index 07ce897c..059fa5c6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,4 +4,5 @@ include: package:flutter_lints/flutter.yaml # https://dart.dev/guides/language/analysis-options linter: rules: - constant_identifier_names: false \ No newline at end of file + constant_identifier_names: false + deprecated_member_use_from_same_package: false \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore index 161bdcda..e755068c 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,7 +1,6 @@ *.iml .gradle /local.properties -/.idea/workspace.xml /.idea/libraries .DS_Store /build diff --git a/android/build.gradle b/android/build.gradle index b275aba2..12f1fe1f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ group 'me.carda.awesome_notifications' -version '0.7.2' +version '0.7.5' buildscript { repositories { @@ -24,6 +24,9 @@ rootProject.allprojects { apply plugin: 'com.android.library' android { + if (project.android.hasProperty('namespace')) { + namespace 'me.carda.awesome_notifications' + } compileSdkVersion 33 compileOptions { @@ -39,7 +42,7 @@ android { dependencies { // implementation project(':awn_core') - implementation 'me.carda:androidcore:0.7.7' + implementation 'me.carda:AndroidAwnCore:0.7.5' implementation 'com.google.guava:guava:31.1-android' @@ -56,4 +59,4 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:core:1.4.0' androidTestImplementation 'androidx.test.ext:junit:1.1.3' -} \ No newline at end of file +} diff --git a/android/src/main/java/me/carda/awesome_notifications/AwesomeNotificationsPlugin.java b/android/src/main/java/me/carda/awesome_notifications/AwesomeNotificationsPlugin.java index 1649bac9..3d4e9d99 100644 --- a/android/src/main/java/me/carda/awesome_notifications/AwesomeNotificationsPlugin.java +++ b/android/src/main/java/me/carda/awesome_notifications/AwesomeNotificationsPlugin.java @@ -273,8 +273,8 @@ public void onMethodCall(@NonNull final MethodCall call, @NonNull final Result r channelMethodInitialize(call, result); return; - case Definitions.CHANNEL_METHOD_SET_ACTION_HANDLE: - channelMethodSetActionHandle(call, result); + case Definitions.CHANNEL_METHOD_SET_EVENT_HANDLES: + channelMethodSetEventsHandle(call, result); return; case Definitions.CHANNEL_METHOD_GET_DRAWABLE_DATA: @@ -309,6 +309,14 @@ public void onMethodCall(@NonNull final MethodCall call, @NonNull final Result r channelRequestUserPermissions(call, result); return; + case Definitions.CHANNEL_METHOD_IS_NOTIFICATION_ACTIVE: + channelMethodIsNotificationActiveOnStatusBar(call, result); + return; + + case Definitions.CHANNEL_METHOD_GET_ALL_ACTIVE_NOTIFICATION_IDS: + channelMethodGetAllActiveNotificationIdsOnStatusBar(call, result); + return; + case Definitions.CHANNEL_METHOD_CREATE_NOTIFICATION: channelMethodCreateNotification(call, result); return; @@ -369,6 +377,14 @@ public void onMethodCall(@NonNull final MethodCall call, @NonNull final Result r channelMethodResetBadge(call, result); return; + case Definitions.CHANNEL_METHOD_SET_LOCALIZATION: + channelMethodSetLocalization(call, result); + return; + + case Definitions.CHANNEL_METHOD_GET_LOCALIZATION: + channelMethodGetLocalization(call, result); + return; + case Definitions.CHANNEL_METHOD_DISMISS_NOTIFICATION: channelMethodDismissNotification(call, result); return; @@ -672,6 +688,23 @@ private void channelMethodDecrementBadge( result.success(badgeCount); } + private void channelMethodSetLocalization( + @NonNull final MethodCall call, + @NonNull final Result result + ) throws AwesomeNotificationsException { + String languageCode = call.arguments(); + boolean success = awesomeNotifications.setLocalization(languageCode); + result.success(success); + } + + private void channelMethodGetLocalization( + @NonNull final MethodCall call, + @NonNull final Result result + ) throws AwesomeNotificationsException { + String languageCode = awesomeNotifications.getLocalization(); + result.success(languageCode); + } + private void channelMethodDismissNotification( @NonNull final MethodCall call, @NonNull final Result result @@ -1250,6 +1283,30 @@ public void handle(List missingPermissions) { }); } + private void channelMethodIsNotificationActiveOnStatusBar( + @NonNull final MethodCall call, + @NonNull final Result result + ) throws Exception { + Integer id = call.arguments(); + if(id == null) + throw ExceptionFactory + .getInstance() + .createNewAwesomeException( + TAG, + ExceptionCode.CODE_MISSING_ARGUMENTS, + "Id is required", + ExceptionCode.DETAILED_REQUIRED_ARGUMENTS); + + result.success(awesomeNotifications.isNotificationActiveOnStatusBar(id)); + } + + private void channelMethodGetAllActiveNotificationIdsOnStatusBar( + @NonNull final MethodCall call, + @NonNull final Result result + ) throws Exception { + result.success(awesomeNotifications.getAllActiveNotificationIdsOnStatusBar()); + } + private void channelMethodCreateNotification( @NonNull final MethodCall call, @NonNull final Result result @@ -1317,7 +1374,8 @@ private void channelMethodInitialize( debug = debug != null && debug; Object backgroundCallbackObj = arguments.get(Definitions.BACKGROUND_HANDLE); - Long backgroundCallback = backgroundCallbackObj == null ? 0L :((Number) backgroundCallbackObj).longValue(); + Long backgroundCallback = backgroundCallbackObj == null + ? 0L :((Number) backgroundCallbackObj).longValue(); awesomeNotifications.initialize( defaultIconPath, @@ -1333,7 +1391,7 @@ private void channelMethodInitialize( } @SuppressWarnings("unchecked") - private void channelMethodSetActionHandle( + private void channelMethodSetEventsHandle( @NonNull final MethodCall call, @NonNull final Result result ) throws Exception { @@ -1348,14 +1406,28 @@ private void channelMethodSetActionHandle( "Arguments are missing", ExceptionCode.DETAILED_REQUIRED_ARGUMENTS); + Object callbackCreatedObj = arguments.get(Definitions.CREATED_HANDLE); + Object callbackDisplayedObj = arguments.get(Definitions.DISPLAYED_HANDLE); Object callbackActionObj = arguments.get(Definitions.ACTION_HANDLE); + Object callbackDismissedObj = arguments.get(Definitions.DISMISSED_HANDLE); - long silentCallback = callbackActionObj == null ? 0L : ((Number) callbackActionObj).longValue(); + long createdCallback = callbackCreatedObj == null + ? 0L : ((Number) callbackCreatedObj).longValue(); + long displayedCallback = callbackDisplayedObj == null + ? 0L : ((Number) callbackDisplayedObj).longValue(); + long actionCallback = callbackActionObj == null + ? 0L : ((Number) callbackActionObj).longValue(); + long dismissedCallback = callbackDismissedObj == null + ? 0L : ((Number) callbackDismissedObj).longValue(); awesomeNotifications.attachAsMainInstance(awesomeEventListener); - awesomeNotifications.setActionHandle(silentCallback); + awesomeNotifications.setEventsHandle( + createdCallback, + displayedCallback, + actionCallback, + dismissedCallback); - boolean success = silentCallback != 0L; + boolean success = actionCallback != 0L; if(!success) Logger.w( TAG, diff --git a/awesome_notifications.iml b/awesome_notifications.iml index 975030f0..18de19ef 100644 --- a/awesome_notifications.iml +++ b/awesome_notifications.iml @@ -4536,6 +4536,6 @@ - + \ No newline at end of file diff --git a/coverage/lcov.info b/coverage/lcov.info new file mode 100644 index 00000000..c2416290 --- /dev/null +++ b/coverage/lcov.info @@ -0,0 +1,1595 @@ +SF:lib/awesome_notifications_empty.dart +DA:10,1 +DA:13,1 +DA:16,1 +DA:19,1 +DA:22,1 +DA:25,1 +DA:28,1 +DA:31,1 +DA:34,1 +DA:47,1 +DA:57,1 +DA:63,1 +DA:68,1 +DA:71,1 +DA:74,1 +DA:77,1 +DA:80,1 +DA:85,1 +DA:90,1 +DA:96,1 +DA:101,1 +DA:106,1 +DA:112,1 +DA:114,1 +DA:117,1 +DA:122,1 +DA:133,1 +DA:138,1 +DA:140,1 +DA:143,1 +DA:148,1 +DA:161,1 +DA:164,1 +DA:168,1 +DA:171,1 +DA:180,1 +DA:190,1 +DA:193,1 +DA:196,1 +DA:199,1 +DA:202,1 +DA:207,1 +DA:212,1 +DA:217,1 +DA:219,1 +DA:222,1 +LF:46 +LH:46 +end_of_record +SF:lib/awesome_notifications_method_channel.dart +DA:26,1 +DA:28,1 +DA:29,2 +DA:32,1 +DA:34,2 +DA:37,1 +DA:39,2 +DA:42,1 +DA:44,2 +DA:48,1 +DA:50,2 +DA:54,1 +DA:56,1 +DA:57,2 +DA:60,1 +DA:62,2 +DA:66,1 +DA:68,2 +DA:72,1 +DA:82,1 +DA:84,2 +DA:85,1 +DA:90,1 +DA:93,1 +DA:100,2 +DA:102,2 +DA:104,1 +DA:109,1 +DA:114,1 +DA:118,2 +DA:119,1 +DA:120,2 +DA:123,2 +DA:124,1 +DA:125,2 +DA:128,2 +DA:129,1 +DA:130,2 +DA:133,2 +DA:134,1 +DA:135,2 +DA:140,2 +DA:142,1 +DA:145,1 +DA:146,1 +DA:147,1 +DA:148,1 +DA:154,1 +DA:157,2 +DA:161,1 +DA:163,1 +DA:164,2 +DA:167,1 +DA:169,2 +DA:172,1 +DA:174,2 +DA:178,1 +DA:180,2 +DA:184,1 +DA:187,2 +DA:189,4 +DA:192,1 +DA:194,2 +DA:202,1 +DA:205,2 +DA:209,1 +DA:210,2 +DA:214,1 +DA:217,2 +DA:221,1 +DA:223,1 +DA:224,1 +DA:228,1 +DA:231,0 +DA:232,1 +DA:234,1 +DA:235,1 +DA:238,2 +DA:243,1 +DA:246,1 +DA:248,1 +DA:249,1 +DA:253,1 +DA:256,2 +DA:260,1 +DA:268,1 +DA:270,3 +DA:272,1 +DA:273,2 +DA:274,2 +DA:277,1 +DA:279,2 +DA:280,2 +DA:288,1 +DA:290,3 +DA:297,1 +DA:299,3 +DA:304,1 +DA:308,1 +DA:311,1 +DA:312,1 +DA:314,1 +DA:315,1 +DA:320,1 +DA:322,1 +DA:323,1 +DA:327,1 +DA:329,1 +DA:331,2 +DA:333,2 +DA:334,1 +DA:336,3 +DA:339,1 +DA:346,1 +DA:348,2 +DA:353,1 +DA:363,1 +DA:364,2 +DA:366,1 +DA:367,1 +DA:370,2 +DA:371,1 +DA:376,1 +DA:379,1 +DA:381,2 +DA:384,1 +DA:387,1 +DA:388,2 +DA:390,2 +DA:394,1 +DA:396,2 +DA:399,1 +DA:405,3 +DA:406,2 +DA:409,1 +DA:410,1 +DA:411,1 +DA:412,1 +DA:416,1 +DA:421,1 +DA:425,1 +DA:429,1 +DA:433,3 +DA:434,1 +DA:435,1 +DA:436,1 +DA:437,1 +DA:441,2 +DA:449,1 +DA:459,1 +DA:461,2 +DA:462,1 +DA:467,1 +DA:470,1 +DA:472,2 +DA:475,1 +DA:477,2 +DA:480,1 +DA:482,2 +DA:486,1 +DA:488,2 +DA:491,1 +DA:493,2 +DA:498,1 +DA:500,2 +DA:505,1 +DA:507,1 +DA:508,1 +DA:509,1 +DA:515,1 +DA:518,2 +DA:520,1 +DA:521,1 +DA:522,2 +DA:523,3 +DA:526,1 +DA:527,2 +DA:528,3 +DA:531,1 +DA:532,2 +DA:533,3 +DA:536,1 +DA:537,2 +DA:538,3 +DA:541,1 +DA:542,3 +DA:543,3 +DA:545,2 +DA:550,1 +DA:554,1 +DA:556,3 +DA:557,1 +DA:562,1 +DA:564,1 +DA:565,2 +DA:567,1 +DA:568,1 +DA:573,1 +DA:575,1 +DA:576,2 +DA:578,1 +DA:579,1 +DA:580,1 +DA:585,1 +LF:204 +LH:203 +end_of_record +SF:lib/awesome_notifications_platform_interface.dart +DA:13,12 +DA:15,12 +DA:20,1 +DA:25,2 +DA:27,1 +DA:28,1 +DA:29,1 +DA:30,1 +DA:33,1 +DA:40,1 +DA:48,1 +DA:49,2 +LF:12 +LH:12 +end_of_record +SF:lib/awesome_notifications.dart +DA:67,4 +DA:68,2 +DA:69,2 +DA:70,2 +DA:71,2 +DA:74,1 +DA:76,13 +DA:78,1 +DA:80,2 +DA:83,1 +DA:85,2 +DA:88,1 +DA:90,2 +DA:93,1 +DA:95,1 +DA:96,1 +DA:99,1 +DA:101,1 +DA:102,1 +DA:105,1 +DA:107,2 +DA:110,1 +DA:112,1 +DA:113,1 +DA:116,1 +DA:118,1 +DA:119,1 +DA:122,1 +DA:132,1 +DA:133,1 +DA:136,1 +DA:143,2 +DA:150,1 +DA:152,1 +DA:153,1 +DA:156,1 +DA:158,2 +DA:161,1 +DA:163,2 +DA:166,1 +DA:168,2 +DA:171,1 +DA:173,1 +DA:174,1 +DA:177,1 +DA:179,1 +DA:180,1 +DA:183,1 +DA:185,2 +DA:188,1 +DA:190,2 +DA:193,1 +DA:195,2 +DA:198,1 +DA:201,2 +DA:206,1 +DA:208,2 +DA:211,1 +DA:213,2 +DA:216,1 +DA:219,1 +DA:220,1 +DA:223,1 +DA:225,2 +DA:228,1 +DA:230,2 +DA:233,1 +DA:241,2 +DA:246,1 +DA:248,2 +DA:251,1 +DA:253,2 +DA:256,1 +DA:258,2 +DA:261,1 +DA:271,1 +DA:272,1 +DA:278,1 +DA:280,2 +DA:283,1 +DA:286,1 +DA:287,1 +DA:290,1 +DA:292,2 +DA:295,1 +DA:301,2 +DA:309,1 +DA:319,2 +DA:325,1 +DA:327,2 +DA:330,1 +DA:332,2 +DA:335,1 +DA:337,1 +DA:338,1 +DA:341,1 +DA:343,2 +DA:346,1 +DA:348,1 +DA:349,1 +DA:352,1 +DA:354,1 +DA:355,1 +DA:358,1 +DA:360,1 +DA:361,1 +LF:106 +LH:106 +end_of_record +SF:lib/src/models/notification_localization.dart +DA:44,3 +DA:60,3 +DA:62,3 +DA:64,3 +DA:65,3 +DA:66,6 +DA:67,3 +DA:68,3 +DA:69,6 +DA:71,6 +DA:74,9 +DA:75,1 +DA:77,2 +DA:78,5 +DA:89,2 +DA:90,2 +DA:91,8 +DA:92,8 +DA:93,5 +DA:94,3 +DA:95,2 +DA:96,5 +DA:97,3 +DA:98,2 +DA:101,3 +LF:25 +LH:25 +end_of_record +SF:lib/src/enumerators/notification_life_cycle.dart +DA:7,1 +LF:1 +LH:1 +end_of_record +SF:lib/src/models/notification_button.dart +DA:29,1 +DA:30,1 +DA:33,2 +DA:34,2 +DA:37,1 +DA:38,1 +DA:41,1 +DA:42,1 +DA:45,1 +DA:46,1 +DA:49,1 +DA:50,1 +DA:53,1 +DA:54,1 +DA:57,1 +DA:58,1 +DA:61,1 +DA:62,1 +DA:65,1 +DA:66,1 +DA:69,4 +DA:91,4 +DA:94,3 +DA:96,3 +DA:97,6 +DA:98,6 +DA:99,6 +DA:101,3 +DA:102,3 +DA:103,6 +DA:105,6 +DA:107,6 +DA:109,6 +DA:111,6 +DA:114,3 +DA:115,3 +DA:120,3 +DA:122,3 +DA:124,1 +DA:125,1 +DA:126,1 +DA:129,3 +DA:130,1 +DA:131,2 +DA:135,3 +DA:138,4 +DA:141,8 +DA:142,1 +DA:144,1 +DA:145,1 +DA:149,3 +DA:151,3 +DA:153,3 +DA:154,3 +DA:155,3 +DA:156,3 +DA:157,3 +DA:158,3 +DA:159,3 +DA:160,3 +DA:161,3 +DA:162,6 +DA:163,6 +DA:167,3 +DA:169,6 +DA:172,6 +DA:178,6 +DA:179,4 +LF:68 +LH:68 +end_of_record +SF:lib/src/models/notification_channel.dart +DA:57,4 +DA:82,4 +DA:83,8 +DA:84,4 +DA:85,8 +DA:86,4 +DA:87,8 +DA:88,4 +DA:89,8 +DA:90,4 +DA:92,8 +DA:93,4 +DA:95,8 +DA:96,4 +DA:97,8 +DA:98,4 +DA:99,8 +DA:100,4 +DA:101,8 +DA:102,4 +DA:103,8 +DA:104,4 +DA:105,8 +DA:106,4 +DA:107,8 +DA:108,4 +DA:109,8 +DA:110,4 +DA:111,8 +DA:112,4 +DA:113,8 +DA:114,4 +DA:115,8 +DA:116,4 +DA:117,8 +DA:118,4 +DA:119,4 +DA:120,4 +DA:121,4 +DA:122,12 +DA:123,8 +DA:124,4 +DA:125,4 +DA:126,8 +DA:127,8 +DA:128,4 +DA:129,8 +DA:130,4 +DA:131,4 +DA:132,4 +DA:133,4 +DA:136,12 +DA:137,4 +DA:140,2 +DA:142,2 +DA:143,2 +DA:144,2 +DA:145,2 +DA:146,2 +DA:147,2 +DA:148,2 +DA:149,2 +DA:150,2 +DA:151,2 +DA:152,2 +DA:153,2 +DA:154,3 +DA:155,4 +DA:156,2 +DA:157,2 +DA:158,2 +DA:159,4 +DA:160,4 +DA:161,4 +DA:162,4 +DA:163,4 +DA:164,2 +DA:165,2 +DA:166,2 +DA:170,1 +DA:172,2 +DA:174,2 +DA:176,2 +DA:178,2 +DA:181,2 +DA:184,1 +DA:185,1 +DA:186,2 +DA:189,2 +DA:191,2 +DA:193,2 +DA:196,2 +DA:198,2 +DA:200,2 +DA:205,2 +DA:207,2 +DA:209,2 +DA:212,2 +DA:213,1 +DA:214,1 +DA:215,2 +DA:218,2 +DA:220,1 +DA:221,1 +DA:223,1 +DA:224,1 +DA:225,1 +DA:226,1 +DA:228,2 +DA:234,1 +DA:236,2 +DA:240,2 +DA:244,2 +LF:113 +LH:113 +end_of_record +SF:lib/src/models/notification_channel_group.dart +DA:10,1 +DA:11,1 +DA:14,1 +DA:15,1 +DA:18,2 +DA:20,2 +DA:21,2 +DA:24,1 +DA:26,2 +DA:28,2 +DA:34,2 +DA:36,2 +DA:37,2 +DA:38,2 +DA:42,1 +DA:44,2 +DA:48,2 +LF:17 +LH:17 +end_of_record +SF:lib/src/models/notification_content.dart +DA:24,1 +DA:25,1 +DA:28,1 +DA:29,1 +DA:32,1 +DA:33,1 +DA:36,1 +DA:37,1 +DA:40,1 +DA:41,1 +DA:44,1 +DA:45,1 +DA:48,1 +DA:49,1 +DA:52,1 +DA:53,1 +DA:56,6 +DA:97,6 +DA:123,3 +DA:125,3 +DA:126,6 +DA:129,3 +DA:130,3 +DA:131,6 +DA:132,3 +DA:133,3 +DA:134,3 +DA:135,3 +DA:137,6 +DA:140,6 +DA:143,6 +DA:147,3 +DA:155,3 +DA:157,3 +DA:160,6 +DA:161,3 +DA:162,3 +DA:163,3 +DA:164,3 +DA:165,3 +DA:166,6 +DA:167,3 +DA:168,3 +DA:173,1 +DA:175,3 +LF:45 +LH:45 +end_of_record +SF:lib/src/models/notification_model.dart +DA:25,6 +DA:28,6 +DA:31,6 +DA:34,2 +DA:38,3 +DA:49,2 +DA:52,4 +DA:53,4 +DA:54,4 +DA:55,4 +DA:62,2 +DA:63,6 +DA:65,2 +DA:66,6 +DA:67,2 +DA:69,2 +DA:72,2 +DA:73,4 +DA:74,4 +DA:76,2 +DA:77,4 +DA:79,2 +DA:80,4 +DA:81,2 +DA:84,1 +DA:85,1 +DA:86,3 +DA:89,3 +DA:92,2 +DA:94,4 +DA:95,4 +DA:97,2 +DA:98,4 +DA:100,2 +DA:101,4 +DA:102,2 +DA:104,2 +DA:110,2 +DA:112,4 +DA:115,4 +DA:117,2 +DA:120,6 +DA:121,4 +DA:122,6 +DA:124,2 +DA:126,4 +DA:133,2 +DA:134,2 +DA:135,6 +DA:136,8 +DA:137,4 +DA:138,4 +DA:139,4 +DA:140,2 +DA:142,3 +DA:143,2 +DA:145,2 +DA:146,4 +DA:150,1 +DA:154,1 +LF:60 +LH:60 +end_of_record +SF:lib/src/models/notification_schedule.dart +DA:12,9 +DA:35,5 +DA:37,10 +DA:39,4 +DA:41,10 +DA:45,10 +DA:49,10 +DA:53,10 +DA:60,5 +DA:62,5 +DA:63,5 +DA:64,5 +DA:65,5 +DA:66,5 +DA:67,5 +LF:15 +LH:15 +end_of_record +SF:lib/src/models/received_models/received_action.dart +DA:18,2 +DA:21,2 +DA:23,2 +DA:25,4 +DA:28,4 +DA:33,4 +DA:35,4 +DA:38,4 +DA:41,4 +DA:48,2 +DA:50,2 +DA:52,4 +DA:54,4 +DA:56,4 +DA:57,3 +DA:58,3 +DA:59,2 +DA:60,2 +LF:18 +LH:18 +end_of_record +SF:lib/src/models/received_models/received_notification.dart +DA:12,3 +DA:14,3 +DA:16,6 +DA:19,6 +DA:22,6 +DA:25,6 +DA:28,6 +DA:37,3 +DA:39,3 +DA:41,6 +DA:42,4 +DA:43,4 +DA:44,4 +DA:46,6 +DA:48,6 +LF:15 +LH:15 +end_of_record +SF:lib/src/definitions.dart +DA:287,54 +DA:288,18 +LF:2 +LH:2 +end_of_record +SF:lib/src/enumerators/media_source.dart +DA:22,2 +DA:26,1 +LF:2 +LH:2 +end_of_record +SF:lib/src/exceptions/awesome_exception.dart +DA:3,391 +DA:5,1 +DA:7,2 +LF:3 +LH:3 +end_of_record +SF:lib/src/exceptions/isolate_callback_exception.dart +DA:5,1 +DA:7,1 +DA:8,2 +LF:3 +LH:3 +end_of_record +SF:lib/src/extensions/extension_navigator_state.dart +DA:4,1 +DA:5,1 +DA:6,1 +DA:10,1 +DA:12,2 +DA:19,1 +DA:21,2 +DA:22,3 +LF:8 +LH:8 +end_of_record +SF:lib/src/helpers/bitmap_helper.dart +DA:15,1 +DA:17,1 +DA:18,1 +DA:20,1 +DA:23,1 +DA:26,1 +DA:27,3 +DA:34,5 +DA:36,1 +DA:37,1 +DA:38,1 +DA:39,1 +DA:40,2 +DA:44,1 +DA:45,1 +DA:46,1 +DA:48,2 +DA:49,1 +DA:50,1 +DA:53,1 +DA:54,1 +DA:55,1 +DA:57,1 +DA:58,1 +DA:59,2 +DA:60,3 +DA:63,1 +DA:64,1 +DA:65,1 +DA:66,2 +DA:67,1 +DA:69,1 +DA:72,1 +DA:73,4 +DA:74,2 +DA:75,1 +DA:80,1 +DA:81,3 +DA:83,3 +DA:84,1 +DA:85,1 +DA:86,2 +DA:87,1 +DA:88,1 +DA:89,1 +DA:90,2 +DA:91,1 +DA:92,1 +DA:93,1 +DA:94,2 +DA:95,1 +DA:96,1 +DA:97,1 +DA:98,1 +DA:103,1 +DA:104,2 +DA:106,1 +DA:113,3 +LF:58 +LH:58 +end_of_record +SF:lib/src/helpers/cron_helper.dart +DA:16,2 +DA:18,2 +DA:21,3 +DA:28,2 +DA:29,4 +DA:33,2 +DA:34,4 +DA:38,2 +DA:39,4 +DA:43,2 +DA:44,6 +DA:48,2 +DA:49,4 +DA:53,2 +DA:54,4 +DA:58,2 +DA:59,2 +DA:63,2 +DA:64,6 +DA:68,2 +DA:69,6 +LF:21 +LH:21 +end_of_record +SF:lib/src/models/notification_android_crontab.dart +DA:11,1 +DA:12,1 +DA:16,1 +DA:17,1 +DA:21,1 +DA:22,1 +DA:26,1 +DA:27,1 +DA:39,3 +DA:48,3 +DA:50,3 +DA:53,3 +DA:55,3 +DA:57,3 +DA:64,1 +DA:68,1 +DA:69,1 +DA:70,1 +DA:74,1 +DA:75,3 +DA:79,1 +DA:81,1 +DA:82,1 +DA:83,1 +DA:87,1 +DA:88,2 +DA:92,1 +DA:94,1 +DA:95,1 +DA:96,1 +DA:100,1 +DA:101,2 +DA:105,1 +DA:107,1 +DA:108,1 +DA:109,1 +DA:113,1 +DA:114,2 +DA:118,1 +DA:120,1 +DA:121,1 +DA:122,1 +DA:126,1 +DA:127,2 +DA:131,1 +DA:133,1 +DA:134,1 +DA:135,1 +DA:139,1 +DA:140,2 +DA:144,1 +DA:146,1 +DA:147,1 +DA:148,1 +DA:152,1 +DA:153,3 +DA:157,1 +DA:159,1 +DA:160,1 +DA:161,1 +DA:165,1 +DA:166,2 +DA:170,1 +DA:172,1 +DA:173,1 +DA:174,1 +DA:178,1 +DA:179,2 +DA:182,2 +DA:184,2 +DA:186,4 +DA:188,4 +DA:190,4 +DA:193,4 +DA:195,2 +DA:196,2 +DA:198,2 +DA:199,1 +DA:201,2 +DA:205,2 +DA:209,3 +DA:211,3 +DA:212,6 +DA:213,3 +DA:215,6 +DA:217,6 +DA:221,6 +DA:222,1 +DA:224,3 +DA:225,1 +DA:227,1 +DA:230,1 +DA:236,1 +DA:238,3 +DA:241,2 +DA:243,2 +LF:96 +LH:96 +end_of_record +SF:lib/src/models/notification_calendar.dart +DA:55,3 +DA:71,3 +DA:76,3 +DA:77,1 +DA:82,1 +DA:87,1 +DA:88,1 +DA:89,1 +DA:94,2 +DA:95,2 +DA:96,2 +DA:97,2 +DA:98,2 +DA:99,2 +DA:102,2 +DA:104,4 +DA:106,4 +DA:108,4 +DA:110,4 +DA:112,4 +DA:114,4 +DA:116,4 +DA:118,4 +DA:120,4 +DA:122,4 +DA:125,2 +DA:128,2 +DA:136,2 +DA:138,2 +DA:139,4 +DA:140,2 +DA:141,2 +DA:142,2 +DA:143,2 +DA:144,2 +DA:145,2 +DA:146,2 +DA:147,2 +DA:148,2 +DA:149,2 +DA:155,1 +DA:157,3 +DA:160,2 +DA:162,2 +DA:163,2 +DA:164,2 +DA:165,2 +DA:166,2 +DA:167,2 +DA:168,2 +DA:169,1 +DA:170,1 +DA:171,1 +DA:176,2 +DA:177,1 +DA:180,4 +DA:181,4 +DA:182,4 +DA:183,4 +DA:184,4 +DA:185,4 +DA:186,4 +DA:187,4 +DA:188,4 +DA:189,4 +LF:65 +LH:65 +end_of_record +SF:lib/src/models/notification_interval.dart +DA:13,7 +DA:19,7 +DA:25,3 +DA:27,3 +DA:29,6 +DA:33,3 +DA:41,4 +DA:43,4 +DA:44,12 +DA:47,2 +DA:49,6 +DA:52,3 +DA:54,6 +DA:59,5 +LF:14 +LH:14 +end_of_record +SF:lib/src/models/received_models/push_notification.dart +DA:10,1 +DA:14,1 +LF:2 +LH:2 +end_of_record +SF:lib/src/utils/assert_utils.dart +DA:10,10 +DA:11,10 +DA:14,8 +DA:16,8 +DA:17,16 +DA:18,4 +DA:19,4 +DA:23,1 +DA:24,1 +DA:26,1 +DA:27,2 +DA:28,3 +DA:34,13 +DA:35,13 +DA:36,13 +DA:37,3 +DA:40,13 +DA:41,2 +DA:44,13 +DA:45,2 +DA:49,13 +DA:51,13 +DA:55,14 +DA:56,14 +DA:57,14 +DA:59,14 +DA:62,13 +DA:64,4 +DA:65,4 +DA:66,2 +DA:68,4 +DA:69,4 +DA:70,4 +DA:77,12 +DA:80,6 +DA:81,6 +DA:85,4 +DA:88,6 +DA:89,2 +DA:90,4 +DA:91,2 +DA:92,4 +DA:93,5 +DA:99,6 +DA:100,1 +DA:103,6 +DA:104,2 +DA:105,2 +DA:107,2 +DA:109,2 +DA:111,2 +DA:119,13 +DA:120,10 +DA:121,10 +DA:124,13 +DA:125,1 +DA:126,1 +DA:129,13 +DA:130,9 +DA:131,9 +DA:132,5 +DA:133,8 +DA:134,1 +DA:137,8 +DA:138,2 +DA:141,8 +DA:142,2 +DA:147,13 +DA:150,3 +DA:151,2 +DA:152,2 +DA:154,13 +DA:156,9 +DA:157,7 +DA:161,12 +DA:166,6 +DA:167,6 +DA:169,6 +DA:170,1 +DA:173,1 +DA:174,1 +DA:180,9 +DA:182,9 +DA:183,9 +DA:185,9 +DA:187,9 +DA:189,9 +DA:195,9 +DA:196,9 +DA:199,9 +DA:201,18 +DA:202,27 +DA:203,9 +DA:208,18 +DA:209,36 +DA:210,18 +DA:214,1 +DA:216,1 +DA:218,1 +DA:219,1 +DA:221,1 +DA:222,2 +DA:223,3 +LF:103 +LH:103 +end_of_record +SF:lib/src/utils/bitmap_utils.dart +DA:9,10 +DA:11,5 +DA:14,15 +DA:18,2 +DA:20,4 +DA:23,1 +DA:25,1 +DA:28,1 +DA:30,1 +LF:9 +LH:9 +end_of_record +SF:lib/src/utils/date_utils.dart +DA:4,1 +DA:6,1 +DA:9,1 +DA:10,1 +DA:13,3 +DA:14,2 +DA:17,5 +DA:19,5 +DA:22,5 +DA:23,5 +DA:26,15 +DA:27,10 +DA:30,7 +DA:33,10 +DA:36,1 +DA:39,3 +DA:40,1 +DA:43,1 +DA:46,3 +DA:47,1 +LF:20 +LH:20 +end_of_record +SF:lib/src/utils/map_utils.dart +DA:5,2 +DA:10,6 +DA:11,2 +LF:3 +LH:3 +end_of_record +SF:lib/src/utils/resource_image_provider.dart +DA:25,3 +DA:35,1 +DA:37,1 +DA:40,1 +DA:43,1 +DA:44,1 +DA:45,1 +DA:49,1 +DA:52,2 +DA:53,1 +DA:54,2 +DA:56,2 +DA:60,1 +DA:61,1 +DA:64,1 +DA:66,3 +DA:67,1 +DA:68,3 +DA:69,3 +DA:72,1 +DA:73,4 +DA:75,1 +DA:77,4 +LF:23 +LH:23 +end_of_record +SF:lib/src/utils/string_utils.dart +DA:2,30 +DA:3,12 +DA:6,20 +DA:8,3 +LF:4 +LH:4 +end_of_record +SF:lib/src/isolates/isolate_main.dart +DA:11,1 +DA:13,1 +DA:19,1 +DA:22,2 +DA:23,0 +DA:24,0 +DA:25,0 +DA:28,0 +DA:29,0 +DA:33,0 +DA:38,2 +DA:43,1 +DA:45,1 +DA:46,1 +DA:49,0 +DA:52,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:61,0 +DA:63,0 +DA:64,0 +DA:70,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:78,0 +DA:80,0 +DA:85,0 +DA:89,0 +DA:93,0 +DA:94,0 +DA:97,0 +DA:99,0 +DA:100,0 +DA:101,0 +LF:36 +LH:8 +end_of_record +SF:lib/src/logs/logger.dart +DA:5,6 +DA:6,2 +DA:7,4 +DA:8,6 +DA:11,1 +DA:12,2 +DA:13,2 +DA:16,2 +DA:17,2 +DA:18,4 +DA:21,1 +DA:22,2 +DA:25,2 +DA:26,2 +DA:27,4 +LF:15 +LH:15 +end_of_record +SF:lib/src/models/base_notification_content.dart +DA:54,6 +DA:57,4 +DA:60,2 +DA:63,6 +DA:66,4 +DA:69,2 +DA:72,4 +DA:75,2 +DA:78,2 +DA:81,2 +DA:84,2 +DA:87,2 +DA:90,4 +DA:93,4 +DA:96,4 +DA:99,4 +DA:102,2 +DA:105,2 +DA:108,0 +DA:111,2 +DA:114,2 +DA:117,2 +DA:120,4 +DA:123,4 +DA:126,4 +DA:129,6 +DA:130,6 +DA:133,1 +DA:136,1 +DA:139,1 +DA:142,1 +DA:145,4 +DA:148,4 +DA:151,6 +DA:152,6 +DA:155,4 +DA:158,4 +DA:161,6 +DA:162,6 +DA:165,4 +DA:168,4 +DA:171,6 +DA:172,6 +DA:175,4 +DA:178,4 +DA:181,6 +DA:182,6 +DA:185,4 +DA:188,4 +DA:191,1 +DA:193,1 +DA:195,1 +DA:197,1 +DA:199,9 +DA:225,9 +DA:226,9 +DA:228,9 +DA:230,9 +DA:232,9 +DA:234,9 +DA:236,9 +DA:238,9 +DA:240,9 +DA:242,9 +DA:244,9 +DA:246,9 +DA:248,9 +DA:250,9 +DA:252,9 +DA:254,9 +DA:256,9 +DA:258,9 +DA:260,18 +DA:261,9 +DA:263,19 +DA:264,9 +DA:266,9 +DA:268,9 +DA:270,9 +DA:272,9 +DA:275,6 +DA:277,6 +DA:279,12 +DA:280,12 +DA:282,12 +DA:284,12 +DA:286,6 +DA:287,6 +DA:288,12 +DA:289,6 +DA:290,6 +DA:291,6 +DA:292,6 +DA:293,12 +DA:294,12 +DA:296,12 +DA:298,12 +DA:300,12 +DA:302,12 +DA:304,12 +DA:306,6 +DA:307,6 +DA:308,12 +DA:310,12 +DA:312,12 +DA:314,12 +DA:316,12 +DA:318,12 +DA:320,12 +DA:322,12 +DA:328,6 +DA:330,6 +DA:331,1 +DA:333,1 +DA:334,1 +DA:336,12 +DA:337,12 +DA:339,1 +DA:340,3 +DA:346,6 +DA:348,6 +DA:349,6 +DA:350,6 +DA:351,6 +DA:352,6 +DA:353,6 +DA:354,6 +DA:355,6 +DA:356,6 +DA:357,6 +DA:358,6 +DA:359,6 +DA:360,6 +DA:361,6 +DA:362,6 +DA:363,7 +DA:364,12 +DA:365,12 +DA:366,7 +DA:367,6 +DA:368,6 +DA:369,6 +DA:370,6 +DA:371,6 +DA:372,7 +DA:373,7 +DA:374,7 +DA:375,6 +DA:376,6 +DA:377,6 +DA:378,7 +DA:382,1 +DA:383,2 +DA:384,3 +DA:387,1 +DA:388,2 +DA:389,3 +DA:392,1 +DA:393,2 +DA:394,3 +DA:397,1 +DA:398,2 +DA:399,3 +DA:402,3 +DA:404,3 +DA:406,4 +DA:408,8 +DA:412,8 +LF:168 +LH:167 +end_of_record +SF:lib/src/utils/html_utils.dart +DA:2,2 +DA:5,2 +DA:7,2 +LF:3 +LH:3 +end_of_record +SF:lib/src/models/model.dart +DA:4,19 +DA:12,1 +DA:13,2 +LF:3 +LH:3 +end_of_record +SF:lib/src/utils/list_utils.dart +DA:2,4 +DA:3,2 +LF:2 +LH:2 +end_of_record +SF:lib/src/utils/media_abstract_utils.dart +DA:7,8 +DA:9,16 +DA:13,16 +DA:17,16 +DA:21,14 +DA:28,4 +DA:29,8 +DA:30,6 +DA:32,8 +DA:33,8 +DA:35,4 +DA:36,4 +DA:44,1 +DA:46,3 +DA:54,5 +DA:55,5 +DA:56,5 +DA:57,5 +DA:59,4 +DA:60,2 +DA:62,4 +DA:63,4 +DA:65,4 +DA:66,4 +LF:24 +LH:24 +end_of_record +SF:lib/src/utils/audio_utils.dart +DA:8,2 +DA:10,1 +DA:13,3 +DA:17,1 +DA:24,1 +DA:31,1 +DA:38,1 +LF:7 +LH:7 +end_of_record diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index e3ffa3f8..09a6bb0f 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,6 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { + namespace 'me.carda.awesome_notifications_example' compileSdkVersion 33 //flutter.compileSdkVersion ndkVersion flutter.ndkVersion diff --git a/example/android/awn_core/.gitignore b/example/android/awn_core/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/example/android/awn_core/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/example/android/awn_core/build.gradle b/example/android/awn_core/build.gradle deleted file mode 100644 index 301162d0..00000000 --- a/example/android/awn_core/build.gradle +++ /dev/null @@ -1,95 +0,0 @@ -plugins { - id 'com.android.library' - id 'maven-publish' -} - -group 'me.carda.awesome_notifications.core' -version '0.7.6' - -android { - signingConfigs { - release { - } - } - compileSdk 33 - - defaultConfig { - minSdk 21 - targetSdk 33 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - profile { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - - implementation 'com.google.code.gson:gson:2.8.9' - implementation 'com.google.guava:guava:31.1-android' - implementation 'me.leolin:ShortcutBadger:1.1.22@aar' - - implementation 'androidx.annotation:annotation:1.5.0' - implementation 'androidx.appcompat:appcompat:1.5.1' - implementation 'androidx.media:media:1.6.0' - implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' - - def lifecycle_version = "2.5.1" - - // ViewModel - implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" - // LiveData - implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" - // Lifecycles only (without ViewModel or LiveData) - implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" - - // Saved state module for ViewModel - implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" - - // Annotation processor -// annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" - // alternately - if using Java8, use the following instead of lifecycle-compiler - implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" - - // optional - helpers for implementing LifecycleOwner in a Service - implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version" - - // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process - implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" - - // optional - ReactiveStreams support for LiveData - implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" - -// implementation 'com.google.android.material:material:1.6.1' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} - -afterEvaluate { - publishing { - publications { - release (MavenPublication) { - from components.release - - groupId = 'me.carda' - artifactId = 'AwnAndroidCore' - version = '0.7.6' - } - } - } -} \ No newline at end of file diff --git a/example/android/awn_core/consumer-rules.pro b/example/android/awn_core/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/example/android/awn_core/jitpack.yml b/example/android/awn_core/jitpack.yml deleted file mode 100644 index 45ec5630..00000000 --- a/example/android/awn_core/jitpack.yml +++ /dev/null @@ -1,4 +0,0 @@ -jdk: - - openjdk11 -before_install - - ./scripts/prepareJitpackEnvironment.sh \ No newline at end of file diff --git a/example/android/awn_core/proguard-rules.pro b/example/android/awn_core/proguard-rules.pro deleted file mode 100644 index d055b8b2..00000000 --- a/example/android/awn_core/proguard-rules.pro +++ /dev/null @@ -1,65 +0,0 @@ --keepattributes SourceFile,LineNumberTable --keep class me.carda.** { *; } - -#Keep all public and protected class --keep public class * { - public protected *; -} -#Keep class members --keepclassmembernames class * { - java.lang.Class class$(java.lang.String); - java.lang.Class class$(java.lang.String, boolean); -} - -#Keep class members and native method --keepclasseswithmembernames,includedescriptorclasses class * { - native ; -} - -#Keep ENUM --keepclassmembers,allowoptimization enum * { - public static **[] values(); public static ** valueOf(java.lang.String); -} - -#Keep Serializable classes and members --keepclassmembers class * implements java.io.Serializable { - static final long serialVersionUID; - private static final java.io.ObjectStreamField[] serialPersistentFields; - private void writeObject(java.io.ObjectOutputStream); - private void readObject(java.io.ObjectInputStream); - java.lang.Object writeReplace(); - java.lang.Object readResolve(); -} - - -##---------------Begin: proguard configuration for Gson ---------- -#Gson uses generic type information stored in a class file when working with fields. Proguard -#removes such information by default, so configure it to keep all of it. --keepattributes Signature --keepattributes InnerClasses - -#For using GSON @Expose annotation --keepattributes *Annotation* - -#Gson specific classes --dontwarn sun.misc.** -#-keep class com.google.gson.stream.** { *; } - -#Application classes that will be serialized/deserialized over Gson --keep class com.google.gson.examples.android.model.** { ; } - -#Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, -#JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) --keep class * extends com.google.gson.TypeAdapter --keep class * implements com.google.gson.TypeAdapterFactory --keep class * implements com.google.gson.JsonSerializer --keep class * implements com.google.gson.JsonDeserializer - -#Prevent R8 from leaving Data object members always null --keepclassmembers,allowobfuscation class * { - @com.google.gson.annotations.SerializedName ; -} - -#Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. --keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken --keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken \ No newline at end of file diff --git a/example/android/awn_core/src/androidTest/java/me/carda/awesome_notifications/core/ExampleInstrumentedTest.java b/example/android/awn_core/src/androidTest/java/me/carda/awesome_notifications/core/ExampleInstrumentedTest.java deleted file mode 100644 index 6ab1e01d..00000000 --- a/example/android/awn_core/src/androidTest/java/me/carda/awesome_notifications/core/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.carda.awesome_notifications.core; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("me.carda.awesome_notifications.core.test", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/example/android/awn_core/src/main/AndroidManifest.xml b/example/android/awn_core/src/main/AndroidManifest.xml deleted file mode 100644 index 0831f91f..00000000 --- a/example/android/awn_core/src/main/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/AwesomeNotifications.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/AwesomeNotifications.java deleted file mode 100644 index d5828d26..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/AwesomeNotifications.java +++ /dev/null @@ -1,890 +0,0 @@ -package me.carda.awesome_notifications.core; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.media.session.MediaSession; -import android.support.v4.media.session.MediaSessionCompat; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.Map; - -import me.carda.awesome_notifications.core.broadcasters.receivers.AwesomeEventsReceiver; -import me.carda.awesome_notifications.core.broadcasters.receivers.DismissedNotificationReceiver; -import me.carda.awesome_notifications.core.broadcasters.receivers.NotificationActionReceiver; -import me.carda.awesome_notifications.core.broadcasters.receivers.ScheduledNotificationReceiver; -import me.carda.awesome_notifications.core.broadcasters.senders.BroadcastSender; -import me.carda.awesome_notifications.core.builders.NotificationBuilder; -import me.carda.awesome_notifications.core.completion_handlers.BitmapCompletionHandler; -import me.carda.awesome_notifications.core.completion_handlers.NotificationThreadCompletionHandler; -import me.carda.awesome_notifications.core.completion_handlers.PermissionCompletionHandler; -import me.carda.awesome_notifications.core.decoders.BitmapResourceDecoder; -import me.carda.awesome_notifications.core.enumerators.ForegroundServiceType; -import me.carda.awesome_notifications.core.enumerators.ForegroundStartMode; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.enumerators.NotificationSource; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.listeners.AwesomeActionEventListener; -import me.carda.awesome_notifications.core.listeners.AwesomeEventListener; -import me.carda.awesome_notifications.core.listeners.AwesomeLifeCycleEventListener; -import me.carda.awesome_notifications.core.listeners.AwesomeNotificationEventListener; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.managers.ActionManager; -import me.carda.awesome_notifications.core.managers.BadgeManager; -import me.carda.awesome_notifications.core.managers.CancellationManager; -import me.carda.awesome_notifications.core.managers.ChannelGroupManager; -import me.carda.awesome_notifications.core.managers.ChannelManager; -import me.carda.awesome_notifications.core.managers.CreatedManager; -import me.carda.awesome_notifications.core.managers.DefaultsManager; -import me.carda.awesome_notifications.core.managers.DismissedManager; -import me.carda.awesome_notifications.core.managers.DisplayedManager; -import me.carda.awesome_notifications.core.managers.LifeCycleManager; -import me.carda.awesome_notifications.core.managers.PermissionManager; -import me.carda.awesome_notifications.core.managers.ScheduleManager; -import me.carda.awesome_notifications.core.models.AbstractModel; -import me.carda.awesome_notifications.core.models.DefaultsModel; -import me.carda.awesome_notifications.core.models.NotificationChannelGroupModel; -import me.carda.awesome_notifications.core.models.NotificationChannelModel; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.models.NotificationScheduleModel; -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; -import me.carda.awesome_notifications.core.services.ForegroundService; -import me.carda.awesome_notifications.core.threads.NotificationScheduler; -import me.carda.awesome_notifications.core.threads.NotificationSender; -import me.carda.awesome_notifications.core.utils.CalendarUtils; -import me.carda.awesome_notifications.core.utils.JsonUtils; -import me.carda.awesome_notifications.core.utils.ListUtils; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class AwesomeNotifications - implements - AwesomeNotificationEventListener, - AwesomeActionEventListener, - AwesomeLifeCycleEventListener -{ - private static final String TAG = "AwesomeNotifications"; - - public static Boolean debug = false; - - private final WeakReference wContext; - private final StringUtils stringUtils; - - public static Class actionReceiverClass = NotificationActionReceiver.class; - public static Class dismissReceiverClass = DismissedNotificationReceiver.class; - public static Class scheduleReceiverClass = ScheduledNotificationReceiver.class; - public static Class backgroundServiceClass; - - private static String packageName; - public static String getPackageName(@NonNull Context context){ - if(packageName == null) - packageName = context.getPackageName(); - return packageName; - } - - // ************************** CONSTRUCTOR *********************************** - - public AwesomeNotifications(@NonNull Context applicationContext) - throws AwesomeNotificationsException { - - AwesomeNotifications.getPackageName(applicationContext); - debug = isApplicationInDebug(applicationContext); - wContext = new WeakReference<>(applicationContext); - stringUtils = StringUtils.getInstance(); - - LifeCycleManager - .getInstance() - .subscribe(this) - .startListeners(); - - initialize(applicationContext); - - NotificationBuilder - .getNewBuilder() - .updateMainTargetClassName(applicationContext) - .setMediaSession( - new MediaSessionCompat( - applicationContext, - "PUSH_MEDIA")); - } - - private boolean isTheMainInstance = false; - public void attachAsMainInstance(AwesomeEventListener awesomeEventlistener){ - if (isTheMainInstance) - return; - - isTheMainInstance = true; - - subscribeOnAwesomeNotificationEvents(awesomeEventlistener); - - AwesomeEventsReceiver - .getInstance() - .subscribeOnActionEvents(this) - .subscribeOnNotificationEvents(this); - - Logger.d(TAG, "Awesome notifications ("+this.hashCode()+") attached to activity"); - } - - public void detachAsMainInstance(AwesomeEventListener awesomeEventlistener){ - if (!isTheMainInstance) - return; - - isTheMainInstance = false; - - unsubscribeOnAwesomeNotificationEvents(awesomeEventlistener); - - AwesomeEventsReceiver - .getInstance() - .unsubscribeOnNotificationEvents(this) - .unsubscribeOnActionEvents(this); - - - Logger.d(TAG, "Awesome notifications ("+this.hashCode()+") detached from activity"); - } - - public void dispose(){ - LifeCycleManager - .getInstance() - .unsubscribe(this); - } - - // ******************** INITIALIZATION METHOD *************************** - - public static AwesomeNotificationsExtension awesomeExtensions; - public static boolean areExtensionsLoaded = false; - - public static void initialize( - @NonNull Context context - ) throws AwesomeNotificationsException { - if(areExtensionsLoaded) return; - - if (AbstractModel.defaultValues.isEmpty()) - AbstractModel - .defaultValues - .putAll(Definitions.initialValues); - - if(awesomeExtensions == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_CLASS_NOT_FOUND, - "Awesome's plugin extension reference was not found.", - ExceptionCode.DETAILED_INITIALIZATION_FAILED+".awesomeNotifications.extensions"); - - awesomeExtensions.loadExternalExtensions(context); - areExtensionsLoaded = true; - } - - // ******************************************************** - - /// ************** EVENT INTERFACES ********************* - - @Override - public void onNewActionReceived(String eventName, ActionReceived actionReceived) { - notifyActionEvent(eventName, actionReceived); - } - - @Override - public boolean onNewActionReceivedWithInterruption(String eventName, ActionReceived actionReceived) { - return false; - } - - @Override - public void onNewNotificationReceived(String eventName, NotificationReceived notificationReceived) { - notifyNotificationEvent(eventName, notificationReceived); - } - - @Override - public void onNewLifeCycleEvent(NotificationLifeCycle lifeCycle) { - - if(!isTheMainInstance) - return; - - switch (lifeCycle){ - - case Foreground: - PermissionManager - .getInstance() - .handlePermissionResult( - PermissionManager.REQUEST_CODE, - null, - null); - break; - - case Background: - break; - - case AppKilled: - -// NotificationScheduler -// .refreshScheduledNotifications( -// wContext.get()); - } - } - - // ******************************************************** - - /// ************** OBSERVER PATTERN ********************* - - static List notificationEventListeners = new ArrayList<>(); - public AwesomeNotifications subscribeOnNotificationEvents(AwesomeNotificationEventListener listener) { - notificationEventListeners.add(listener); - return this; - } - - public AwesomeNotifications unsubscribeOnNotificationEvents(AwesomeNotificationEventListener listener) { - notificationEventListeners.remove(listener); - return this; - } - - private void notifyNotificationEvent(String eventName, NotificationReceived notificationReceived) { - notifyAwesomeEvent(eventName, notificationReceived); - for (AwesomeNotificationEventListener listener : notificationEventListeners) - listener.onNewNotificationReceived(eventName, notificationReceived); - } - - // ******************************************************** - - static List notificationActionListeners = new ArrayList<>(); - public AwesomeNotifications subscribeOnActionEvents(AwesomeActionEventListener listener) { - notificationActionListeners.add(listener); - return this; - } - - public AwesomeNotifications unsubscribeOnActionEvents(AwesomeActionEventListener listener) { - notificationActionListeners.remove(listener); - return this; - } - - private void notifyActionEvent(String eventName, ActionReceived actionReceived) { - notifyAwesomeEvent(eventName, actionReceived); - for (AwesomeActionEventListener listener : notificationActionListeners) - listener.onNewActionReceived(eventName, actionReceived); - } - - // ******************************************************** - - static List awesomeEventListeners = new ArrayList<>(); - public AwesomeNotifications subscribeOnAwesomeNotificationEvents(AwesomeEventListener listener) { - awesomeEventListeners.add(listener); - return this; - } - - public AwesomeNotifications unsubscribeOnAwesomeNotificationEvents(AwesomeEventListener listener) { - awesomeEventListeners.remove(listener); - return this; - } - - private void notifyAwesomeEvent(String eventName, ActionReceived actionReceived) { - for (AwesomeEventListener listener : awesomeEventListeners) - listener.onNewAwesomeEvent(eventName, actionReceived.toMap()); - } - - private void notifyAwesomeEvent(String eventName, NotificationReceived notificationReceived) { - for (AwesomeEventListener listener : awesomeEventListeners) - listener.onNewAwesomeEvent(eventName, notificationReceived.toMap()); - } - - private void notifyAwesomeEvent(String eventName, Map content) { - for (AwesomeEventListener listener : awesomeEventListeners) - listener.onNewAwesomeEvent(eventName, content); - } - - /// *********************************** - - public static NotificationLifeCycle getApplicationLifeCycle(){ - return LifeCycleManager.getApplicationLifeCycle(); - } - - public void getDrawableData(String bitmapReference, BitmapCompletionHandler completionHandler){ - new BitmapResourceDecoder( - wContext.get(), - bitmapReference, - completionHandler - ).execute(); - } - - public void setActionHandle(Long actionCallbackHandle) throws AwesomeNotificationsException { - DefaultsManager.setActionCallbackDispatcher(wContext.get(), actionCallbackHandle); - DefaultsManager.commitChanges(wContext.get()); - - if(actionCallbackHandle != 0L) - { - recoverNotificationCreated(wContext.get()); - recoverNotificationDisplayed(wContext.get()); - recoverNotificationsDismissed(wContext.get()); - recoverNotificationActions(wContext.get()); - } - } - - public Long getActionHandle() throws AwesomeNotificationsException { - DefaultsModel defaults = DefaultsManager.getDefaults(wContext.get()); - return defaults.silentDataCallback == null ? - 0L : Long.parseLong(defaults.silentDataCallback); - } - - public List listAllPendingSchedules() throws AwesomeNotificationsException { - NotificationScheduler.refreshScheduledNotifications(wContext.get()); - return ScheduleManager.listSchedules(wContext.get()); - } - - public boolean isApplicationInDebug(@NonNull Context context){ - return 0 != ( - context.getApplicationInfo().flags & - ApplicationInfo.FLAG_DEBUGGABLE); - } - - // ***************************** INITIALIZATION FUNCTIONS ********************************** - - public void initialize( - @Nullable String defaultIconPath, - @Nullable List channelsData, - @Nullable List channelGroupsData, - @NonNull Long dartCallback, - boolean debug - ) throws AwesomeNotificationsException { - - Context currentContext = wContext.get(); - - DefaultsManager - .saveDefault( - currentContext, - defaultIconPath, - dartCallback); - - DefaultsManager - .commitChanges(currentContext); - - if (!ListUtils.isNullOrEmpty(channelGroupsData)) - setChannelGroups(wContext.get(), channelGroupsData); - - if (ListUtils.isNullOrEmpty(channelsData)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INITIALIZATION_EXCEPTION, - "At least one channel is required", - ExceptionCode.DETAILED_REQUIRED_ARGUMENTS+".channelList"); - - setChannels(currentContext, channelsData); - - AwesomeNotifications.debug = debug && isApplicationInDebug(currentContext); - - NotificationScheduler - .refreshScheduledNotifications(currentContext); - - if (AwesomeNotifications.debug) - Logger.d(TAG, "Awesome Notifications initialized"); - } - - private void setChannelGroups( - @NonNull Context context, - @NonNull List channelGroupsData) - throws AwesomeNotificationsException { - - List channelGroups = new ArrayList<>(); - - for (Object channelDataObject : channelGroupsData) - if (channelDataObject instanceof Map) { - @SuppressWarnings("unchecked") - Map channelData = (Map) channelDataObject; - NotificationChannelGroupModel channelGroup = - new NotificationChannelGroupModel().fromMap(channelData); - - if (channelGroup != null) { - channelGroups.add(channelGroup); - } else { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INITIALIZATION_EXCEPTION, - "Invalid channel group: " + JsonUtils.toJson(channelData), - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channelGroup.data"); - } - } - - for (NotificationChannelGroupModel channelGroupModel : channelGroups) - ChannelGroupManager.saveChannelGroup(context, channelGroupModel); - - ChannelManager - .getInstance() - .commitChanges(context); - } - - private void setChannels( - @NonNull Context context, - @NonNull List channelsData) - throws AwesomeNotificationsException { - - List channels = new ArrayList<>(); - boolean forceUpdate = false; - - for (Object channelDataObject : channelsData) { - if (channelDataObject instanceof Map) { - @SuppressWarnings("unchecked") - Map channelData = (Map) channelDataObject; - NotificationChannelModel channelModel = new NotificationChannelModel().fromMap(channelData); - - Object forceUpdateObject = channelData.get(Definitions.CHANNEL_FORCE_UPDATE); - forceUpdate = forceUpdateObject != null && Boolean.parseBoolean(forceUpdateObject.toString()); - - if (channelModel != null) { - channels.add(channelModel); - } else { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid channel: " + JsonUtils.toJson(channelData), - ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".channel.data"); - } - } - if (channelDataObject instanceof NotificationChannelModel) { - @SuppressWarnings("unchecked") - NotificationChannelModel channelModel = (NotificationChannelModel) channelDataObject; - channels.add(channelModel); - } - } - - ChannelManager channelManager = ChannelManager.getInstance(); - for (NotificationChannelModel channelModel : channels) - channelManager.saveChannel(context, channelModel, true, forceUpdate); - - channelManager.commitChanges(context); - } - - // ***************************** RECOVER FUNCTIONS ********************************** - - private void recoverNotificationCreated(@NonNull Context context) throws AwesomeNotificationsException { - List lostCreated = CreatedManager.listCreated(context); - - if (lostCreated != null) - for (NotificationReceived created : lostCreated) - try { - - created.validate(context); - - CreatedManager.removeCreated(context, created.id); - CreatedManager.commitChanges(context); - - BroadcastSender.sendBroadcastNotificationCreated(context, created); - - } catch (AwesomeNotificationsException e) { - if (AwesomeNotifications.debug) - Logger.d(TAG, String.format("%s", e.getMessage())); - e.printStackTrace(); - } - } - - private void recoverNotificationDisplayed(@NonNull Context context) throws AwesomeNotificationsException { - List lostDisplayed = DisplayedManager.listDisplayed(context); - - if (lostDisplayed != null) - for (NotificationReceived displayed : lostDisplayed) - try { - displayed.validate(context); - - DisplayedManager.removeDisplayed(context, displayed.id); - DisplayedManager.commitChanges(context); - - BroadcastSender.sendBroadcastNotificationDisplayed(context, displayed); - - } catch (AwesomeNotificationsException e) { - if (AwesomeNotifications.debug) - Logger.d(TAG, String.format("%s", e.getMessage())); - e.printStackTrace(); - } - } - - private void recoverNotificationActions(@NonNull Context context) throws AwesomeNotificationsException { - List lostActions = ActionManager.listActions(context); - - if (lostActions != null) - for (ActionReceived received : lostActions) - try { - received.validate(context); - - ActionManager.removeAction(context, received.id); - ActionManager.commitChanges(context); - - BroadcastSender.sendBroadcastDefaultAction(context, received, false); - - } catch (AwesomeNotificationsException e) { - if (AwesomeNotifications.debug) - Logger.d(TAG, String.format("%s", e.getMessage())); - e.printStackTrace(); - } - } - - private void recoverNotificationsDismissed(@NonNull Context context) throws AwesomeNotificationsException { - List lostDismissed = DismissedManager.listDismisses(context); - - if (lostDismissed != null) - for (ActionReceived received : lostDismissed) - try { - received.validate(context); - - DismissedManager.removeDismissed(context, received.id); - DismissedManager.commitChanges(context); - - BroadcastSender.sendBroadcastNotificationDismissed(context, received); - - } catch (AwesomeNotificationsException e) { - if (AwesomeNotifications.debug) - Logger.d(TAG, String.format("%s", e.getMessage())); - e.printStackTrace(); - } - } - - // ***************************** NOTIFICATION METHODS ********************************** - - public void createNotification(@NonNull NotificationModel notificationModel, NotificationThreadCompletionHandler threadCompletionHandler) throws AwesomeNotificationsException { - - if (!PermissionManager - .getInstance() - .areNotificationsGloballyAllowed(wContext.get())) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INSUFFICIENT_PERMISSIONS, - "Notifications are disabled", - ExceptionCode.DETAILED_INSUFFICIENT_PERMISSIONS+".global"); - - if (notificationModel.schedule == null) - NotificationSender - .send( - wContext.get(), - NotificationBuilder.getNewBuilder(), - NotificationSource.Local, - AwesomeNotifications.getApplicationLifeCycle(), - notificationModel, - null, - threadCompletionHandler); - else - NotificationScheduler - .schedule( - wContext.get(), - NotificationSource.Schedule, - notificationModel, - threadCompletionHandler); - } - - public void clearStoredActions() throws AwesomeNotificationsException { - ActionManager.clearAllActions(wContext.get()); - } - - public boolean captureNotificationActionFromActivity(Activity startActivity) throws Exception { - if (startActivity == null) - return false; - return captureNotificationActionFromIntent(startActivity.getIntent(), true); - } - - public boolean captureNotificationActionFromIntent(Intent intent) throws Exception { - return captureNotificationActionFromIntent(intent, false); - } - - public boolean captureNotificationActionFromIntent(Intent intent, boolean onInitialization) throws Exception { - if (intent == null) - return false; - - String actionName = intent.getAction(); - if (actionName == null) - return false; - - Boolean isStandardAction = Definitions.SELECT_NOTIFICATION.equals(actionName); - Boolean isButtonAction = actionName.startsWith(Definitions.NOTIFICATION_BUTTON_ACTION_PREFIX); - - boolean isNotificationAction = isStandardAction || isButtonAction; - if (isNotificationAction) - NotificationActionReceiver.receiveActionIntent(wContext.get(), intent, onInitialization); - - return isNotificationAction; - } - - public ActionReceived getInitialNotificationAction( - @NonNull boolean removeActionEvent - ) throws AwesomeNotificationsException { - ActionReceived initialActionReceived = ActionManager.getInitialActionReceived(); - if (!removeActionEvent) return initialActionReceived; - if(initialActionReceived == null) return null; - - Context context = wContext.get(); - ActionManager.removeAction(context, initialActionReceived.id); - ActionManager.commitChanges(context); - - return initialActionReceived; - } - - // ***************************** CHANNEL METHODS ********************************** - - public boolean setChannel(@NonNull NotificationChannelModel channelModel, boolean forceUpdate) - throws AwesomeNotificationsException { - - ChannelManager - .getInstance() - .saveChannel(wContext.get(), channelModel, false, forceUpdate) - .commitChanges(wContext.get()); - - return true; - } - - public boolean removeChannel(@NonNull String channelKey) throws AwesomeNotificationsException { - boolean removed = ChannelManager - .getInstance() - .removeChannel(wContext.get(), channelKey); - - ChannelManager - .getInstance() - .commitChanges(wContext.get()); - - return removed; - } - - public List getAllChannels(@NonNull Context context) throws AwesomeNotificationsException { - return ChannelManager - .getInstance() - .listChannels(context); - } - - // ************************** FOREGROUND SERVICE METHODS ******************************* - - public void startForegroundService( - @NonNull NotificationModel notificationModel, - @NonNull ForegroundStartMode startMode, - @NonNull ForegroundServiceType foregroundServiceType - ){ - ForegroundService.startNewForegroundService( - wContext.get(), - notificationModel, - startMode, - foregroundServiceType); - } - - public void stopForegroundService(Integer notificationId) { - ForegroundService.stop(notificationId); - } - - public Calendar getNextValidDate(@NonNull NotificationScheduleModel scheduleModel, @Nullable Calendar fixedDate) throws AwesomeNotificationsException { - return scheduleModel.getNextValidDate(fixedDate); - } - - public String getLocalTimeZone() { - return CalendarUtils.getInstance().getLocalTimeZone().getID(); - } - - public Object getUtcTimeZone() { - return CalendarUtils.getInstance().getUtcTimeZone().getID(); - } - - public int getGlobalBadgeCounter() { - return BadgeManager.getInstance().getGlobalBadgeCounter(wContext.get()); - } - - public void setGlobalBadgeCounter(@NonNull Integer count) { - // Android resets badges automatically when all notifications are cleared - BadgeManager.getInstance().setGlobalBadgeCounter(wContext.get(), count); - } - - public void resetGlobalBadgeCounter() { - BadgeManager.getInstance().resetGlobalBadgeCounter(wContext.get()); - } - - public int incrementGlobalBadgeCounter() { - return BadgeManager.getInstance().incrementGlobalBadgeCounter(wContext.get()); - } - - public int decrementGlobalBadgeCounter() { - return BadgeManager.getInstance().decrementGlobalBadgeCounter(wContext.get()); - } - - public boolean dismissNotification( - @NonNull Integer notificationId - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .dismissNotification(wContext.get(), notificationId); - } - - public boolean cancelSchedule( - @NonNull Integer notificationId - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .cancelSchedule(wContext.get(), notificationId); - } - - public boolean cancelNotification( - @NonNull Integer notificationId - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .cancelNotification(wContext.get(), notificationId); - } - - public boolean dismissNotificationsByChannelKey( - @NonNull String channelKey - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .dismissNotificationsByChannelKey(wContext.get(), channelKey); - } - - public boolean cancelSchedulesByChannelKey( - @NonNull String channelKey - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .cancelSchedulesByChannelKey(wContext.get(), channelKey); - } - - public boolean cancelNotificationsByChannelKey( - @NonNull String channelKey - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .cancelNotificationsByChannelKey(wContext.get(), channelKey); - } - - public boolean dismissNotificationsByGroupKey( - @NonNull String groupKey - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .dismissNotificationsByGroupKey(wContext.get(), groupKey); - } - - public boolean cancelSchedulesByGroupKey( - @NonNull String groupKey - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .cancelSchedulesByGroupKey(wContext.get(), groupKey); - } - - public boolean cancelNotificationsByGroupKey( - @NonNull String groupKey - ) throws AwesomeNotificationsException { - return CancellationManager - .getInstance() - .cancelNotificationsByGroupKey(wContext.get(), groupKey); - } - - public void dismissAllNotifications() throws AwesomeNotificationsException { - CancellationManager - .getInstance() - .dismissAllNotifications(wContext.get()); - } - - public void cancelAllSchedules() throws AwesomeNotificationsException { - - CancellationManager - .getInstance() - .cancelAllSchedules(wContext.get()); - } - - public void cancelAllNotifications() throws AwesomeNotificationsException { - CancellationManager - .getInstance() - .cancelAllNotifications(wContext.get()); - } - - public Object areNotificationsGloballyAllowed() { - return PermissionManager - .getInstance() - .areNotificationsGloballyAllowed(wContext.get()); - } - - public void showNotificationPage( - @Nullable String channelKey, - @NonNull PermissionCompletionHandler permissionCompletionHandler - ){ - if(stringUtils.isNullOrEmpty(channelKey)) - PermissionManager - .getInstance() - .showNotificationConfigPage( - wContext.get(), - permissionCompletionHandler); - else - PermissionManager - .getInstance() - .showChannelConfigPage( - wContext.get(), - channelKey, - permissionCompletionHandler); - } - - public void showPreciseAlarmPage( - @NonNull PermissionCompletionHandler permissionCompletionHandler - ){ - PermissionManager - .getInstance() - .showPreciseAlarmPage( - wContext.get(), - permissionCompletionHandler); - } - - public void showDnDGlobalOverridingPage( - @NonNull PermissionCompletionHandler permissionCompletionHandler - ){ - PermissionManager - .getInstance() - .showDnDGlobalOverridingPage( - wContext.get(), - permissionCompletionHandler); - } - - public List arePermissionsAllowed( - @Nullable String channelKey, - @NonNull List permissions - ) throws AwesomeNotificationsException { - return PermissionManager - .getInstance() - .arePermissionsAllowed( - wContext.get(), - channelKey, - permissions); - } - - public List shouldShowRationale( - @Nullable String channelKey, - @NonNull List permissions - ) throws AwesomeNotificationsException { - return PermissionManager - .getInstance() - .shouldShowRationale( - wContext.get(), - channelKey, - permissions); - } - - public void requestUserPermissions( - @NonNull Activity activity, - @Nullable String channelKey, - @NonNull List permissions, - @NonNull PermissionCompletionHandler permissionCompletionHandler) - throws AwesomeNotificationsException - { - PermissionManager - .getInstance() - .requestUserPermissions( - activity, - wContext.get(), - channelKey, - permissions, - permissionCompletionHandler); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/AwesomeNotificationsExtension.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/AwesomeNotificationsExtension.java deleted file mode 100644 index 8e06b811..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/AwesomeNotificationsExtension.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.carda.awesome_notifications.core; - -import android.content.Context; - -public abstract class AwesomeNotificationsExtension { - public abstract void loadExternalExtensions(Context context); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/Definitions.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/Definitions.java deleted file mode 100644 index 0584a058..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/Definitions.java +++ /dev/null @@ -1,298 +0,0 @@ -package me.carda.awesome_notifications.core; - -import java.util.HashMap; -import java.util.Map; - -import me.carda.awesome_notifications.core.enumerators.ActionType; -import me.carda.awesome_notifications.core.enumerators.DefaultRingtoneType; -import me.carda.awesome_notifications.core.enumerators.GroupAlertBehaviour; -import me.carda.awesome_notifications.core.enumerators.GroupSort; -import me.carda.awesome_notifications.core.enumerators.NotificationImportance; -import me.carda.awesome_notifications.core.enumerators.NotificationLayout; -import me.carda.awesome_notifications.core.enumerators.NotificationPrivacy; -import me.carda.awesome_notifications.core.utils.CalendarUtils; - -public interface Definitions { - - String AWESOME_FOREGROUND_ID = "me.carda.awesome_notifications.notifications.system.services.ForegroundService$StartParameter"; - String BROADCAST_FCM_TOKEN = "me.carda.awesome_notifications.notifications.system.services.firebase.TOKEN"; - String EXTRA_BROADCAST_FCM_TOKEN = "token"; - String EXTRA_ANDROID_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON"; - - String MEDIA_VALID_NETWORK = "^((http|https):\\/\\/)(www\\.)?[a-zA-Z0-9@:%.\\-_\\\\+~#?&\\/\\/=]{2,2048}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%.\\-_\\\\+~#?&\\/=]*)$"; - String MEDIA_VALID_FILE = "^file?:\\/\\/"; - String MEDIA_VALID_ASSET = "^asset?:\\/\\/"; - String MEDIA_VALID_RESOURCE = "^resource?:\\/\\/"; - - String INITIALIZE_DEBUG_MODE = "debug"; - String INITIALIZE_STORE_INITIAL_ACTION = "storeInitialAction"; - String INITIALIZE_DEFAULT_ICON = "defaultIcon"; - String INITIALIZE_CHANNELS = "initializeChannels"; - String INITIALIZE_CHANNEL_GROUPS = "initializeChannelGroups"; - - String BROADCAST_CREATED_NOTIFICATION = "broadcast.awesome_notifications.CREATED_NOTIFICATION"; - String BROADCAST_DISPLAYED_NOTIFICATION = "broadcast.awesome_notifications.DISPLAYED_NOTIFICATION"; - String BROADCAST_DISMISSED_NOTIFICATION = "broadcast.awesome_notifications.DISMISSED_NOTIFICATION"; - String BROADCAST_SILENT_ACTION = "broadcast.awesome_notifications.SILENT_ACTION"; - String BROADCAST_DEFAULT_ACTION = "broadcast.awesome_notifications.DEFAULT_ACTION"; - String BROADCAST_BACKGROUND_ACTION ="broadcast.awesome_notifications.BACKGROUND_ACTION"; - String EXTRA_BROADCAST_MESSAGE = "notification"; - - String CHANNEL_FLUTTER_PLUGIN = "awesome_notifications"; - String DART_REVERSE_CHANNEL = "awesome_notifications_reverse"; - - String BADGE_COUNT = "badgeCount"; - String ACTION_HANDLE = "actionHandle"; - String SILENT_HANDLE = "silentHandle"; - String STORE_INITIAL_ACTION = "storeInitialAction"; - String BACKGROUND_HANDLE = "awesomeDartBGHandle"; - - String SCHEDULER_HELPER_SHARED = "awnot.sh."; - String SCHEDULER_HELPER_ALL = "all"; - String SCHEDULER_HELPER_GROUP = "group"; - String SCHEDULER_HELPER_CHANNEL = "channel"; - - String NOTIFICATION_MODEL = "notificationModel"; - String NOTIFICATION_MODEL_CONTENT = "content"; - String NOTIFICATION_MODEL_SCHEDULE = "schedule"; - String NOTIFICATION_MODEL_BUTTONS = "actionButtons"; - String NOTIFICATION_SILENT_ACTION = "silentAction"; - String NOTIFICATION_RECEIVED_ACTION = "receivedAction"; - - String NOTIFICATION_SERVICE_START_MODE = "startMode"; - String NOTIFICATION_FOREGROUND_SERVICE_TYPE = "foregroundServiceType"; - - String SHARED_DEFAULTS = "defaults"; - String SHARED_MANAGER = "sharedManager"; - String SHARED_CHANNELS = "channels"; - String SHARED_CREATED = "created"; - String SHARED_CHANNEL_GROUP = "channelGroup"; - String SHARED_DISPLAYED = "displayed"; - String SHARED_DISMISSED = "dismissed"; - String SHARED_SCHEDULED_NOTIFICATIONS = "schedules"; - String SHARED_SCHEDULED_NOT_IDS = "schedulesIds"; - String SHARED_SCHEDULED_NOT_GROUPS = "schedulesGroups"; - String SHARED_SCHEDULED_NOT_CHANNELS = "schedulesChannels"; - - String CHANNEL_METHOD_INITIALIZE = "initialize"; - String CHANNEL_METHOD_PUSH_NEXT_DATA = "pushNext"; - String CHANNEL_METHOD_GET_DRAWABLE_DATA = "getDrawableData"; - String CHANNEL_METHOD_ENABLE_WAKELOCK = "enableWakelock"; - String CHANNEL_METHOD_CREATE_NOTIFICATION = "createNewNotification"; - - String CHANNEL_METHOD_GET_UTC_TIMEZONE_IDENTIFIER = "getUtcTimeZoneIdentifier"; - String CHANNEL_METHOD_GET_LOCAL_TIMEZONE_IDENTIFIER = "getLocalTimeZoneIdentifier"; - - String CHANNEL_METHOD_GET_FCM_TOKEN = "getFirebaseToken"; - String CHANNEL_METHOD_NEW_FCM_TOKEN = "newTokenReceived"; - String CHANNEL_METHOD_IS_FCM_AVAILABLE = "isFirebaseAvailable"; - - String CHANNEL_METHOD_SET_NOTIFICATION_CHANNEL = "setNotificationChannel"; - String CHANNEL_METHOD_REMOVE_NOTIFICATION_CHANNEL = "removeNotificationChannel"; - - String CHANNEL_METHOD_SHOW_NOTIFICATION_PAGE = "showNotificationPage"; - String CHANNEL_METHOD_SHOW_ALARM_PAGE = "showAlarmPage"; - String CHANNEL_METHOD_SHOW_GLOBAL_DND_PAGE = "showGlobalDndPage"; - String CHANNEL_METHOD_GET_INITIAL_ACTION = "getInitialAction"; - String CHANNEL_METHOD_CLEAR_STORED_ACTION = "clearStoredActions"; - String CHANNEL_METHOD_IS_NOTIFICATION_ALLOWED = "isNotificationAllowed"; - String CHANNEL_METHOD_REQUEST_NOTIFICATIONS = "requestNotifications"; - String CHANNEL_METHOD_CHECK_PERMISSIONS = "checkPermissions"; - String CHANNEL_METHOD_SHOULD_SHOW_RATIONALE = "shouldShowRationale"; - - String CHANNEL_METHOD_GET_BADGE_COUNT = "getBadgeCount"; - String CHANNEL_METHOD_SET_BADGE_COUNT = "setBadgeCount"; - String CHANNEL_METHOD_INCREMENT_BADGE_COUNT = "incBadgeCount"; - String CHANNEL_METHOD_DECREMENT_BADGE_COUNT = "decBadgeCount"; - String CHANNEL_METHOD_RESET_BADGE = "resetBadge"; - - String CHANNEL_METHOD_GET_APP_LIFE_CYCLE = "getAppLifeCycle"; - - String CHANNEL_METHOD_GET_NEXT_DATE = "getNextDate"; - String CHANNEL_METHOD_DISMISS_NOTIFICATION = "dismissNotification"; - String CHANNEL_METHOD_CANCEL_NOTIFICATION = "cancelNotification"; - String CHANNEL_METHOD_CANCEL_SCHEDULE = "cancelSchedule"; - String CHANNEL_METHOD_DISMISS_NOTIFICATIONS_BY_CHANNEL_KEY = "dismissNotificationsByChannelKey"; - String CHANNEL_METHOD_CANCEL_NOTIFICATIONS_BY_CHANNEL_KEY = "cancelNotificationsByChannelKey"; - String CHANNEL_METHOD_CANCEL_SCHEDULES_BY_CHANNEL_KEY = "cancelSchedulesByChannelKey"; - String CHANNEL_METHOD_DISMISS_NOTIFICATIONS_BY_GROUP_KEY = "dismissNotificationsByGroupKey"; - String CHANNEL_METHOD_CANCEL_NOTIFICATIONS_BY_GROUP_KEY = "cancelNotificationsByGroupKey"; - String CHANNEL_METHOD_CANCEL_SCHEDULES_BY_GROUP_KEY = "cancelSchedulesByGroupKey"; - String CHANNEL_METHOD_DISMISS_ALL_NOTIFICATIONS = "dismissAllNotifications"; - String CHANNEL_METHOD_CANCEL_ALL_SCHEDULES = "cancelAllSchedules"; - String CHANNEL_METHOD_CANCEL_ALL_NOTIFICATIONS = "cancelAllNotifications"; - String CHANNEL_METHOD_SILENT_CALLBACK = "silentCallbackReference"; - String CHANNEL_METHOD_SET_ACTION_HANDLE = "setActionHandle"; - - String CHANNEL_METHOD_START_FOREGROUND = "startForeground"; - String CHANNEL_METHOD_STOP_FOREGROUND = "stopForeground"; - - String CHANNEL_METHOD_LIST_ALL_SCHEDULES = "listAllSchedules"; - String CHANNEL_FORCE_UPDATE = "forceUpdate"; - - String EVENT_NOTIFICATION_CREATED = "notificationCreated"; - String EVENT_NOTIFICATION_DISPLAYED = "notificationDisplayed"; - String EVENT_NOTIFICATION_DISMISSED = "notificationDismissed"; - String EVENT_DEFAULT_ACTION = "defaultAction"; - String EVENT_SILENT_ACTION = "silentAction"; - - String FIREBASE_ENABLED = "FIREBASE_ENABLED"; - String SELECT_NOTIFICATION = "SELECT_NOTIFICATION"; - String DISMISSED_NOTIFICATION = "DISMISSED_NOTIFICATION"; - String MEDIA_BUTTON = "MEDIA_BUTTON"; - String NOTIFICATION_BUTTON_ACTION_PREFIX = "ACTION_NOTIFICATION"; - - String SHARED_PREFERENCES_CHANNEL_MANAGER = "channel_manager"; - - String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - - String NOTIFICATION_ICON_RESOURCE_ID = "iconResourceId"; - - String NOTIFICATION_SCHEDULE_CREATED_DATE = "createdDate"; - String NOTIFICATION_SCHEDULE_ERA = "era"; - String NOTIFICATION_SCHEDULE_TIMEZONE = "timeZone"; - String NOTIFICATION_SCHEDULE_PRECISE_ALARM = "preciseAlarm"; - String NOTIFICATION_SCHEDULE_YEAR = "year"; - String NOTIFICATION_SCHEDULE_MONTH = "month"; - String NOTIFICATION_SCHEDULE_DAY = "day"; - String NOTIFICATION_SCHEDULE_HOUR = "hour"; - String NOTIFICATION_SCHEDULE_MINUTE = "minute"; - String NOTIFICATION_SCHEDULE_SECOND = "second"; - String NOTIFICATION_SCHEDULE_MILLISECOND = "millisecond"; - String NOTIFICATION_SCHEDULE_WEEKDAY = "weekday"; - String NOTIFICATION_SCHEDULE_WEEKOFMONTH = "weekOfMonth"; - String NOTIFICATION_SCHEDULE_WEEKOFYEAR = "weekOfYear"; - String NOTIFICATION_SCHEDULE_INTERVAL = "interval"; - String NOTIFICATION_SCHEDULE_REPEATS = "repeats"; - - String NOTIFICATION_CREATED_SOURCE = "createdSource"; - String NOTIFICATION_CREATED_LIFECYCLE = "createdLifeCycle"; - String NOTIFICATION_DISPLAYED_LIFECYCLE = "displayedLifeCycle"; - String NOTIFICATION_DISMISSED_LIFECYCLE = "dismissedLifeCycle"; - String NOTIFICATION_ACTION_LIFECYCLE = "actionLifeCycle"; - String NOTIFICATION_CREATED_DATE = "createdDate"; - String NOTIFICATION_ACTION_DATE = "actionDate"; - String NOTIFICATION_DISPLAYED_DATE = "displayedDate"; - String NOTIFICATION_DISMISSED_DATE = "dismissedDate"; - - String NOTIFICATION_ID = "id"; - String NOTIFICATION_RANDOM_ID = "randomId"; - String NOTIFICATION_LAYOUT = "notificationLayout"; - String NOTIFICATION_TITLE = "title"; - String NOTIFICATION_BODY = "body"; - String NOTIFICATION_TIMESTAMP = "timestamp"; - String NOTIFICATION_SUMMARY = "summary"; - String NOTIFICATION_SHOW_WHEN = "showWhen"; - String NOTIFICATION_BUTTON_KEY_PRESSED = "buttonKeyPressed"; - String NOTIFICATION_BUTTON_KEY_INPUT = "buttonKeyInput"; - String NOTIFICATION_JSON = "notificationJson"; - String NOTIFICATION_ACTION_JSON = "notificationActionJson"; - - String NOTIFICATION_MESSAGES = "messages"; - String NOTIFICATION_BUTTON_KEY = "key"; - String NOTIFICATION_BUTTON_ICON = "icon"; - String NOTIFICATION_BUTTON_LABEL = "label"; - String NOTIFICATION_ACTION_TYPE = "actionType"; - String NOTIFICATION_REQUIRE_INPUT_TEXT = "requireInputText"; - - String NOTIFICATION_PAYLOAD = "payload"; - String NOTIFICATION_INITIAL_FIXED_DATE = "fixedDate"; - - String NOTIFICATION_ROUNDED_LARGE_ICON = "roundedLargeIcon"; - String NOTIFICATION_ROUNDED_BIG_PICTURE = "roundedBigPicture"; - - String NOTIFICATION_INITIAL_DATE_TIME = "initialDateTime"; - String NOTIFICATION_EXPIRATION_DATE_TIME = "expirationDateTime"; - String NOTIFICATION_CRONTAB_EXPRESSION = "crontabExpression"; - String NOTIFICATION_PRECISE_SCHEDULES = "preciseSchedules"; - String NOTIFICATION_ENABLED = "enabled"; - String NOTIFICATION_AUTO_DISMISSIBLE = "autoDismissible"; - String NOTIFICATION_IS_DANGEROUS_OPTION = "isDangerousOption"; - String NOTIFICATION_PERMISSIONS = "permissions"; - - String NOTIFICATION_SHOW_IN_COMPACT_VIEW = "showInCompactView"; - String NOTIFICATION_LOCKED = "locked"; - String NOTIFICATION_DISPLAY_ON_FOREGROUND = "displayOnForeground"; - String NOTIFICATION_DISPLAY_ON_BACKGROUND = "displayOnBackground"; - String NOTIFICATION_ICON = "icon"; - String NOTIFICATION_FULL_SCREEN_INTENT = "fullScreenIntent"; - String NOTIFICATION_WAKE_UP_SCREEN = "wakeUpScreen"; - String NOTIFICATION_PLAY_SOUND = "playSound"; - String NOTIFICATION_SOUND_SOURCE = "soundSource"; - String NOTIFICATION_ENABLE_VIBRATION = "enableVibration"; - String NOTIFICATION_VIBRATION_PATTERN = "vibrationPattern"; - String NOTIFICATION_GROUP_KEY = "groupKey"; - String NOTIFICATION_GROUP_SORT = "groupSort"; - String NOTIFICATION_GROUP_ALERT_BEHAVIOR = "groupAlertBehavior"; - String NOTIFICATION_PRIVACY = "privacy"; - String NOTIFICATION_CATEGORY = "category"; - String NOTIFICATION_CUSTOM_SOUND = "customSound"; - String NOTIFICATION_DEFAULT_PRIVACY = "defaultPrivacy"; - String NOTIFICATION_DEFAULT_RINGTONE_TYPE = "defaultRingtoneType"; - String NOTIFICATION_PRIVATE_MESSAGE = "privateMessage"; - String NOTIFICATION_ONLY_ALERT_ONCE = "onlyAlertOnce"; - String NOTIFICATION_CHANNEL_KEY = "channelKey"; - String NOTIFICATION_CHANNEL_NAME = "channelName"; - String NOTIFICATION_CHANNEL_DESCRIPTION = "channelDescription"; - String NOTIFICATION_CHANNEL_SHOW_BADGE = "channelShowBadge"; - String NOTIFICATION_CHANNEL_GROUP_NAME = "channelGroupName"; - String NOTIFICATION_CHANNEL_GROUP_KEY = "channelGroupKey"; - String NOTIFICATION_CHANNEL_CRITICAL_ALERTS = "criticalAlerts"; - String NOTIFICATION_IMPORTANCE = "importance"; - String NOTIFICATION_COLOR = "color"; - String NOTIFICATION_BACKGROUND_COLOR = "backgroundColor"; - String NOTIFICATION_DEFAULT_COLOR = "defaultColor"; - String NOTIFICATION_APP_ICON = "defaultIcon"; - String NOTIFICATION_LARGE_ICON = "largeIcon"; - String NOTIFICATION_BIG_PICTURE = "bigPicture"; - String NOTIFICATION_HIDE_LARGE_ICON_ON_EXPAND = "hideLargeIconOnExpand"; - String NOTIFICATION_PROGRESS = "progress"; - String NOTIFICATION_BADGE = "badge"; - String NOTIFICATION_ENABLE_LIGHTS = "enableLights"; - String NOTIFICATION_LED_COLOR = "ledColor"; - String NOTIFICATION_LED_ON_MS = "ledOnMs"; - String NOTIFICATION_LED_OFF_MS = "ledOffMs"; - String NOTIFICATION_TICKER = "ticker"; - String NOTIFICATION_ALLOW_WHILE_IDLE = "allowWhileIdle"; - String NOTIFICATION_BG_HANDLE_CLASS = "bgHandleClass"; - - Map initialValues = new HashMap(){{ - put(Definitions.NOTIFICATION_SCHEDULE_REPEATS, true); - put(Definitions.NOTIFICATION_ID, 0); - put(Definitions.NOTIFICATION_IMPORTANCE, NotificationImportance.Default); - put(Definitions.NOTIFICATION_LAYOUT, NotificationLayout.Default); - put(Definitions.NOTIFICATION_GROUP_SORT, GroupSort.Desc); - put(Definitions.NOTIFICATION_GROUP_ALERT_BEHAVIOR, GroupAlertBehaviour.All); - put(Definitions.NOTIFICATION_DEFAULT_PRIVACY, NotificationPrivacy.Private); - //put(Definitions.NOTIFICATION_PRIVACY, NotificationPrivacy.Private); - put(Definitions.NOTIFICATION_CHANNEL_DESCRIPTION, "Notifications"); - put(Definitions.NOTIFICATION_CHANNEL_NAME, "Notifications"); - put(Definitions.NOTIFICATION_CHANNEL_SHOW_BADGE, false); - put(Definitions.NOTIFICATION_DISPLAY_ON_FOREGROUND, true); - put(Definitions.NOTIFICATION_DISPLAY_ON_BACKGROUND, true); - put(Definitions.NOTIFICATION_HIDE_LARGE_ICON_ON_EXPAND, false); - put(Definitions.NOTIFICATION_ENABLED, true); - put(Definitions.NOTIFICATION_SHOW_WHEN, true); - put(Definitions.NOTIFICATION_REQUIRE_INPUT_TEXT, false); - put(Definitions.NOTIFICATION_ACTION_TYPE, ActionType.Default); - put(Definitions.NOTIFICATION_PAYLOAD, null); - put(Definitions.NOTIFICATION_ENABLE_VIBRATION, true); - put(Definitions.NOTIFICATION_DEFAULT_COLOR, 0xFF000000); - put(Definitions.NOTIFICATION_LED_COLOR, 0xFFFFFFFF); - put(Definitions.NOTIFICATION_ENABLE_LIGHTS, true); - put(Definitions.NOTIFICATION_LED_OFF_MS, 700); - put(Definitions.NOTIFICATION_LED_ON_MS, 300); - put(Definitions.NOTIFICATION_PLAY_SOUND, true); - put(Definitions.NOTIFICATION_AUTO_DISMISSIBLE, true); - put(Definitions.NOTIFICATION_DEFAULT_RINGTONE_TYPE, DefaultRingtoneType.Notification); - put(Definitions.NOTIFICATION_SCHEDULE_TIMEZONE, CalendarUtils.getInstance().getLocalTimeZone().toString()); - put(Definitions.NOTIFICATION_ALLOW_WHILE_IDLE, false); - put(Definitions.NOTIFICATION_ONLY_ALERT_ONCE, false); - put(Definitions.NOTIFICATION_SHOW_IN_COMPACT_VIEW, true); - put(Definitions.NOTIFICATION_IS_DANGEROUS_OPTION, false); - put(Definitions.NOTIFICATION_WAKE_UP_SCREEN, false); - put(Definitions.NOTIFICATION_CHANNEL_CRITICAL_ALERTS, false); - put(Definitions.NOTIFICATION_ROUNDED_LARGE_ICON, false); - put(Definitions.NOTIFICATION_ROUNDED_BIG_PICTURE, false); - }}; -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/background/BackgroundExecutor.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/background/BackgroundExecutor.java deleted file mode 100644 index 11a2d74f..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/background/BackgroundExecutor.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.carda.awesome_notifications.core.background; - -import android.content.Context; -import android.content.Intent; - -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; - -public abstract class BackgroundExecutor { - private static final String TAG = "BackgroundExecutor"; - - private static BackgroundExecutor runningInstance; - - protected Long dartCallbackHandle = 0L; - protected Long silentCallbackHandle = 0L; - - private static Class awesomeBackgroundExecutorClass; - - public static void setBackgroundExecutorClass ( - Class awesomeBackgroundExecutorClass - ){ - BackgroundExecutor.awesomeBackgroundExecutorClass = - awesomeBackgroundExecutorClass; - } - - public abstract boolean isDone(); - public abstract boolean runBackgroundAction(Context context, Intent silentIntent); - - public static void runBackgroundExecutor( - Context context, - Intent silentIntent, - Long dartCallbackHandle, - Long silentCallbackHandle - ) throws AwesomeNotificationsException { - - try { - if(awesomeBackgroundExecutorClass == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INITIALIZATION_EXCEPTION, - "There is no valid background executor available to run.", - ExceptionCode.DETAILED_INSUFFICIENT_REQUIREMENTS - +".backgroundExecutorClass"); - - if(runningInstance == null || runningInstance.isDone()) { - runningInstance = - awesomeBackgroundExecutorClass.newInstance(); - - runningInstance.dartCallbackHandle = dartCallbackHandle; - runningInstance.silentCallbackHandle = silentCallbackHandle; - } - - if(!runningInstance.runBackgroundAction( - context, - silentIntent - )){ - runningInstance = null; - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_BACKGROUND_EXECUTION_EXCEPTION, - "The background executor could not be started.", - ExceptionCode.DETAILED_INSUFFICIENT_REQUIREMENTS - +".backgroundExecutor.run"); - } - - } catch (IllegalAccessException | InstantiationException e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_BACKGROUND_EXECUTION_EXCEPTION, - String.format("%s", e.getLocalizedMessage()), - e); - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeBroadcastReceiver.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeBroadcastReceiver.java deleted file mode 100644 index f0fe58b6..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeBroadcastReceiver.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.carda.awesome_notifications.core.broadcasters.receivers; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; - -public abstract class AwesomeBroadcastReceiver extends BroadcastReceiver { - private static final String TAG = "AwesomeBroadcastReceiver"; - - public abstract void onReceiveBroadcastEvent(Context context, Intent intent) throws Exception; - public abstract void initializeExternalPlugins(Context context) throws Exception; - - @Override - public void onReceive(Context context, Intent intent) { - try { - initializeExternalPlugins(context); - AwesomeNotifications.initialize(context); - onReceiveBroadcastEvent(context, intent); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } catch (Exception e) { - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.CODE_UNKNOWN_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR+"."+e.getClass().getSimpleName(), - e); - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeEventsReceiver.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeEventsReceiver.java deleted file mode 100644 index 8ff771da..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeEventsReceiver.java +++ /dev/null @@ -1,319 +0,0 @@ -package me.carda.awesome_notifications.core.broadcasters.receivers; - -import android.content.Context; - -import java.util.ArrayList; -import java.util.List; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.listeners.AwesomeActionEventListener; -import me.carda.awesome_notifications.core.listeners.AwesomeNotificationEventListener; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class AwesomeEventsReceiver { - - public static String TAG = "AwesomeEventsReceiver"; - - // ************** SINGLETON PATTERN *********************** - - private static boolean isAwesomeCoreAttached = false; - private static AwesomeEventsReceiver instance; - - private AwesomeEventsReceiver(StringUtils stringUtils){ - this.stringUtils = stringUtils; - } - - public static AwesomeEventsReceiver getInstance() { - if (instance == null) - instance = new AwesomeEventsReceiver( - StringUtils.getInstance()); - return instance; - } - - public boolean isEmpty(){ - return notificationActionListeners.isEmpty(); - } - - public boolean isReadyToReceiveEvents(){ - return isAwesomeCoreAttached; - } - - // ******************************************************** - - protected final StringUtils stringUtils; - - /// ************** OBSERVER PATTERN ********************* - - static List notificationEventListeners = new ArrayList<>(); - public AwesomeEventsReceiver subscribeOnNotificationEvents(AwesomeNotificationEventListener listener) { - notificationEventListeners.add(listener); - - if (listener instanceof AwesomeNotifications){ - isAwesomeCoreAttached = true; - } - - if(AwesomeNotifications.debug) - Logger.d(TAG, listener.getClass().getSimpleName() + " subscribed to receive notification events"); - - return this; - } - public AwesomeEventsReceiver unsubscribeOnNotificationEvents(AwesomeNotificationEventListener listener) { - notificationEventListeners.remove(listener); - - if (listener instanceof AwesomeNotifications){ - boolean hasAwesome = false; - for (AwesomeNotificationEventListener awnListener : notificationEventListeners) { - if (awnListener instanceof AwesomeNotifications) { - hasAwesome = true; - } - } - isAwesomeCoreAttached = hasAwesome; - } - - if(AwesomeNotifications.debug) - Logger.d(TAG, listener.getClass().getSimpleName() + " unsubscribed from notification events"); - - return this; - } - private void notifyNotificationEvent(String eventName, NotificationReceived notificationReceived) { - - if(AwesomeNotifications.debug && notificationEventListeners.isEmpty()) - Logger.w(TAG, "New event "+eventName+" ignored, as there is no listeners waiting for new notification events"); - - for (AwesomeNotificationEventListener listener : notificationEventListeners) - listener.onNewNotificationReceived(eventName, notificationReceived); - } - - // ******************************************************** - - static List notificationActionListeners = new ArrayList<>(); - public AwesomeEventsReceiver subscribeOnActionEvents(AwesomeActionEventListener listener) { - notificationActionListeners.add(listener); - - if(AwesomeNotifications.debug) - Logger.d(TAG, listener.getClass().getSimpleName() + " subscribed to receive action events"); - - return this; - } - public AwesomeEventsReceiver unsubscribeOnActionEvents(AwesomeActionEventListener listener) { - notificationActionListeners.remove(listener); - - if(AwesomeNotifications.debug) - Logger.d(TAG, listener.getClass().getSimpleName() + " unsubscribed from action events"); - - return this; - } - private void notifyActionEvent(String eventName, ActionReceived actionReceived) { - - if(AwesomeNotifications.debug && notificationEventListeners.isEmpty()) - Logger.w(TAG, "New event "+eventName+" ignored, as there is no listeners waiting for new action events"); - - boolean interrupted = false; - for (AwesomeActionEventListener listener : notificationActionListeners) - interrupted = interrupted || listener.onNewActionReceivedWithInterruption(eventName, actionReceived); - - if (interrupted) return; - - for (AwesomeActionEventListener listener : notificationActionListeners) - listener.onNewActionReceived(eventName, actionReceived); - } - - // ******************************************************** - - public void addNotificationEvent(Context context, String eventName, NotificationReceived notificationReceived){ - - if(notificationEventListeners.isEmpty()) { - if(AwesomeNotifications.debug) - Logger.w(TAG, "New event "+eventName+" ignored, as there is no listeners waiting for new notification events"); - return; - } - - try { - switch (eventName) { - - case Definitions.BROADCAST_CREATED_NOTIFICATION: - onBroadcastNotificationCreated(context, notificationReceived); - return; - - case Definitions.BROADCAST_DISPLAYED_NOTIFICATION: - onBroadcastNotificationDisplayed(context, notificationReceived); - return; - - default: - if (AwesomeNotifications.debug) - Logger.d(TAG, "Received unknown notification event: " + ( - stringUtils.isNullOrEmpty(eventName) ? "empty" : eventName)); - - } - } catch (Exception e) { - if (AwesomeNotifications.debug) - Logger.d(TAG, String.format("%s", e.getMessage())); - e.printStackTrace(); - } - } - - public void addAwesomeActionEvent(Context context, String eventName, ActionReceived actionReceived){ - - if(notificationEventListeners.isEmpty()) { - if(AwesomeNotifications.debug) - Logger.w(TAG, "New event "+eventName+" ignored, as there is no listeners waiting for new action events"); - return; - } - - try { - switch (eventName) { - - case Definitions.BROADCAST_DEFAULT_ACTION: - onBroadcastDefaultActionNotification(context, actionReceived); - return; - - case Definitions.BROADCAST_DISMISSED_NOTIFICATION: - onBroadcastNotificationDismissed(context, actionReceived); - return; - - case Definitions.BROADCAST_SILENT_ACTION: - onBroadcastSilentActionNotification(context, actionReceived); - return; - - case Definitions.BROADCAST_BACKGROUND_ACTION: - onBroadcastBackgroundActionNotification(context, actionReceived); - return; - - default: - if (AwesomeNotifications.debug) - Logger.d(TAG, "Received unknown action event: " + ( - stringUtils.isNullOrEmpty(eventName) ? "empty" : eventName)); - - } - } catch (Exception e) { - if (AwesomeNotifications.debug) - Logger.d(TAG, String.format("%s", e.getMessage())); - e.printStackTrace(); - } - } - - /// *********************************** - - private void onBroadcastNotificationCreated(Context context, NotificationReceived notificationReceived) throws AwesomeNotificationsException { - try { - notificationReceived.validate(context); - - if (AwesomeNotifications.debug) - Logger.d(TAG, "New notification creation event"); - - notifyNotificationEvent(Definitions.EVENT_NOTIFICATION_CREATED, notificationReceived); - - } catch (Exception e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_EVENT_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - e); - } - } - - private void onBroadcastNotificationDisplayed(Context context, NotificationReceived notificationReceived) throws AwesomeNotificationsException { - try { - notificationReceived.validate(context); - - if (AwesomeNotifications.debug) - Logger.d(TAG, "New notification display event"); - - notifyNotificationEvent(Definitions.EVENT_NOTIFICATION_DISPLAYED, notificationReceived); - - } catch (Exception e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_EVENT_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - e); - } - } - - private void onBroadcastDefaultActionNotification(Context context, ActionReceived actionReceived) { - try { - actionReceived.validate(context); - - if(AwesomeNotifications.debug) - Logger.d(TAG, "New notification action event"); - - notifyActionEvent(Definitions.EVENT_DEFAULT_ACTION, actionReceived); - - } catch (Exception e) { - if(AwesomeNotifications.debug) - Logger.d(TAG, String.format("%s", e.getMessage())); - e.printStackTrace(); - } - } - - private void onBroadcastNotificationDismissed(Context context, ActionReceived actionReceived) throws AwesomeNotificationsException { - try { - actionReceived.validate(context); - - if (AwesomeNotifications.debug) - Logger.d(TAG, "New notification dismiss event"); - - notifyActionEvent(Definitions.EVENT_NOTIFICATION_DISMISSED, actionReceived); - - } catch (Exception e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_EVENT_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - e); - } - } - - private void onBroadcastSilentActionNotification(Context context, ActionReceived actionReceived) throws AwesomeNotificationsException { - try { - actionReceived.validate(context); - - if(AwesomeNotifications.debug) - Logger.d(TAG, "New silent action event"); - - notifyActionEvent(Definitions.EVENT_SILENT_ACTION, actionReceived); - - } catch (Exception e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_EVENT_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - e); - } - } - - private void onBroadcastBackgroundActionNotification(Context context, ActionReceived actionReceived) throws AwesomeNotificationsException { - try { - actionReceived.validate(context); - - if (AwesomeNotifications.debug) - Logger.d(TAG, "New background silent action event"); - - notifyActionEvent(Definitions.EVENT_SILENT_ACTION, actionReceived); - - } catch (Exception e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_EVENT_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - e); - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeExceptionReceiver.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeExceptionReceiver.java deleted file mode 100644 index 2d7915a3..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/AwesomeExceptionReceiver.java +++ /dev/null @@ -1,66 +0,0 @@ -package me.carda.awesome_notifications.core.broadcasters.receivers; - -import java.util.ArrayList; -import java.util.List; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.listeners.AwesomeExceptionListener; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class AwesomeExceptionReceiver { - - public static String TAG = "AwesomeEventsReceiver"; - - // ************** SINGLETON PATTERN *********************** - - private static AwesomeExceptionReceiver instance; - - private AwesomeExceptionReceiver(StringUtils stringUtils){ - this.stringUtils = stringUtils; - } - - public static AwesomeExceptionReceiver getInstance() { - if (instance == null) - instance = new AwesomeExceptionReceiver( - StringUtils.getInstance()); - return instance; - } - - public boolean isEmpty(){ - return exceptionListeners.isEmpty(); - } - - // ******************************************************** - - protected final StringUtils stringUtils; - - /// ************** OBSERVER PATTERN ********************* - - private final List exceptionListeners = new ArrayList<>(); - public AwesomeExceptionReceiver subscribeOnNotificationEvents(AwesomeExceptionListener listener) { - exceptionListeners.add(listener); - - if(AwesomeNotifications.debug) - Logger.d(TAG, listener.getClass().getSimpleName() + " subscribed to receive exception events"); - - return this; - } - public AwesomeExceptionReceiver unsubscribeOnNotificationEvents(AwesomeExceptionListener listener) { - exceptionListeners.remove(listener); - - if(AwesomeNotifications.debug) - Logger.d(TAG, listener.getClass().getSimpleName() + " unsubscribed from exception events"); - - return this; - } - public void notifyNewException(String className, Exception exception) { - Logger.e(className, exception.getLocalizedMessage()); - if(exceptionListeners.isEmpty()){ - exception.printStackTrace(); - return; - } - for (AwesomeExceptionListener listener : exceptionListeners) - listener.onNewAwesomeException(exception); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/DismissedNotificationReceiver.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/DismissedNotificationReceiver.java deleted file mode 100644 index 43b1bd86..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/DismissedNotificationReceiver.java +++ /dev/null @@ -1,61 +0,0 @@ -package me.carda.awesome_notifications.core.broadcasters.receivers; - -import android.content.Context; -import android.content.Intent; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.broadcasters.senders.BroadcastSender; -import me.carda.awesome_notifications.core.builders.NotificationBuilder; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.managers.StatusBarManager; -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; - -public abstract class DismissedNotificationReceiver extends AwesomeBroadcastReceiver -{ - static String TAG = "DismissedNotificationReceiver"; - - @Override - public void onReceiveBroadcastEvent(Context context, Intent intent) throws Exception { - - String action = intent.getAction(); - if (action != null && action.equals(Definitions.DISMISSED_NOTIFICATION)) { - - NotificationLifeCycle appLifeCycle = - AwesomeNotifications - .getApplicationLifeCycle(); - - ActionReceived actionReceived = null; - try { - actionReceived = NotificationBuilder - .getNewBuilder() - .buildNotificationActionFromIntent( - context, - intent, - appLifeCycle); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - - if(actionReceived == null) { - if(AwesomeNotifications.debug) - Logger.i(TAG, - "The action received do not contain any awesome " + - "notification data and was discarded"); - return; - } - - actionReceived.registerDismissedEvent(appLifeCycle); - - // In this case, the notification is always dismissed - StatusBarManager - .getInstance(context) - .unregisterActiveNotification(context, actionReceived.id); - - BroadcastSender - .sendBroadcastNotificationDismissed(context, actionReceived); - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/NotificationActionReceiver.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/NotificationActionReceiver.java deleted file mode 100644 index 8f3d4443..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/NotificationActionReceiver.java +++ /dev/null @@ -1,154 +0,0 @@ -package me.carda.awesome_notifications.core.broadcasters.receivers; - -import android.content.Context; -import android.content.Intent; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.broadcasters.senders.BroadcastSender; -import me.carda.awesome_notifications.core.builders.NotificationBuilder; -import me.carda.awesome_notifications.core.enumerators.ActionType; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.enumerators.NotificationSource; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.managers.LifeCycleManager; -import me.carda.awesome_notifications.core.managers.StatusBarManager; -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; -import me.carda.awesome_notifications.core.services.ForegroundService; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public abstract class NotificationActionReceiver extends AwesomeBroadcastReceiver { - - public static String TAG = "NotificationActionReceiver"; - - @Override - public void onReceiveBroadcastEvent(final Context context, Intent intent) throws Exception { - receiveActionIntent(context, intent); - } - - public static void receiveActionIntent(final Context context, Intent intent) throws Exception { - receiveActionIntent(context, intent, false); - } - - public static void receiveActionIntent(final Context context, Intent intent, boolean onInitialization) throws Exception { - - if(AwesomeNotifications.debug) - Logger.d(TAG, "New action received"); - - NotificationBuilder notificationBuilder = NotificationBuilder.getNewBuilder(); - - NotificationLifeCycle appLifeCycle - = LifeCycleManager - .getApplicationLifeCycle(); - - ActionReceived actionReceived = null; - try { - actionReceived = notificationBuilder - .buildNotificationActionFromIntent( - context, - intent, - appLifeCycle); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - - // In case there is not a valid notification intent - if (actionReceived == null) { - if(AwesomeNotifications.debug) - Logger.w( - TAG, - "The action received do not contain any awesome " + - "notification data and was discarded"); - return; - } - - if(actionReceived.actionType == ActionType.DismissAction) - actionReceived.registerDismissedEvent(appLifeCycle); - else - actionReceived.registerUserActionEvent(appLifeCycle); - - boolean shouldAutoDismiss - = actionReceived.actionType == ActionType.DismissAction || - notificationBuilder - .notificationActionShouldAutoDismiss(actionReceived); - - if(shouldAutoDismiss) { - if (actionReceived.createdSource == NotificationSource.ForegroundService) - ForegroundService - .stop(actionReceived.id); - else - StatusBarManager - .getInstance(context) - .dismissNotification(context, actionReceived.id); - } - else { - if ( - StringUtils.getInstance().isNullOrEmpty(actionReceived.buttonKeyInput) && - actionReceived.actionType != ActionType.KeepOnTop - ) - StatusBarManager - .getInstance(context) - .closeStatusBar(context); - } - - try { - - switch (actionReceived.actionType){ - - case Default: - BroadcastSender.sendBroadcastDefaultAction( - context, - actionReceived, - onInitialization); - break; - - case KeepOnTop: - if (appLifeCycle != NotificationLifeCycle.AppKilled) - BroadcastSender.sendBroadcastBackgroundAction( - context, - actionReceived); - else - BroadcastSender.enqueueSilentAction( - context, - intent.getAction(), - actionReceived, - intent); - break; - - case SilentAction: - if (appLifeCycle != NotificationLifeCycle.AppKilled) - BroadcastSender.sendBroadcastSilentAction( - context, - actionReceived); - else - BroadcastSender.enqueueSilentAction( - context, - intent.getAction(), - actionReceived, - intent); - break; - - case SilentBackgroundAction: - BroadcastSender.enqueueSilentBackgroundAction( - context, - intent.getAction(), - actionReceived, - intent); - break; - - case DismissAction: - BroadcastSender - .sendBroadcastNotificationDismissed( - context, - actionReceived); - break; - - case DisabledAction: - break; - } - - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/RefreshSchedulesReceiver.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/RefreshSchedulesReceiver.java deleted file mode 100644 index 2dd39fca..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/RefreshSchedulesReceiver.java +++ /dev/null @@ -1,21 +0,0 @@ -package me.carda.awesome_notifications.core.broadcasters.receivers; - -import android.content.Context; -import android.content.Intent; - -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.threads.NotificationScheduler; - -public abstract class RefreshSchedulesReceiver extends AwesomeBroadcastReceiver -{ - @Override - public void onReceiveBroadcastEvent( - final Context context, Intent intent - ) throws AwesomeNotificationsException { - - String action = intent.getAction(); - if (action != null) { - NotificationScheduler.refreshScheduledNotifications(context); - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/ScheduledNotificationReceiver.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/ScheduledNotificationReceiver.java deleted file mode 100644 index 496891ee..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/receivers/ScheduledNotificationReceiver.java +++ /dev/null @@ -1,65 +0,0 @@ -package me.carda.awesome_notifications.core.broadcasters.receivers; - -import android.content.Context; -import android.content.Intent; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.builders.NotificationBuilder; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.threads.NotificationScheduler; -import me.carda.awesome_notifications.core.threads.NotificationSender; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public abstract class ScheduledNotificationReceiver extends AwesomeBroadcastReceiver { - - static String TAG = "ScheduledNotificationReceiver"; - - @Override - public void onReceiveBroadcastEvent(final Context context, Intent intent) { - - //Toast.makeText(context, "ScheduledNotificationReceiver", Toast.LENGTH_SHORT).show(); - - String notificationDetailsJson = intent.getStringExtra(Definitions.NOTIFICATION_JSON); - if (!StringUtils.getInstance().isNullOrEmpty(notificationDetailsJson)) { - - try { - NotificationModel notificationModel = new NotificationModel().fromJson(notificationDetailsJson); - - if(notificationModel == null){ return; } - - NotificationSender - .send( - context, - NotificationBuilder.getNewBuilder(), - AwesomeNotifications.getApplicationLifeCycle(), - notificationModel, - null); - - if(notificationModel.schedule.repeats) { - NotificationScheduler - .schedule( - context, - notificationModel, - intent, - null); - } - else { - NotificationScheduler - .cancelSchedule( - context, - notificationModel); - - if(AwesomeNotifications.debug) - Logger.d(TAG, - "Schedule "+ notificationModel.content.id.toString() + - " finished since repeat option is off"); - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/senders/BroadcastSender.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/senders/BroadcastSender.java deleted file mode 100644 index d8c78909..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/broadcasters/senders/BroadcastSender.java +++ /dev/null @@ -1,195 +0,0 @@ -package me.carda.awesome_notifications.core.broadcasters.senders; - -import android.content.Context; -import android.content.Intent; - -import androidx.core.app.JobIntentService; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.broadcasters.receivers.AwesomeEventsReceiver; -import me.carda.awesome_notifications.core.builders.NotificationBuilder; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.managers.ActionManager; -import me.carda.awesome_notifications.core.managers.CreatedManager; -import me.carda.awesome_notifications.core.managers.DismissedManager; -import me.carda.awesome_notifications.core.managers.DisplayedManager; -import me.carda.awesome_notifications.core.managers.LifeCycleManager; -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; - -public class BroadcastSender { - - private static final String TAG = "BroadcastSender"; - - private static boolean withoutListenersAvailable(){ - return - LifeCycleManager.getApplicationLifeCycle() == NotificationLifeCycle.AppKilled || - AwesomeEventsReceiver.getInstance().isEmpty() || - !AwesomeEventsReceiver.getInstance().isReadyToReceiveEvents(); - } - - public static void sendBroadcastNotificationCreated(Context context, NotificationReceived notificationReceived){ - - if (notificationReceived != null) - try { - - if(withoutListenersAvailable()) { - CreatedManager.saveCreated(context, notificationReceived); - CreatedManager.commitChanges(context); - } - else { - AwesomeEventsReceiver - .getInstance() - .addNotificationEvent( - context, - Definitions.BROADCAST_CREATED_NOTIFICATION, - notificationReceived); - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void sendBroadcastNotificationDisplayed(Context context, NotificationReceived notificationReceived){ - - if (notificationReceived != null) - try { - - if(withoutListenersAvailable()) { - DisplayedManager.saveDisplayed(context, notificationReceived); - DisplayedManager.commitChanges(context); - } - else { - AwesomeEventsReceiver - .getInstance() - .addNotificationEvent( - context, - Definitions.BROADCAST_DISPLAYED_NOTIFICATION, - notificationReceived); - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void sendBroadcastNotificationDismissed(Context context, ActionReceived actionReceived){ - if (actionReceived != null) - try { - - if(withoutListenersAvailable()) { - DismissedManager.saveDismissed(context, actionReceived); - DismissedManager.commitChanges(context); - } - else { - AwesomeEventsReceiver - .getInstance() - .addAwesomeActionEvent( - context, - Definitions.BROADCAST_DISMISSED_NOTIFICATION, - actionReceived); - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void sendBroadcastDefaultAction(Context context, ActionReceived actionReceived, boolean onInitialization){ - if (actionReceived != null) - try { - - if(onInitialization){ - ActionManager.setInitialNotificationAction(context, actionReceived); - } - - if(withoutListenersAvailable()) { - ActionManager.saveAction(context, actionReceived); - ActionManager.commitChanges(context); - } - else { - AwesomeEventsReceiver - .getInstance() - .addAwesomeActionEvent( - context, - Definitions.BROADCAST_DEFAULT_ACTION, - actionReceived); - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void sendBroadcastSilentAction(Context context, ActionReceived actionReceived){ - if (actionReceived != null) - try { - AwesomeEventsReceiver - .getInstance() - .addAwesomeActionEvent( - context, - Definitions.BROADCAST_SILENT_ACTION, - actionReceived); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void sendBroadcastBackgroundAction(Context context, ActionReceived actionReceived){ - - if (actionReceived != null) - try { - AwesomeEventsReceiver - .getInstance() - .addAwesomeActionEvent( - context, - Definitions.BROADCAST_BACKGROUND_ACTION, - actionReceived); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void enqueueSilentAction(Context context, String previousAction, ActionReceived actionReceived, Intent originalIntent){ - if (AwesomeNotifications.backgroundServiceClass == null) return; - - Intent serviceIntent = - NotificationBuilder - .getNewBuilder() - .buildNotificationIntentFromActionModel( - context, - originalIntent, - previousAction, - actionReceived, - AwesomeNotifications.backgroundServiceClass); - - JobIntentService.enqueueWork( - context, - AwesomeNotifications.backgroundServiceClass, - 42, - serviceIntent); - } - - public static void enqueueSilentBackgroundAction(Context context, String previousAction, ActionReceived actionReceived, Intent originalIntent){ - - Intent serviceIntent = - NotificationBuilder - .getNewBuilder() - .buildNotificationIntentFromActionModel( - context, - originalIntent, - previousAction, - actionReceived, - AwesomeNotifications.backgroundServiceClass); - - JobIntentService.enqueueWork( - context, - AwesomeNotifications.backgroundServiceClass, - 42, - serviceIntent); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/builders/NotificationBuilder.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/builders/NotificationBuilder.java deleted file mode 100644 index bc2dfd6a..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/builders/NotificationBuilder.java +++ /dev/null @@ -1,1339 +0,0 @@ -package me.carda.awesome_notifications.core.builders; - -import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS; - -import android.annotation.SuppressLint; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.PowerManager; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.text.Spanned; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.core.app.NotificationCompat; -import androidx.media.app.NotificationCompat.MediaStyle; -import androidx.core.app.Person; -import androidx.core.app.RemoteInput; -import androidx.core.graphics.drawable.IconCompat; -import androidx.core.text.HtmlCompat; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.completion_handlers.NotificationThreadCompletionHandler; -import me.carda.awesome_notifications.core.enumerators.ActionType; -import me.carda.awesome_notifications.core.enumerators.GroupSort; -import me.carda.awesome_notifications.core.enumerators.NotificationImportance; -import me.carda.awesome_notifications.core.enumerators.NotificationLayout; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.enumerators.NotificationPermission; -import me.carda.awesome_notifications.core.enumerators.NotificationPrivacy; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.managers.BadgeManager; -import me.carda.awesome_notifications.core.managers.ChannelManager; -import me.carda.awesome_notifications.core.managers.DefaultsManager; -import me.carda.awesome_notifications.core.managers.PermissionManager; -import me.carda.awesome_notifications.core.managers.StatusBarManager; -import me.carda.awesome_notifications.core.models.NotificationButtonModel; -import me.carda.awesome_notifications.core.models.NotificationChannelModel; -import me.carda.awesome_notifications.core.models.NotificationContentModel; -import me.carda.awesome_notifications.core.models.NotificationMessageModel; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; -import me.carda.awesome_notifications.core.threads.NotificationSender; -import me.carda.awesome_notifications.core.utils.BitmapUtils; -import me.carda.awesome_notifications.core.utils.BooleanUtils; -import me.carda.awesome_notifications.core.utils.HtmlUtils; -import me.carda.awesome_notifications.core.utils.IntegerUtils; -import me.carda.awesome_notifications.core.utils.ListUtils; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class NotificationBuilder { - - public static String TAG = "NotificationBuilder"; - - private static String mainTargetClassName; - - private final BitmapUtils bitmapUtils; - private final StringUtils stringUtils; - private final PermissionManager permissionManager; - private static MediaSessionCompat mediaSession; - - // *************** DEPENDENCY INJECTION CONSTRUCTOR *************** - - NotificationBuilder( - StringUtils stringUtils, - BitmapUtils bitmapUtils, - PermissionManager permissionManager - ){ - this.stringUtils = stringUtils; - this.bitmapUtils = bitmapUtils; - this.permissionManager = permissionManager; - } - - public static NotificationBuilder getNewBuilder() { - return new NotificationBuilder( - StringUtils.getInstance(), - BitmapUtils.getInstance(), - PermissionManager.getInstance()); - } - - // **************************************************************** - - public void forceBringAppToForeground(Context context){ - Intent startActivity = new Intent(context, getMainTargetClass(context)); - startActivity.setFlags( - // Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | - Intent.FLAG_ACTIVITY_SINGLE_TOP | - // Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY | - Intent.FLAG_ACTIVITY_NEW_TASK); - - context.startActivity(startActivity); - } - - public ActionReceived receiveNotificationActionFromIntent( - Context context, - Intent intent, - NotificationLifeCycle appLifeCycle - ) throws Exception { - - ActionReceived actionReceived - = buildNotificationActionFromIntent(context, intent, appLifeCycle); - - if (actionReceived != null) { - if (notificationActionShouldAutoDismiss(actionReceived)) - StatusBarManager - .getInstance(context) - .dismissNotification(context, actionReceived.id); - - if (actionReceived.actionType == ActionType.DisabledAction) - return null; - } - - return actionReceived; - } - - public boolean notificationActionShouldAutoDismiss(ActionReceived actionReceived){ - if (!StringUtils.getInstance().isNullOrEmpty(actionReceived.buttonKeyInput)){ - return false; - } - return actionReceived.shouldAutoDismiss && actionReceived.autoDismissible; - } - - @SuppressWarnings("unchecked") - public Notification createNewAndroidNotification(Context context, Intent originalIntent, NotificationModel notificationModel) throws AwesomeNotificationsException { - - NotificationChannelModel channelModel = - ChannelManager - .getInstance() - .getChannelByKey(context, notificationModel.content.channelKey); - - if (channelModel == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel '" + notificationModel.content.channelKey + "' does not exist", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.notFound."+notificationModel.content.channelKey); - - if (!ChannelManager - .getInstance() - .isChannelEnabled(context, notificationModel.content.channelKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INSUFFICIENT_PERMISSIONS, - "Channel '" + notificationModel.content.channelKey + "' is disabled", - ExceptionCode.DETAILED_INSUFFICIENT_PERMISSIONS+".channel.disabled."+notificationModel.content.channelKey); - - NotificationCompat.Builder builder = - getNotificationBuilderFromModel( - context, - originalIntent, - channelModel, - notificationModel); - - Notification androidNotification = builder.build(); - if(androidNotification.extras == null) - androidNotification.extras = new Bundle(); - - updateTrackingExtras(notificationModel, channelModel, androidNotification.extras); - - setWakeUpScreen(context, notificationModel); - setCriticalAlert(context, channelModel); - setCategoryFlags(context, notificationModel, androidNotification); - - setBadge(context, notificationModel, channelModel, builder); - - return androidNotification; - } - - public Intent buildNotificationIntentFromNotificationModel( - Context context, - Intent originalIntent, - String ActionReference, - NotificationModel notificationModel, - NotificationChannelModel channel, - ActionType actionType, - Class targetClass - ) { - Intent intent = new Intent(context, targetClass); - - // Preserves analytics extras - if(originalIntent != null) - intent.putExtras(originalIntent.getExtras()); - - intent.setAction(ActionReference); - - if(actionType == ActionType.Default) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - Bundle extras = intent.getExtras(); - if(extras == null) - extras = new Bundle(); - - String jsonData = notificationModel.toJson(); - extras.putString(Definitions.NOTIFICATION_JSON, jsonData); - - updateTrackingExtras(notificationModel, channel, extras); - intent.putExtras(extras); - - return intent; - } - - public Intent buildNotificationIntentFromActionModel( - Context context, - Intent originalIntent, - String ActionReference, - ActionReceived actionReceived, - Class targetAction - ) { - Intent intent = new Intent(context, targetAction); - - // Preserves analytics extras - if(originalIntent != null) - intent.putExtras(originalIntent.getExtras()); - - intent.setAction(ActionReference); - - Bundle extras = intent.getExtras(); - if(extras == null) - extras = new Bundle(); - - String jsonData = actionReceived.toJson(); - extras.putString(Definitions.NOTIFICATION_ACTION_JSON, jsonData); - intent.putExtras(extras); - - return intent; - } - - private PendingIntent getPendingActionIntent( - Context context, - Intent originalIntent, - NotificationModel notificationModel, - NotificationChannelModel channelModel - ){ - ActionType actionType = notificationModel.content.actionType; - - Intent actionIntent = buildNotificationIntentFromNotificationModel( - context, - originalIntent, - Definitions.SELECT_NOTIFICATION, - notificationModel, - channelModel, - actionType, - actionType == ActionType.Default ? - getMainTargetClass(context) : - AwesomeNotifications.actionReceiverClass - ); - - if(actionType == ActionType.Default) - actionIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - return actionType == ActionType.Default ? - PendingIntent.getActivity( - context, - notificationModel.content.id, - actionIntent, - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : - PendingIntent.FLAG_UPDATE_CURRENT) - : - PendingIntent.getBroadcast( - context, - notificationModel.content.id, - actionIntent, - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : - PendingIntent.FLAG_UPDATE_CURRENT); - } - - private PendingIntent getPendingDismissIntent( - Context context, - Intent originalIntent, - NotificationModel notificationModel, - NotificationChannelModel channelModel - ){ - Intent deleteIntent = buildNotificationIntentFromNotificationModel( - context, - originalIntent, - Definitions.DISMISSED_NOTIFICATION, - notificationModel, - channelModel, - notificationModel.content.actionType, - AwesomeNotifications.dismissReceiverClass - ); - - return PendingIntent.getBroadcast( - context, - notificationModel.content.id, - deleteIntent, - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : - PendingIntent.FLAG_UPDATE_CURRENT - ); - } - - @SuppressWarnings("unchecked") - private void updateTrackingExtras(NotificationModel notificationModel, NotificationChannelModel channel, Bundle extras) { - String groupKey = getGroupKey(notificationModel.content, channel); - - extras.putInt(Definitions.NOTIFICATION_ID, notificationModel.content.id); - extras.putString(Definitions.NOTIFICATION_CHANNEL_KEY, stringUtils.digestString(notificationModel.content.channelKey)); - extras.putString(Definitions.NOTIFICATION_GROUP_KEY, stringUtils.digestString(groupKey)); - extras.putBoolean(Definitions.NOTIFICATION_AUTO_DISMISSIBLE, notificationModel.content.autoDismissible); - extras.putString(Definitions.NOTIFICATION_ACTION_TYPE, - notificationModel.content.actionType != null ? - notificationModel.content.actionType.toString() : ActionType.Default.toString()); - - if(!ListUtils.isNullOrEmpty(notificationModel.content.messages)) { - Map contentData = notificationModel.content.toMap(); - List contentMessageData = null; - if(contentData.get(Definitions.NOTIFICATION_MESSAGES) instanceof List) { - contentMessageData = (List) contentData.get(Definitions.NOTIFICATION_MESSAGES); - } - if(contentMessageData != null) - extras.putSerializable( - Definitions.NOTIFICATION_MESSAGES, - (Serializable) contentMessageData); - } - } - - private Class tryResolveClassName(String className){ - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.CODE_CLASS_NOT_FOUND, - "Was not possible to resolve the class named '"+className+"'", - ExceptionCode.DETAILED_CLASS_NOT_FOUND+"."+className); - return null; - } - } - - public Class getMainTargetClass( - Context applicationContext - ){ - if(mainTargetClassName == null) - updateMainTargetClassName(applicationContext); - - if(mainTargetClassName == null) - mainTargetClassName = AwesomeNotifications.getPackageName(applicationContext) + ".MainActivity"; - - Class clazz = tryResolveClassName(mainTargetClassName); - if(clazz != null) return clazz; - - return tryResolveClassName("MainActivity"); - } - - public NotificationBuilder updateMainTargetClassName(Context applicationContext) { - - String packageName = AwesomeNotifications.getPackageName(applicationContext); - Intent intent = new Intent(); - intent.setPackage(packageName); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - - List resolveInfoList = - applicationContext - .getPackageManager() - .queryIntentActivities(intent, 0); - - if(resolveInfoList.size() > 0) - mainTargetClassName = resolveInfoList.get(0).activityInfo.name; - - return this; - } - - public NotificationBuilder setMediaSession(MediaSessionCompat mediaSession) { - NotificationBuilder.mediaSession = mediaSession; - return this; - } - - public Intent getLaunchIntent(Context applicationContext){ - String packageName = AwesomeNotifications.getPackageName(applicationContext); - return applicationContext.getPackageManager().getLaunchIntentForPackage(packageName); - } - - public ActionReceived buildNotificationActionFromIntent(Context context, Intent intent, NotificationLifeCycle lifeCycle) throws AwesomeNotificationsException { - String buttonKeyPressed = intent.getAction(); - - if (buttonKeyPressed == null) return null; - - boolean isNormalAction = - Definitions.SELECT_NOTIFICATION.equals(buttonKeyPressed) || - Definitions.DISMISSED_NOTIFICATION.equals(buttonKeyPressed); - - boolean isButtonAction = - buttonKeyPressed - .startsWith(Definitions.NOTIFICATION_BUTTON_ACTION_PREFIX); - - if (isNormalAction || isButtonAction) { - - String notificationActionJson = intent.getStringExtra(Definitions.NOTIFICATION_ACTION_JSON); - if(!stringUtils.isNullOrEmpty(notificationActionJson)){ - ActionReceived actionModel = new ActionReceived().fromJson(notificationActionJson); - if (actionModel != null) return actionModel; - } - - String notificationJson = intent.getStringExtra(Definitions.NOTIFICATION_JSON); - - NotificationModel notificationModel = new NotificationModel().fromJson(notificationJson); - if (notificationModel == null) return null; - - ActionReceived actionModel = - new ActionReceived( - notificationModel.content, - intent); - - actionModel.registerUserActionEvent(lifeCycle); - - if (actionModel.displayedDate == null) - actionModel.registerDisplayedEvent(lifeCycle); - - actionModel.autoDismissible = intent.getBooleanExtra(Definitions.NOTIFICATION_AUTO_DISMISSIBLE, true); - actionModel.shouldAutoDismiss = actionModel.autoDismissible; - - actionModel.actionType = - stringUtils.getEnumFromString( - ActionType.class, - intent.getStringExtra(Definitions.NOTIFICATION_ACTION_TYPE)); - - if (isButtonAction) { - - actionModel.buttonKeyPressed = intent.getStringExtra(Definitions.NOTIFICATION_BUTTON_KEY); - - Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - if (remoteInput != null) - actionModel.buttonKeyInput = remoteInput.getCharSequence( - actionModel.buttonKeyPressed).toString(); - else - actionModel.buttonKeyInput = ""; - - if ( - !stringUtils.isNullOrEmpty(actionModel.buttonKeyInput) - ) - updateRemoteHistoryOnActiveNotification( - context, - notificationModel, - actionModel, - null); - } - - return actionModel; - } - return null; - } - - public void updateRemoteHistoryOnActiveNotification( - Context context, - NotificationModel notificationModel, - ActionReceived actionModel, - NotificationThreadCompletionHandler completionHandler - ) throws AwesomeNotificationsException { - if( - !stringUtils.isNullOrEmpty(actionModel.buttonKeyInput) && - Build.VERSION.SDK_INT >= Build.VERSION_CODES.N /*Android 7*/ - ) { - actionModel.shouldAutoDismiss = false; - - switch (notificationModel.content.notificationLayout){ - - case Inbox: - case BigText: - case BigPicture: - case ProgressBar: - case MediaPlayer: - case Default: - notificationModel.remoteHistory = actionModel.buttonKeyInput; - NotificationSender - .send( - context, - this, - notificationModel.content.displayedLifeCycle, - notificationModel, - completionHandler); - break; - } - } - } - - public String getAppName(Context context){ - ApplicationInfo applicationInfo = context.getApplicationInfo(); - int stringId = applicationInfo.labelRes; - return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : context.getString(stringId); - } - - @RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH) - public void wakeUpScreen(Context context){ - - String appName = getAppName(context); - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - - boolean isScreenOn = Build.VERSION.SDK_INT >= 20 ? pm.isInteractive() : pm.isScreenOn(); // check if screen is on - if (!isScreenOn) { - PowerManager.WakeLock wl = - pm.newWakeLock( - PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, - appName+":"+TAG+":WakeupLock"); - - wl.acquire(3000); //set your time in milliseconds - } - /* - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - boolean isScreenOn = pm.isInteractive(); - if(!isScreenOn) - { - String appName = getAppName(context); - - PowerManager.WakeLock wl = pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK | - PowerManager.ACQUIRE_CAUSES_WAKEUP | - PowerManager.ON_AFTER_RELEASE, - appName+":"+TAG+":WakeupLock"); - wl.acquire(10000); - - PowerManager.WakeLock wl_cpu = pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - appName+":"+TAG+":WakeupCpuLock"); - wl_cpu.acquire(10000); - wl_cpu.acquire(10000); - }*/ - } - - public void ensureCriticalAlert(Context context) throws AwesomeNotificationsException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - if (PermissionManager.getInstance().isDndOverrideAllowed(context)) { - if (!permissionManager.isSpecifiedPermissionGloballyAllowed(context, NotificationPermission.CriticalAlert)){ - notificationManager.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P /*Android 9*/) { - NotificationManager.Policy policy = new NotificationManager.Policy(PRIORITY_CATEGORY_ALARMS, 0, 0); - notificationManager.setNotificationPolicy(policy); - } - } - } - } - } - - private NotificationCompat.Builder getNotificationBuilderFromModel( - Context context, - Intent originalIntent, - NotificationChannelModel channel, - NotificationModel notificationModel - ) throws AwesomeNotificationsException { - - NotificationCompat.Builder builder = - new NotificationCompat.Builder( - context, - notificationModel.content.channelKey); - - setChannelKey(context, channel, builder); - setNotificationId(notificationModel); - setTitle(notificationModel, channel, builder); - setBody(notificationModel, builder); - - setGroupKey(notificationModel, channel); - setSmallIcon(context, notificationModel, channel, builder); - setRemoteHistory(notificationModel, builder); - setGrouping(context, notificationModel, channel, builder); - setVisibility(context, notificationModel, channel, builder); - setShowWhen(notificationModel, builder); - setLayout(context, notificationModel, channel, builder); - setAutoCancel(notificationModel, builder); - setTicker(notificationModel, builder); - setOnlyAlertOnce(notificationModel, channel, builder); - setLockedNotification(notificationModel, channel, builder); - setImportance(channel, builder); - setCategory(notificationModel, builder); - - setSound(context, notificationModel, channel, builder); - setVibrationPattern(channel, builder); - setLights(channel, builder); - - setSmallIcon(context, notificationModel, channel, builder); - setLargeIcon(context, notificationModel, builder); - setLayoutColor(context, notificationModel, channel, builder); - - PendingIntent pendingActionIntent = - getPendingActionIntent(context, originalIntent, notificationModel, channel); - PendingIntent pendingDismissIntent = - getPendingDismissIntent(context, originalIntent, notificationModel, channel); - - setFullScreenIntent(context, pendingActionIntent, notificationModel, builder); - - setNotificationPendingIntents(notificationModel, pendingActionIntent, pendingDismissIntent, builder); - createActionButtons(context, originalIntent, notificationModel, channel, builder); - - return builder; - } - - private void setChannelKey(Context context, NotificationChannelModel channel, NotificationCompat.Builder builder) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/){ - NotificationChannel androidChannel = - ChannelManager - .getInstance() - .getAndroidChannel(context, channel.channelKey); - - builder.setChannelId(androidChannel.getId()); - } - } - - private void setNotificationId(NotificationModel notificationModel) { - if (notificationModel.content.id == null || notificationModel.content.id < 0) - notificationModel.content.id = IntegerUtils.generateNextRandomId(); - } - - private void setGroupKey(NotificationModel notificationModel, NotificationChannelModel channel) { - notificationModel.content.groupKey = getGroupKey(notificationModel.content, channel); - } - - private void setCategoryFlags(Context context, NotificationModel notificationModel, Notification androidNotification) { - - if(notificationModel.content.category != null) - switch (notificationModel.content.category){ - - case Alarm: - androidNotification.flags |= Notification.FLAG_INSISTENT; - androidNotification.flags |= Notification.FLAG_NO_CLEAR; - break; - - case Call: - androidNotification.flags |= Notification.FLAG_INSISTENT; - androidNotification.flags |= Notification.FLAG_HIGH_PRIORITY; - androidNotification.flags |= Notification.FLAG_NO_CLEAR; - break; - } - } - - private void setNotificationPendingIntents(NotificationModel notificationModel, PendingIntent pendingActionIntent, PendingIntent pendingDismissIntent, NotificationCompat.Builder builder) { - builder.setContentIntent(pendingActionIntent); - if(!notificationModel.groupSummary) - builder.setDeleteIntent(pendingDismissIntent); - } - - private void setWakeUpScreen(Context context, NotificationModel notificationModel) { - if (notificationModel.content.wakeUpScreen) -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) - wakeUpScreen(context); - } - - private void setCriticalAlert(Context context, NotificationChannelModel channel) throws AwesomeNotificationsException { - if (channel.criticalAlerts) - ensureCriticalAlert(context); - } - - private void setFullScreenIntent(Context context, PendingIntent pendingIntent, NotificationModel notificationModel, NotificationCompat.Builder builder) { - if (BooleanUtils.getInstance().getValue(notificationModel.content.fullScreenIntent)) { - builder.setFullScreenIntent(pendingIntent, true); - } - } - - private void setShowWhen(NotificationModel notificationModel, NotificationCompat.Builder builder) { - builder.setShowWhen(BooleanUtils.getInstance().getValueOrDefault(notificationModel.content.showWhen, true)); - } - - private Integer getBackgroundColor(NotificationModel notificationModel, NotificationChannelModel channel, NotificationCompat.Builder builder) { - Integer bgColorValue; - bgColorValue = IntegerUtils.extractInteger(notificationModel.content.backgroundColor, null); - if (bgColorValue != null) { - builder.setColorized(true); - } else { - bgColorValue = getLayoutColor(notificationModel, channel); - } - return bgColorValue; - } - - private Integer getLayoutColor(NotificationModel notificationModel, NotificationChannelModel channel) { - Integer layoutColorValue; - layoutColorValue = IntegerUtils.extractInteger(notificationModel.content.color, channel.defaultColor); - layoutColorValue = IntegerUtils.extractInteger(layoutColorValue, Color.BLACK); - return layoutColorValue; - } - - private void setImportance(NotificationChannelModel channel, NotificationCompat.Builder builder) { - builder.setPriority(NotificationImportance.toAndroidPriority(channel.importance)); - } - - private void setCategory(NotificationModel notificationModel, NotificationCompat.Builder builder){ - if(notificationModel.content.category != null) - builder.setCategory(notificationModel.content.category.rawValue); - } - - private void setOnlyAlertOnce(NotificationModel notificationModel, NotificationChannelModel channel, NotificationCompat.Builder builder) { - boolean onlyAlertOnceValue = BooleanUtils.getInstance().getValue(notificationModel.content.notificationLayout == NotificationLayout.ProgressBar || channel.onlyAlertOnce); - builder.setOnlyAlertOnce(onlyAlertOnceValue); - } - - private void setRemoteHistory(NotificationModel notificationModel, NotificationCompat.Builder builder) { - if(!stringUtils.isNullOrEmpty(notificationModel.remoteHistory) && notificationModel.content.notificationLayout == NotificationLayout.Default) - builder.setRemoteInputHistory(new CharSequence[]{notificationModel.remoteHistory}); - } - - private void setLockedNotification(NotificationModel notificationModel, NotificationChannelModel channel, NotificationCompat.Builder builder) { - boolean contentLocked = BooleanUtils.getInstance().getValue(notificationModel.content.locked); - boolean channelLocked = BooleanUtils.getInstance().getValue(channel.locked); - - if (contentLocked) { - builder.setOngoing(true); - } else if (channelLocked) { - boolean lockedValue = BooleanUtils.getInstance().getValueOrDefault(notificationModel.content.locked, true); - builder.setOngoing(lockedValue); - } - } - - private void setTicker(NotificationModel notificationModel, NotificationCompat.Builder builder) { - String tickerValue; - tickerValue = stringUtils.getValueOrDefault(notificationModel.content.ticker, ""); - tickerValue = stringUtils.getValueOrDefault(tickerValue, notificationModel.content.summary); - tickerValue = stringUtils.getValueOrDefault(tickerValue, notificationModel.content.body); - tickerValue = stringUtils.getValueOrDefault(tickerValue, notificationModel.content.title); - builder.setTicker(tickerValue); - } - - private void setBadge(Context context, NotificationModel notificationModel, NotificationChannelModel channelModel, NotificationCompat.Builder builder) { - if (notificationModel.content.badge != null){ - BadgeManager.getInstance().setGlobalBadgeCounter(context, notificationModel.content.badge); - return; - } - if (!notificationModel.groupSummary && BooleanUtils.getInstance().getValue(channelModel.channelShowBadge)) { - BadgeManager.getInstance().incrementGlobalBadgeCounter(context); - builder.setNumber(1); - } - } - - private void setAutoCancel(NotificationModel notificationModel, NotificationCompat.Builder builder) { - builder.setAutoCancel(BooleanUtils.getInstance().getValueOrDefault(notificationModel.content.autoDismissible, true)); - } - - private void setBody(NotificationModel notificationModel, NotificationCompat.Builder builder) { - builder.setContentText(HtmlUtils.fromHtml(notificationModel.content.body)); - } - - private void setTitle(NotificationModel notificationModel, NotificationChannelModel channelModel, NotificationCompat.Builder builder) { - if (notificationModel.content.title != null) { - builder.setContentTitle(HtmlUtils.fromHtml(notificationModel.content.title)); - } - } - - private void setVibrationPattern(NotificationChannelModel channelModel, NotificationCompat.Builder builder) { - if (BooleanUtils.getInstance().getValue(channelModel.enableVibration)) { - if (channelModel.vibrationPattern != null && channelModel.vibrationPattern.length > 0) { - builder.setVibrate(channelModel.vibrationPattern); - } - } else { - builder.setVibrate(new long[]{0}); - } - } - - private void setLights(NotificationChannelModel channelModel, NotificationCompat.Builder builder) { - if (BooleanUtils.getInstance().getValue(channelModel.enableLights)) { - Integer ledColorValue = IntegerUtils.extractInteger(channelModel.ledColor, Color.WHITE); - Integer ledOnMsValue = IntegerUtils.extractInteger(channelModel.ledOnMs, 300); - Integer ledOffMsValue = IntegerUtils.extractInteger(channelModel.ledOffMs, 700); - builder.setLights(ledColorValue, ledOnMsValue, ledOffMsValue); - } - } - - private void setVisibility(Context context, NotificationModel notificationModel, NotificationChannelModel channelModel, NotificationCompat.Builder builder) { - -// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - NotificationPrivacy privacy = - notificationModel.content.privacy != null ? - notificationModel.content.privacy : - channelModel.defaultPrivacy; - - builder.setVisibility(NotificationPrivacy.toAndroidPrivacy(privacy)); -// } - } - - private void setLayoutColor(Context context, NotificationModel notificationModel, NotificationChannelModel channelModel, NotificationCompat.Builder builder) { - - if (notificationModel.content.backgroundColor == null) { - builder.setColor(getLayoutColor(notificationModel, channelModel)); - } else { - builder.setColor(getBackgroundColor(notificationModel, channelModel, builder)); - } - } - - private void setLargeIcon(Context context, NotificationModel notificationModel, NotificationCompat.Builder builder) { - if (notificationModel.content.notificationLayout != NotificationLayout.BigPicture) - if (!stringUtils.isNullOrEmpty(notificationModel.content.largeIcon)) { - Bitmap largeIcon = bitmapUtils.getBitmapFromSource( - context, - notificationModel.content.largeIcon, - notificationModel.content.roundedLargeIcon); - if (largeIcon != null) - builder.setLargeIcon(largeIcon); - } - } - - @SuppressLint("WrongConstant") - @NonNull - public void createActionButtons( - Context context, - Intent originalIntent, - NotificationModel notificationModel, - NotificationChannelModel channel, - NotificationCompat.Builder builder - ){ - if (ListUtils.isNullOrEmpty(notificationModel.actionButtons)) return; - - for (NotificationButtonModel buttonProperties : notificationModel.actionButtons) { - - // If reply is not available, do not show it - if ( - Build.VERSION.SDK_INT < Build.VERSION_CODES.N /*Android 7*/ && - buttonProperties.requireInputText - ){ - continue; - } - - ActionType actionType = buttonProperties.actionType; - - Intent actionIntent = buildNotificationIntentFromNotificationModel( - context, - originalIntent, - Definitions.NOTIFICATION_BUTTON_ACTION_PREFIX + "_" + buttonProperties.key, - notificationModel, - channel, - buttonProperties.actionType, - actionType == ActionType.Default ? - getMainTargetClass(context): - AwesomeNotifications.actionReceiverClass - ); - - if(buttonProperties.actionType == ActionType.Default) - actionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - actionIntent.putExtra(Definitions.NOTIFICATION_AUTO_DISMISSIBLE, buttonProperties.autoDismissible); - actionIntent.putExtra(Definitions.NOTIFICATION_SHOW_IN_COMPACT_VIEW, buttonProperties.showInCompactView); - actionIntent.putExtra(Definitions.NOTIFICATION_ENABLED, buttonProperties.enabled); - actionIntent.putExtra(Definitions.NOTIFICATION_BUTTON_KEY, buttonProperties.key); - actionIntent.putExtra(Definitions.NOTIFICATION_ACTION_TYPE, - buttonProperties.actionType != null ? - buttonProperties.actionType.toString() : ActionType.Default.toString()); - - PendingIntent actionPendingIntent = null; - if (buttonProperties.enabled) { - - actionPendingIntent = - actionType == ActionType.Default ? - PendingIntent.getActivity( - context, - notificationModel.content.id, - actionIntent, - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : - PendingIntent.FLAG_UPDATE_CURRENT) - : - PendingIntent.getBroadcast( - context, - notificationModel.content.id, - actionIntent, - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : - PendingIntent.FLAG_UPDATE_CURRENT); - } - - int iconResource = 0; - if (!stringUtils.isNullOrEmpty(buttonProperties.icon)) { - iconResource = bitmapUtils.getDrawableResourceId(context, buttonProperties.icon); - } - - Spanned htmlLabel = - HtmlCompat.fromHtml( - buttonProperties.isDangerousOption ? - "" + buttonProperties.label + "" : - ( - buttonProperties.color != null ? - "" + buttonProperties.label + "": - buttonProperties.label - ), - HtmlCompat.FROM_HTML_MODE_LEGACY - ); - - if ( buttonProperties.requireInputText != null && buttonProperties.requireInputText ){ - - RemoteInput remoteInput = - new RemoteInput - .Builder(buttonProperties.key) - .setLabel(buttonProperties.label) - .build(); - - NotificationCompat.Action replyAction = - new NotificationCompat.Action - .Builder( - iconResource, - htmlLabel, - actionPendingIntent) - .addRemoteInput(remoteInput) - .build(); - - builder.addAction(replyAction); - - } else { - - builder.addAction( - iconResource, - htmlLabel, - actionPendingIntent); - } - } - } - - private void setSound(Context context, NotificationModel notificationModel, NotificationChannelModel channelModel, NotificationCompat.Builder builder) { - - Uri uri = null; - - if ( - !notificationModel.content.isRefreshNotification && - notificationModel.remoteHistory == null && - BooleanUtils.getInstance().getValue(channelModel.playSound) - ) { - String soundSource = stringUtils.isNullOrEmpty(notificationModel.content.customSound) ? channelModel.soundSource : notificationModel.content.customSound; - uri = ChannelManager - .getInstance() - .retrieveSoundResourceUri(context, channelModel.defaultRingtoneType, soundSource); - } - - builder.setSound(uri); - } - - private void setSmallIcon(Context context, NotificationModel notificationModel, NotificationChannelModel channelModel, NotificationCompat.Builder builder) throws AwesomeNotificationsException { - if (!stringUtils.isNullOrEmpty(notificationModel.content.icon)) { - builder.setSmallIcon(bitmapUtils.getDrawableResourceId(context, notificationModel.content.icon)); - } else if (!stringUtils.isNullOrEmpty(channelModel.icon)) { - builder.setSmallIcon(bitmapUtils.getDrawableResourceId(context, channelModel.icon)); - } else { - String defaultIcon = DefaultsManager.getDefaultIcon(context); - - if (stringUtils.isNullOrEmpty(defaultIcon)) { - - // for backwards compatibility: this is for handling the old way references to the icon used to be kept but should be removed in future - if (channelModel.iconResourceId != null) { - builder.setSmallIcon(channelModel.iconResourceId); - } else { - try { - int defaultResource = context.getResources().getIdentifier( - "ic_launcher", - "mipmap", - AwesomeNotifications.getPackageName(context) - ); - - if (defaultResource > 0) { - builder.setSmallIcon(defaultResource); - } - } catch (Exception e){ - e.printStackTrace(); - } - } - } else { - int resourceIndex = bitmapUtils.getDrawableResourceId(context, defaultIcon); - if (resourceIndex > 0) { - builder.setSmallIcon(resourceIndex); - } - } - } - } - - private void setGrouping(Context context, NotificationModel notificationModel, NotificationChannelModel channelModel, NotificationCompat.Builder builder) { - - if( // Grouping key is reserved to arrange messaging and messaging group layouts - notificationModel.content.notificationLayout == NotificationLayout.Messaging || - notificationModel.content.notificationLayout == NotificationLayout.MessagingGroup - ) return; - - String groupKey = getGroupKey(notificationModel.content, channelModel); - - if (!stringUtils.isNullOrEmpty(groupKey)) { - builder.setGroup(groupKey); - - if(notificationModel.groupSummary) - builder.setGroupSummary(true); - - String idText = notificationModel.content.id.toString(); - String sortKey = Long.toString( - (channelModel.groupSort == GroupSort.Asc ? System.currentTimeMillis() : Long.MAX_VALUE - System.currentTimeMillis()) - ); - - builder.setSortKey(sortKey + idText); - - builder.setGroupAlertBehavior(channelModel.groupAlertBehavior.ordinal()); - } - } - - private void setLayout( - Context context, - NotificationModel notificationModel, - NotificationChannelModel channelModel, - NotificationCompat.Builder builder - ) throws AwesomeNotificationsException { - switch (notificationModel.content.notificationLayout) { - - case BigPicture: - if (setBigPictureLayout(context, notificationModel.content, builder)) return; - break; - - case BigText: - if (setBigTextStyle(context, notificationModel.content, builder)) return; - break; - - case Inbox: - if (setInboxLayout(context, notificationModel.content, builder)) return; - break; - - case Messaging: - if (setMessagingLayout(context, false, notificationModel.content, channelModel, builder)) return; - break; - - case MessagingGroup: - if(setMessagingLayout(context, true, notificationModel.content, channelModel, builder)) return; - break; - - case MediaPlayer: - if (setMediaPlayerLayout(context, notificationModel.content, notificationModel.actionButtons, builder)) return; - break; - - case ProgressBar: - setProgressLayout(notificationModel, builder); - break; - - case Default: - default: - break; - } - } - - private Boolean setBigPictureLayout(Context context, NotificationContentModel contentModel, NotificationCompat.Builder builder) { - - Bitmap bigPicture = null, largeIcon = null; - - if (!stringUtils.isNullOrEmpty(contentModel.bigPicture)) - bigPicture = bitmapUtils.getBitmapFromSource(context, contentModel.bigPicture, contentModel.roundedBigPicture); - - if (contentModel.hideLargeIconOnExpand) - largeIcon = bigPicture != null ? - bigPicture : (!stringUtils.isNullOrEmpty(contentModel.largeIcon) ? - bitmapUtils.getBitmapFromSource( - context, - contentModel.largeIcon, - contentModel.roundedLargeIcon || contentModel.roundedBigPicture) : null); - else { - boolean areEqual = - !stringUtils.isNullOrEmpty(contentModel.largeIcon) && - contentModel.largeIcon.equals(contentModel.bigPicture); - - if(areEqual) - largeIcon = bigPicture; - else if(!stringUtils.isNullOrEmpty(contentModel.largeIcon)) - largeIcon = - bitmapUtils.getBitmapFromSource(context, contentModel.largeIcon, contentModel.roundedLargeIcon); - } - - if (largeIcon != null) - builder.setLargeIcon(largeIcon); - - if (bigPicture == null) - return false; - - NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); - - bigPictureStyle.bigPicture(bigPicture); - bigPictureStyle.bigLargeIcon(contentModel.hideLargeIconOnExpand ? null : largeIcon); - - if (!stringUtils.isNullOrEmpty(contentModel.title)) { - CharSequence contentTitle = HtmlUtils.fromHtml(contentModel.title); - bigPictureStyle.setBigContentTitle(contentTitle); - } - - if (!stringUtils.isNullOrEmpty(contentModel.body)) { - CharSequence summaryText = HtmlUtils.fromHtml(contentModel.body); - bigPictureStyle.setSummaryText(summaryText); - } - - builder.setStyle(bigPictureStyle); - - return true; - } - - private Boolean setBigTextStyle(Context context, NotificationContentModel contentModel, NotificationCompat.Builder builder) { - - NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle(); - - if (stringUtils.isNullOrEmpty(contentModel.body)) return false; - - CharSequence bigBody = HtmlUtils.fromHtml(contentModel.body); - bigTextStyle.bigText(bigBody); - - if (!stringUtils.isNullOrEmpty(contentModel.summary)) { - CharSequence bigSummary = HtmlUtils.fromHtml(contentModel.summary); - bigTextStyle.setSummaryText(bigSummary); - } - - if (!stringUtils.isNullOrEmpty(contentModel.title)) { - CharSequence bigTitle = HtmlUtils.fromHtml(contentModel.title); - bigTextStyle.setBigContentTitle(bigTitle); - } - - builder.setStyle(bigTextStyle); - - return true; - } - - private Boolean setInboxLayout(Context context, NotificationContentModel contentModel, NotificationCompat.Builder builder) { - - NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); - - if (stringUtils.isNullOrEmpty(contentModel.body)) return false; - - List lines = new ArrayList<>(Arrays.asList(contentModel.body.split("\\r?\\n"))); - - if (ListUtils.isNullOrEmpty(lines)) return false; - - CharSequence summary; - if (stringUtils.isNullOrEmpty(contentModel.summary)) { - summary = "+ " + lines.size() + " more"; - } else { - summary = HtmlUtils.fromHtml(contentModel.body); - } - inboxStyle.setSummaryText(summary); - - if (!stringUtils.isNullOrEmpty(contentModel.title)) { - CharSequence contentTitle = HtmlUtils.fromHtml(contentModel.title); - inboxStyle.setBigContentTitle(contentTitle); - } - - if (contentModel.summary != null) { - CharSequence summaryText = HtmlUtils.fromHtml(contentModel.summary); - inboxStyle.setSummaryText(summaryText); - } - - for (String line : lines) { - inboxStyle.addLine(HtmlUtils.fromHtml(line)); - } - - builder.setStyle(inboxStyle); - return true; - } - - public String getGroupKey(NotificationContentModel contentModel, NotificationChannelModel channelModel){ - return !stringUtils.isNullOrEmpty(contentModel.groupKey) ? - contentModel.groupKey : channelModel.groupKey; - } - - public static final ConcurrentHashMap> messagingQueue = new ConcurrentHashMap>(); - - @SuppressWarnings("unchecked") - private Boolean setMessagingLayout(Context context, boolean isGrouping, NotificationContentModel contentModel, NotificationChannelModel channelModel, NotificationCompat.Builder builder) throws AwesomeNotificationsException { - String groupKey = getGroupKey(contentModel, channelModel); - - //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M /*Android 6*/) { - - String messageQueueKey = groupKey + (isGrouping ? ".Gr" : ""); - - int firstNotificationId = contentModel.id; - List groupIDs = StatusBarManager - .getInstance(context) - .activeNotificationsGroup.get(groupKey); - - if(groupIDs == null || groupIDs.size() == 0) - messagingQueue.remove(messageQueueKey); - else - firstNotificationId = Integer.parseInt(groupIDs.get(0)); - - NotificationMessageModel currentMessage = new NotificationMessageModel( - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ? - contentModel.title : contentModel.summary, - contentModel.body, - contentModel.largeIcon - ); - - List messages = contentModel.messages; - if(ListUtils.isNullOrEmpty(messages)) { - messages = messagingQueue.get(messageQueueKey); - if (messages == null) - messages = new ArrayList<>(); - } - - messages.add(currentMessage); - messagingQueue.put(messageQueueKey, messages); - - contentModel.id = firstNotificationId; - contentModel.messages = messages; - - NotificationCompat.MessagingStyle messagingStyle = - new NotificationCompat.MessagingStyle(contentModel.summary); - - for(NotificationMessageModel message : contentModel.messages) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P /*Android 9*/) { - - Person.Builder personBuilder = new Person.Builder() - .setName(message.title); - - String personIcon = message.largeIcon != null? message.largeIcon :contentModel.largeIcon; - if(!stringUtils.isNullOrEmpty(personIcon)){ - Bitmap largeIcon = bitmapUtils.getBitmapFromSource( - context, - personIcon, - contentModel.roundedLargeIcon); - if(largeIcon != null) - personBuilder.setIcon( - IconCompat.createWithBitmap(largeIcon)); - } - - Person person = personBuilder.build(); - - messagingStyle.addMessage( - message.message, message.timestamp, person); - } else { - messagingStyle.addMessage( - message.message, message.timestamp, message.title); - } - } - - if ( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.P /*Android 9*/ && - !stringUtils.isNullOrEmpty(contentModel.summary) - ){ - messagingStyle.setConversationTitle(contentModel.summary); - messagingStyle.setGroupConversation(isGrouping); - } - - builder.setStyle((NotificationCompat.Style) messagingStyle); - /*} - else { - if(stringUtils.isNullOrEmpty(groupKey)){ - builder.setGroup("Messaging."+groupKey); - } - }*/ - - return true; - } - - private Boolean setMediaPlayerLayout(Context context, NotificationContentModel contentModel, List actionButtons, NotificationCompat.Builder builder) throws AwesomeNotificationsException { - - ArrayList indexes = new ArrayList<>(); - for (int i = 0; i < actionButtons.size(); i++) { - NotificationButtonModel b = actionButtons.get(i); - if (b.showInCompactView) indexes.add(i); - } - - if(!StatusBarManager - .getInstance(context) - .isFirstActiveOnGroupKey(contentModel.groupKey) - ){ - List lastIds = StatusBarManager.getInstance(context).activeNotificationsGroup.get(contentModel.groupKey); - if(lastIds != null && lastIds.size() > 0) - contentModel.id = Integer.parseInt(lastIds.get(0)); - } - - int[] showInCompactView = toIntArray(indexes); - - /* - * This fix is to show the notification in Android versions >= 11 in the QuickSettings area. - * https://developer.android.com/guide/topics/media/media-controls - * https://github.com/rafaelsetragni/awesome_notifications/pull/364 - */ - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R /*Android 11*/){ - - if(mediaSession == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INITIALIZATION_EXCEPTION, - "There is no valid media session available", - ExceptionCode.DETAILED_INSUFFICIENT_REQUIREMENTS); - - mediaSession.setMetadata( - new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, contentModel.title) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, contentModel.body) - .build()); - } - - builder.setStyle( - new MediaStyle() - .setMediaSession(mediaSession.getSessionToken()) - .setShowActionsInCompactView(showInCompactView) - .setShowCancelButton(true)); - - if (!stringUtils.isNullOrEmpty(contentModel.summary)) - builder.setSubText(contentModel.summary); - - if(contentModel.progress != null && IntegerUtils.isBetween(contentModel.progress, 0, 100)) - builder.setProgress( - 100, - Math.max(0, Math.min(100, IntegerUtils.extractInteger(contentModel.progress, 0))), - contentModel.progress == null); - - builder.setShowWhen(false); - - return true; - } - - private void setProgressLayout(NotificationModel notificationModel, NotificationCompat.Builder builder) { - builder.setProgress( - 100, - Math.max(0, Math.min(100, IntegerUtils.extractInteger(notificationModel.content.progress, 0))), - notificationModel.content.progress == null - ); - } - - private int[] toIntArray(ArrayList list) { - if (list == null || list.size() <= 0) return new int[0]; - - int[] result = new int[list.size()]; - for (int i = 0; i < list.size(); i++) { - result[i] = list.get(i); - } - - return result; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/ActivityCompletionHandler.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/ActivityCompletionHandler.java deleted file mode 100644 index fb93fd95..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/ActivityCompletionHandler.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.carda.awesome_notifications.core.completion_handlers; - -public interface ActivityCompletionHandler { - public void handle(); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/BitmapCompletionHandler.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/BitmapCompletionHandler.java deleted file mode 100644 index 9b84b630..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/BitmapCompletionHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.carda.awesome_notifications.core.completion_handlers; - -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; - -public interface BitmapCompletionHandler { - public void handle(byte[] byteArray, AwesomeNotificationsException exception); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/NotificationThreadCompletionHandler.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/NotificationThreadCompletionHandler.java deleted file mode 100644 index 035445c9..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/NotificationThreadCompletionHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.carda.awesome_notifications.core.completion_handlers; - -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; - -public interface NotificationThreadCompletionHandler { - public void handle(boolean success, AwesomeNotificationsException exception) throws AwesomeNotificationsException; -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/PermissionCompletionHandler.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/PermissionCompletionHandler.java deleted file mode 100644 index e4dea89d..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/completion_handlers/PermissionCompletionHandler.java +++ /dev/null @@ -1,6 +0,0 @@ -package me.carda.awesome_notifications.core.completion_handlers; -import java.util.List; - -public interface PermissionCompletionHandler { - public void handle(List missingPermissions); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/decoders/BitmapResourceDecoder.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/decoders/BitmapResourceDecoder.java deleted file mode 100644 index 9399630a..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/decoders/BitmapResourceDecoder.java +++ /dev/null @@ -1,91 +0,0 @@ -package me.carda.awesome_notifications.core.decoders; - -import android.content.Context; -import android.graphics.Bitmap; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.ByteArrayOutputStream; -import java.lang.ref.WeakReference; - -import me.carda.awesome_notifications.core.completion_handlers.BitmapCompletionHandler; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.threads.NotificationThread; -import me.carda.awesome_notifications.core.utils.BitmapUtils; - -public class BitmapResourceDecoder extends NotificationThread { - - public static final String TAG = "BitmapResourceDecoder"; - - //the reason to use a weak reference is to protect from memory leak issues. - private final WeakReference wContextReference; - private final String bitmapReference; - - private final BitmapCompletionHandler completionHandler; - - private Exception exception; - - public BitmapResourceDecoder( - @NonNull Context context, - @Nullable String bitmapReference, - @NonNull BitmapCompletionHandler completionHandler - ){ - this.wContextReference = new WeakReference<>(context); - this.bitmapReference = bitmapReference; - this.completionHandler = completionHandler; - } - - byte[] convertBitmapToByteArray( - @NonNull Bitmap bitmap, - @NonNull ByteArrayOutputStream outputStream - ){ - bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream); - byte[] byteArray = outputStream.toByteArray(); - bitmap.recycle(); - - return byteArray; - } - - @Override - protected byte[] doInBackground() throws AwesomeNotificationsException { - Context context = wContextReference.get(); - if(context != null) { - Bitmap bitmap = BitmapUtils - .getInstance() - .getBitmapFromResource(context, bitmapReference); - - if(bitmap == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_BACKGROUND_EXECUTION_EXCEPTION, - "File '"+ - (bitmapReference == null ? "null" : bitmapReference)+ - "' not found or invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".bitmap.reference"); - - return convertBitmapToByteArray( - bitmap, - new ByteArrayOutputStream()); - } - else - return null; - } - - @Override - protected byte[] onPostExecute(byte[] byteArray) { - return byteArray; - } - - @Override - protected void whenComplete( - @Nullable byte[] returnedValue, - @Nullable AwesomeNotificationsException exception - ) { - completionHandler.handle(returnedValue, exception); - } -} \ No newline at end of file diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ActionType.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ActionType.java deleted file mode 100644 index b1413378..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ActionType.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -public enum ActionType implements SafeEnum { - Default("Default"), - DisabledAction("DisabledAction"), - KeepOnTop("KeepOnTop"), - SilentAction("SilentAction"), - SilentBackgroundAction("SilentBackgroundAction"), - DismissAction("DismissAction"), - @Deprecated - InputField("InputField"); - - private final String safeName; - ActionType(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return safeName; - } - - static ActionType[] valueList = ActionType.class.getEnumConstants(); - public static ActionType getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (ActionType candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'd')){ - if(SafeEnum.charMatches(reference, stringLength, 1, 'e')) return Default; - if(SafeEnum.charMatches(reference, stringLength, 3, 'a')) return DisabledAction; - return DismissAction; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 's')){ - if(SafeEnum.charMatches(reference, stringLength, 6, 'a')) return SilentAction; - return SilentBackgroundAction; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'k')){ - return KeepOnTop; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'i')){ - return InputField; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/DefaultRingtoneType.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/DefaultRingtoneType.java deleted file mode 100644 index ad765b03..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/DefaultRingtoneType.java +++ /dev/null @@ -1,42 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -public enum DefaultRingtoneType implements SafeEnum { - Ringtone("Ringtone"), - Notification("Notification"), - Alarm("Alarm"); - - private final String safeName; - DefaultRingtoneType(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return safeName; - } - - static DefaultRingtoneType[] valueList = DefaultRingtoneType.class.getEnumConstants(); - public static DefaultRingtoneType getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (DefaultRingtoneType candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'n')){ - return Notification; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'r')){ - return Ringtone; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'a')){ - return Alarm; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ForegroundServiceType.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ForegroundServiceType.java deleted file mode 100644 index c857bf8f..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ForegroundServiceType.java +++ /dev/null @@ -1,97 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -import android.content.pm.ServiceInfo; - -import java.util.Locale; - -// https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA -public enum ForegroundServiceType implements SafeEnum { - - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE). - none("none"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST). - manifest("manifest"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC). - dataSync("dataSync"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK). - mediaPlayback("mediaPlayback"), - /// Corresponds to [`Service.START_REDELIVER_INTENT`](https://developer.android.com/reference/android/app/Service#START_REDELIVER_INTENT). - redeliveryIntent("redeliveryIntent"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_PHONE_CALL). - phoneCall("phoneCall"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE). - connectedDevice("connectedDevice"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION). - mediaProjection("mediaProjection"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION). - location("location"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA). - camera("camera"), - /// Corresponds to [`ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE`](https://developer.android.com/reference/android/content/pm/ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE). - microphone("microphone"); - - private final String safeName; - ForegroundServiceType(final String safeName){ - this.safeName = safeName.toLowerCase(Locale.ENGLISH); - } - - public int toAndroidServiceType() { - switch (this){ - case camera: return ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; - case connectedDevice: return ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE; - case dataSync: return ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC; - case location: return ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; - case manifest: return ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; - case mediaPlayback: return ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK; - case mediaProjection: return ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION; - case microphone: return ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; - case phoneCall: return ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL; - case none: - default: - return ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE; - } - } - - @Override - public String getSafeName() { - return this.safeName; - } - - static ForegroundServiceType[] valueList = ForegroundServiceType.class.getEnumConstants(); - public static ForegroundServiceType getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (ForegroundServiceType candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'p')){ - return phoneCall; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'n')){ - return none; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'c')){ - if(SafeEnum.charMatches(reference, stringLength, 1, 'a')) return camera; - return connectedDevice; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'm')){ - if(SafeEnum.charMatches(reference, stringLength, 1, 'i')) return microphone; - if(SafeEnum.charMatches(reference, stringLength, 5, 'l')) return mediaPlayback; - if(SafeEnum.charMatches(reference, stringLength, 5, 'r')) return mediaProjection; - return manifest; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'd')){ - return dataSync; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'l')){ - return location; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ForegroundStartMode.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ForegroundStartMode.java deleted file mode 100644 index 921d73c0..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/ForegroundStartMode.java +++ /dev/null @@ -1,62 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -import static android.app.Service.START_NOT_STICKY; -import static android.app.Service.START_REDELIVER_INTENT; -import static android.app.Service.START_STICKY; -import static android.app.Service.START_STICKY_COMPATIBILITY; - -public enum ForegroundStartMode implements SafeEnum { - - stick("stick"), - stickCompatibility("stickCompatibility"), - notStick("notStick"), - deliverIntent("deliverIntent"); - - private final String safeName; - ForegroundStartMode(final String safeName){ - this.safeName = safeName; - } - - public int toAndroidStartMode() { - switch (this){ - case notStick: return START_NOT_STICKY; - case stickCompatibility: return START_STICKY_COMPATIBILITY; - case deliverIntent: return START_REDELIVER_INTENT; - case stick: - default: - return START_STICKY; - } - } - - @Override - public String getSafeName() { - return this.safeName; - } - - static ForegroundStartMode[] valueList = ForegroundStartMode.class.getEnumConstants(); - public static ForegroundStartMode getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (ForegroundStartMode candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 's')){ - if(SafeEnum.charMatches(reference, stringLength, 5, 'c')) - return stickCompatibility; - return stick; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'n')){ - return notStick; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'd')){ - return deliverIntent; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/GroupAlertBehaviour.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/GroupAlertBehaviour.java deleted file mode 100644 index 12f9ea42..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/GroupAlertBehaviour.java +++ /dev/null @@ -1,44 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -import java.util.Locale; - -public enum GroupAlertBehaviour implements SafeEnum { - All("All"), - Summary("Summary"), - Children("Children"); - - private final String safeName; - GroupAlertBehaviour(final String safeName){ - this.safeName = safeName.toLowerCase(Locale.ENGLISH); - } - - @Override - public String getSafeName() { - return this.safeName; - } - - static GroupAlertBehaviour[] valueList = GroupAlertBehaviour.class.getEnumConstants(); - public static GroupAlertBehaviour getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (GroupAlertBehaviour candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'c')){ - return Children; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'a')){ - return All; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 's')){ - return Summary; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/GroupSort.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/GroupSort.java deleted file mode 100644 index 6463ecb0..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/GroupSort.java +++ /dev/null @@ -1,40 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -import java.util.Locale; - -public enum GroupSort implements SafeEnum { - Asc("Asc"), - Desc("Desc"); - - private final String safeName; - GroupSort(final String safeName){ - this.safeName = safeName.toLowerCase(Locale.ENGLISH); - } - - @Override - public String getSafeName() { - return safeName; - } - - static GroupSort[] valueList = GroupSort.class.getEnumConstants(); - public static GroupSort getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (GroupSort candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'a')){ - return Asc; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'd')){ - return Desc; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/LogLevel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/LogLevel.java deleted file mode 100644 index 90dbdbd1..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/LogLevel.java +++ /dev/null @@ -1,46 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -public enum LogLevel implements SafeEnum { - none("none"), - error("error"), - warnings("warnings"), - all("all"); - - private final String safeName; - LogLevel(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return this.safeName; - } - - static LogLevel[] valueList = LogLevel.class.getEnumConstants(); - public static LogLevel getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (LogLevel candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'n')){ - return none; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'a')){ - return all; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'e')){ - return error; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'w')){ - return warnings; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/MediaSource.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/MediaSource.java deleted file mode 100644 index 40183f99..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/MediaSource.java +++ /dev/null @@ -1,50 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -public enum MediaSource implements SafeEnum { - Resource("Resource"), - Asset("Asset"), - File("File"), - Network("Network"), - Unknown("Unknown"); - - private final String safeName; - MediaSource(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return safeName; - } - - static MediaSource[] valueList = MediaSource.class.getEnumConstants(); - public static MediaSource getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (MediaSource candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'n')){ - return Network; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'a')){ - return Asset; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'r')){ - return Resource; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'f')){ - return File; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'u')){ - return Unknown; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationCategory.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationCategory.java deleted file mode 100644 index 14d1429a..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationCategory.java +++ /dev/null @@ -1,94 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -import androidx.annotation.NonNull; - -public enum NotificationCategory implements SafeEnum { - Alarm("Alarm", "alarm"), - Call("Call", "call"), - Email("Email", "email"), - Error("Error", "err"), - Event("Event", "event"), - LocalSharing("LocalSharing", "location_sharing"), - Message("Message", "msg"), - MissedCall("MissedCall", "missed_call" ), - Navigation("Navigation", "navigation"), - Progress("Progress", "progress"), - Promo("Promo", "promo"), - Recommendation("Recommendation", "recommendation"), - Reminder("Reminder", "reminder"), - Service("Service", "service"), - Social("Social", "social"), - Status("Status", "status"), - StopWatch("StopWatch", "stopwatch" ), - Transport("Transport", "transport"), - Workout("Workout", "workout"); - - private final String safeName; - public final String rawValue; - NotificationCategory(@NonNull String safeName, @NonNull String rawValue) { - this.safeName = safeName; - this.rawValue = rawValue; - } - - @Override - public String getSafeName() { - return safeName; - } - - static NotificationCategory[] valueList = NotificationCategory.class.getEnumConstants(); - public static NotificationCategory getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (NotificationCategory candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'a')){ - return Alarm; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'c')){ - return Call; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'm')){ - if(SafeEnum.charMatches(reference, stringLength, 1, 'e')) return Message; - return MissedCall; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'r')){ - if(SafeEnum.charMatches(reference, stringLength, 2, 'c')) return Recommendation; - return Reminder; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'l')){ - return LocalSharing; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'n')){ - return Navigation; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'p')){ - if(SafeEnum.charMatches(reference, stringLength, 3, 'g')) return Progress; - return Promo; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'e')){ - if(SafeEnum.charMatches(reference, stringLength, 1, 'm')) return Email; - if(SafeEnum.charMatches(reference, stringLength, 1, 'r')) return Error; - return Event; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 's')){ - if(SafeEnum.charMatches(reference, stringLength, 1, 'e')) return Service; - if(SafeEnum.charMatches(reference, stringLength, 1, 'o')) return Social; - if(SafeEnum.charMatches(reference, stringLength, 2, 'a')) return Status; - return StopWatch; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'w')){ - return Workout; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 't')){ - return Transport; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationImportance.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationImportance.java deleted file mode 100644 index e46d83ac..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationImportance.java +++ /dev/null @@ -1,93 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; - -public enum NotificationImportance implements SafeEnum { - None("None"), - Min("Min"), - Low("Low"), - Default("Default"), - High("High"), - Max("Max"); - - private final String safeName; - NotificationImportance(final String safeName){ - this.safeName = safeName; - } - - public static int toAndroidPriority(@Nullable NotificationImportance importance){ - switch (importance == null ? NotificationImportance.Default : importance){ - case None: - case Min: return NotificationCompat.PRIORITY_MIN; - case Low: return NotificationCompat.PRIORITY_LOW; - case High: return NotificationCompat.PRIORITY_HIGH; - case Max: return NotificationCompat.PRIORITY_MAX; - case Default: - default: - return NotificationCompat.PRIORITY_DEFAULT; - } - //return Math.min(Math.max(IntegerUtils.extractInteger(importance) - 3, -2), 2); - } - - public static int toAndroidImportance(@Nullable NotificationImportance importance){ - return importance == null ? NotificationImportance.Default.ordinal() : importance.ordinal(); - } - - public static NotificationImportance fromAndroidPriority(int ordinal){ - if(ordinal < -2) ordinal = -2; - if(ordinal > 2) ordinal = 2; - switch (ordinal){ - case NotificationCompat.PRIORITY_MIN: return NotificationImportance.Min; - case NotificationCompat.PRIORITY_LOW: return NotificationImportance.Low; - case NotificationCompat.PRIORITY_HIGH: return NotificationImportance.High; - case NotificationCompat.PRIORITY_MAX: return NotificationImportance.Max; - case NotificationCompat.PRIORITY_DEFAULT: - default: return NotificationImportance.Default; - } - //return NotificationImportance.values()[Math.max(Math.min(ordinal + 3, 5), 0)]; - } - - public static NotificationImportance fromAndroidImportance(int ordinal){ - if(ordinal < 0) ordinal = 0; - if(ordinal > 5) ordinal = 5; - return NotificationImportance.values()[ordinal]; - } - - @Override - public String getSafeName() { - return safeName; - } - - static NotificationImportance[] valueList = NotificationImportance.class.getEnumConstants(); - public static NotificationImportance getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (NotificationImportance candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'h')){ - return High; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'd')){ - return Default; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'l')){ - return Low; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'm')){ - if (SafeEnum.charMatches(reference, stringLength, 1, 'a')) return Max; - return Min; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'n')){ - return None; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationLayout.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationLayout.java deleted file mode 100644 index 75d484c2..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationLayout.java +++ /dev/null @@ -1,57 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -public enum NotificationLayout implements SafeEnum { - Default("Default"), - BigPicture("BigPicture"), - BigText("BigText"), - Inbox("Inbox"), - ProgressBar("ProgressBar"), - Messaging("Messaging"), - MessagingGroup("MessagingGroup"), - MediaPlayer("MediaPlayer"); - - private final String safeName; - NotificationLayout(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return safeName; - } - - static NotificationLayout[] valueList = NotificationLayout.class.getEnumConstants(); - public static NotificationLayout getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (NotificationLayout candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'd')){ - return Default; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'b')){ - if(SafeEnum.charMatches(reference, stringLength, 3, 'p')) return BigPicture; - return BigText; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'm')){ - if(SafeEnum.charMatches(reference, stringLength, 2, 'd')) return MediaPlayer; - if(SafeEnum.charMatches(reference, stringLength, 9, 'g')) return MessagingGroup; - return Messaging; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'i')){ - return Inbox; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'p')){ - return ProgressBar; - } - return null; - } -} - diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationLifeCycle.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationLifeCycle.java deleted file mode 100644 index ef53424b..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationLifeCycle.java +++ /dev/null @@ -1,42 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -public enum NotificationLifeCycle implements SafeEnum { - Foreground("Foreground"), - Background("Background"), - AppKilled("AppKilled"); - - private final String safeName; - NotificationLifeCycle(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return safeName; - } - - static NotificationLifeCycle[] valueList = NotificationLifeCycle.class.getEnumConstants(); - public static NotificationLifeCycle getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (NotificationLifeCycle candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'a')){ - return AppKilled; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'f')){ - return Foreground; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'b')){ - return Background; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationPermission.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationPermission.java deleted file mode 100644 index 394450ef..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationPermission.java +++ /dev/null @@ -1,107 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -public enum NotificationPermission implements SafeEnum { - - /** - * [Alert] The ability to display alerts. - */ - Alert("Alert"), - - /** - * [Sound] The ability to display alerts. - */ - Sound("Sound"), - - /** - * [Badge] The ability to play sounds. - */ - Badge("Badge"), - - /** - * [Vibration] The ability to vibrates the device. - */ - Vibration("Vibration"), - - /** - * [Lights] The ability to turn on alert lights on device. - */ - Light("Light"), - - /** - * [CriticalAlert] The ability to play sounds for critical alerts. - */ - CriticalAlert("CriticalAlert"), - - - /** - * [OverrideDnD] The ability to deactivate DnD when needs to show critical alerts. - */ - OverrideDnD("OverrideDnD"), - - /** - * [Provisional] The ability to post noninterrupting notifications provisionally to the Notification Center. - */ - Provisional("Provisional"), - - PreciseAlarms("PreciseAlarms"), - FullScreenIntent("FullScreenIntent"), - /** - * [Car] The ability to display notifications in a CarPlay environment. - */ - Car("Car"); - - private final String safeName; - NotificationPermission(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return safeName; - } - - static NotificationPermission[] valueList = NotificationPermission.class.getEnumConstants(); - public static NotificationPermission getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (NotificationPermission candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'a')){ - return Alert; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 's')){ - return Sound; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'b')){ - return Badge; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'v')){ - return Vibration; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'p')){ - if (SafeEnum.charMatches(reference, stringLength, 2, 'e')) return PreciseAlarms; - return Provisional; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'o')){ - return OverrideDnD; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'c')){ - if (SafeEnum.charMatches(reference, stringLength, 1, 'r')) return CriticalAlert; - return Car; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'l')){ - return Light; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'f')){ - return FullScreenIntent; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationPrivacy.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationPrivacy.java deleted file mode 100644 index 5ae69290..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationPrivacy.java +++ /dev/null @@ -1,71 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -import android.app.Notification; - -import androidx.annotation.Nullable; - -public enum NotificationPrivacy implements SafeEnum { - - /** - * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. - */ - Secret("Secret"), - - /** - * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or - * private information on secure lockscreens. - */ - Private("Private"), - - /** - * Notification visibility: Show this notification on every lockscreens. - */ - Public("Public"); - - private final String safeName; - NotificationPrivacy(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return safeName; - } - - public static int toAndroidPrivacy(@Nullable NotificationPrivacy importance){ - switch (importance == null ? NotificationPrivacy.Private : importance){ - case Secret: - return Notification.VISIBILITY_SECRET; - case Public: - return Notification.VISIBILITY_PUBLIC; - case Private: - default: - return Notification.VISIBILITY_PRIVATE; - } - } - - static NotificationPrivacy[] valueList = NotificationPrivacy.class.getEnumConstants(); - public static NotificationPrivacy getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (NotificationPrivacy candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 's')){ - return Secret; - } - if (SafeEnum.charMatches(reference, stringLength, 1, 'u')){ - return Public; - } - if (SafeEnum.charMatches(reference, stringLength, 1, 'r')){ - return Private; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationSource.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationSource.java deleted file mode 100644 index 03ad4c34..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/NotificationSource.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - - -public enum NotificationSource implements SafeEnum { - Local("Local"), - Schedule("Schedule"), - ForegroundService("ForegroundService"), - Firebase("Firebase"), - OneSignal("OneSignal"), - CallKit("CallKit"); - - private final String safeName; - NotificationSource(final String safeName){ - this.safeName = safeName; - } - - @Override - public String getSafeName() { - return safeName; - } - - static NotificationSource[] valueList = NotificationSource.class.getEnumConstants(); - public static NotificationSource getSafeEnum(String reference) { - if (reference == null) return null; - int stringLength = reference.length(); - if (stringLength == 0) return null; - -// if(valueList == null) return null; -// for (NotificationSource candidate : valueList) { -// if (candidate.getSafeName().equalsIgnoreCase(reference)) { -// return candidate; -// } -// } - - if (SafeEnum.charMatches(reference, stringLength, 0, 'l')){ - return Local; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'f')){ - if (SafeEnum.charMatches(reference, stringLength, 1, 'o')) return ForegroundService; - return Firebase; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 's')){ - return Schedule; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'c')){ - return CallKit; - } - if (SafeEnum.charMatches(reference, stringLength, 0, 'o')){ - return OneSignal; - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/SafeEnum.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/SafeEnum.java deleted file mode 100644 index e88175e4..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/enumerators/SafeEnum.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.carda.awesome_notifications.core.enumerators; - -import androidx.annotation.NonNull; - -/// Protected Enums against minification and obfuscation -public interface SafeEnum { - String getSafeName(); - - static boolean charMatches(@NonNull String text, int stringLength, int pos, char match) { - if (stringLength <= pos) return false; - return match == Character.toLowerCase(text.charAt(pos)); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/AwesomeNotificationsException.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/AwesomeNotificationsException.java deleted file mode 100644 index b58cdb01..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/AwesomeNotificationsException.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.carda.awesome_notifications.core.exceptions; - -import androidx.annotation.NonNull; - -public class AwesomeNotificationsException extends Exception { - - final String code; - final String detailedCode; - - AwesomeNotificationsException( - @NonNull String code, - @NonNull String message, - @NonNull String detailedCode - ){ - super(message); - this.code = code; - this.detailedCode = detailedCode; - } - - AwesomeNotificationsException( - @NonNull String code, - @NonNull String message, - @NonNull String detailedCode, - @NonNull StackTraceElement[] stackTraceElement - ){ - super(message); - this.code = code; - this.detailedCode = detailedCode; - this.setStackTrace(stackTraceElement); - } - - AwesomeNotificationsException( - @NonNull String code, - @NonNull String message, - @NonNull String detailedCode, - @NonNull Exception originalException - ){ - super(message); - this.code = code; - this.detailedCode = detailedCode; - this.setStackTrace(originalException.getStackTrace()); - } - - @NonNull - public final String getCode(){ - return code; - } - - @NonNull - public String getDetailedCode() { - return this.detailedCode; - } -} \ No newline at end of file diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/ExceptionCode.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/ExceptionCode.java deleted file mode 100644 index 8d4530d5..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/ExceptionCode.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.carda.awesome_notifications.core.exceptions; - -public interface ExceptionCode { - - String CODE_UNKNOWN_EXCEPTION = "UNKNOWN_EXCEPTION"; - String CODE_INITIALIZATION_EXCEPTION = "INITIALIZATION_EXCEPTION"; - String CODE_MISSING_ARGUMENTS = "MISSING_ARGUMENTS"; - String CODE_INVALID_ARGUMENTS = "INVALID_ARGUMENTS"; - String CODE_INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS"; - String CODE_SHARED_PREFERENCES_NOT_AVAILABLE = "SHARED_PREFERENCES_NOT_AVAILABLE"; - String CODE_INVALID_IMAGE = "INVALID_IMAGE"; - String CODE_CLASS_NOT_FOUND = "CLASS_NOT_FOUND"; - String CODE_BACKGROUND_EXECUTION_EXCEPTION = "BACKGROUND_EXECUTION_EXCEPTION"; - String CODE_NOTIFICATION_THREAD_EXCEPTION = "NOTIFICATION_THREAD_EXCEPTION"; - String CODE_PAGE_NOT_FOUND = "PAGE_NOT_FOUND"; - String CODE_EVENT_EXCEPTION = "EVENT_EXCEPTION"; - - - String DETAILED_UNEXPECTED_ERROR = "unexpectedError"; - String DETAILED_REQUIRED_ARGUMENTS = "arguments.required"; - String DETAILED_CLASS_NOT_FOUND = "class.notFound"; - String DETAILED_INVALID_ARGUMENTS = "arguments.invalid"; - String DETAILED_PAGE_NOT_FOUND = "pageNotFound"; - String DETAILED_INITIALIZATION_FAILED = "initialization"; - String DETAILED_SHARED_PREFERENCES = "sharedPreferences"; - String DETAILED_INSUFFICIENT_PERMISSIONS = "insufficientPermissions"; - String DETAILED_INSUFFICIENT_REQUIREMENTS = "insufficientRequirements"; -} \ No newline at end of file diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/ExceptionFactory.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/ExceptionFactory.java deleted file mode 100644 index f1e67ea6..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/exceptions/ExceptionFactory.java +++ /dev/null @@ -1,135 +0,0 @@ -package me.carda.awesome_notifications.core.exceptions; - -import androidx.annotation.NonNull; - -import me.carda.awesome_notifications.core.broadcasters.receivers.AwesomeExceptionReceiver; - -public class ExceptionFactory { - - public static String TAG = "ExceptionFactory"; - - // ************** SINGLETON PATTERN *********************** - - private static ExceptionFactory instance; - - private ExceptionFactory(){} - - public static ExceptionFactory getInstance() { - if (instance == null) - instance = new ExceptionFactory(); - return instance; - } - - /// ************** FACTORY METHODS ********************* - - public AwesomeNotificationsException createNewAwesomeException( - @NonNull String className, - @NonNull String code, - @NonNull String message, - @NonNull String detailedCode - ) { - return createNewAwesomeException( - className, - new AwesomeNotificationsException( - code, - message, - detailedCode)); - } - - public AwesomeNotificationsException createNewAwesomeException( - @NonNull String className, - @NonNull String code, - @NonNull String message, - @NonNull String detailedCode, - @NonNull Exception originalException - ) { - return createNewAwesomeException( - className, - new AwesomeNotificationsException( - code, - message, - detailedCode, - originalException)); - } - - public AwesomeNotificationsException createNewAwesomeException( - @NonNull String className, - @NonNull String code, - @NonNull String detailedCode, - @NonNull Exception e - ) { - return createNewAwesomeException( - className, - new AwesomeNotificationsException( - code, - String.format("%s", e.getLocalizedMessage()), - detailedCode, - e)); - } - - public void registerNewAwesomeException( - @NonNull String className, - @NonNull String code, - @NonNull String message, - @NonNull String detailedCode - ) { - registerAwesomeException( - className, - new AwesomeNotificationsException( - code, - message, - detailedCode)); - } - - public void registerNewAwesomeException( - @NonNull String className, - @NonNull String code, - @NonNull String message, - @NonNull String detailedCode, - @NonNull Exception originalException - ) { - registerAwesomeException( - className, - new AwesomeNotificationsException( - code, - message, - detailedCode, - originalException)); - } - - public void registerNewAwesomeException( - @NonNull String className, - @NonNull String code, - @NonNull String detailedCode, - @NonNull Exception originalException - ) { - registerAwesomeException( - className, - new AwesomeNotificationsException( - code, - String.format("%s", originalException.getLocalizedMessage()), - detailedCode, - originalException)); - } - - /// ************** FACTORY METHODS ********************* - - private AwesomeNotificationsException createNewAwesomeException( - @NonNull String className, - @NonNull AwesomeNotificationsException exception - ) { - AwesomeExceptionReceiver - .getInstance() - .notifyNewException(className, exception); - return exception; - } - - private void registerAwesomeException( - @NonNull String className, - @NonNull AwesomeNotificationsException exception - ) { - AwesomeExceptionReceiver - .getInstance() - .notifyNewException(className, exception); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/externalLibs/CronExpression.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/externalLibs/CronExpression.java deleted file mode 100644 index 8860ccbe..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/externalLibs/CronExpression.java +++ /dev/null @@ -1,1665 +0,0 @@ -package me.carda.awesome_notifications.core.externalLibs; - -/* - * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - */ - -import java.io.Serializable; -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; -import java.util.SortedSet; -import java.util.StringTokenizer; -import java.util.TimeZone; -import java.util.TreeSet; - -/** - * Provides a parser and evaluator for unix-like cron expressions. Cron - * expressions provide the ability to specify complex time combinations such as - * "At 8:00am every Monday through Friday" or "At 1:30am every - * last Friday of the month". - *

- * Cron expressions are comprised of 6 required fields and one optional field - * separated by white space. The fields respectively are described as follows: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Field Name Allowed Values Allowed Special Characters
Seconds  - * 0-59  - * , - * /
Minutes  - * 0-59  - * , - * /
Hours  - * 0-23  - * , - * /
Day-of-month  - * 1-31  - * , - * ? / L W
Month  - * 0-11 or JAN-DEC  - * , - * /
Day-of-Week  - * 1-7 or SUN-SAT  - * , - * ? / L #
Year (Optional)  - * empty, 1970-2199  - * , - * /
- *

- * The '*' character is used to specify all values. For example, "*" - * in the minute field means "every minute". - *

- * The '?' character is allowed for the day-of-month and day-of-week fields. It - * is used to specify 'no specific value'. This is useful when you need to - * specify something in one of the two fields, but not the other. - *

- * The '-' character is used to specify ranges For example "10-12" in - * the hour field means "the hours 10, 11 and 12". - *

- * The ',' character is used to specify additional values. For example - * "MON,WED,FRI" in the day-of-week field means "the days Monday, - * Wednesday, and Friday". - *

- * The '/' character is used to specify increments. For example "0/15" - * in the seconds field means "the seconds 0, 15, 30, and 45". And - * "5/15" in the seconds field means "the seconds 5, 20, 35, and - * 50". Specifying '*' before the '/' is equivalent to specifying 0 is - * the value to start with. Essentially, for each field in the expression, there - * is a set of numbers that can be turned on or off. For seconds and minutes, - * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to - * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn - * on every "nth" value in the given set. Thus "7/6" in the - * month field only turns on month "7", it does NOT mean every 6th - * month, please note that subtlety. - *

- * The 'L' character is allowed for the day-of-month and day-of-week fields. - * This character is short-hand for "last", but it has different - * meaning in each of the two fields. For example, the value "L" in - * the day-of-month field means "the last day of the month" - day 31 - * for January, day 28 for February on non-leap years. If used in the - * day-of-week field by itself, it simply means "7" or - * "SAT". But if used in the day-of-week field after another value, it - * means "the last xxx day of the month" - for example "6L" - * means "the last friday of the month". You can also specify an offset - * from the last day of the month, such as "L-3" which would mean the third-to-last - * day of the calendar month. When using the 'L' option, it is important not to - * specify lists, or ranges of values, as you'll get confusing/unexpected results. - *

- * The 'W' character is allowed for the day-of-month field. This character - * is used to specify the weekday (Monday-Friday) nearest the given day. As an - * example, if you were to specify "15W" as the value for the - * day-of-month field, the meaning is: "the nearest weekday to the 15th of - * the month". So if the 15th is a Saturday, the trigger will fire on - * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the - * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. - * However if you specify "1W" as the value for day-of-month, and the - * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not - * 'jump' over the boundary of a month's days. The 'W' character can only be - * specified when the day-of-month is a single day, not a range or list of days. - *

- * The 'L' and 'W' characters can also be combined for the day-of-month - * expression to yield 'LW', which translates to "last weekday of the - * month". - *

- * The '#' character is allowed for the day-of-week field. This character is - * used to specify "the nth" XXX day of the month. For example, the - * value of "6#3" in the day-of-week field means the third Friday of - * the month (day 6 = Friday and "#3" = the 3rd one in the month). - * Other examples: "2#1" = the first Monday of the month and - * "4#5" = the fifth Wednesday of the month. Note that if you specify - * "#5" and there is not 5 of the given day-of-week in the month, then - * no firing will occur that month. If the '#' character is used, there can - * only be one expression in the day-of-week field ("3#1,6#3" is - * not valid, since there are two expressions). - *

- * - *

- * The legal characters and the names of months and days of the week are not - * case sensitive. - * - *

- * NOTES: - *

    - *
  • Support for specifying both a day-of-week and a day-of-month value is - * not complete (you'll need to use the '?' character in one of these fields). - *
  • - *
  • Overflowing ranges is supported - that is, having a larger number on - * the left hand side than the right. You might do 22-2 to catch 10 o'clock - * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is - * very important to note that overuse of overflowing ranges creates ranges - * that don't make sense and no effort has been made to determine which - * interpretation CronExpression chooses. An example would be - * "0 0 14-6 ? * FRI-MON".
  • - *
- *

- * - * - * @author Sharada Jambula, James House - * @author Contributions from Mads Henderson - * @author Refactoring from CronTrigger to CronExpression by Aaron Craven - */ -public class CronExpression implements Serializable, Cloneable { - - private static final long serialVersionUID = 12423409423L; - - protected static final int SECOND = 0; - protected static final int MINUTE = 1; - protected static final int HOUR = 2; - protected static final int DAY_OF_MONTH = 3; - protected static final int MONTH = 4; - protected static final int DAY_OF_WEEK = 5; - protected static final int YEAR = 6; - protected static final int ALL_SPEC_INT = 99; // '*' - protected static final int NO_SPEC_INT = 98; // '?' - protected static final Integer ALL_SPEC = ALL_SPEC_INT; - protected static final Integer NO_SPEC = NO_SPEC_INT; - - protected static final Map monthMap = new HashMap(20); - protected static final Map dayMap = new HashMap(60); - static { - monthMap.put("JAN", 0); - monthMap.put("FEB", 1); - monthMap.put("MAR", 2); - monthMap.put("APR", 3); - monthMap.put("MAY", 4); - monthMap.put("JUN", 5); - monthMap.put("JUL", 6); - monthMap.put("AUG", 7); - monthMap.put("SEP", 8); - monthMap.put("OCT", 9); - monthMap.put("NOV", 10); - monthMap.put("DEC", 11); - - dayMap.put("SUN", 1); - dayMap.put("MON", 2); - dayMap.put("TUE", 3); - dayMap.put("WED", 4); - dayMap.put("THU", 5); - dayMap.put("FRI", 6); - dayMap.put("SAT", 7); - } - - private final String cronExpression; - private TimeZone timeZone = null; - protected transient TreeSet seconds; - protected transient TreeSet minutes; - protected transient TreeSet hours; - protected transient TreeSet daysOfMonth; - protected transient TreeSet months; - protected transient TreeSet daysOfWeek; - protected transient TreeSet years; - - protected transient boolean lastdayOfWeek = false; - protected transient int nthdayOfWeek = 0; - protected transient boolean lastdayOfMonth = false; - protected transient boolean nearestWeekday = false; - protected transient int lastdayOffset = 0; - protected transient boolean expressionParsed = false; - - public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; - - /** - * Constructs a new CronExpression based on the specified - * parameter. - * - * @param cronExpression String representation of the cron expression the - * new object should represent - * @throws ParseException - * if the string expression cannot be parsed into a valid - * CronExpression - */ - public CronExpression(String cronExpression) throws ParseException { - if (cronExpression == null) { - throw new IllegalArgumentException("cronExpression cannot be null"); - } - - this.cronExpression = cronExpression.toUpperCase(Locale.US); - - buildExpression(this.cronExpression); - } - - /** - * Constructs a new {@code CronExpression} as a copy of an existing - * instance. - * - * @param expression - * The existing cron expression to be copied - */ - public CronExpression(CronExpression expression) { - /* - * We don't call the other constructor here since we need to swallow the - * ParseException. We also elide some of the sanity checking as it is - * not logically trippable. - */ - this.cronExpression = expression.getCronExpression(); - try { - buildExpression(cronExpression); - } catch (ParseException ex) { - throw new AssertionError(); - } - if (expression.getTimeZone() != null) { - setTimeZone((TimeZone) expression.getTimeZone().clone()); - } - } - - /** - * Indicates whether the given date satisfies the cron expression. Note that - * milliseconds are ignored, so two Dates falling on different milliseconds - * of the same second will always have the same result here. - * - * @param date the date to evaluate - * @return a boolean indicating whether the given date satisfies the cron - * expression - */ - public boolean isSatisfiedBy(Date date) { - Calendar testDateCal = Calendar.getInstance(getTimeZone()); - testDateCal.setTime(date); - testDateCal.set(Calendar.MILLISECOND, 0); - Date originalDate = testDateCal.getTime(); - - testDateCal.add(Calendar.SECOND, -1); - - Date timeAfter = getTimeAfter(testDateCal.getTime()); - - return ((timeAfter != null) && (timeAfter.equals(originalDate))); - } - - /** - * Returns the next date/time after the given date/time which - * satisfies the cron expression. - * - * @param date the date/time at which to begin the search for the next valid - * date/time - * @return the next valid date/time - */ - public Date getNextValidTimeAfter(Date date) { - return getTimeAfter(date); - } - - /** - * Returns the next date/time after the given date/time which does - * not satisfy the expression - * - * @param date the date/time at which to begin the search for the next - * invalid date/time - * @return the next valid date/time - */ - public Date getNextInvalidTimeAfter(Date date) { - long difference = 1000; - - //move back to the nearest second so differences will be accurate - Calendar adjustCal = Calendar.getInstance(getTimeZone()); - adjustCal.setTime(date); - adjustCal.set(Calendar.MILLISECOND, 0); - Date lastDate = adjustCal.getTime(); - - Date newDate; - - //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution. - - //keep getting the next included time until it's farther than one second - // apart. At that point, lastDate is the last valid fire time. We return - // the second immediately following it. - while (difference == 1000) { - newDate = getTimeAfter(lastDate); - if(newDate == null) - break; - - difference = newDate.getTime() - lastDate.getTime(); - - if (difference == 1000) { - lastDate = newDate; - } - } - - return new Date(lastDate.getTime() + 1000); - } - - /** - * Returns the time zone for which this CronExpression - * will be resolved. - */ - public TimeZone getTimeZone() { - if (timeZone == null) { - timeZone = TimeZone.getDefault(); - } - - return timeZone; - } - - /** - * Sets the time zone for which this CronExpression - * will be resolved. - */ - public void setTimeZone(TimeZone timeZone) { - this.timeZone = timeZone; - } - - /** - * Returns the string representation of the CronExpression - * - * @return a string representation of the CronExpression - */ - @Override - public String toString() { - return cronExpression; - } - - /** - * Indicates whether the specified cron expression can be parsed into a - * valid cron expression - * - * @param cronExpression the expression to evaluate - * @return a boolean indicating whether the given expression is a valid cron - * expression - */ - public static boolean isValidExpression(String cronExpression) { - - try { - new CronExpression(cronExpression); - } catch (ParseException pe) { - return false; - } - - return true; - } - - public static void validateExpression(String cronExpression) throws ParseException { - - new CronExpression(cronExpression); - } - - - //////////////////////////////////////////////////////////////////////////// - // - // Expression Parsing Functions - // - //////////////////////////////////////////////////////////////////////////// - - protected void buildExpression(String expression) throws ParseException { - expressionParsed = true; - - try { - - if (seconds == null) { - seconds = new TreeSet(); - } - if (minutes == null) { - minutes = new TreeSet(); - } - if (hours == null) { - hours = new TreeSet(); - } - if (daysOfMonth == null) { - daysOfMonth = new TreeSet(); - } - if (months == null) { - months = new TreeSet(); - } - if (daysOfWeek == null) { - daysOfWeek = new TreeSet(); - } - if (years == null) { - years = new TreeSet(); - } - - int exprOn = SECOND; - - StringTokenizer exprsTok = new StringTokenizer(expression, " \t", - false); - - while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { - String expr = exprsTok.nextToken().trim(); - - // throw an exception if L is used with other days of the month - if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { - throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); - } - // throw an exception if L is used with other days of the week - if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { - throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1); - } - if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) { - throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1); - } - - StringTokenizer vTok = new StringTokenizer(expr, ","); - while (vTok.hasMoreTokens()) { - String v = vTok.nextToken(); - storeExpressionVals(0, v, exprOn); - } - - exprOn++; - } - - if (exprOn <= DAY_OF_WEEK) { - throw new ParseException("Unexpected end of expression.", - expression.length()); - } - - if (exprOn <= YEAR) { - storeExpressionVals(0, "*", YEAR); - } - - TreeSet dow = getSet(DAY_OF_WEEK); - TreeSet dom = getSet(DAY_OF_MONTH); - - // Copying the logic from the UnsupportedOperationException below - boolean dayOfMSpec = !dom.contains(NO_SPEC); - boolean dayOfWSpec = !dow.contains(NO_SPEC); - - if (!dayOfMSpec || dayOfWSpec) { - if (!dayOfWSpec || dayOfMSpec) { - throw new ParseException( - "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0); - } - } - } catch (ParseException pe) { - throw pe; - } catch (Exception e) { - throw new ParseException("Illegal cron expression format (" - + e.toString() + ")", 0); - } - } - - protected int storeExpressionVals(int pos, String s, int type) - throws ParseException { - - int incr = 0; - int i = skipWhiteSpace(pos, s); - if (i >= s.length()) { - return i; - } - char c = s.charAt(i); - if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) { - String sub = s.substring(i, i + 3); - int sval = -1; - int eval = -1; - if (type == MONTH) { - sval = getMonthNumber(sub) + 1; - if (sval <= 0) { - throw new ParseException("Invalid Month value: '" + sub + "'", i); - } - if (s.length() > i + 3) { - c = s.charAt(i + 3); - if (c == '-') { - i += 4; - sub = s.substring(i, i + 3); - eval = getMonthNumber(sub) + 1; - if (eval <= 0) { - throw new ParseException("Invalid Month value: '" + sub + "'", i); - } - } - } - } else if (type == DAY_OF_WEEK) { - sval = getDayOfWeekNumber(sub); - if (sval < 0) { - throw new ParseException("Invalid Day-of-Week value: '" - + sub + "'", i); - } - if (s.length() > i + 3) { - c = s.charAt(i + 3); - if (c == '-') { - i += 4; - sub = s.substring(i, i + 3); - eval = getDayOfWeekNumber(sub); - if (eval < 0) { - throw new ParseException( - "Invalid Day-of-Week value: '" + sub - + "'", i); - } - } else if (c == '#') { - try { - i += 4; - nthdayOfWeek = Integer.parseInt(s.substring(i)); - if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { - throw new Exception(); - } - } catch (Exception e) { - throw new ParseException( - "A numeric value between 1 and 5 must follow the '#' option", - i); - } - } else if (c == 'L') { - lastdayOfWeek = true; - i++; - } - } - - } else { - throw new ParseException( - "Illegal characters for this position: '" + sub + "'", - i); - } - if (eval != -1) { - incr = 1; - } - addToSet(sval, eval, incr, type); - return (i + 3); - } - - if (c == '?') { - i++; - if ((i + 1) < s.length() - && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { - throw new ParseException("Illegal character after '?': " - + s.charAt(i), i); - } - if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { - throw new ParseException( - "'?' can only be specified for Day-of-Month or Day-of-Week.", - i); - } - if (type == DAY_OF_WEEK && !lastdayOfMonth) { - int val = daysOfMonth.last(); - if (val == NO_SPEC_INT) { - throw new ParseException( - "'?' can only be specified for Day-of-Month -OR- Day-of-Week.", - i); - } - } - - addToSet(NO_SPEC_INT, -1, 0, type); - return i; - } - - if (c == '*' || c == '/') { - if (c == '*' && (i + 1) >= s.length()) { - addToSet(ALL_SPEC_INT, -1, incr, type); - return i + 1; - } else if (c == '/' - && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s - .charAt(i + 1) == '\t')) { - throw new ParseException("'/' must be followed by an integer.", i); - } else if (c == '*') { - i++; - } - c = s.charAt(i); - if (c == '/') { // is an increment specified? - i++; - if (i >= s.length()) { - throw new ParseException("Unexpected end of string.", i); - } - - incr = getNumericValue(s, i); - - i++; - if (incr > 10) { - i++; - } - checkIncrementRange(incr, type, i); - } else { - incr = 1; - } - - addToSet(ALL_SPEC_INT, -1, incr, type); - return i; - } else if (c == 'L') { - i++; - if (type == DAY_OF_MONTH) { - lastdayOfMonth = true; - } - if (type == DAY_OF_WEEK) { - addToSet(7, 7, 0, type); - } - if(type == DAY_OF_MONTH && s.length() > i) { - c = s.charAt(i); - if(c == '-') { - ValueSet vs = getValue(0, s, i+1); - lastdayOffset = vs.value; - if(lastdayOffset > 30) - throw new ParseException("Offset from last day must be <= 30", i+1); - i = vs.pos; - } - if(s.length() > i) { - c = s.charAt(i); - if(c == 'W') { - nearestWeekday = true; - i++; - } - } - } - return i; - } else if (c >= '0' && c <= '9') { - int val = Integer.parseInt(String.valueOf(c)); - i++; - if (i >= s.length()) { - addToSet(val, -1, -1, type); - } else { - c = s.charAt(i); - if (c >= '0' && c <= '9') { - ValueSet vs = getValue(val, s, i); - val = vs.value; - i = vs.pos; - } - i = checkNext(i, s, val, type); - return i; - } - } else { - throw new ParseException("Unexpected character: " + c, i); - } - - return i; - } - - private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException { - if (incr > 59 && (type == SECOND || type == MINUTE)) { - throw new ParseException("Increment > 60 : " + incr, idxPos); - } else if (incr > 23 && (type == HOUR)) { - throw new ParseException("Increment > 24 : " + incr, idxPos); - } else if (incr > 31 && (type == DAY_OF_MONTH)) { - throw new ParseException("Increment > 31 : " + incr, idxPos); - } else if (incr > 7 && (type == DAY_OF_WEEK)) { - throw new ParseException("Increment > 7 : " + incr, idxPos); - } else if (incr > 12 && (type == MONTH)) { - throw new ParseException("Increment > 12 : " + incr, idxPos); - } - } - - protected int checkNext(int pos, String s, int val, int type) - throws ParseException { - - int end = -1; - int i = pos; - - if (i >= s.length()) { - addToSet(val, end, -1, type); - return i; - } - - char c = s.charAt(pos); - - if (c == 'L') { - if (type == DAY_OF_WEEK) { - if(val < 1 || val > 7) - throw new ParseException("Day-of-Week values must be between 1 and 7", -1); - lastdayOfWeek = true; - } else { - throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); - } - TreeSet set = getSet(type); - set.add(val); - i++; - return i; - } - - if (c == 'W') { - if (type == DAY_OF_MONTH) { - nearestWeekday = true; - } else { - throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); - } - if(val > 31) - throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); - TreeSet set = getSet(type); - set.add(val); - i++; - return i; - } - - if (c == '#') { - if (type != DAY_OF_WEEK) { - throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i); - } - i++; - try { - nthdayOfWeek = Integer.parseInt(s.substring(i)); - if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { - throw new Exception(); - } - } catch (Exception e) { - throw new ParseException( - "A numeric value between 1 and 5 must follow the '#' option", - i); - } - - TreeSet set = getSet(type); - set.add(val); - i++; - return i; - } - - if (c == '-') { - i++; - c = s.charAt(i); - int v = Integer.parseInt(String.valueOf(c)); - end = v; - i++; - if (i >= s.length()) { - addToSet(val, end, 1, type); - return i; - } - c = s.charAt(i); - if (c >= '0' && c <= '9') { - ValueSet vs = getValue(v, s, i); - end = vs.value; - i = vs.pos; - } - if (i < s.length() && ((c = s.charAt(i)) == '/')) { - i++; - c = s.charAt(i); - int v2 = Integer.parseInt(String.valueOf(c)); - i++; - if (i >= s.length()) { - addToSet(val, end, v2, type); - return i; - } - c = s.charAt(i); - if (c >= '0' && c <= '9') { - ValueSet vs = getValue(v2, s, i); - int v3 = vs.value; - addToSet(val, end, v3, type); - i = vs.pos; - return i; - } else { - addToSet(val, end, v2, type); - return i; - } - } else { - addToSet(val, end, 1, type); - return i; - } - } - - if (c == '/') { - if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') { - throw new ParseException("'/' must be followed by an integer.", i); - } - - i++; - c = s.charAt(i); - int v2 = Integer.parseInt(String.valueOf(c)); - i++; - if (i >= s.length()) { - checkIncrementRange(v2, type, i); - addToSet(val, end, v2, type); - return i; - } - c = s.charAt(i); - if (c >= '0' && c <= '9') { - ValueSet vs = getValue(v2, s, i); - int v3 = vs.value; - checkIncrementRange(v3, type, i); - addToSet(val, end, v3, type); - i = vs.pos; - return i; - } else { - throw new ParseException("Unexpected character '" + c + "' after '/'", i); - } - } - - addToSet(val, end, 0, type); - i++; - return i; - } - - public String getCronExpression() { - return cronExpression; - } - - public String getExpressionSummary() { - StringBuilder buf = new StringBuilder(); - - buf.append("seconds: "); - buf.append(getExpressionSetSummary(seconds)); - buf.append("\n"); - buf.append("minutes: "); - buf.append(getExpressionSetSummary(minutes)); - buf.append("\n"); - buf.append("hours: "); - buf.append(getExpressionSetSummary(hours)); - buf.append("\n"); - buf.append("daysOfMonth: "); - buf.append(getExpressionSetSummary(daysOfMonth)); - buf.append("\n"); - buf.append("months: "); - buf.append(getExpressionSetSummary(months)); - buf.append("\n"); - buf.append("daysOfWeek: "); - buf.append(getExpressionSetSummary(daysOfWeek)); - buf.append("\n"); - buf.append("lastdayOfWeek: "); - buf.append(lastdayOfWeek); - buf.append("\n"); - buf.append("nearestWeekday: "); - buf.append(nearestWeekday); - buf.append("\n"); - buf.append("NthDayOfWeek: "); - buf.append(nthdayOfWeek); - buf.append("\n"); - buf.append("lastdayOfMonth: "); - buf.append(lastdayOfMonth); - buf.append("\n"); - buf.append("years: "); - buf.append(getExpressionSetSummary(years)); - buf.append("\n"); - - return buf.toString(); - } - - protected String getExpressionSetSummary(java.util.Set set) { - - if (set.contains(NO_SPEC)) { - return "?"; - } - if (set.contains(ALL_SPEC)) { - return "*"; - } - - StringBuilder buf = new StringBuilder(); - - Iterator itr = set.iterator(); - boolean first = true; - while (itr.hasNext()) { - Integer iVal = itr.next(); - String val = iVal.toString(); - if (!first) { - buf.append(","); - } - buf.append(val); - first = false; - } - - return buf.toString(); - } - - protected String getExpressionSetSummary(java.util.ArrayList list) { - - if (list.contains(NO_SPEC)) { - return "?"; - } - if (list.contains(ALL_SPEC)) { - return "*"; - } - - StringBuilder buf = new StringBuilder(); - - Iterator itr = list.iterator(); - boolean first = true; - while (itr.hasNext()) { - Integer iVal = itr.next(); - String val = iVal.toString(); - if (!first) { - buf.append(","); - } - buf.append(val); - first = false; - } - - return buf.toString(); - } - - protected int skipWhiteSpace(int i, String s) { - for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) { - ; - } - - return i; - } - - protected int findNextWhiteSpace(int i, String s) { - for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) { - ; - } - - return i; - } - - protected void addToSet(int val, int end, int incr, int type) - throws ParseException { - - TreeSet set = getSet(type); - - if (type == SECOND || type == MINUTE) { - if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) { - throw new ParseException( - "Minute and Second values must be between 0 and 59", - -1); - } - } else if (type == HOUR) { - if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) { - throw new ParseException( - "Hour values must be between 0 and 23", -1); - } - } else if (type == DAY_OF_MONTH) { - if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) - && (val != NO_SPEC_INT)) { - throw new ParseException( - "Day of month values must be between 1 and 31", -1); - } - } else if (type == MONTH) { - if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) { - throw new ParseException( - "Month values must be between 1 and 12", -1); - } - } else if (type == DAY_OF_WEEK) { - if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) - && (val != NO_SPEC_INT)) { - throw new ParseException( - "Day-of-Week values must be between 1 and 7", -1); - } - } - - if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) { - if (val != -1) { - set.add(val); - } else { - set.add(NO_SPEC); - } - - return; - } - - int startAt = val; - int stopAt = end; - - if (val == ALL_SPEC_INT && incr <= 0) { - incr = 1; - set.add(ALL_SPEC); // put in a marker, but also fill values - } - - if (type == SECOND || type == MINUTE) { - if (stopAt == -1) { - stopAt = 59; - } - if (startAt == -1 || startAt == ALL_SPEC_INT) { - startAt = 0; - } - } else if (type == HOUR) { - if (stopAt == -1) { - stopAt = 23; - } - if (startAt == -1 || startAt == ALL_SPEC_INT) { - startAt = 0; - } - } else if (type == DAY_OF_MONTH) { - if (stopAt == -1) { - stopAt = 31; - } - if (startAt == -1 || startAt == ALL_SPEC_INT) { - startAt = 1; - } - } else if (type == MONTH) { - if (stopAt == -1) { - stopAt = 12; - } - if (startAt == -1 || startAt == ALL_SPEC_INT) { - startAt = 1; - } - } else if (type == DAY_OF_WEEK) { - if (stopAt == -1) { - stopAt = 7; - } - if (startAt == -1 || startAt == ALL_SPEC_INT) { - startAt = 1; - } - } else if (type == YEAR) { - if (stopAt == -1) { - stopAt = MAX_YEAR; - } - if (startAt == -1 || startAt == ALL_SPEC_INT) { - startAt = 1970; - } - } - - // if the end of the range is before the start, then we need to overflow into - // the next day, month etc. This is done by adding the maximum amount for that - // type, and using modulus max to determine the value being added. - int max = -1; - if (stopAt < startAt) { - switch (type) { - case SECOND : max = 60; break; - case MINUTE : max = 60; break; - case HOUR : max = 24; break; - case MONTH : max = 12; break; - case DAY_OF_WEEK : max = 7; break; - case DAY_OF_MONTH : max = 31; break; - case YEAR : throw new IllegalArgumentException("Start year must be less than stop year"); - default : throw new IllegalArgumentException("Unexpected type encountered"); - } - stopAt += max; - } - - for (int i = startAt; i <= stopAt; i += incr) { - if (max == -1) { - // ie: there's no max to overflow over - set.add(i); - } else { - // take the modulus to get the real value - int i2 = i % max; - - // 1-indexed ranges should not include 0, and should include their max - if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) { - i2 = max; - } - - set.add(i2); - } - } - } - - TreeSet getSet(int type) { - switch (type) { - case SECOND: - return seconds; - case MINUTE: - return minutes; - case HOUR: - return hours; - case DAY_OF_MONTH: - return daysOfMonth; - case MONTH: - return months; - case DAY_OF_WEEK: - return daysOfWeek; - case YEAR: - return years; - default: - return null; - } - } - - protected ValueSet getValue(int v, String s, int i) { - char c = s.charAt(i); - StringBuilder s1 = new StringBuilder(String.valueOf(v)); - while (c >= '0' && c <= '9') { - s1.append(c); - i++; - if (i >= s.length()) { - break; - } - c = s.charAt(i); - } - ValueSet val = new ValueSet(); - - val.pos = (i < s.length()) ? i : i + 1; - val.value = Integer.parseInt(s1.toString()); - return val; - } - - protected int getNumericValue(String s, int i) { - int endOfVal = findNextWhiteSpace(i, s); - String val = s.substring(i, endOfVal); - return Integer.parseInt(val); - } - - protected int getMonthNumber(String s) { - Integer integer = monthMap.get(s); - - if (integer == null) { - return -1; - } - - return integer; - } - - protected int getDayOfWeekNumber(String s) { - Integer integer = dayMap.get(s); - - if (integer == null) { - return -1; - } - - return integer; - } - - //////////////////////////////////////////////////////////////////////////// - // - // Computation Functions - // - //////////////////////////////////////////////////////////////////////////// - - public Date getTimeAfter(Date afterTime) { - - // Computation is based on Gregorian year only. - Calendar cl = new java.util.GregorianCalendar(getTimeZone()); - - // move ahead one second, since we're computing the time *after* the - // given time - afterTime = new Date(afterTime.getTime() + 1000); - // CronTrigger does not deal with milliseconds - cl.setTime(afterTime); - cl.set(Calendar.MILLISECOND, 0); - - boolean gotOne = false; - // loop until we've computed the next time, or we've past the endTime - while (!gotOne) { - - //if (endTime != null && cl.getTime().after(endTime)) return null; - if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop... - return null; - } - - SortedSet st = null; - int t = 0; - - int sec = cl.get(Calendar.SECOND); - int min = cl.get(Calendar.MINUTE); - - // get second................................................. - st = seconds.tailSet(sec); - if (st != null && st.size() != 0) { - sec = st.first(); - } else { - sec = seconds.first(); - min++; - cl.set(Calendar.MINUTE, min); - } - cl.set(Calendar.SECOND, sec); - - min = cl.get(Calendar.MINUTE); - int hr = cl.get(Calendar.HOUR_OF_DAY); - t = -1; - - // get minute................................................. - st = minutes.tailSet(min); - if (st != null && st.size() != 0) { - t = min; - min = st.first(); - } else { - min = minutes.first(); - hr++; - } - if (min != t) { - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, min); - setCalendarHour(cl, hr); - continue; - } - cl.set(Calendar.MINUTE, min); - - hr = cl.get(Calendar.HOUR_OF_DAY); - int day = cl.get(Calendar.DAY_OF_MONTH); - t = -1; - - // get hour................................................... - st = hours.tailSet(hr); - if (st != null && st.size() != 0) { - t = hr; - hr = st.first(); - } else { - hr = hours.first(); - day++; - } - if (hr != t) { - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.DAY_OF_MONTH, day); - setCalendarHour(cl, hr); - continue; - } - cl.set(Calendar.HOUR_OF_DAY, hr); - - day = cl.get(Calendar.DAY_OF_MONTH); - int mon = cl.get(Calendar.MONTH) + 1; - // '+ 1' because calendar is 0-based for this field, and we are - // 1-based - t = -1; - int tmon = mon; - - // get day................................................... - boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); - boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); - if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule - st = daysOfMonth.tailSet(day); - if (lastdayOfMonth) { - if(!nearestWeekday) { - t = day; - day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - day -= lastdayOffset; - if(t > day) { - mon++; - if(mon > 12) { - mon = 1; - tmon = 3333; // ensure test of mon != tmon further below fails - cl.add(Calendar.YEAR, 1); - } - day = 1; - } - } else { - t = day; - day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - day -= lastdayOffset; - - Calendar tcal = Calendar.getInstance(getTimeZone()); - tcal.set(Calendar.SECOND, 0); - tcal.set(Calendar.MINUTE, 0); - tcal.set(Calendar.HOUR_OF_DAY, 0); - tcal.set(Calendar.DAY_OF_MONTH, day); - tcal.set(Calendar.MONTH, mon - 1); - tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); - - int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - int dow = tcal.get(Calendar.DAY_OF_WEEK); - - if(dow == Calendar.SATURDAY && day == 1) { - day += 2; - } else if(dow == Calendar.SATURDAY) { - day -= 1; - } else if(dow == Calendar.SUNDAY && day == ldom) { - day -= 2; - } else if(dow == Calendar.SUNDAY) { - day += 1; - } - - tcal.set(Calendar.SECOND, sec); - tcal.set(Calendar.MINUTE, min); - tcal.set(Calendar.HOUR_OF_DAY, hr); - tcal.set(Calendar.DAY_OF_MONTH, day); - tcal.set(Calendar.MONTH, mon - 1); - Date nTime = tcal.getTime(); - if(nTime.before(afterTime)) { - day = 1; - mon++; - } - } - } else if(nearestWeekday) { - t = day; - day = daysOfMonth.first(); - - Calendar tcal = Calendar.getInstance(getTimeZone()); - tcal.set(Calendar.SECOND, 0); - tcal.set(Calendar.MINUTE, 0); - tcal.set(Calendar.HOUR_OF_DAY, 0); - tcal.set(Calendar.DAY_OF_MONTH, day); - tcal.set(Calendar.MONTH, mon - 1); - tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); - - int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - int dow = tcal.get(Calendar.DAY_OF_WEEK); - - if(dow == Calendar.SATURDAY && day == 1) { - day += 2; - } else if(dow == Calendar.SATURDAY) { - day -= 1; - } else if(dow == Calendar.SUNDAY && day == ldom) { - day -= 2; - } else if(dow == Calendar.SUNDAY) { - day += 1; - } - - - tcal.set(Calendar.SECOND, sec); - tcal.set(Calendar.MINUTE, min); - tcal.set(Calendar.HOUR_OF_DAY, hr); - tcal.set(Calendar.DAY_OF_MONTH, day); - tcal.set(Calendar.MONTH, mon - 1); - Date nTime = tcal.getTime(); - if(nTime.before(afterTime)) { - day = daysOfMonth.first(); - mon++; - } - } else if (st != null && st.size() != 0) { - t = day; - day = st.first(); - // make sure we don't over-run a short month, such as february - int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - if (day > lastDay) { - day = daysOfMonth.first(); - mon++; - } - } else { - day = daysOfMonth.first(); - mon++; - } - - if (day != t || mon != tmon) { - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, day); - cl.set(Calendar.MONTH, mon - 1); - // '- 1' because calendar is 0-based for this field, and we - // are 1-based - continue; - } - } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule - if (lastdayOfWeek) { // are we looking for the last XXX day of - // the month? - int dow = daysOfWeek.first(); // desired - // d-o-w - int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w - int daysToAdd = 0; - if (cDow < dow) { - daysToAdd = dow - cDow; - } - if (cDow > dow) { - daysToAdd = dow + (7 - cDow); - } - - int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - - if (day + daysToAdd > lDay) { // did we already miss the - // last one? - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, 1); - cl.set(Calendar.MONTH, mon); - // no '- 1' here because we are promoting the month - continue; - } - - // find date of last occurrence of this day in this month... - while ((day + daysToAdd + 7) <= lDay) { - daysToAdd += 7; - } - - day += daysToAdd; - - if (daysToAdd > 0) { - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, day); - cl.set(Calendar.MONTH, mon - 1); - // '- 1' here because we are not promoting the month - continue; - } - - } else if (nthdayOfWeek != 0) { - // are we looking for the Nth XXX day in the month? - int dow = daysOfWeek.first(); // desired - // d-o-w - int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w - int daysToAdd = 0; - if (cDow < dow) { - daysToAdd = dow - cDow; - } else if (cDow > dow) { - daysToAdd = dow + (7 - cDow); - } - - boolean dayShifted = false; - if (daysToAdd > 0) { - dayShifted = true; - } - - day += daysToAdd; - int weekOfMonth = day / 7; - if (day % 7 > 0) { - weekOfMonth++; - } - - daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; - day += daysToAdd; - if (daysToAdd < 0 - || day > getLastDayOfMonth(mon, cl - .get(Calendar.YEAR))) { - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, 1); - cl.set(Calendar.MONTH, mon); - // no '- 1' here because we are promoting the month - continue; - } else if (daysToAdd > 0 || dayShifted) { - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, day); - cl.set(Calendar.MONTH, mon - 1); - // '- 1' here because we are NOT promoting the month - continue; - } - } else { - int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w - int dow = daysOfWeek.first(); // desired - // d-o-w - st = daysOfWeek.tailSet(cDow); - if (st != null && st.size() > 0) { - dow = st.first(); - } - - int daysToAdd = 0; - if (cDow < dow) { - daysToAdd = dow - cDow; - } - if (cDow > dow) { - daysToAdd = dow + (7 - cDow); - } - - int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - - if (day + daysToAdd > lDay) { // will we pass the end of - // the month? - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, 1); - cl.set(Calendar.MONTH, mon); - // no '- 1' here because we are promoting the month - continue; - } else if (daysToAdd > 0) { // are we swithing days? - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd); - cl.set(Calendar.MONTH, mon - 1); - // '- 1' because calendar is 0-based for this field, - // and we are 1-based - continue; - } - } - } else { // dayOfWSpec && !dayOfMSpec - throw new UnsupportedOperationException( - "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); - } - cl.set(Calendar.DAY_OF_MONTH, day); - - mon = cl.get(Calendar.MONTH) + 1; - // '+ 1' because calendar is 0-based for this field, and we are - // 1-based - int year = cl.get(Calendar.YEAR); - t = -1; - - // test for expressions that never generate a valid fire date, - // but keep looping... - if (year > MAX_YEAR) { - return null; - } - - // get month................................................... - st = months.tailSet(mon); - if (st != null && st.size() != 0) { - t = mon; - mon = st.first(); - } else { - mon = months.first(); - year++; - } - if (mon != t) { - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, 1); - cl.set(Calendar.MONTH, mon - 1); - // '- 1' because calendar is 0-based for this field, and we are - // 1-based - cl.set(Calendar.YEAR, year); - continue; - } - cl.set(Calendar.MONTH, mon - 1); - // '- 1' because calendar is 0-based for this field, and we are - // 1-based - - year = cl.get(Calendar.YEAR); - t = -1; - - // get year................................................... - st = years.tailSet(year); - if (st != null && st.size() != 0) { - t = year; - year = st.first(); - } else { - return null; // ran out of years... - } - - if (year != t) { - cl.set(Calendar.SECOND, 0); - cl.set(Calendar.MINUTE, 0); - cl.set(Calendar.HOUR_OF_DAY, 0); - cl.set(Calendar.DAY_OF_MONTH, 1); - cl.set(Calendar.MONTH, 0); - // '- 1' because calendar is 0-based for this field, and we are - // 1-based - cl.set(Calendar.YEAR, year); - continue; - } - cl.set(Calendar.YEAR, year); - - gotOne = true; - } // while( !done ) - - return cl.getTime(); - } - - /** - * Advance the calendar to the particular hour paying particular attention - * to daylight saving problems. - * - * @param cal the calendar to operate on - * @param hour the hour to set - */ - protected void setCalendarHour(Calendar cal, int hour) { - cal.set(Calendar.HOUR_OF_DAY, hour); - if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) { - cal.set(Calendar.HOUR_OF_DAY, hour + 1); - } - } - - /** - * NOT YET IMPLEMENTED: Returns the time before the given time - * that the CronExpression matches. - */ - public Date getTimeBefore(Date endTime) { - // FUTURE_TODO: implement QUARTZ-423 - return null; - } - - /** - * NOT YET IMPLEMENTED: Returns the final time that the - * CronExpression will match. - */ - public Date getFinalFireTime() { - // FUTURE_TODO: implement QUARTZ-423 - return null; - } - - protected boolean isLeapYear(int year) { - return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); - } - - protected int getLastDayOfMonth(int monthNum, int year) { - - switch (monthNum) { - case 1: - return 31; - case 2: - return (isLeapYear(year)) ? 29 : 28; - case 3: - return 31; - case 4: - return 30; - case 5: - return 31; - case 6: - return 30; - case 7: - return 31; - case 8: - return 31; - case 9: - return 30; - case 10: - return 31; - case 11: - return 30; - case 12: - return 31; - default: - throw new IllegalArgumentException("Illegal month number: " - + monthNum); - } - } - - - private void readObject(java.io.ObjectInputStream stream) - throws java.io.IOException, ClassNotFoundException { - - stream.defaultReadObject(); - try { - buildExpression(cronExpression); - } catch (Exception ignore) { - } // never happens - } - - @Override - @Deprecated - public Object clone() { - return new CronExpression(this); - } -} - -class ValueSet { - public int value; - - public int pos; -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/externalLibs/SuperCronExpression.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/externalLibs/SuperCronExpression.java deleted file mode 100644 index c45cb234..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/externalLibs/SuperCronExpression.java +++ /dev/null @@ -1,680 +0,0 @@ -package me.carda.awesome_notifications.core.externalLibs; - -/* -public class CronNotationType { - public static final String anyOne = "\*"; // anything - public static final String list = "^\d+(,\d+)*$"; // 1,5,3,45 - public static final String range = "^\d+-\d+$"; // 1-5 - public static final String interval = "^(\*|\d+)\/\d+$"; // 5 - public static final String wildCard = "^\?$"; // ? - public static final String unknow = ""; -} - -class ComponentRange { - public final int min, max, initial; - ComponentRange(int min, int max, int initial){ - this.min = min; - this.max = max; - this.initial = initial; - } -} - -public enum CronComponent { - - second, - minute, - hour, - day, - month, - weekday, - year; - - public static CronComponent getMostRelevant() { - return CronComponent.year; - } - - public static CronComponent getLessRelevant() { - return CronComponent.second; - } - - public static List getDateComponents() { - return Arrays.asList( - CronComponent.second, - CronComponent.minute, - CronComponent.hour, - CronComponent.day, - CronComponent.month, - CronComponent.year - ); - } - - public CronComponent bellowComponent() { - switch (this) { - case CronComponent.second: return null; - case CronComponent.minute: return CronComponent.second; - case CronComponent.hour: return CronComponent.minute; - case CronComponent.day: return CronComponent.hour; - case CronComponent.weekday: return CronComponent.hour; - case CronComponent.month: return CronComponent.day; - case CronComponent.year: return CronComponent.month; - } - return null; - } - - public CronComponent aboveComponent() { - switch (this) { - case CronComponent.second: return CronComponent.minute; - case CronComponent.minute: return CronComponent.hour; - case CronComponent.hour: return CronComponent.day; - case CronComponent.day: return CronComponent.month; - case CronComponent.weekday: return CronComponent.month; - case CronComponent.month: return CronComponent.year; - case CronComponent.year: return null; - } - return null; - } - - public int getRespectiveCalendar() { - switch (this) { - case CronComponent.second: return Calendar.SECOND; - case CronComponent.minute: return Calendar.MINUTE; - case CronComponent.hour: return Calendar.HOUR; - case CronComponent.weekday: return Calendar.DAY_OF_WEEK; - case CronComponent.day: return Calendar.DAY_OF_MONTH; - case CronComponent.month: return Calendar.MONTH; - case CronComponent.year: return Calendar.YEAR; - } - } -} - -public class SuperCronExpression { - - public TimeZone timeZone = DateUtils.getUtcTimeZone(); - public Boolean isValidExpression = false; - - Date now = new Date(); - Calendar calendar = Calendar.getInstance(); - - List filterSets = new ArrayList<>(); - - Map> translationSets = - new HashMap>(){{ - put(CronComponent.month, new HashMap(){{ - put("JAN", 1); - put("FEB", 2); - put("MAR", 3); - put("APR", 4); - put("MAY", 5); - put("JUN", 6); - put("JUL", 7); - put("AUG", 8); - put("SEP", 9); - put("OCT",10); - put("NOV", 11); - put("DEC", 12); - }}); - put(CronComponent.weekday, new HashMap(){{ - put("SUN", 1); - put("MON", 2); - put("TUE", 3); - put("WED", 4); - put("THU", 5); - put("FRI", 6); - put("SAT", 7); - }}); - }}; - - boolean isLeapYear(int year) { - return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); - } - - public ComponentRange getDateInterval(CronComponent component, Map fromComponents) { - switch (component) { - - case CronComponent.second: - case CronComponent.minute: - return new ComponentRange(0, 59, 0); - - case CronComponent.hour: - return new ComponentRange(0, 23, 0); - - case CronComponent.weekday: - return new ComponentRange(0, 6, 0); - - case CronComponent.day: - switch (fromComponents.get(CronComponent.month)) { - case 1: - return new ComponentRange(1, 31, 1); - case 2: - return new ComponentRange( - 1, - isLeapYear(fromComponents.get(CronComponent.year)) ? - 29 : 28, - 1); - case 3: - return new ComponentRange(1, 31, 1); - case 4: - return new ComponentRange(1, 30, 1); - case 5: - return new ComponentRange(1, 31, 1); - case 6: - return new ComponentRange(1, 30, 1); - case 7: - return new ComponentRange(1, 31, 1); - case 8: - return new ComponentRange(1, 31, 1); - case 9: - return new ComponentRange(1, 30, 1); - case 10: - return new ComponentRange(1, 31, 1); - case 11: - return new ComponentRange(1, 30, 1); - case 12: - return new ComponentRange(1, 31, 1); - default: - return new ComponentRange(-1, -1, -1); - } - case CronComponent.month: - return new ComponentRange(0, 12, 0); - case CronComponent.year: - return new ComponentRange(0, 9999, fromComponents.get(CronComponent.year)); - } - } - - func getDateInterval(_ component:CronComponent, fromDate:Date) -> ComponentRange { - switch component { - case .second: - return ComponentRange(0, 59, 0) - case .minute: - return ComponentRange(0, 59, 0) - case .hour: - return ComponentRange(0, 23, 0) - case .weekday: - return ComponentRange(0, 6, 0) - case .day: - switch calendar.component(.month, from: fromDate) { - case 1: - return ComponentRange(1, 31, 1) - case 2: - return ComponentRange(1, isLeapYear(calendar.component(.year, from: fromDate)) ? 29 : 28, 1) - case 3: - return ComponentRange(1, 31, 1) - case 4: - return ComponentRange(1, 30, 1) - case 5: - return ComponentRange(1, 31, 1) - case 6: - return ComponentRange(1, 30, 1) - case 7: - return ComponentRange(1, 31, 1) - case 8: - return ComponentRange(1, 31, 1) - case 9: - return ComponentRange(1, 30, 1) - case 10: - return ComponentRange(1, 31, 1) - case 11: - return ComponentRange(1, 30, 1) - case 12: - return ComponentRange(1, 31, 1) - default: - return ComponentRange(-1, -1, -1) - } - case .month: - return ComponentRange(0, 12, 0) - case .year: - return ComponentRange(0, 9999, calendar.component(.year, from: fromDate)) - } - } - - public convenience init(_ cronExpression: String) throws { - try self.init(cronExpression, fixedDate:Date()) - } - - public init(_ cronExpression: String, fixedDate:Date) throws { - - self.isValidExpression = false - - filterSets = [ - .second: TreeSet(), - .minute: TreeSet(), - .hour: TreeSet(), - .weekday: TreeSet(), - .day: TreeSet(), - .month: TreeSet(), - .year: TreeSet() - ] - - now = fixedDate - calendar = Calendar.current - calendar.timeZone = timeZone - - try parse(cronExpression) - } - - public static func validate(cronExpression: String) -> Bool { - do { - let cronExpression = try CronExpression(cronExpression) - return cronExpression.isValidExpression - } catch { - return false - } - } - - func parse(_ cronExpression: String) throws { - - let cronElements = cronExpression.split(regex:"\\s+") - - if(cronElements.count != filterSets.count){ - throw CronError.invalidExpression(msg:"Unexpected amount of elements on cron expression (expected: \(filterSets.count), founded: \(cronElements.count))") - } - - var pos:Int = 0 - for component in CronComponent.allCases { - try fillRespectiveSet(component, cronElements[pos]) - pos += 1 - } - - if(!filterSets[CronComponent.day]!.isEmpty && !filterSets[CronComponent.weekday]!.isEmpty){ - throw CronError.invalidExpression(msg:"Cannot schedule based on day and weekday at same time") - } - - self.isValidExpression = true - } - - func fillRespectiveSet(_ component:CronComponent, _ cronElement: String) throws { - - let translatedElement = translate(component, cronElement) - - switch detectCronNotation(cronElement) { - - case .AnyOne: - if(!populateFromAnyOne(component, translatedElement)) - {throw CronError.invalidExpression(msg:"AnyOne paramater is invalid (\(cronElement))")} - break - - case .List: - if(!populateFromList(component, translatedElement)) - {throw CronError.invalidExpression(msg:"List paramater is invalid (\(cronElement))")} - break - - case .Range: - if(!populateFromRange(component, translatedElement)) - {throw CronError.invalidExpression(msg:"Range paramater is invalid (\(cronElement))")} - break - - case .Interval: - if(!populateFromInterval(component, translatedElement)) - {throw CronError.invalidExpression(msg:"Interval paramater is invalid (\(cronElement))")} - break - - case .WildCard: - break - //if(!populateFromWildCard(component, translatedElement)) - //{throw CronError.invalidExpression(msg:"WildCard paramater is invalid (\(cronElement))")} - //break - - case .Unknow: - throw CronError.invalidExpression(msg:"Unknow paramater is invalid (\(cronElement))") - } - - } - - func translate(_ component:CronComponent, _ cronElement: String) -> String{ - var translated = cronElement - for (key, value) in translationSets[component] ?? [:] { - translated = translated.replacingOccurrences(of: key, with: String(value)) - } - return translated - } - - public func detectCronNotation(_ cronElement:String) -> CronNotationType { - - for type in CronNotationType.allCases { - let matches:Bool = cronElement.matches(type.rawValue) - if(matches){ - return type - } - } - - return CronNotationType.Unknow - } - - func populateFromAnyOne(_ component:CronComponent, _ rule:String) -> Bool { - - // Empty filter sets are the same than all values allowed - return true - } - - func populateFromList(_ component:CronComponent, _ rule:String) -> Bool { - let range:ComponentRange = getDateInterval(component, fromDate: now) - - let values:[String] = rule.components(separatedBy: ",") - for textValue in values { - - let value = Int(textValue)! - - if(range.min <= value && range.max >= value ){ - if(!(filterSets[component]?.insert(value) ?? false)){ return false } - } - else { - return false - } - } - - return true - } - - func populateFromRange(_ component:CronComponent, _ rule:String) -> Bool { - let range:ComponentRange = getDateInterval(component, fromDate: now) - - let limits:[String] = rule.components(separatedBy: "-") - let min:Int = Int(limits[0])! - let max:Int = Int(limits[1])! - - if(min < range.min || max > range.max){ - return false - } - - let array = [Int](min...max) - for value in array { - if(!(filterSets[component]?.insert(value) ?? false)){ return false } - } - - return true - } - - func populateFromInterval(_ component:CronComponent, _ rule:String) -> Bool { - let range:ComponentRange = getDateInterval(component, fromDate: now) - - let elements:[String] = rule.components(separatedBy: "/") - - let fraction = Int(elements[1])! - let initialValue = elements[0] == "*" ? fraction : Int(elements[0])! - - var nextValue = initialValue - - while(nextValue >= range.min && nextValue <= range.max){ - if(!(filterSets[component]?.insert(nextValue) ?? false)){ return false } - nextValue += fraction - } - - return !(filterSets[component]?.isEmpty ?? true) - } - - func populateFromWildCard(_ component:CronComponent, _ rule:String) -> Bool { - - return filterSets[component]?.insert(calendar.component(component.respectiveCalendar, from: now)) ?? false - } - - public func isValidDate(referenceDate:Date) -> Bool { - if(!isValidExpression) { return false } - - return ( - ( - filterSets[CronComponent.year]!.isEmpty || - filterSets[CronComponent.year]!.contains( - calendar.component(CronComponent.year.respectiveCalendar, from: referenceDate) - ) - ) && - ( - filterSets[CronComponent.month]!.isEmpty || - filterSets[CronComponent.month]!.contains( - calendar.component(CronComponent.month.respectiveCalendar, from: referenceDate) - ) - ) && - ( - filterSets[CronComponent.day]!.isEmpty || - filterSets[CronComponent.day]!.contains( - calendar.component(CronComponent.day.respectiveCalendar, from: referenceDate) - ) - ) && - ( - filterSets[CronComponent.hour]!.isEmpty || - filterSets[CronComponent.hour]!.contains( - calendar.component(CronComponent.hour.respectiveCalendar, from: referenceDate) - ) - ) && - ( - filterSets[CronComponent.minute]!.isEmpty || - filterSets[CronComponent.minute]!.contains( - calendar.component(CronComponent.minute.respectiveCalendar, from: referenceDate) - ) - ) && - ( - filterSets[CronComponent.second]!.isEmpty || - filterSets[CronComponent.second]!.contains( - calendar.component(CronComponent.second.respectiveCalendar, from: referenceDate) - ) - ) - ) - } - - func incrementByComponentDate(component:CronComponent, date: Date) -> Date { - return calendar.date(byAdding: component.respectiveCalendar, value: 1, to: date)! - } - - public func getNextValidDate(referenceDate:Date?) -> Date? { - if(!isValidExpression || referenceDate == nil) { return nil } - - // add 1 second to ensure to return the next valid date - let secondFuruteDate:Date? = incrementByComponentDate(component: CronComponent.lessRelevant, date: referenceDate!) - let shiftedToFuture:Bool = false - - var finalComponents:[CronComponent:Int] = [:] - - finalComponents[.year] = calendar.component(.year, from: secondFuruteDate!) - finalComponents[.month] = calendar.component(.month, from: secondFuruteDate!) - finalComponents[.day] = calendar.component(.day, from: secondFuruteDate!) - finalComponents[.hour] = calendar.component(.hour, from: secondFuruteDate!) - finalComponents[.minute] = calendar.component(.minute, from: secondFuruteDate!) - finalComponents[.second] = calendar.component(.second, from: secondFuruteDate!) - - let isInvalidDate = runSuperCronAlgorithm( - currentComponent: CronComponent.mostRelevant, - shiftedToFuture: shiftedToFuture, - finalComponents: &finalComponents - ) - - let finalDate:Date? = isInvalidDate ? nil : getDateFromComponents(components: finalComponents) - - if(finalDate != nil && !isValidDate(referenceDate: finalDate!)){ - return nil - } - - return finalDate - } - - func runSuperCronAlgorithm( - currentComponent: CronComponent, - shiftedToFuture: Bool, - finalComponents: inout [CronComponent:Int] - ) -> Bool { - - var reachsInvalidDate = false - - // shifted mark should not propagates towards to top - var shiftedToFuture:Bool = shiftedToFuture - - // if the components bellow reach their limits, i should run again - repeat { - - let currentFilter:TreeSet = filterSets[currentComponent]! - - // if is the first time this level runs and and above component was shifted to future, - // every lower component must be initial, otherwise is current - var currentValue = - !reachsInvalidDate && shiftedToFuture ? - currentFilter.first ?? getDateInterval(currentComponent, fromComponents: finalComponents).initial : - finalComponents[currentComponent]! - - // update current component value - finalComponents[currentComponent] = currentValue - - // get the respective lower component. if is on the bottom, sinalizes it with null - var nextComponent:CronComponent? = currentComponent.bellowComponent - - // if the present value does not match the boundaries or the previous rules - // so it should be changed - if(reachsInvalidDate || !isValidComponent(currentComponent, currentValue, finalComponents)){ - - // now every generated date is in the future - shiftedToFuture = true - - let nextValue:Int? = getNextValidComponent(currentComponent, finalComponents) - - // if there is no more next valid componet, so the base component needs to be shifted one level above - if(nextValue == nil){ - - // if tail valid elements has ended, the top component must be shifted - nextComponent = currentComponent.aboveComponent - - // if there is no more valid component to shift, there is no more next valid dates - if(nextComponent == nil){ - return true - } - - } - else { - currentValue = nextValue! - } - - } - - // update current component value - finalComponents[currentComponent] = currentValue - - // if it reachs the bottom component, so we found a next valid date - if(nextComponent == nil){ - - return false - - } - else { - - // tell to above component that he needs to shift to the future - if(nextComponent == currentComponent.aboveComponent){ - return true - } - - // process next step - (reachsInvalidDate) = runSuperCronAlgorithm( - currentComponent: nextComponent!, - shiftedToFuture: shiftedToFuture, - finalComponents: &finalComponents - ) - - } - - // if this component should run again, do it without call another method - // (to save memory) - } while (reachsInvalidDate) - - // if the levels bellow reached the bottom component, so we found a next valid date - return false - } - - func getDateFromComponents(components:[CronComponent:Int]) -> Date? { - - // avoid calendar to be unsync in case of invalid components - for (component, value) in components { - if(!isValidComponent(component, value, components)){ - return nil - } - } - - let calendarDateComponents: DateComponents = DateComponents( - year: components[.year], - month: components[.month], - day: components[.day], - hour: components[.hour], - minute: components[.minute], - second: components[.second] - ) - - return calendar.date(from: calendarDateComponents) - } - - func isValidComponent(_ component:CronComponent, _ value:Int, _ components:[CronComponent:Int]) -> Bool { - - let currentFilter = filterSets[component]! - let range:ComponentRange = getDateInterval(component, fromComponents: components) - - return (currentFilter.isEmpty || currentFilter.contains(value)) && (value <= range.max && value >= range.min); - } - - func getNextValidComponent(_ component:CronComponent, _ components:[CronComponent:Int]) -> Int? { - - var nextValue:Int? - let currentFilter = filterSets[component]! - - if currentFilter.isEmpty { - nextValue = components[component]! - nextValue! += 1 - } - else { - nextValue = currentFilter.tail(reference: components[component]!) - } - - // if has next filter - if(nextValue != nil){ - let range:ComponentRange = getDateInterval(component, fromComponents: components) - - // if next filter is invalid to current date - if(nextValue! > range.max){ - return nil - } - } - - return nextValue - } - - /// Produces a cartesian product of dates components, expressed by UNNotificationTrigger objects, that togetter are capable to represent a single cron expression - @available(iOS 10.0, *) - func getTriggerList() -> [UNCalendarNotificationTrigger] { - var triggerList:[UNCalendarNotificationTrigger] = [] - - var positions:[CronComponent:Int] = [:] - for (component, filters) in filterSets { - if(!filters.isEmpty){ - positions[component] = 0 - } - } - - var notExausted:Bool = true - repeat { - - var dateComponents = DateComponents() - for (component, position) in positions { - - switch component { - case .second: dateComponents.second = filterSets[component]?.atIndex(position); break - case .minute: dateComponents.minute = filterSets[component]?.atIndex(position); break - case .hour: dateComponents.hour = filterSets[component]?.atIndex(position); break - case .day: dateComponents.day = filterSets[component]?.atIndex(position); break - case .month: dateComponents.month = filterSets[component]?.atIndex(position); break - case .weekday: dateComponents.weekday = filterSets[component]?.atIndex(position); break - case .year: dateComponents.year = filterSets[component]?.atIndex(position); break - } - } - triggerList.append(UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)) - - notExausted = false - positions.forEach({ (component, _) -> Void in - positions[component]! += 1 - if positions[component]! >= filterSets[component]!.count { - positions[component] = 0 - } else { - notExausted = true - return - } - }) - - } while notExausted - - return triggerList - } - } - -} -*/ diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeActionEventListener.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeActionEventListener.java deleted file mode 100644 index 63c31188..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeActionEventListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package me.carda.awesome_notifications.core.listeners; - -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; - -public interface AwesomeActionEventListener { - public void onNewActionReceived(String eventName, ActionReceived actionReceived); - public boolean onNewActionReceivedWithInterruption(String eventName, ActionReceived actionReceived); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeEventListener.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeEventListener.java deleted file mode 100644 index a2cfaafe..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeEventListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.carda.awesome_notifications.core.listeners; - -import java.util.Map; - -public interface AwesomeEventListener { - public void onNewAwesomeEvent(String eventType, Map content); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeExceptionListener.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeExceptionListener.java deleted file mode 100644 index c7a22e2a..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeExceptionListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.carda.awesome_notifications.core.listeners; - -public interface AwesomeExceptionListener { - public void onNewAwesomeException(Exception exception); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeLifeCycleEventListener.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeLifeCycleEventListener.java deleted file mode 100644 index 9b102306..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeLifeCycleEventListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.carda.awesome_notifications.core.listeners; - -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; - -public interface AwesomeLifeCycleEventListener { - public void onNewLifeCycleEvent(NotificationLifeCycle lifeCycle); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeNotificationEventListener.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeNotificationEventListener.java deleted file mode 100644 index 7ad9b190..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/listeners/AwesomeNotificationEventListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.carda.awesome_notifications.core.listeners; - -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; - -public interface AwesomeNotificationEventListener { - public void onNewNotificationReceived(String eventName, NotificationReceived notificationReceived); -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/logs/Logger.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/logs/Logger.java deleted file mode 100644 index 34ca2cb8..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/logs/Logger.java +++ /dev/null @@ -1,48 +0,0 @@ -package me.carda.awesome_notifications.core.logs; - -import android.util.Log; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -public class Logger { - - private static final String redColor = "\u001B[31m"; - private static final String greenColor = "\u001B[32m"; - private static final String blueColor = "\u001B[94m"; - private static final String yellowColor = "\u001B[33m"; - private static final String resetColor = "\u001B[0m"; - - private static final DateFormat dateFormat = - new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)", Locale.US); - - private static String getCurrentTime(){ - return dateFormat.format(new Date()); - } - - private static String getLastLine(){ - StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - if(stackTrace.length < 5) - return "?"; - return String.valueOf(stackTrace[4].getLineNumber()); - } - - public static void d(String className, String message){ - Log.d("Android: "+greenColor+"[Awesome Notifications]"+resetColor, message + " (" + className + ":" + getLastLine() + ")"); - } - - public static void e(String className, String message){ - Log.e("Android: "+redColor+"[Awesome Notifications]", message + " (" + className + ":" + getLastLine() + ")" + resetColor); - } - - public static void i(String className, String message){ - Log.i("Android: "+blueColor+"[Awesome Notifications]", message + " (" + className + ":" + getLastLine() + ")" + resetColor); - } - - public static void w(String className, String message){ - Log.w("Android: "+yellowColor+"[Awesome Notifications]", message + " (" + className + ":" + getLastLine() + ")" + resetColor); - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ActionManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ActionManager.java deleted file mode 100644 index e51f484d..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ActionManager.java +++ /dev/null @@ -1,72 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.content.Context; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.enumerators.ActionType; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class ActionManager { - -// private static final SharedManager shared -// = new SharedManager<>( -// StringUtils.getInstance(), -// "ActionManager", -// ActionReceived.class, -// "ActionReceived"); - - private static Map actionCache = new HashMap<>(); - private static ActionReceived initialActionReceived; - - public static Boolean removeAction(Context context, Integer id) throws AwesomeNotificationsException { - actionCache.remove(id); - return true; -// return shared.remove(context, Definitions.SHARED_DISMISSED, id.toString()); - } - - public static List listActions(Context context) throws AwesomeNotificationsException { - return new ArrayList(actionCache.values()); -// return shared.getAllObjects(context, Definitions.SHARED_DISMISSED); - } - - public static void setInitialNotificationAction(Context context, ActionReceived received){ - initialActionReceived = received; - } - - public static void saveAction(Context context, ActionReceived received) throws AwesomeNotificationsException { - actionCache.put(received.id, received); -// shared.set(context, Definitions.SHARED_DISMISSED, received.id.toString(), received); - } - - public static ActionReceived getActionByKey(Context context, Integer id) throws AwesomeNotificationsException { - return actionCache.get(id); -// return shared.get(context, Definitions.SHARED_DISMISSED, id.toString()); - } - - public static void clearAllActions(Context context) throws AwesomeNotificationsException { -// List receivedList = shared.getAllObjects(context, Definitions.SHARED_DISMISSED); -// if (receivedList != null){ -// for (ActionReceived received : receivedList) { -// removeAction(context, received.id); -// } -// } - initialActionReceived = null; - actionCache.clear(); - } - - public static ActionReceived getInitialActionReceived(){ - return initialActionReceived; - } - - public static void commitChanges(Context context) throws AwesomeNotificationsException { -// shared.commit(context); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/BadgeManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/BadgeManager.java deleted file mode 100644 index bc3a14af..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/BadgeManager.java +++ /dev/null @@ -1,92 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import static java.lang.Math.max; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Build; -import android.preference.PreferenceManager; -import android.provider.Settings; - -import me.carda.awesome_notifications.core.Definitions; -import me.leolin.shortcutbadger.ShortcutBadger; - -public class BadgeManager { - - // ************** SINGLETON PATTERN *********************** - - protected static BadgeManager instance; - - protected BadgeManager(){} - - public static BadgeManager getInstance() { - if (instance == null) - instance = new BadgeManager(); - return instance; - } - - // ******************************************************** - - public int getGlobalBadgeCounter(Context context) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - // Read previous value. If not found, use 0 as default value. - return max(prefs.getInt(Definitions.BADGE_COUNT, 0),0); - } - - public void setGlobalBadgeCounter(Context context, int count) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - SharedPreferences.Editor editor = prefs.edit(); - - editor.putInt(Definitions.BADGE_COUNT, count); - ShortcutBadger.applyCount(context, count); - - editor.apply(); - } - - public void resetGlobalBadgeCounter(Context context) { - setGlobalBadgeCounter(context, 0); - } - - public int incrementGlobalBadgeCounter(Context context) { - int totalAmount = getGlobalBadgeCounter(context); - totalAmount++; - setGlobalBadgeCounter(context, totalAmount); - return totalAmount; - } - - public int decrementGlobalBadgeCounter(Context context) { - int totalAmount = max(getGlobalBadgeCounter(context) - 1, 0); - setGlobalBadgeCounter(context, totalAmount); - return totalAmount; - } - - boolean isBadgeDeviceGloballyAllowed(Context context){ - try { - return Settings.Secure.getInt(context.getContentResolver(), "notification_badging") == PermissionManager.ON; - } catch (Settings.SettingNotFoundException ignored) { - return true; - } - } - - boolean isBadgeNumberingAllowed(Context context){ - try { - int currentBadgeCount = getGlobalBadgeCounter(context); - ShortcutBadger.applyCountOrThrow(context, currentBadgeCount); - return true; - } catch (Exception ignored) { - return false; - } - } - - boolean isBadgeAppGloballyAllowed(Context context){ - // TODO missing global badge checking for the current application scope - //Settings.Secure.getInt(context.getContentResolver(), "notification_badging").contains(context.getPackageName()); - return true; - } - - public boolean isBadgeGloballyAllowed(Context context){ - return Build.VERSION.SDK_INT < Build.VERSION_CODES.N /*Android 7*/ || - isBadgeDeviceGloballyAllowed(context) && - isBadgeAppGloballyAllowed(context); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/CancellationManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/CancellationManager.java deleted file mode 100644 index 2ff25e9a..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/CancellationManager.java +++ /dev/null @@ -1,205 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.threads.NotificationScheduler; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class CancellationManager { - - private static final String TAG = "CancellationManager"; - - private final StringUtils stringUtils; - - // ************** SINGLETON PATTERN *********************** - - protected static CancellationManager instance; - - protected CancellationManager(StringUtils stringUtils){ - this.stringUtils = stringUtils; - } - - public static CancellationManager getInstance() { - if (instance == null) - instance = new CancellationManager( - StringUtils.getInstance()); - return instance; - } - - // ******************************************************** - - public boolean dismissNotification(@NonNull final Context context, Integer notificationId) throws AwesomeNotificationsException { - - if (notificationId == null || notificationId < 0) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid notification id", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationId"); - - StatusBarManager - .getInstance(context) - .dismissNotification(context, notificationId); - - return true; - } - - public boolean dismissNotificationsByChannelKey(@NonNull final Context context, @NonNull final String channelKey) throws AwesomeNotificationsException { - - if(stringUtils.isNullOrEmpty(channelKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid channel key", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channelKey"); - - StatusBarManager - .getInstance(context) - .dismissNotificationsByChannelKey(context, channelKey); - - return true; - } - - public boolean dismissNotificationsByGroupKey(@NonNull final Context context, @NonNull final String groupKey) throws AwesomeNotificationsException { - - if(stringUtils.isNullOrEmpty(groupKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid group key", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".groupKey"); - - StatusBarManager - .getInstance(context) - .dismissNotificationsByGroupKey(context, groupKey); - - return true; - } - - public void dismissAllNotifications(@NonNull final Context context) throws AwesomeNotificationsException { - StatusBarManager - .getInstance(context) - .dismissAllNotifications(context); - } - - public boolean cancelSchedule(@NonNull final Context context, Integer notificationId) throws AwesomeNotificationsException { - - if (notificationId == null || notificationId < 0) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid notification id", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationId"); - - NotificationScheduler.cancelScheduleById(context, notificationId); - - return true; - } - - public boolean cancelSchedulesByChannelKey(@NonNull final Context context, @NonNull final String channelKey) throws AwesomeNotificationsException { - - if(stringUtils.isNullOrEmpty(channelKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid channel key", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channelKey"); - - NotificationScheduler.cancelSchedulesByChannelKey(context, channelKey); - - return true; - } - - public boolean cancelSchedulesByGroupKey(@NonNull final Context context, @NonNull final String groupKey) throws AwesomeNotificationsException { - - if(stringUtils.isNullOrEmpty(groupKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid group key", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".groupKey"); - - NotificationScheduler.cancelSchedulesByGroupKey(context, groupKey); - - return true; - } - - public void cancelAllSchedules(@NonNull final Context context) throws AwesomeNotificationsException { - NotificationScheduler.cancelAllSchedules(context); - } - - public boolean cancelNotification(@NonNull final Context context, Integer notificationId) throws AwesomeNotificationsException { - - if (notificationId == null || notificationId < 0) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid notification id", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationId"); - - cancelSchedule(context, notificationId); - dismissNotification(context, notificationId); - - return true; - } - - public boolean cancelNotificationsByChannelKey(@NonNull final Context context, @NonNull final String channelKey) throws AwesomeNotificationsException { - - if(stringUtils.isNullOrEmpty(channelKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid channel key", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channelKey"); - - dismissNotificationsByChannelKey(context, channelKey); - cancelSchedulesByChannelKey(context, channelKey); - - return true; - } - - public boolean cancelNotificationsByGroupKey(@NonNull final Context context, @NonNull final String groupKey) throws AwesomeNotificationsException { - - if(stringUtils.isNullOrEmpty(groupKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid group key", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".groupKey"); - - dismissNotificationsByGroupKey(context, groupKey); - cancelSchedulesByGroupKey(context, groupKey); - - return true; - } - - - public void cancelAllNotifications(@NonNull final Context context) throws AwesomeNotificationsException { - - dismissAllNotifications(context); - cancelAllSchedules(context); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ChannelGroupManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ChannelGroupManager.java deleted file mode 100644 index d7698685..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ChannelGroupManager.java +++ /dev/null @@ -1,74 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.app.NotificationChannelGroup; -import android.app.NotificationManager; -import android.content.Context; -import android.os.Build; - -import androidx.annotation.RequiresApi; - -import java.util.List; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.models.NotificationChannelGroupModel; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class ChannelGroupManager { - - private static final SharedManager shared - = new SharedManager<>( - StringUtils.getInstance(), - "ChannelGroupManager", - NotificationChannelGroupModel.class, - "NotificationChannelGroup"); - - public static Boolean removeChannelGroup(Context context, String channelGroupKey) throws AwesomeNotificationsException { - return shared.remove(context, Definitions.SHARED_CHANNEL_GROUP, channelGroupKey); - } - - public static List listChannelGroup(Context context) throws AwesomeNotificationsException { - return shared.getAllObjects(context, Definitions.SHARED_CHANNEL_GROUP); - } - - public static void saveChannelGroup(Context context, NotificationChannelGroupModel channelGroupModel) { - try { - channelGroupModel.validate(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - setAndroidChannelGroup(context, channelGroupModel); - - shared.set(context, Definitions.SHARED_CHANNEL_GROUP, channelGroupModel.channelGroupKey, channelGroupModel); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } - - public static NotificationChannelGroupModel getChannelGroupByKey(Context context, String channelGroupKey) throws AwesomeNotificationsException { - return shared.get(context, Definitions.SHARED_CHANNEL_GROUP, channelGroupKey); - } - - public static void cancelAllChannelGroup(Context context) throws AwesomeNotificationsException { - List channelGroupList = shared.getAllObjects(context, Definitions.SHARED_CHANNEL_GROUP); - if (channelGroupList != null){ - for (NotificationChannelGroupModel channelGroup : channelGroupList) { - cancelChannelGroup(context, channelGroup.channelGroupKey); - } - } - } - - public static void cancelChannelGroup(Context context, String channelGroupKey) throws AwesomeNotificationsException { - NotificationChannelGroupModel channelGroup = getChannelGroupByKey(context, channelGroupKey); - if(channelGroup !=null) - removeChannelGroup(context, channelGroupKey); - } - - public static void commitChanges(Context context) throws AwesomeNotificationsException { - shared.commit(context); - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - public static void setAndroidChannelGroup(Context context, NotificationChannelGroupModel newChannelGroup) { - NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.createNotificationChannelGroup(new NotificationChannelGroup(newChannelGroup.channelGroupKey, newChannelGroup.channelGroupName)); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ChannelManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ChannelManager.java deleted file mode 100644 index 311aadfa..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ChannelManager.java +++ /dev/null @@ -1,440 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Context; -import android.media.AudioAttributes; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Build; - -import androidx.annotation.RequiresApi; - -import java.util.Arrays; -import java.util.List; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.enumerators.DefaultRingtoneType; -import me.carda.awesome_notifications.core.enumerators.NotificationImportance; -import me.carda.awesome_notifications.core.enumerators.NotificationPrivacy; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.models.NotificationChannelGroupModel; -import me.carda.awesome_notifications.core.models.NotificationChannelModel; -import me.carda.awesome_notifications.core.utils.AudioUtils; -import me.carda.awesome_notifications.core.utils.BooleanUtils; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class ChannelManager { - - private static final SharedManager shared - = new SharedManager<>( - StringUtils.getInstance(), - "ChannelManager", - NotificationChannelModel.class, - "NotificationChannelModel"); - - private static final String TAG = "ChannelManager"; - - - private final StringUtils stringUtils; - private final AudioUtils audioUtils; - - // ************** SINGLETON PATTERN *********************** - - private static ChannelManager instance; - - private ChannelManager(StringUtils stringUtils, AudioUtils audioUtils){ - this.stringUtils = stringUtils; - this.audioUtils = audioUtils; - } - - public static ChannelManager getInstance() { - if (instance == null) - instance = new ChannelManager( - StringUtils.getInstance(), - AudioUtils.getInstance() - ); - return instance; - } - - // ******************************************************** - - public Boolean removeChannel(Context context, String channelKey) throws AwesomeNotificationsException { - - NotificationChannelModel channelModel = getChannelByKey(context, channelKey); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) { - removeAndroidChannel(context, channelKey, channelModel != null ? channelModel.getChannelHashKey(context, false) : null); - } - - return shared.remove(context, Definitions.SHARED_CHANNELS, channelKey); - } - - public NotificationChannelModel getChannelByKey(Context context, String channelKey) throws AwesomeNotificationsException { - - if(stringUtils.isNullOrEmpty(channelKey)) { - if(AwesomeNotifications.debug) - Logger.w(TAG, "'"+channelKey+"' cannot be empty or null"); - return null; - } - - NotificationChannelModel channelModel = shared.get(context, Definitions.SHARED_CHANNELS, channelKey); - if(channelModel == null) { - if(AwesomeNotifications.debug) - Logger.w(TAG, "Channel model '"+channelKey+"' was not found"); - return null; - } - - channelModel.refreshIconResource(context); - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/){ - - NotificationChannel androidChannel = getAndroidChannel(context, channelKey); - if(androidChannel == null) { - if(AwesomeNotifications.debug) - Logger.w(TAG, "Android native channel '"+channelKey+"' was not found"); - return null; - } - - if(androidChannel.getImportance() == NotificationManager.IMPORTANCE_NONE){ - if(AwesomeNotifications.debug) - Logger.w(TAG, "Android native channel '"+channelKey+"' is disabled"); - } - - updateChannelModelThroughAndroidChannel(channelModel, androidChannel); - } - - return channelModel; - } - - @RequiresApi(api = Build.VERSION_CODES.O) - private void updateChannelModelThroughAndroidChannel(NotificationChannelModel channelModel, NotificationChannel androidChannel){ - channelModel.channelName = String.valueOf(androidChannel.getName()); - channelModel.channelDescription = androidChannel.getDescription(); - channelModel.channelShowBadge = androidChannel.canShowBadge(); - channelModel.playSound = androidChannel.getSound() != null; - channelModel.enableLights = androidChannel.shouldShowLights(); - channelModel.enableVibration = androidChannel.shouldVibrate(); - channelModel.importance = NotificationImportance.fromAndroidImportance(androidChannel.getImportance()); - } - - public List listChannels(Context context) throws AwesomeNotificationsException { - return shared.getAllObjects(context, Definitions.SHARED_CHANNELS); - } - - public void commitChanges(Context context) throws AwesomeNotificationsException { - shared.commit(context); - } - - public boolean isChannelEnabled(Context context, String channelKey) throws AwesomeNotificationsException { - - if (stringUtils.isNullOrEmpty(channelKey)) return false; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) { - - NotificationChannel firstAndroidChannel = getAndroidChannel(context, channelKey); - if (firstAndroidChannel != null){ - return firstAndroidChannel.getImportance() != NotificationManager.IMPORTANCE_NONE; - } - - NotificationChannelModel channelModel = getChannelByKey(context, channelKey); - String awesomeHashKey = channelModel.getChannelHashKey(context, false); - - NotificationChannel forcedAndroidChannel = getAndroidChannel(context, null, awesomeHashKey); - return (forcedAndroidChannel != null && forcedAndroidChannel.getImportance() != NotificationManager.IMPORTANCE_NONE); - - } else { - - NotificationChannelModel channelModel = getChannelByKey(context, channelKey); - return channelModel != null && channelModel.isChannelEnabled(); - } - } - - public ChannelManager saveChannel(Context context, NotificationChannelModel newChannel, Boolean setOnlyNew, Boolean forceUpdate) throws AwesomeNotificationsException { - - newChannel.refreshIconResource(context); - newChannel.validate(context); - - NotificationChannelModel oldChannelModel = getChannelByKey(context, newChannel.channelKey); - - // If nothing has changed, so there is nothing to do - if(setOnlyNew && oldChannelModel != null && !oldChannelModel.equals(newChannel)) - return this; - - // Android Channels are only available on Android Oreo and beyond. - // On older versions, channel models are only used to organize notifications - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O /*Android 8*/) { - - // If nothing has changed, so there is nothing to do - if(oldChannelModel != null && oldChannelModel.equals(newChannel)) return this; - - // Save into shared manager - shared.set(context, Definitions.SHARED_CHANNELS, newChannel.channelKey, newChannel); - shared.commit(context); - - if(AwesomeNotifications.debug) - Logger.d(TAG, "Notification channel "+newChannel.channelName+ - (oldChannelModel == null ? " created" : " updated")); - } - else { - - // Save into shared manager - shared.set(context, Definitions.SHARED_CHANNELS, newChannel.channelKey, newChannel); - shared.commit(context); - - saveAndroidChannel(context, oldChannelModel, newChannel, forceUpdate); - } - - return this; - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - private void saveAndroidChannel(Context context, NotificationChannelModel oldChannelModel, NotificationChannelModel newChannel, Boolean forceUpdate) throws AwesomeNotificationsException { - - String newHashKey = newChannel.getChannelHashKey(context, false); - - NotificationChannel actualAndroidChannel = getAndroidChannel( - context, - newChannel.channelKey, - newHashKey); - - // created - if(actualAndroidChannel == null){ - - if(oldChannelModel != null){ - // Ensure that the previous standards will be updated - removeOldAndroidChannelStandards(context, oldChannelModel.channelKey, oldChannelModel.channelName); - } - - setAndroidChannel(context, newChannel, true); - if(AwesomeNotifications.debug) - Logger.d(TAG, "Notification channel "+ newChannel.channelName+" created"); - } - // updated - else { - String currentChannelKey = actualAndroidChannel.getId(); - - // Only first channels have the channel key equals to the originals - if(newChannel.channelKey.equals(currentChannelKey)){ - - if(forceUpdate && androidChannelNeedsForceUpdate(newChannel, actualAndroidChannel)){ - - // From this point on, the android channel needs to be slight different from the originals - removeAndroidChannel(context, currentChannelKey, null); - setAndroidChannel(context, newChannel, false); - if(AwesomeNotifications.debug) - Logger.d(TAG, "Notification channel "+ newChannel.channelName+" updated with forceUpdate"); - } - else - if(androidChannelNeedsUpdate(newChannel, actualAndroidChannel)){ - setAndroidChannel(context, newChannel, true); - if(AwesomeNotifications.debug) - Logger.d(TAG, "Notification channel "+ newChannel.channelName+" updated"); - } - } - // For cases where forceUpdated was applied - else { - if(!currentChannelKey.equals(newHashKey) && forceUpdate){ - removeAndroidChannel(context, currentChannelKey, newHashKey); - setAndroidChannel(context, newChannel, false); - if(AwesomeNotifications.debug) - Logger.d(TAG, "Notification channel "+ newChannel.channelName+" updated with forceUpdate"); - } - else - if(androidChannelNeedsUpdate(newChannel, actualAndroidChannel)){ - setAndroidChannel(context, newChannel, false); - if(AwesomeNotifications.debug) - Logger.d(TAG, "Notification channel "+ newChannel.channelName+" updated"); - } - } - } - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - private boolean androidChannelNeedsUpdate(NotificationChannelModel channelModel, NotificationChannel androidChannel){ - return !( - androidChannel.getName().equals(channelModel.channelName) && - androidChannel.getDescription().equals(channelModel.channelDescription) - ); - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - private boolean androidChannelNeedsForceUpdate(NotificationChannelModel channelModel, NotificationChannel androidChannel){ - Uri uriSound = androidChannel.getSound(); - return !( - (Arrays.equals(channelModel.vibrationPattern, androidChannel.getVibrationPattern())) && - java.util.Objects.equals(channelModel.groupKey, androidChannel.getGroup()) && - (channelModel.channelShowBadge == androidChannel.canShowBadge()) && - (channelModel.ledColor == null || channelModel.ledColor == androidChannel.getLightColor()) && - (channelModel.defaultPrivacy == NotificationPrivacy.values()[androidChannel.getLockscreenVisibility()]) && - (channelModel.importance == NotificationImportance.values()[androidChannel.getImportance()]) && - (!channelModel.playSound && uriSound == null || uriSound.getPath().contains(channelModel.soundSource)) - ); - } - - public Uri retrieveSoundResourceUri(Context context, DefaultRingtoneType ringtoneType, String soundSource) { - Uri uri = null; - if (stringUtils.isNullOrEmpty(soundSource)) { - - int defaultRingtoneKey; - switch (ringtoneType){ - - case Ringtone: - defaultRingtoneKey = RingtoneManager.TYPE_RINGTONE; - break; - - case Alarm: - defaultRingtoneKey = RingtoneManager.TYPE_ALARM; - break; - - case Notification: - default: - defaultRingtoneKey = RingtoneManager.TYPE_NOTIFICATION; - break; - } - uri = RingtoneManager.getDefaultUri(defaultRingtoneKey); - - } else { - int soundResourceId = audioUtils.getAudioResourceId(context, soundSource); - if(soundResourceId > 0){ - uri = Uri.parse("android.resource://" + AwesomeNotifications.getPackageName(context) + "/" + soundResourceId); - } - } - return uri; - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - public NotificationChannel getAndroidChannel(Context context, String channelKey){ - return getAndroidChannel(context, channelKey, null); - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - public NotificationChannel getAndroidChannel(Context context, String channelKey, String awesomeChannelHashKey){ - - NotificationManager notificationManager = getAndroidNotificationManager(context); - - // Returns channel from another packages with same name43 - if(channelKey != null){ - NotificationChannel standardAndroidChannel = notificationManager.getNotificationChannel(channelKey); - if(standardAndroidChannel != null){ - return standardAndroidChannel; - } - } - - // Try to search for a forcedUpdatedChannel - List notificationChannels = notificationManager.getNotificationChannels(); - for(NotificationChannel currentAndroidChannel : notificationChannels){ - String androidChannelKey = currentAndroidChannel.getId(); - if(androidChannelKey.startsWith(channelKey + "_")) - return currentAndroidChannel; - } - - // If hash key was not defined, so there is nothing more to do - if(awesomeChannelHashKey == null) return null; - return notificationManager.getNotificationChannel(awesomeChannelHashKey); - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - public void removeOldAndroidChannelStandards(Context context, String channelKey, String channelName){ - NotificationManager notificationManager = getAndroidNotificationManager(context); - - List notificationChannels = notificationManager.getNotificationChannels(); - for(NotificationChannel currentAndroidChannel : notificationChannels){ - String androidChannelKey = currentAndroidChannel.getId(); - if( // delete channels with older standards - (!androidChannelKey.equals(channelKey)) && - (androidChannelKey.length() == 32) && - (currentAndroidChannel.getName().equals(channelName)) - ){ - notificationManager.deleteNotificationChannel(androidChannelKey); - } - } - } - - public NotificationManager getAndroidNotificationManager(Context context){ - return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - public void setAndroidChannel(Context context, NotificationChannelModel newChannel, boolean firstChannel) throws AwesomeNotificationsException { - - NotificationManager notificationManager = getAndroidNotificationManager(context); - - NotificationChannel newAndroidNotificationChannel = new NotificationChannel( - firstChannel ? - newChannel.channelKey : - newChannel.getChannelHashKey(context, false), - newChannel.channelName, - newChannel.importance.ordinal() - ); - - newAndroidNotificationChannel.setDescription(newChannel.channelDescription); - - NotificationChannelGroupModel channelGroup = null; - if (!stringUtils.isNullOrEmpty(newChannel.channelGroupKey)) { - channelGroup = ChannelGroupManager.getChannelGroupByKey(context, newChannel.channelGroupKey); - - if(channelGroup != null) - newAndroidNotificationChannel.setGroup(newChannel.channelGroupKey); - else - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel group "+newChannel.channelGroupKey+" does not exist.", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channelGroup."+newChannel.channelGroupKey); - } - - if (channelGroup != null) - newAndroidNotificationChannel.setGroup(newChannel.channelGroupKey); - - if (newChannel.playSound) { - - /// TODO NEED TO IMPROVE AUDIO RESOURCES TO BE MORE VERSATILE, SUCH AS BITMAP ONES - AudioAttributes audioAttributes = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION) - .build(); - - Uri uri = retrieveSoundResourceUri(context, newChannel.defaultRingtoneType, newChannel.soundSource); - newAndroidNotificationChannel.setSound(uri, audioAttributes); - - } else { - newAndroidNotificationChannel.setSound(null, null); - } - - newAndroidNotificationChannel.enableVibration(BooleanUtils.getInstance().getValue(newChannel.enableVibration)); - if (newChannel.vibrationPattern != null && newChannel.vibrationPattern.length > 0) { - newAndroidNotificationChannel.setVibrationPattern(newChannel.vibrationPattern); - } - - boolean enableLights = BooleanUtils.getInstance().getValue(newChannel.enableLights); - newAndroidNotificationChannel.enableLights(enableLights); - - if (enableLights && newChannel.ledColor != null) { - newAndroidNotificationChannel.setLightColor(newChannel.ledColor); - } - - if(newChannel.criticalAlerts) { - newAndroidNotificationChannel.setBypassDnd(true); - } - - newAndroidNotificationChannel.setShowBadge(BooleanUtils.getInstance().getValue(newChannel.channelShowBadge)); - - notificationManager.createNotificationChannel(newAndroidNotificationChannel); - } - - @RequiresApi(api = Build.VERSION_CODES.O /*Android 8*/) - private void removeAndroidChannel(Context context, String channelKey, String newHashKey) { - NotificationManager notificationManager = getAndroidNotificationManager(context); - - notificationManager.deleteNotificationChannel(channelKey); - - if(!stringUtils.isNullOrEmpty(newHashKey)) - notificationManager.deleteNotificationChannel(newHashKey); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/CreatedManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/CreatedManager.java deleted file mode 100644 index b6c80e04..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/CreatedManager.java +++ /dev/null @@ -1,55 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.content.Context; - -import java.util.List; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class CreatedManager { - - private static final SharedManager shared - = new SharedManager<>( - StringUtils.getInstance(), - "CreatedManager", - NotificationReceived.class, - "NotificationReceived"); - - public static Boolean removeCreated(Context context, Integer id) throws AwesomeNotificationsException { - return shared.remove(context, Definitions.SHARED_CREATED, id.toString()); - } - - public static List listCreated(Context context) throws AwesomeNotificationsException { - return shared.getAllObjects(context, Definitions.SHARED_CREATED); - } - - public static void saveCreated(Context context, NotificationReceived received) throws AwesomeNotificationsException { - shared.set(context, Definitions.SHARED_CREATED, received.id.toString(), received); - } - - public static NotificationReceived getCreatedByKey(Context context, Integer id) throws AwesomeNotificationsException { - return shared.get(context, Definitions.SHARED_CREATED, id.toString()); - } - - public static void cancelAllCreated(Context context) throws AwesomeNotificationsException { - List receivedList = shared.getAllObjects(context, Definitions.SHARED_CREATED); - if (receivedList != null){ - for (NotificationReceived received : receivedList) { - cancelCreated(context, received.id); - } - } - } - - public static void cancelCreated(Context context, Integer id) throws AwesomeNotificationsException { - NotificationReceived received = getCreatedByKey(context, id); - if(received !=null) - removeCreated(context, received.id); - } - - public static void commitChanges(Context context) throws AwesomeNotificationsException { - shared.commit(context); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DefaultsManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DefaultsManager.java deleted file mode 100644 index cc5e9872..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DefaultsManager.java +++ /dev/null @@ -1,110 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import me.carda.awesome_notifications.core.AwesomeNotificationsExtension; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.enumerators.MediaSource; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.models.DefaultsModel; -import me.carda.awesome_notifications.core.utils.BitmapUtils; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class DefaultsManager { - - private static DefaultsModel instance; - private static final SharedManager shared - = new SharedManager<>( - StringUtils.getInstance(), - "DefaultsManager", - DefaultsModel.class, - "DefaultsModel"); - - public static void saveDefault( - @NonNull Context context, - @Nullable String defaultIconPath, - @Nullable Long dartCallback - ) throws AwesomeNotificationsException { - if (BitmapUtils.getInstance().getMediaSourceType(defaultIconPath) != MediaSource.Resource) - defaultIconPath = null; - - DefaultsModel defaults = getDefaults(context); - - if (defaults == null) { - defaults = new DefaultsModel( - defaultIconPath, - dartCallback, - null, - null); - } - else { - defaults.appIcon = defaultIconPath; - defaults.reverseDartCallback = dartCallback == null ? null : dartCallback.toString(); - } - - saveDefault(context, defaults); - - } - - private static void saveDefault(Context context, DefaultsModel defaults) throws AwesomeNotificationsException { - shared.set(context, Definitions.SHARED_DEFAULTS, "Defaults", defaults); - } - - public static DefaultsModel getDefaults(Context context) throws AwesomeNotificationsException { - if (instance == null) - instance = shared.get(context, Definitions.SHARED_DEFAULTS, "Defaults"); - - return instance == null ? new DefaultsModel() : instance; - } - - public static String getDefaultIcon(Context context) throws AwesomeNotificationsException { - DefaultsModel defaults = getDefaults(context); - return (defaults != null) ? defaults.appIcon : null; - } - - public static void setDefaultIcon(Context context, String appIcon) throws AwesomeNotificationsException { - DefaultsModel defaults = getDefaults(context); - defaults.appIcon = appIcon; - saveDefault(context, defaults); - } - - public static Long getSilentCallbackDispatcher(Context context) throws AwesomeNotificationsException { - DefaultsModel defaults = getDefaults(context); - return Long.parseLong(defaults.silentDataCallback); - } - - public static void setActionCallbackDispatcher(Context context, Long silentDataCallback) throws AwesomeNotificationsException { - DefaultsModel defaults = getDefaults(context); - defaults.silentDataCallback = silentDataCallback.toString(); - saveDefault(context, defaults); - } - - public static Long getDartCallbackDispatcher(Context context) throws AwesomeNotificationsException { - DefaultsModel defaults = getDefaults(context); - return Long.parseLong(defaults.reverseDartCallback); - } - - public static void setDartCallbackDispatcher(Context context, Long reverseDartCallback) throws AwesomeNotificationsException { - DefaultsModel defaults = getDefaults(context); - defaults.reverseDartCallback = reverseDartCallback.toString(); - saveDefault(context, defaults); - } - - public static String getAwesomeExtensionClassName(Context context) throws AwesomeNotificationsException { - DefaultsModel defaults = getDefaults(context); - return defaults.backgroundHandleClass; - } - - public static void setAwesomeExtensionClassName(Context context, Class backgroundHandleClass) throws AwesomeNotificationsException { - DefaultsModel defaults = getDefaults(context); - defaults.backgroundHandleClass = backgroundHandleClass.getName(); - saveDefault(context, defaults); - } - - public static void commitChanges(Context context) throws AwesomeNotificationsException { - shared.commit(context); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DismissedManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DismissedManager.java deleted file mode 100644 index 22f675c5..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DismissedManager.java +++ /dev/null @@ -1,55 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.content.Context; - -import java.util.List; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.models.returnedData.ActionReceived; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class DismissedManager { - - private static final SharedManager shared - = new SharedManager<>( - StringUtils.getInstance(), - "DismissedManager", - ActionReceived.class, - "ActionReceived"); - - public static Boolean removeDismissed(Context context, Integer id) throws AwesomeNotificationsException { - return shared.remove(context, Definitions.SHARED_DISMISSED, id.toString()); - } - - public static List listDismisses(Context context) throws AwesomeNotificationsException { - return shared.getAllObjects(context, Definitions.SHARED_DISMISSED); - } - - public static void saveDismissed(Context context, ActionReceived received) throws AwesomeNotificationsException { - shared.set(context, Definitions.SHARED_DISMISSED, received.id.toString(), received); - } - - public static ActionReceived getDismissedByKey(Context context, Integer id) throws AwesomeNotificationsException { - return shared.get(context, Definitions.SHARED_DISMISSED, id.toString()); - } - - public static void clearAllDismissed(Context context) throws AwesomeNotificationsException { - List receivedList = shared.getAllObjects(context, Definitions.SHARED_DISMISSED); - if (receivedList != null){ - for (ActionReceived received : receivedList) { - cancelDismissed(context, received.id); - } - } - } - - public static void cancelDismissed(Context context, Integer id) throws AwesomeNotificationsException { - ActionReceived received = getDismissedByKey(context, id); - if(received !=null) - removeDismissed(context, received.id); - } - - public static void commitChanges(Context context) throws AwesomeNotificationsException { - shared.commit(context); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DisplayedManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DisplayedManager.java deleted file mode 100644 index e4ce3e0e..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/DisplayedManager.java +++ /dev/null @@ -1,54 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.content.Context; - -import java.util.List; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class DisplayedManager { - - private static final SharedManager shared - = new SharedManager<>( - StringUtils.getInstance(), - "DisplayedManager", - NotificationReceived.class, - "NotificationReceived"); - - public static Boolean removeDisplayed(Context context, Integer id) throws AwesomeNotificationsException { - return shared.remove(context, Definitions.SHARED_DISPLAYED, id.toString()); - } - - public static List listDisplayed(Context context) throws AwesomeNotificationsException { - return shared.getAllObjects(context, Definitions.SHARED_DISPLAYED); - } - - public static void saveDisplayed(Context context, NotificationReceived received) throws AwesomeNotificationsException { - shared.set(context, Definitions.SHARED_DISPLAYED, received.id.toString(), received); - } - - public static NotificationReceived getDisplayedByKey(Context context, Integer id) throws AwesomeNotificationsException { - return shared.get(context, Definitions.SHARED_DISPLAYED, id.toString()); - } - - public static void cancelAllDisplayed(Context context) throws AwesomeNotificationsException { - List displayedList = shared.getAllObjects(context, Definitions.SHARED_DISPLAYED); - if(displayedList != null) - for(NotificationReceived displayed : displayedList){ - cancelDisplayed(context, displayed.id); - } - } - - public static void cancelDisplayed(Context context, Integer id) throws AwesomeNotificationsException { - NotificationReceived received = getDisplayedByKey(context, id); - if(received != null) - removeDisplayed(context, received.id); - } - - public static void commitChanges(Context context) throws AwesomeNotificationsException { - shared.commit(context); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/LifeCycleManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/LifeCycleManager.java deleted file mode 100644 index 0a59aee7..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/LifeCycleManager.java +++ /dev/null @@ -1,138 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import androidx.annotation.NonNull; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.OnLifecycleEvent; -import androidx.lifecycle.ProcessLifecycleOwner; - -import java.util.ArrayList; -import java.util.List; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.listeners.AwesomeLifeCycleEventListener; -import me.carda.awesome_notifications.core.logs.Logger; - -public class LifeCycleManager implements LifecycleObserver { - - private static final String TAG = "LifeCycleManager"; - - protected static NotificationLifeCycle appLifeCycle = NotificationLifeCycle.AppKilled; - public static NotificationLifeCycle getApplicationLifeCycle(){ - return appLifeCycle; - } - - // ************** SINGLETON PATTERN *********************** - - static LifeCycleManager instance; - - private LifeCycleManager(){ - } - - public static LifeCycleManager getInstance() { - if (instance == null) { - instance = new LifeCycleManager(); - } - return instance; - } - - // ************** OBSERVER PATTERN ************************ - - List listeners = new ArrayList<>(); - - public LifeCycleManager subscribe(@NonNull AwesomeLifeCycleEventListener listener) { - listeners.add(listener); - return this; - } - - public LifeCycleManager unsubscribe(@NonNull AwesomeLifeCycleEventListener listener) { - listeners.remove(listener); - return this; - } - - public void notify(@NonNull NotificationLifeCycle lifeCycle) { - for (AwesomeLifeCycleEventListener listener : listeners) - listener.onNewLifeCycleEvent(lifeCycle); - } - - // ******************************************************** - - boolean hasStarted = false; - public void startListeners(){ - if(hasStarted) return; - hasStarted = true; - - ProcessLifecycleOwner.get().getLifecycle().addObserver(this); - - if(AwesomeNotifications.debug) - Logger.d(TAG, "LiceCycleManager listener successfully attached to Android"); - } - - public void stopListeners(){ - if(!hasStarted) return; - hasStarted = false; - ProcessLifecycleOwner.get().getLifecycle().removeObserver(this); - - if(AwesomeNotifications.debug) - Logger.d(TAG, "LiceCycleManager listener successfully removed from Android"); - } - - boolean hasGoneForeground = false; - public void updateAppLifeCycle(NotificationLifeCycle lifeCycle){ - if(appLifeCycle == lifeCycle) - return; - - hasGoneForeground = - hasGoneForeground || - appLifeCycle == NotificationLifeCycle.Foreground; - - appLifeCycle = lifeCycle; - notify(appLifeCycle); - - if(AwesomeNotifications.debug) - Logger.d(TAG, "App is now "+lifeCycle); - } - - @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) - public void onCreated() { - updateAppLifeCycle( - hasGoneForeground ? - NotificationLifeCycle.Background : - NotificationLifeCycle.AppKilled); - } - - boolean wasNotCreated = true; - @OnLifecycleEvent(Lifecycle.Event.ON_START) - public void onStarted() { - updateAppLifeCycle( - hasGoneForeground ? - NotificationLifeCycle.Background : - NotificationLifeCycle.AppKilled); - } - - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - public void onResumed() { - updateAppLifeCycle( - NotificationLifeCycle.Foreground); - } - - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) - public void onPaused() { - updateAppLifeCycle( - NotificationLifeCycle.Foreground); - } - - @OnLifecycleEvent(Lifecycle.Event.ON_STOP) - public void onStopped() { - updateAppLifeCycle( - NotificationLifeCycle.Background); - } - - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - public void onDestroyed() { - updateAppLifeCycle( - NotificationLifeCycle.AppKilled); - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/PermissionManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/PermissionManager.java deleted file mode 100644 index a8344882..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/PermissionManager.java +++ /dev/null @@ -1,785 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.Manifest; -import android.app.Activity; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.provider.Settings; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.core.app.ActivityCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingDeque; - -import javax.annotation.Nullable; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.completion_handlers.ActivityCompletionHandler; -import me.carda.awesome_notifications.core.completion_handlers.PermissionCompletionHandler; -import me.carda.awesome_notifications.core.enumerators.NotificationImportance; -import me.carda.awesome_notifications.core.enumerators.NotificationPermission; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.models.NotificationChannelModel; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class PermissionManager { - - public static final int REQUEST_CODE = 101; - final static int ON = 1; - final static int OFF = 0; - - private final String TAG = "PermissionManager"; - - private final StringUtils stringUtils; - - // ************** SINGLETON PATTERN *********************** - - private static PermissionManager instance; - - private PermissionManager(StringUtils stringUtils){ - this.stringUtils = stringUtils; - } - - public static PermissionManager getInstance() { - if (instance == null) - instance = new PermissionManager( - StringUtils.getInstance() - ); - return instance; - } - - // ******************************************************** - - private final BlockingQueue activityQueue - = new LinkedBlockingDeque(); - - public Boolean areNotificationsGloballyAllowed(Context context) { - NotificationManagerCompat manager = NotificationManagerCompat.from(context); - return manager.areNotificationsEnabled(); - } - - public List arePermissionsAllowed(Context context, String channelKey, List permissions) throws AwesomeNotificationsException { - List permissionsAllowed = new ArrayList(); - - if(!areNotificationsGloballyAllowed(context)) - return permissionsAllowed; - - for (String permission : permissions) { - NotificationPermission permissionEnum = stringUtils.getEnumFromString(NotificationPermission.class, permission); - if( - permissionEnum != null && - isSpecifiedPermissionGloballyAllowed(context, permissionEnum) && - (channelKey == null || isSpecifiedChannelPermissionAllowed(context, channelKey, permissionEnum)) - ) { - permissionsAllowed.add(permission); - } - } - - return permissionsAllowed; - } - - private final List oldAndroidShouldShowRationale = new ArrayList(){{ - add(NotificationPermission.Sound.toString()); - add(NotificationPermission.CriticalAlert.toString()); - add(NotificationPermission.OverrideDnD.toString()); - }}; - - private final List newAndroidShouldntShowRationale = new ArrayList(){{ - add(NotificationPermission.FullScreenIntent.toString()); - add(NotificationPermission.Provisional.toString()); - }}; - - public List shouldShowRationale(Context context, String channelKey, List permissions) throws AwesomeNotificationsException { - - if(!areNotificationsGloballyAllowed(context)) - return permissions; - - // Channel's permission under Android 8 special condition - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/){ - permissions.removeAll(newAndroidShouldntShowRationale); - List allowedPermissions = arePermissionsAllowed(context, channelKey, permissions); - permissions.removeAll(allowedPermissions); - } - else { - List permissionsShouldShowRationale = new ArrayList(); - for (String permission : oldAndroidShouldShowRationale) - if (permissions.contains(permission)) - permissionsShouldShowRationale.add(permission); - permissions = permissionsShouldShowRationale; - - List allowedGlobalPermissions = arePermissionsAllowed(context, null, permissions); - permissions.removeAll(allowedGlobalPermissions); - } - - return permissions; - } - - public boolean isSpecifiedPermissionGloballyAllowed(Context context, NotificationPermission permission){ - - switch (permission){ - - case PreciseAlarms: - return ScheduleManager.isPreciseAlarmGloballyAllowed(context); - - case CriticalAlert: - return isCriticalAlertsGloballyAllowed(context); - - case Badge: - return BadgeManager.getInstance().getInstance().isBadgeGloballyAllowed(context); - - case OverrideDnD: - return isDndOverrideAllowed(context); - - case Sound: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N /*Android 7*/) - return isNotificationSoundGloballyAllowed(context); - break; - - case FullScreenIntent: - // check in android manifest - - case Alert: - case Vibration: - case Light: - - case Provisional: - case Car: - default: - break; - } - - String permissionCode = getManifestPermissionCode(permission); - - if(permissionCode == null) - return true; - - return ContextCompat.checkSelfPermission(context, permissionCode) == PackageManager.PERMISSION_GRANTED; - } - - @RequiresApi(api = Build.VERSION_CODES.N) - public boolean isNotificationSoundGloballyAllowed(Context context){ - NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - int importance = manager.getImportance(); - return importance < 0 || importance >= NotificationManager.IMPORTANCE_DEFAULT; - } - - public boolean isCriticalAlertsGloballyAllowed(Context context){ - NotificationManager notificationManager = - ChannelManager - .getInstance() - .getAndroidNotificationManager(context); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M /*Android 6*/) { - return notificationManager.getCurrentInterruptionFilter() != NotificationManager.INTERRUPTION_FILTER_NONE; - } - - // Critical alerts until Android 6 are "Treat as priority" or "Priority" -// return (notificationManager.getNotificationPolicy().state -// & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1; - // TODO read "Treat as priority" or "Priority" property on notifications page - return true; - } - - public boolean isDndOverrideAllowed(Context context){ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - NotificationManager notificationManager = null; - notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - return notificationManager.isNotificationPolicyAccessGranted(); - } - return true; - } - - public boolean isSpecifiedChannelPermissionAllowed( - Context context, - String channelKey, - NotificationPermission permissionEnum - ) throws AwesomeNotificationsException { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) { - NotificationChannel channel = - ChannelManager - .getInstance() - .getAndroidChannel(context, channelKey); - - if(channel == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel "+channelKey+" does not exist.", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.key"); - - if(channel.getImportance() != NotificationManager.IMPORTANCE_NONE){ - - switch (permissionEnum){ - - case Alert: - return channel.getImportance() >= NotificationManager.IMPORTANCE_HIGH; - - case Sound: - return channel.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT && (channel.getSound() != null); - - case Vibration: - return channel.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT && channel.shouldVibrate(); - - case Light: - return channel.shouldShowLights(); - - case Badge: - return channel.canShowBadge(); - - case CriticalAlert: - return channel.canBypassDnd(); - - case PreciseAlarms: - default: - return true; - } - - } - } - else { - NotificationChannelModel channelModel = - ChannelManager - .getInstance() - .getChannelByKey(context, channelKey); - - if(channelModel == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel "+channelKey+" does not exist.", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.key"); - - if(channelModel.importance != NotificationImportance.None){ - - switch (permissionEnum){ - - case Alert: - return channelModel.importance.ordinal() >= NotificationImportance.High.ordinal(); - - case Sound: - return channelModel.importance.ordinal() >= NotificationImportance.Default.ordinal() && channelModel.playSound; - - case Vibration: - return channelModel.importance.ordinal() >= NotificationImportance.Default.ordinal() && channelModel.enableVibration; - - case Light: - return channelModel.enableLights; - - case Badge: - return channelModel.channelShowBadge; - - case CriticalAlert: - return channelModel.criticalAlerts; - - case PreciseAlarms: - default: - return true; - } - - } - } - return false; - } - - public void requestUserPermissions( - final Activity activity, - final Context context, - final String channelKey, - final List permissions, - final PermissionCompletionHandler permissionCompletionHandler - ) throws AwesomeNotificationsException { - - if(!permissions.isEmpty()){ - - if(!areNotificationsGloballyAllowed(context)){ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - - String globalNotificationsPermissionCode = getManifestPermissionCode(null); - boolean requestAlreadyDenied = - activity - .shouldShowRequestPermissionRationale(globalNotificationsPermissionCode); - - if(!requestAlreadyDenied){ - shouldShowAndroidRequestDialog( - activity, - context, - channelKey, - permissions, - Collections.singletonList(globalNotificationsPermissionCode), - permissionCompletionHandler); - return; - } - } - - shouldShowRationalePage( - context, - channelKey, - null, - permissions, - permissionCompletionHandler); - return; - } - - ListpermissionsRequested = new ArrayList(permissions); - List allowedPermissions = arePermissionsAllowed(context, channelKey, permissionsRequested); - - permissionsRequested.removeAll(allowedPermissions); - List permissionsNeedingRationale = shouldShowRationale(context, channelKey, permissionsRequested); - - List manifestPermissions = new ArrayList(); - for (String permissionNeeded : permissionsNeedingRationale) { - NotificationPermission permissionEnum = stringUtils.getEnumFromString(NotificationPermission.class, permissionNeeded); - String permissionCode = getManifestPermissionCode(permissionEnum); - - if( - permissionCode == null || - Build.VERSION.SDK_INT < Build.VERSION_CODES.M || - activity != null && activity.shouldShowRequestPermissionRationale(permissionCode) - ){ - shouldShowRationalePage( - context, - channelKey, - permissionEnum, - permissionsRequested, - permissionCompletionHandler); - return; - } - else manifestPermissions.add(permissionCode); - } - - // System will prompt a standard dialog - if(activity != null && !manifestPermissions.isEmpty()){ - shouldShowAndroidRequestDialog( - activity, - context, - channelKey, - permissionsRequested, - manifestPermissions, - permissionCompletionHandler); - return; - } - } - - refreshReturnedPermissions(context, channelKey, permissions, permissionCompletionHandler); - } - - @RequiresApi(api = Build.VERSION_CODES.M) - private void shouldShowAndroidRequestDialog( - final Activity activity, - final Context context, - final String channelKey, - final List permissions, - final List manifestPermissions, - final PermissionCompletionHandler permissionCompletionHandler - ) throws AwesomeNotificationsException { - activity.requestPermissions(manifestPermissions.toArray(new String[0]), REQUEST_CODE); - activityQueue.add(new ActivityCompletionHandler() { - @Override - public void handle() { - refreshReturnedPermissions(context, channelKey, permissions, permissionCompletionHandler); - } - }); - } - - private void shouldShowRationalePage( - final Context context, - final String channelKey, - final @Nullable NotificationPermission permissionEnum, - final List permissions, - final PermissionCompletionHandler permissionCompletionHandler - ) throws AwesomeNotificationsException { - - boolean success; - - if(permissionEnum == null) - success = gotoAndroidAppNotificationPage(context); - else - switch (permissionEnum){ - - case PreciseAlarms: - success = gotoPreciseAlarmPage(context); - break; - - case OverrideDnD: - success = gotoControlsDnDPage(context); - break; - - case Badge: - case Alert: - case Sound: - case Light: - case Vibration: - case CriticalAlert: - success = gotoAndroidChannelPage(context, channelKey); - break; - - case FullScreenIntent: - case Car: - default: - success = gotoAndroidAppNotificationPage(context); - } - - if(success) - activityQueue.add(new ActivityCompletionHandler() { - @Override - public void handle() { - refreshReturnedPermissions(context, channelKey, permissions, permissionCompletionHandler); - } - }); - else - refreshReturnedPermissions(context, channelKey, permissions, permissionCompletionHandler); - } - - private void refreshReturnedPermissions( - final Context context, - final String channelKey, - List permissionsNeeded, - final PermissionCompletionHandler permissionCompletionHandler - ){ - try { - if(!permissionsNeeded.isEmpty()) { - - if (!stringUtils.isNullOrEmpty(channelKey)){ - List allowedPermissions = arePermissionsAllowed( - context, - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) ? - channelKey : null, - permissionsNeeded); - updateChannelModelThroughPermissions(context, channelKey, allowedPermissions); - } - - List allowedPermissions = arePermissionsAllowed(context, channelKey, permissionsNeeded); - permissionsNeeded.removeAll(allowedPermissions); - } - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - permissionCompletionHandler.handle(permissionsNeeded); - } - - private void updateChannelModelThroughPermissions( - final Context context, - final @NonNull String channelKey, - final @NonNull List permissionsNeeded - ) throws AwesomeNotificationsException { - - // For Android 8 and above, channels are updated at every load process - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) - return; - - NotificationChannelModel channelModel = - ChannelManager - .getInstance() - .getChannelByKey(context, channelKey); - - if(channelModel == null) return; - - for (String permission : permissionsNeeded) { - boolean isAllowed = isSpecifiedPermissionGloballyAllowed(context, stringUtils.getEnumFromString(NotificationPermission.class, permission)); - NotificationPermission permissionEnum = stringUtils.getEnumFromString(NotificationPermission.class, permission); - setChannelPropertyThroughPermission(channelModel, permissionEnum, isAllowed); - } - - try { - ChannelManager - .getInstance() - .saveChannel(context, channelModel, false,false); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } - - private void setChannelPropertyThroughPermission(NotificationChannelModel channelModel, NotificationPermission permission, boolean allowed){ - switch (permission) { - - case Alert: - if (allowed) { - if (channelModel.importance.ordinal() < NotificationImportance.High.ordinal()) - channelModel.importance = NotificationImportance.High; - } - else { - if (channelModel.importance.ordinal() >= NotificationImportance.High.ordinal()) - channelModel.importance = NotificationImportance.Default; - } - break; - - case Sound: - channelModel.playSound = allowed; - break; - - case Badge: - channelModel.channelShowBadge = allowed; - break; - - case Vibration: - channelModel.enableVibration = allowed; - break; - - case Light: - channelModel.enableLights = allowed; - break; - - case CriticalAlert: - channelModel.criticalAlerts = allowed; - break; - - case OverrideDnD: - case Provisional: - case PreciseAlarms: - case FullScreenIntent: - case Car: - break; - } - } - - private String getManifestPermissionCode(@Nullable NotificationPermission permission){ - - if (permission == null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU /*Android 10*/) - return Manifest.permission.POST_NOTIFICATIONS; - return null; - } - - switch (permission){ - - case FullScreenIntent: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q /*Android 10*/) - return Manifest.permission.USE_FULL_SCREEN_INTENT; - return null; - - case PreciseAlarms: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S /*Android 12*/) - return Manifest.permission.SCHEDULE_EXACT_ALARM; - return null; - - case OverrideDnD: - // For permission testing purposes only - // return Manifest.permission.READ_EXTERNAL_STORAGE; - // Does not call any Android dialog until version 12 - // return Manifest.permission.ACCESS_NOTIFICATION_POLICY; - - case Badge: - case Alert: - case Sound: - case CriticalAlert: - case Provisional: - case Car: - default: - return null; - } - } - - public void showNotificationConfigPage(final Context context, final PermissionCompletionHandler permissionCompletionHandler){ - if (gotoAndroidAppNotificationPage(context)) - activityQueue.add(new ActivityCompletionHandler() { - @Override - public void handle() { - permissionCompletionHandler.handle(new ArrayList()); - } - }); - else permissionCompletionHandler.handle(new ArrayList()); - } - - public void showChannelConfigPage(final Context context, final String channelKey, final PermissionCompletionHandler permissionCompletionHandler){ - if (gotoAndroidChannelPage(context, channelKey)) - activityQueue.add(new ActivityCompletionHandler() { - @Override - public void handle() { - permissionCompletionHandler.handle(new ArrayList()); - } - }); - else permissionCompletionHandler.handle(new ArrayList()); - } - - public void showPreciseAlarmPage(final Context context, final PermissionCompletionHandler permissionCompletionHandler){ - if (gotoPreciseAlarmPage(context)) - activityQueue.add(new ActivityCompletionHandler() { - @Override - public void handle() { - permissionCompletionHandler.handle(new ArrayList()); - } - }); - else permissionCompletionHandler.handle(new ArrayList()); - } - - public void showDnDGlobalOverridingPage(final Context context, final PermissionCompletionHandler permissionCompletionHandler){ - if (gotoControlsDnDPage(context)) - activityQueue.add(new ActivityCompletionHandler() { - @Override - public void handle() { - permissionCompletionHandler.handle(new ArrayList()); - } - }); - else permissionCompletionHandler.handle(new ArrayList()); - } - - private boolean gotoAndroidGlobalNotificationsPage(final Context context){ - final Intent intent = new Intent(); - - // TODO missing action link to global notifications page - intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.parse("package:" + AwesomeNotifications.getPackageName(context))); - - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return startVerifiedActivity(context, intent); - } - - private boolean gotoAndroidAppNotificationPage(final Context context){ - final Intent intent = new Intent(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) { - - intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, AwesomeNotifications.getPackageName(context)); - - } else { - - intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); - intent.putExtra("app_package", AwesomeNotifications.getPackageName(context)); - intent.putExtra("app_uid", context.getApplicationInfo().uid); - - } - - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - return startVerifiedActivity(context, intent); - } - - private boolean gotoAndroidChannelPage( - @NonNull final Context context, - @NonNull final String channelKey - ){ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = - ChannelManager - .getInstance() - .getAndroidChannel(context, channelKey); - - Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) - .putExtra(Settings.EXTRA_APP_PACKAGE, AwesomeNotifications.getPackageName(context)); - - if(channel != null) - intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId()); - else if (!stringUtils.isNullOrEmpty(channelKey)) - intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelKey); - - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if(startVerifiedActivity(context, intent)) - return true; - } - return gotoAndroidAppNotificationPage(context); - } - - private boolean gotoPreciseAlarmPage( - @NonNull final Context context - ){ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S /*Android 12*/) { - final Intent intent = new Intent(); - - intent.setAction(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, AwesomeNotifications.getPackageName(context)); - intent.setData(Uri.parse("package:" + AwesomeNotifications.getPackageName(context))); - - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if(startVerifiedActivity(context, intent)) - return true; - } - return gotoAndroidAppNotificationPage(context); - } - - private boolean gotoControlsDnDPage( - @NonNull final Context context - ){ - final Intent intent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - intent = new Intent( - Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if(startVerifiedActivity(context, intent)) - return true; - } - return gotoAndroidAppNotificationPage(context); - } - - public boolean handlePermissionResult( - final int requestCode, - final String[] permissions, - final int[] grantResults - ) { - if(requestCode != REQUEST_CODE) - return false; - - fireActivityCompletionHandle(); - return true; - } - - public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { - if(requestCode != REQUEST_CODE) - return false; - - fireActivityCompletionHandle(); - return false; - } - - private void fireActivityCompletionHandle(){ - if(activityQueue.isEmpty()) - return; - - if(AwesomeNotifications.debug) - Logger.d(TAG, "New permissions request found waiting for user response"); - - int retries = 3; - ActivityCompletionHandler completionHandler; - do{ - completionHandler = null; - try { - if(!activityQueue.isEmpty()) - completionHandler = activityQueue.take(); - } catch (InterruptedException e) { - retries--; - } - - if(completionHandler != null){ - completionHandler.handle(); - retries = 3; - } - } while (retries > 0 && completionHandler != null); - } - - private boolean startVerifiedActivity(Context context, Intent intent){ - PackageManager packageManager = context.getPackageManager(); - if (intent.resolveActivity(packageManager) != null) { - context.startActivity(intent); - return true; - } else { - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.CODE_PAGE_NOT_FOUND, - "Activity permission action '"+intent.getAction()+"' not found!", - ExceptionCode.DETAILED_PAGE_NOT_FOUND+".permissionPage."+intent.getAction()); - return false; - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ScheduleManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ScheduleManager.java deleted file mode 100644 index f77a7ddf..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/ScheduleManager.java +++ /dev/null @@ -1,245 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.app.AlarmManager; -import android.content.Context; -import android.content.SharedPreferences; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class ScheduleManager { - - private static final SharedManager shared - = new SharedManager<>( - StringUtils.getInstance(), - "ScheduleManager", - NotificationModel.class, - "NotificationModel"); - - public static List listSchedules(Context context) throws AwesomeNotificationsException { - return shared.getAllObjects(context, Definitions.SHARED_SCHEDULED_NOTIFICATIONS); - } - - public static NotificationModel getScheduleById(Context context, String Id) throws AwesomeNotificationsException { - return shared.get(context, Definitions.SHARED_SCHEDULED_NOTIFICATIONS, Id); - } - - public static List listScheduledIds(Context context) { - return _getHelper(context, Definitions.SCHEDULER_HELPER_ALL, ""); - } - - public static List listScheduledIdsFromChannel(Context context, String channelKey) { - return _getHelper(context, Definitions.SCHEDULER_HELPER_CHANNEL, channelKey); - } - - public static List listScheduledIdsFromGroup(Context context, String groupKey) { - return _getHelper(context, Definitions.SCHEDULER_HELPER_GROUP, groupKey); - } - - public static Boolean removeSchedule(Context context, NotificationModel notificationModel) throws AwesomeNotificationsException { - String targetId = notificationModel.content.id.toString(); - - boolean successHelper = - _removeHelperId( - context, - targetId, - notificationModel.content.channelKey, - notificationModel.content.groupKey); - - boolean successShared = - _removeNotificationFromShared( - context, - targetId); - - return successHelper && successShared; - } - - public static Boolean saveSchedule(Context context, NotificationModel notificationModel) throws AwesomeNotificationsException { - String targetId = notificationModel.content.id.toString(); - - boolean successHelper = - _setHelperId( - context, - targetId, - notificationModel.content.channelKey, - notificationModel.content.groupKey); - - boolean successShared = - _setNotificationOnShared( - context, - targetId, - notificationModel); - - return successHelper && successShared; - } - - public static void cancelScheduleById(Context context, String id) throws AwesomeNotificationsException { - NotificationModel schedule = getScheduleById(context, id); - if(schedule != null) - removeSchedule(context, schedule); - else { - _removeNotificationFromShared( - context, - id); - } - } - - public static void cancelSchedulesByChannelKey(Context context, String channelKey) throws AwesomeNotificationsException { - List allIds = _getHelper(context, Definitions.SCHEDULER_HELPER_ALL, ""); - List channelIds = _getHelper(context, Definitions.SCHEDULER_HELPER_CHANNEL, channelKey); - - for (String targetId : channelIds) { - allIds.remove(targetId); - _removeNotificationFromShared( - context, - targetId); - } - - _updateHelper(context, Definitions.SCHEDULER_HELPER_ALL, "", allIds); - _removeHelper(context, Definitions.SCHEDULER_HELPER_CHANNEL, channelKey); - } - - public static void cancelSchedulesByGroupKey(Context context, String groupKey) throws AwesomeNotificationsException { - List allIds = _getHelper(context, Definitions.SCHEDULER_HELPER_ALL, ""); - List groupIds = _getHelper(context, Definitions.SCHEDULER_HELPER_GROUP, groupKey); - - for (String targetId : groupIds) { - allIds.remove(targetId); - _removeNotificationFromShared( - context, - targetId); - } - - _updateHelper(context, Definitions.SCHEDULER_HELPER_ALL, "", allIds); - _removeHelper(context, Definitions.SCHEDULER_HELPER_GROUP, groupKey); - } - - public static void cancelAllSchedules(Context context) throws AwesomeNotificationsException { - shared.removeAll(context); - _removeAllHelpers(context, Definitions.SCHEDULER_HELPER_ALL); - _removeAllHelpers(context, Definitions.SCHEDULER_HELPER_CHANNEL); - _removeAllHelpers(context, Definitions.SCHEDULER_HELPER_GROUP); - } - - public static AlarmManager getAlarmManager(Context context) { - return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - } - - public static boolean isPreciseAlarmGloballyAllowed(Context context){ - AlarmManager alarmManager = getAlarmManager(context); - return isPreciseAlarmGloballyAllowed(alarmManager); - } - - public static boolean isPreciseAlarmGloballyAllowed(AlarmManager alarmManager){ - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S /*Android 12*/) - return alarmManager.canScheduleExactAlarms(); - return true; - } - - public static void commitChanges(Context context) throws AwesomeNotificationsException { - shared.commit(context); - } - - private static boolean _setNotificationOnShared(Context context, String id, NotificationModel notificationModel) throws AwesomeNotificationsException { - return shared.set(context, Definitions.SHARED_SCHEDULED_NOTIFICATIONS, id, notificationModel); - } - - private static boolean _removeNotificationFromShared(Context context, String targetId) throws AwesomeNotificationsException { - return shared.remove(context, Definitions.SHARED_SCHEDULED_NOTIFICATIONS, targetId); - } - - private static boolean _setHelperId(Context context, String targetId, String channelKey, String groupKey){ - StringUtils stringUtils = StringUtils.getInstance(); - - List ids = _getHelper(context, Definitions.SCHEDULER_HELPER_ALL, ""); - ids.add(targetId); - _updateHelper(context, Definitions.SCHEDULER_HELPER_ALL, "", ids); - - if(!stringUtils.isNullOrEmpty(channelKey)){ - List channelIds = _getHelper(context, Definitions.SCHEDULER_HELPER_CHANNEL, channelKey); - channelIds.add(targetId); - _updateHelper(context, Definitions.SCHEDULER_HELPER_CHANNEL, channelKey, channelIds); - } - - if(!stringUtils.isNullOrEmpty(groupKey)){ - List groupIds = _getHelper(context, Definitions.SCHEDULER_HELPER_GROUP, groupKey); - groupIds.add(targetId); - _updateHelper(context, Definitions.SCHEDULER_HELPER_GROUP, groupKey, groupIds); - } - - return true; - } - - private static boolean _removeHelperId(Context context, String targetId, String channelKey, String groupKey){ - StringUtils stringUtils = StringUtils.getInstance(); - - List ids = _getHelper(context, Definitions.SCHEDULER_HELPER_ALL, ""); - ids.remove(targetId); - _updateHelper(context, Definitions.SCHEDULER_HELPER_ALL, "", ids); - - if(!stringUtils.isNullOrEmpty(channelKey)){ - List channelIds = _getHelper(context, Definitions.SCHEDULER_HELPER_CHANNEL, channelKey); - channelIds.remove(targetId); - _updateHelper(context, Definitions.SCHEDULER_HELPER_CHANNEL, channelKey, channelIds); - } - - if(!stringUtils.isNullOrEmpty(groupKey)){ - List groupIds = _getHelper(context, Definitions.SCHEDULER_HELPER_GROUP, groupKey); - groupIds.remove(targetId); - _updateHelper(context, Definitions.SCHEDULER_HELPER_GROUP, groupKey, groupIds); - } - - return true; - } - - private static List _getHelper(Context context, String type, String referenceKey){ - - SharedPreferences preferences = context.getSharedPreferences( - AwesomeNotifications.getPackageName(context) + Definitions.SCHEDULER_HELPER_SHARED + type, - Context.MODE_PRIVATE); - - ArrayList ids = new ArrayList(); - ids.addAll(preferences.getStringSet(referenceKey, new HashSet())); - - return ids; - } - - private static void _updateHelper(Context context, String type, String referenceKey, List ids){ - - SharedPreferences preferences = context.getSharedPreferences( - AwesomeNotifications.getPackageName(context) + Definitions.SCHEDULER_HELPER_SHARED + type, - Context.MODE_PRIVATE); - - SharedPreferences.Editor editor = preferences.edit(); - editor.putStringSet(referenceKey, new HashSet(ids)); - editor.apply(); - } - - private static void _removeHelper(Context context, String type, String referenceKey){ - - SharedPreferences preferences = context.getSharedPreferences( - AwesomeNotifications.getPackageName(context) + Definitions.SCHEDULER_HELPER_SHARED + type, - Context.MODE_PRIVATE); - - SharedPreferences.Editor editor = preferences.edit(); - editor.remove(referenceKey); - editor.apply(); - } - - private static void _removeAllHelpers(Context context, String type){ - SharedPreferences preferences = context.getSharedPreferences( - AwesomeNotifications.getPackageName(context) + Definitions.SCHEDULER_HELPER_SHARED + type, - Context.MODE_PRIVATE); - - SharedPreferences.Editor editor = preferences.edit(); - editor.clear(); - editor.apply(); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/SharedManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/SharedManager.java deleted file mode 100644 index 0283351e..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/SharedManager.java +++ /dev/null @@ -1,273 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.models.AbstractModel; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class SharedManager { - private Class clazz; - private String className; - - private static String TAG = "SharedManager"; - private static String packageName; - - private String reference; - private String hashedReference = "default"; - private StringUtils stringUtils; - - public SharedManager(StringUtils stringUtils, String fileIdentifier, Class targetClass, String className){ - this.clazz = targetClass; - this.stringUtils = stringUtils; - this.className = className; - this.reference = Definitions.SHARED_MANAGER +"."+ fileIdentifier +"."+ className; - try { - hashedReference = stringUtils.digestString(reference); - //LogUtils.d(TAG, fileIdentifier+": file initialized = "+ hashedReference); - } catch (Exception e) { - this.hashedReference = reference; - // TODO change register exception to throw exception - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - "SharedManager could not be correctly initialized: "+ e.getMessage(), - ExceptionCode.DETAILED_INITIALIZATION_FAILED+"."+reference); - } - } - - private SharedPreferences getSharedInstance(Context context) throws AwesomeNotificationsException { - - if(packageName == null) - packageName = AwesomeNotifications.getPackageName(context); - - SharedPreferences preferences = context.getSharedPreferences( - hashedReference, - Context.MODE_PRIVATE); - - if(preferences == null){ - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - "SharedPreferences.getSharedPreferences is not available", - ExceptionCode.DETAILED_SHARED_PREFERENCES+".getSharedInstance"); - } - - return preferences; - } - - private String generateSharedKey(String tag, String referenceKey){ - return tag+'_'+referenceKey; - } - - public void commit(Context context) throws AwesomeNotificationsException { - try { - - SharedPreferences shared = getSharedInstance(context); - SharedPreferences.Editor editor = shared.edit(); - - commitAsync(reference, editor); - - } catch (Exception e){ - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - ExceptionCode.DETAILED_SHARED_PREFERENCES+".commit", - e); - } - } - - @SuppressWarnings("unchecked") - public List getAllObjects(Context context, String tag) throws AwesomeNotificationsException { - List returnedList = new ArrayList<>(); - try { - SharedPreferences shared = getSharedInstance(context); - - Map tempMap = shared.getAll(); - - if(tempMap != null){ - for (Map.Entry entry : tempMap.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - - if(key.startsWith(tag) && value instanceof String){ - T object = clazz.newInstance(); - returnedList.add((T) object.fromJson((String) value)); - } - } - } - } catch (Exception e){ - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - ExceptionCode.DETAILED_SHARED_PREFERENCES+".getAllObjects", - e); - } - - return returnedList; - } - - @SuppressWarnings("unchecked") - public T get(Context context, String tag, String referenceKey) throws AwesomeNotificationsException { - - try { - SharedPreferences shared = getSharedInstance(context); - - String sharedKey = generateSharedKey(tag, referenceKey); - String json = shared.getString(sharedKey, null); - - T returnedObject = null; - if (!stringUtils.isNullOrEmpty(json)) { - T genericModel = clazz.newInstance(); - - AbstractModel parsedModel = genericModel.fromJson(json); - if(parsedModel != null){ - returnedObject = (T) parsedModel; - } - } - - return returnedObject; - - } catch (AwesomeNotificationsException awesomeException) { - throw awesomeException; - } catch (Exception exception) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - ExceptionCode.DETAILED_SHARED_PREFERENCES+".get", - exception); - } - } - - public Boolean set(Context context, String tag, String referenceKey, T data) throws AwesomeNotificationsException { - - try { - - SharedPreferences shared = getSharedInstance(context); - - String sharedKey = generateSharedKey(tag, referenceKey); - - String json = data.toJson(); - - SharedPreferences.Editor editor = shared.edit(); - - editor.putString(sharedKey, json); - editor.apply(); - - return true; - - } catch (AwesomeNotificationsException awesomeException) { - throw awesomeException; - } catch (Exception e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - ExceptionCode.DETAILED_SHARED_PREFERENCES+".set", - e); - } - } - - public Boolean remove(Context context, String tag, String referenceKey) throws AwesomeNotificationsException { - - try { - - SharedPreferences shared = getSharedInstance(context); - - String sharedKey = generateSharedKey(tag, referenceKey); - - SharedPreferences.Editor editor = shared.edit(); - editor.remove(sharedKey); - - editor.apply(); - - return true; - - } catch (AwesomeNotificationsException awesomeException) { - throw awesomeException; - } catch (Exception e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - ExceptionCode.DETAILED_SHARED_PREFERENCES+".remove", - e); - } - } - - public Boolean removeAll(Context context) throws AwesomeNotificationsException { - - try { - SharedPreferences shared = getSharedInstance(context); - SharedPreferences.Editor editor = shared.edit(); - editor.clear(); - - editor.apply(); - - return true; - - } catch (AwesomeNotificationsException awesomeException) { - throw awesomeException; - } catch (Exception e) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - ExceptionCode.DETAILED_SHARED_PREFERENCES+".removeAll", - e); - } - } - - private static void commitAsync(final String reference, final SharedPreferences.Editor editor) throws AwesomeNotificationsException { - if(!editor.commit()) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_SHARED_PREFERENCES_NOT_AVAILABLE, - "Android function editor.commit failed", - ExceptionCode.DETAILED_SHARED_PREFERENCES+".commitAsync"); - /* - new AsyncTask() { - @Override - protected Boolean doInBackground(Void... voids) { - return editor.commit(); - } - - @Override - protected void onPostExecute(Boolean value) { - if(!value){ - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.SHARED_PREFERENCES_NOT_AVAILABLE, - "Android function editor.commit failed", - ExceptionCode.DETAILED_SHARED_PREFERENCES+".commitAsync"); - } - } - }.execute();*/ - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/StatusBarManager.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/StatusBarManager.java deleted file mode 100644 index 81f2c1f3..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/managers/StatusBarManager.java +++ /dev/null @@ -1,523 +0,0 @@ -package me.carda.awesome_notifications.core.managers; - -import static android.content.Context.NOTIFICATION_SERVICE; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Build; -import android.service.notification.StatusBarNotification; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.core.app.NotificationManagerCompat; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.enumerators.NotificationLayout; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class StatusBarManager { - - private static final String TAG = "CancellationManager"; - - private final StringUtils stringUtils; - - private final SharedPreferences preferences; - public final Map> activeNotificationsGroup; - public final Map> activeNotificationsChannel; - - // ************** SINGLETON PATTERN *********************** - - private static StatusBarManager instance; - - private StatusBarManager(@NonNull final Context context, @NonNull StringUtils stringUtils){ - this.stringUtils = stringUtils; - - preferences = context.getSharedPreferences( - AwesomeNotifications.getPackageName(context) + "." + stringUtils.digestString(TAG), - Context.MODE_PRIVATE); - - activeNotificationsGroup = loadNotificationIdFromPreferences("group"); - activeNotificationsChannel = loadNotificationIdFromPreferences("channel"); - } - - public static StatusBarManager getInstance(@NonNull final Context context) { - if (instance == null) - instance = new StatusBarManager( - context, - StringUtils.getInstance()); - return instance; - } - - // ******************************************************** - - // The status bar cannot be closed from a notification action since Android 12 - // https://developer.android.com/about/versions/12/behavior-changes-all?hl=pt-br#close-system-dialogs-exceptions - // https://developer.android.com/reference/android/content/Intent?hl=pt-br#ACTION_CLOSE_SYSTEM_DIALOGS - public void closeStatusBar(Context context){ - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S /*Android 12*/) { - Intent closingIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.sendBroadcast(closingIntent); - } - } - - public void showNotificationOnStatusBar(@NonNull Context context, NotificationModel notificationModel, Notification notification) throws Exception { - - registerActiveNotification(notificationModel, notificationModel.content.id); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationManager notificationManager = getNotificationManager(context); - notificationManager.notify(notificationModel.content.id, notification); - } - else { - NotificationManagerCompat notificationManagerCompat = getAdaptedOldNotificationManager(context); - notificationManagerCompat.notify(String.valueOf(notificationModel.content.id), notificationModel.content.id, notification); - } - } - - public void dismissNotification(Context context, Integer id) throws AwesomeNotificationsException { - - String idKey = id.toString(); - int idKeyValue = Integer.parseInt(idKey); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) { - NotificationManager notificationManager = getNotificationManager(context); - - notificationManager.cancel(idKey, idKeyValue); - notificationManager.cancel(idKeyValue); - - // Dismiss the last notification group summary notification - String groupKey = getIndexActiveNotificationGroup(idKey); - if (!groupKey.equals("")) { - try { - dismissNotificationsByGroupKey(context, groupKey); - } catch (AwesomeNotificationsException ignored) { - } - } - } - else { - NotificationManagerCompat notificationManager = getAdaptedOldNotificationManager(context); - - notificationManager.cancel(idKey, idKeyValue); - notificationManager.cancel(idKeyValue); - } - - unregisterActiveNotification(context, id); - } - - public boolean dismissNotificationsByChannelKey(@NonNull Context context, @NonNull final String channelKey) throws AwesomeNotificationsException { - - List notificationIds = unregisterActiveChannelKey(channelKey); - - if (notificationIds != null) - for (String idKey : notificationIds) - dismissNotification(context, Integer.parseInt(idKey)); - - return notificationIds != null; - } - - public boolean dismissNotificationsByGroupKey(@NonNull Context context, @NonNull final String groupKey) throws AwesomeNotificationsException { - - List notificationIds = unregisterActiveGroupKey(groupKey); - - if (notificationIds != null) - for (String idKey : notificationIds) - dismissNotification(context, Integer.parseInt(idKey)); - - return notificationIds != null; - } - - public void dismissAllNotifications(@NonNull Context context) throws AwesomeNotificationsException { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) { - NotificationManagerCompat notificationManager = getAdaptedOldNotificationManager(context); - notificationManager.cancelAll(); - } - else { - NotificationManager notificationManager - = (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - - notificationManager.cancelAll(); - } - - resetRegisters(); - } - - public boolean isFirstActiveOnGroupKey(String groupKey){ - if(stringUtils.isNullOrEmpty(groupKey)) - return false; - - List activeGroupedNotifications = - activeNotificationsGroup.get(groupKey); - - return activeGroupedNotifications == null || activeGroupedNotifications.size() == 0; - } - - public boolean isFirstActiveOnChannelKey(String channelKey){ - if(stringUtils.isNullOrEmpty(channelKey)) - return false; - - List activeGroupedNotifications = - activeNotificationsChannel.get(channelKey); - - return activeGroupedNotifications == null || activeGroupedNotifications.size() == 0; - } - - @RequiresApi(Build.VERSION_CODES.O) - private NotificationManager getNotificationManager(@NonNull Context context) throws AwesomeNotificationsException { - try { - return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - } catch (Exception exception) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_MISSING_ARGUMENTS, - "Notification Service is not available", - ExceptionCode.DETAILED_REQUIRED_ARGUMENTS+".notificationService", - exception); - } - } - - private NotificationManagerCompat getAdaptedOldNotificationManager(@NonNull Context context) throws AwesomeNotificationsException { - try { - return NotificationManagerCompat.from(context); - } catch (Exception exception) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_MISSING_ARGUMENTS, - "Notification Manager is not available", - ExceptionCode.DETAILED_REQUIRED_ARGUMENTS+".notificationManager", - exception); - } - } - - private void setIndexActiveNotificationChannel(SharedPreferences.Editor editor, String idKey, String channelKey) throws AwesomeNotificationsException { - try { - editor.putString("ic:"+idKey, channelKey); - } catch (Exception exception) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_MISSING_ARGUMENTS, - "Shared preferences is not available", - ExceptionCode.DETAILED_REQUIRED_ARGUMENTS+".sharedPreferences", - exception); - } - } - - private String getIndexActiveNotificationChannel(String idKey){ - return preferences.getString("ic:"+idKey, ""); - } - - private void removeIndexActiveNotificationChannel(SharedPreferences.Editor editor, String idKey){ - editor.remove("ic:"+idKey); - } - - private void setIndexActiveNotificationGroup(SharedPreferences.Editor editor, String idKey, String groupKey){ - editor.putString("ig:"+idKey, groupKey); - } - - private String getIndexActiveNotificationGroup(String idKey){ - return preferences.getString("ig:"+idKey, ""); - } - - private void removeIndexActiveNotificationGroup(SharedPreferences.Editor editor, String idKey){ - editor.remove("ig:"+idKey); - } - - private void setIndexCollapsedLayout(SharedPreferences.Editor editor, String idKey, boolean isCollapsed){ - editor.putBoolean("cl:"+idKey, isCollapsed); - } - - private boolean isIndexCollapsedLayout(String groupKey) { - return preferences.getBoolean("cl:" + groupKey, false); - } - - private void removeIndexCollapsedLayout(SharedPreferences.Editor editor, String idKey){ - editor.remove("cl:"+idKey); - } - - private void registerActiveNotification(@NonNull NotificationModel notificationModel, int id) throws Exception { - - String idKey = String.valueOf(id); - String groupKey = !stringUtils.isNullOrEmpty(notificationModel.content.groupKey) ? notificationModel.content.groupKey : ""; - String channelKey = !stringUtils.isNullOrEmpty(notificationModel.content.channelKey) ? notificationModel.content.channelKey : ""; - - SharedPreferences.Editor editor = preferences.edit(); - - if(!channelKey.equals("")){ - registerNotificationIdOnPreferences(editor, "channel", activeNotificationsChannel, channelKey, idKey); - setIndexActiveNotificationChannel(editor, idKey, channelKey); - } - - if(!groupKey.equals("")){ - registerNotificationIdOnPreferences(editor, "group", activeNotificationsGroup, groupKey, idKey); - setIndexActiveNotificationGroup(editor, idKey, groupKey); - } - - setIndexCollapsedLayout(editor, idKey, notificationModel.content.notificationLayout != NotificationLayout.Default); - - editor.apply(); - } - - private void registerNotificationIdOnPreferences(SharedPreferences.Editor editor, String type, Map> map, String reference, String notificationId){ - List list = map.get(reference); - - if(list == null) - list = new ArrayList(); - - if(!list.contains(notificationId)) - list.add(notificationId); - - map.put(reference, list); - updateActiveMapIntoPreferences(editor, type, map); - } - - public void unregisterActiveNotification(Context context, int notificationId) throws AwesomeNotificationsException { - - SharedPreferences.Editor editor = preferences.edit(); - - String idKey = String.valueOf(notificationId); - String groupKey = getIndexActiveNotificationGroup(idKey); - if(!groupKey.equals("")){ - - List listToRemove = activeNotificationsGroup.get(groupKey); - if(listToRemove != null){ - if(listToRemove.remove(idKey)){ - if(listToRemove.isEmpty()) - activeNotificationsGroup.remove(groupKey); - else - activeNotificationsGroup.put(groupKey, listToRemove); - updateActiveMapIntoPreferences(editor, "group", activeNotificationsGroup); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - boolean isCollapsedLayout = isIndexCollapsedLayout(groupKey); - - // For collapsed layouts, where the group has 1 left notification, - // the missing summary orphan group should be removed - if(!isCollapsedLayout && listToRemove.size() == 1) - dismissNotification(context, Integer.parseInt(listToRemove.get(0))); - } - } - } - } - - String channelKey = getIndexActiveNotificationChannel(idKey); - if(!channelKey.equals("")){ - List listToRemove = activeNotificationsChannel.get(channelKey); - if(listToRemove != null){ - listToRemove.remove(idKey); - if(listToRemove.isEmpty()) - activeNotificationsChannel.remove(channelKey); - else - activeNotificationsChannel.put(channelKey, listToRemove); - updateActiveMapIntoPreferences(editor, "channel", activeNotificationsChannel); - } - } - - removeAllIndexes(editor, idKey); - editor.apply(); - } - - private void removeAllIndexes(SharedPreferences.Editor editor, String idKey){ - removeIndexActiveNotificationChannel(editor, idKey); - removeIndexActiveNotificationGroup(editor, idKey); - removeIndexCollapsedLayout(editor, idKey); - } - - private List unregisterActiveChannelKey(String channelKey){ - - if(!stringUtils.isNullOrEmpty(channelKey)){ - List removed = activeNotificationsChannel.remove(channelKey); - if(removed != null){ - - SharedPreferences.Editor editor = preferences.edit(); - - boolean hasGroup = false; - for(String idKey : removed){ - String groupKey = getIndexActiveNotificationGroup(idKey); - if(!groupKey.equals("")){ - List listToRemove = activeNotificationsGroup.get(groupKey); - if(listToRemove != null){ - hasGroup = true; - listToRemove.remove(idKey); - if(listToRemove.isEmpty()) - activeNotificationsGroup.remove(groupKey); - else - activeNotificationsGroup.put(channelKey, listToRemove); - } - } - removeAllIndexes(editor, idKey); - } - - updateActiveMapIntoPreferences(editor, "channel", activeNotificationsChannel); - if(hasGroup) - updateActiveMapIntoPreferences(editor, "group", activeNotificationsGroup); - - editor.apply(); - return removed; - } - } - - return null; - } - - public List unregisterActiveGroupKey(String groupKey){ - - if(!stringUtils.isNullOrEmpty(groupKey)){ - List removed = activeNotificationsGroup.remove(groupKey); - if(removed != null){ - - SharedPreferences.Editor editor = preferences.edit(); - - boolean hasGroup = false; - for(String idKey : removed){ - String channelKey = getIndexActiveNotificationChannel(idKey); - if(!channelKey.equals("")){ - List listToRemove = activeNotificationsChannel.get(channelKey); - if(listToRemove != null){ - hasGroup = true; - listToRemove.remove(idKey); - if(listToRemove.isEmpty()) - activeNotificationsChannel.remove(channelKey); - else - activeNotificationsChannel.put(channelKey, listToRemove); - } - } - removeAllIndexes(editor, idKey); - } - - updateActiveMapIntoPreferences(editor, "group", activeNotificationsGroup); - if(hasGroup) - updateActiveMapIntoPreferences(editor, "channel", activeNotificationsChannel); - - editor.apply(); - return removed; - } - } - - return null; - } - - private void updateActiveMapIntoPreferences(SharedPreferences.Editor editor, String type, Map> map) { - Gson gson = new Gson(); - String mapString = gson.toJson(map); - editor.putString(type, mapString); - } - - private Map> loadNotificationIdFromPreferences(String type){ - String mapString = preferences.getString(type, null); - - if (mapString == null){ - return new HashMap>(); - } - - Gson gson = new Gson(); - Type mapType = new TypeToken>>(){}.getType(); - - return gson.fromJson(mapString, mapType); - } - - private void resetRegisters(){ - preferences - .edit() - .clear() - .apply(); - - activeNotificationsGroup.clear(); - activeNotificationsChannel.clear(); - } - - @RequiresApi(api = Build.VERSION_CODES.M) - public Notification getAndroidNotificationById(Context context, int id){ - if(context != null){ - - NotificationManager manager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - StatusBarNotification[] currentActiveNotifications = manager.getActiveNotifications(); - - if(currentActiveNotifications != null){ - for (StatusBarNotification activeNotification : currentActiveNotifications) { - if(activeNotification.getId() == id){ - return activeNotification.getNotification(); - } - } - } - } - return null; - } - - @RequiresApi(api = Build.VERSION_CODES.M) - public List getAllAndroidActiveNotificationsByChannelKey(Context context, String channelKey){ - List notifications = new ArrayList<>(); - if(context != null && !stringUtils.isNullOrEmpty(channelKey)){ - - NotificationManager manager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - StatusBarNotification[] currentActiveNotifications = manager.getActiveNotifications(); - - String hashedKey = stringUtils.digestString(channelKey); - - if(currentActiveNotifications != null){ - for (StatusBarNotification activeNotification : currentActiveNotifications) { - - Notification notification = activeNotification.getNotification(); - - String notificationChannelKey = notification.extras - .getString(Definitions.NOTIFICATION_CHANNEL_KEY); - - if(notificationChannelKey != null && notificationChannelKey.equals(hashedKey)){ - notifications.add(notification); - } - } - } - } - return notifications; - } - - @RequiresApi(api = Build.VERSION_CODES.M) - public List getAllAndroidActiveNotificationsByGroupKey(Context context, String groupKey){ - List notifications = new ArrayList<>(); - if(context != null && !stringUtils.isNullOrEmpty(groupKey)){ - - NotificationManager manager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - StatusBarNotification[] currentActiveNotifications = manager.getActiveNotifications(); - - String hashedKey = stringUtils.digestString(groupKey); - - if(currentActiveNotifications != null){ - for (StatusBarNotification activeNotification : currentActiveNotifications) { - - Notification notification = activeNotification.getNotification(); - - String notificationGroupKey = notification.extras - .getString(Definitions.NOTIFICATION_GROUP_KEY); - - if(notificationGroupKey != null && notificationGroupKey.equals(hashedKey)){ - notifications.add(notification); - } - } - } - } - return notifications; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/AbstractModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/AbstractModel.java deleted file mode 100644 index c1c5df27..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/AbstractModel.java +++ /dev/null @@ -1,646 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.common.primitives.Longs; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import me.carda.awesome_notifications.core.enumerators.ActionType; -import me.carda.awesome_notifications.core.enumerators.DefaultRingtoneType; -import me.carda.awesome_notifications.core.enumerators.ForegroundServiceType; -import me.carda.awesome_notifications.core.enumerators.ForegroundStartMode; -import me.carda.awesome_notifications.core.enumerators.GroupAlertBehaviour; -import me.carda.awesome_notifications.core.enumerators.GroupSort; -import me.carda.awesome_notifications.core.enumerators.LogLevel; -import me.carda.awesome_notifications.core.enumerators.MediaSource; -import me.carda.awesome_notifications.core.enumerators.NotificationCategory; -import me.carda.awesome_notifications.core.enumerators.NotificationImportance; -import me.carda.awesome_notifications.core.enumerators.NotificationLayout; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.enumerators.NotificationPermission; -import me.carda.awesome_notifications.core.enumerators.NotificationPrivacy; -import me.carda.awesome_notifications.core.enumerators.NotificationSource; -import me.carda.awesome_notifications.core.enumerators.SafeEnum; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.utils.JsonUtils; -import me.carda.awesome_notifications.core.utils.SerializableUtils; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public abstract class AbstractModel implements Cloneable { - protected final SerializableUtils serializableUtils; - protected final StringUtils stringUtils; - - public static Map defaultValues = new HashMap<>(); - - protected AbstractModel(){ - this.serializableUtils = SerializableUtils.getInstance(); - this.stringUtils = StringUtils.getInstance(); - } - protected AbstractModel(SerializableUtils serializableUtils, StringUtils stringUtils){ - this.serializableUtils = serializableUtils; - this.stringUtils = stringUtils; - } - - public abstract AbstractModel fromMap(Map arguments); - public abstract Map toMap(); - - public abstract String toJson(); - public abstract AbstractModel fromJson(String json); - - protected String templateToJson(){ - return JsonUtils.toJson(this.toMap()); - } - - protected AbstractModel templateFromJson(String json) { - if(json == null || json.isEmpty()) return null; - Map map = JsonUtils.fromJson(json); - return this.fromMap(map); - } - - public AbstractModel getClone () { - try { - return (AbstractModel)this.clone(); - } - catch (CloneNotSupportedException ex) { - ex.printStackTrace(); - return null; - } - } - - // *********************** OUTPUT SERIALIZATION METHODS ********************************* - - public void putDataOnSerializedMap( - @NonNull String reference, - @NonNull Map mapData, - @Nullable Serializable value - ){ - if(value == null) return; - if(value instanceof SafeEnum) - putSafeEnumOnSerializedMap( - reference, - mapData, - (SafeEnum) value); - else - mapData.put(reference, value); - } - - public void putDataOnSerializedMap( - @NonNull String reference, - @NonNull Map mapData, - @Nullable AbstractModel value - ){ - if (value == null) return; - mapData.put( - reference, - value.toMap()); - } - - private void putSafeEnumOnSerializedMap( - @NonNull String reference, - @NonNull Map mapData, - @Nullable SafeEnum value - ){ - if (value == null) return; - mapData.put( - reference, - value.getSafeName()); - } - - public void putDataOnSerializedMap( - @NonNull String reference, - @NonNull Map mapData, - @Nullable Calendar value - ){ - if (value == null) return; - mapData.put( - reference, - serializableUtils.serializeCalendar(value)); - } - - public void putDataOnSerializedMap( - @NonNull String reference, - @NonNull Map mapData, - @Nullable TimeZone value - ){ - if (value == null) return; - mapData.put( - reference, - serializableUtils.serializeTimeZone(value)); - } - - public void putDataOnSerializedMap( - @NonNull String reference, - @NonNull Map mapData, - @Nullable List value - ){ - if (value == null) return; - if (value.isEmpty()) return; - - List response = new ArrayList<>(); - for (Object object : value){ - if (object instanceof AbstractModel) - response.add(((AbstractModel) object).toMap()); - if (object instanceof SafeEnum) - response.add(((SafeEnum) object).getSafeName()); - if (object instanceof Serializable) - response.add(object); - } - mapData.put( - reference, - response); - } - - public void putDataOnSerializedMap( - @NonNull String reference, - @NonNull Map mapData, - @Nullable Map value - ){ - if (value == null) return; - if (value.isEmpty()) return; - - Map serializedMap = new HashMap<>(); - for(Object objEntry : value.entrySet()) { - Map.Entry entry = (Map.Entry) objEntry; - Object innerValue = entry.getValue(); - if (innerValue != null) - if (innerValue instanceof AbstractModel) - serializedMap.put((String) entry.getKey(), ((AbstractModel)innerValue).toMap()); - else - serializedMap.put((String) entry.getKey(), innerValue); - } - - mapData.put( - reference, - serializedMap); - } - - // *********************** INPUT SERIALIZATION METHODS ********************************* - - public ActionType getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable ActionType defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return ActionType.getSafeEnum((String) value); - - return defaultValue; - } - - public DefaultRingtoneType getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable DefaultRingtoneType defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return DefaultRingtoneType.getSafeEnum((String) value); - - return defaultValue; - } - - public ForegroundServiceType getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable ForegroundServiceType defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return ForegroundServiceType.getSafeEnum((String) value); - - return defaultValue; - } - - public ForegroundStartMode getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable ForegroundStartMode defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return ForegroundStartMode.getSafeEnum((String) value); - - return defaultValue; - } - - public GroupAlertBehaviour getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable GroupAlertBehaviour defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return GroupAlertBehaviour.getSafeEnum((String) value); - - return defaultValue; - } - - public GroupSort getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable GroupSort defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return GroupSort.getSafeEnum((String) value); - - return defaultValue; - } - - public LogLevel getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable LogLevel defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return LogLevel.getSafeEnum((String) value); - - return defaultValue; - } - - public MediaSource getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable MediaSource defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return MediaSource.getSafeEnum((String) value); - - return defaultValue; - } - - public NotificationCategory getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable NotificationCategory defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return NotificationCategory.getSafeEnum((String) value); - - return defaultValue; - } - - public NotificationImportance getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable NotificationImportance defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return NotificationImportance.getSafeEnum((String) value); - - return defaultValue; - } - - public NotificationLayout getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable NotificationLayout defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return NotificationLayout.getSafeEnum((String) value); - - if(value instanceof NotificationLayout) - return (NotificationLayout) value; - - return defaultValue; - } - - public NotificationLifeCycle getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable NotificationLifeCycle defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return NotificationLifeCycle.getSafeEnum((String) value); - - if(value instanceof NotificationLifeCycle) - return (NotificationLifeCycle) value; - - return defaultValue; - } - - public NotificationPermission getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable NotificationPermission defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return NotificationPermission.getSafeEnum((String) value); - - if(value instanceof NotificationPermission) - return (NotificationPermission) value; - - return defaultValue; - } - - public NotificationPrivacy getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable NotificationPrivacy defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return NotificationPrivacy.getSafeEnum((String) value); - - if(value instanceof NotificationPrivacy) - return (NotificationPrivacy) value; - - return defaultValue; - } - - public NotificationSource getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable NotificationSource defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return NotificationSource.getSafeEnum((String) value); - - if(value instanceof NotificationSource) - return (NotificationSource) value; - - return defaultValue; - } - - public TimeZone getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable TimeZone defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - return serializableUtils.deserializeTimeZone((String) value); - } - - public Calendar getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Calendar defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return serializableUtils.deserializeCalendar((String) value); - - return defaultValue; - } - - public String getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable String defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof String) - return (String) value; - - return defaultValue; - } - - public Integer getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Integer defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof Number) - return ((Number) value).intValue(); - - return defaultValue; - } - - public Float getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Float defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof Number) - return ((Number) value).floatValue(); - - return defaultValue; - } - - public Double getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Double defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof Number) - return ((Number) value).doubleValue(); - - return defaultValue; - } - - public Long getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Long defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof Number) - return ((Number) value).longValue(); - - if(type == Long.class && value instanceof String){ - Pattern pattern = Pattern.compile("(0x|#)(\\w{2})?(\\w{6})", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher((String) value); - - // 0x000000 hexadecimal color conversion - if(matcher.find()) { - String transparency = matcher.group(2); - String textValue = (transparency == null ? "FF" : transparency) + matcher.group(3); - long finalValue = 0L; - if(!StringUtils.getInstance().isNullOrEmpty(textValue)){ - finalValue += Long.parseLong(textValue, 16); - } - return type.cast(finalValue); - } - } - - return defaultValue; - } - - public Short getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Short defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof Number) - return ((Number) value).shortValue(); - - return defaultValue; - } - - public Byte getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Byte defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof Number) - return ((Number) value).byteValue(); - - return defaultValue; - } - - public Boolean getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Boolean defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof Boolean) - return (Boolean) value; - - return defaultValue; - } - - @SuppressWarnings("unchecked") - public long[] getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable long[] defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof List) - return Longs.toArray((List)value); - - if(value instanceof long[]) - return (long[]) value; - - return defaultValue; - } - - public List getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable List defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof List) - return (List)value; - - return defaultValue; - } - - @SuppressWarnings("unchecked") - public Map getValueOrDefault( - @NonNull Map map, - @NonNull String reference, - @NonNull Class type, - @Nullable Map defaultValue - ){ - Object value = map.get(reference); - if(value == null) return defaultValue; - - if(value instanceof Map) - return (Map) value; - - return defaultValue; - } - - public abstract void validate(Context context) throws AwesomeNotificationsException; - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/DefaultsModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/DefaultsModel.java deleted file mode 100644 index 0d6f60fb..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/DefaultsModel.java +++ /dev/null @@ -1,71 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.HashMap; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; - -public class DefaultsModel extends AbstractModel { - - public String appIcon; - public String silentDataCallback = "0"; - public String reverseDartCallback = "0"; - public String backgroundHandleClass; - - public DefaultsModel(){} - - public DefaultsModel( - @Nullable String defaultAppIcon, - @Nullable Long reverseDartCallback, - @Nullable Long silentDataCallback, - @Nullable String backgroundHandleClass - ){ - this.appIcon = defaultAppIcon; - this.silentDataCallback = silentDataCallback == null ? null : silentDataCallback.toString(); - this.reverseDartCallback = reverseDartCallback == null ? null : reverseDartCallback.toString(); - this.backgroundHandleClass = backgroundHandleClass; - } - - @Override - public AbstractModel fromMap(Map arguments) { - appIcon = getValueOrDefault(arguments, Definitions.NOTIFICATION_APP_ICON, String.class, null); - silentDataCallback = getValueOrDefault(arguments, Definitions.SILENT_HANDLE, String.class, null); - reverseDartCallback = getValueOrDefault(arguments, Definitions.BACKGROUND_HANDLE, String.class, null); - backgroundHandleClass = getValueOrDefault(arguments, Definitions.NOTIFICATION_BG_HANDLE_CLASS, String.class, null); - - return this; - } - - @Override - public Map toMap() { - Map dataMap = new HashMap<>(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_APP_ICON, dataMap, appIcon); - putDataOnSerializedMap(Definitions.SILENT_HANDLE, dataMap, silentDataCallback); - putDataOnSerializedMap(Definitions.BACKGROUND_HANDLE, dataMap, reverseDartCallback); - putDataOnSerializedMap(Definitions.NOTIFICATION_BG_HANDLE_CLASS, dataMap, backgroundHandleClass); - - return dataMap; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public DefaultsModel fromJson(String json){ - return (DefaultsModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationButtonModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationButtonModel.java deleted file mode 100644 index 562c79dd..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationButtonModel.java +++ /dev/null @@ -1,123 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import java.util.HashMap; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.enumerators.ActionType; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.logs.Logger; - -public class NotificationButtonModel extends AbstractModel { - - private static final String TAG = "NotificationButtonModel"; - - public String key; - public String icon; - public String label; - public Integer color; - public Boolean enabled; - public Boolean requireInputText = false; - public Boolean autoDismissible; - public Boolean showInCompactView; - public Boolean isDangerousOption; - public ActionType actionType; - - @Override - public NotificationButtonModel fromMap(Map arguments) { - - processRetroCompatibility(arguments); - - key = getValueOrDefault(arguments, Definitions.NOTIFICATION_BUTTON_KEY, String.class, null); - icon = getValueOrDefault(arguments, Definitions.NOTIFICATION_BUTTON_ICON, String.class, null); - label = getValueOrDefault(arguments, Definitions.NOTIFICATION_BUTTON_LABEL, String.class, null); - color = getValueOrDefault(arguments, Definitions.NOTIFICATION_COLOR, Integer.class, null); - actionType = getValueOrDefault(arguments, Definitions.NOTIFICATION_ACTION_TYPE, ActionType.class, ActionType.Default); - enabled = getValueOrDefault(arguments, Definitions.NOTIFICATION_ENABLED, Boolean.class, true); - requireInputText = getValueOrDefault(arguments, Definitions.NOTIFICATION_REQUIRE_INPUT_TEXT, Boolean.class, false); - isDangerousOption = getValueOrDefault(arguments, Definitions.NOTIFICATION_IS_DANGEROUS_OPTION, Boolean.class, false); - autoDismissible = getValueOrDefault(arguments, Definitions.NOTIFICATION_AUTO_DISMISSIBLE, Boolean.class, true); - showInCompactView = getValueOrDefault(arguments, Definitions.NOTIFICATION_SHOW_IN_COMPACT_VIEW, Boolean.class, false); - - return this; - } - - // Retro-compatibility with 0.6.X - private void processRetroCompatibility(Map arguments){ - - if (arguments.containsKey("autoCancel")) { - Logger.w("AwesomeNotifications", "autoCancel is deprecated. Please use autoDismissible instead."); - autoDismissible = getValueOrDefault(arguments, "autoCancel", Boolean.class, true); - } - - if (arguments.containsKey("buttonType")){ - Logger.w("AwesomeNotifications", "buttonType is deprecated. Please use actionType instead."); - actionType = getValueOrDefault(arguments, "buttonType", ActionType.class, ActionType.Default); - } - - adaptInputFieldToRequireText(); - } - - // Retro-compatibility with 0.6.X - private void adaptInputFieldToRequireText(){ - if (actionType == ActionType.InputField) { - Logger.d("AwesomeNotifications", "InputField is deprecated. Please use requireInputText instead."); - actionType = ActionType.SilentAction; - requireInputText = true; - } - } - - @Override - public Map toMap() { - Map dataMap = new HashMap<>(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_BUTTON_KEY, dataMap, key); - putDataOnSerializedMap(Definitions.NOTIFICATION_BUTTON_KEY, dataMap, key); - putDataOnSerializedMap(Definitions.NOTIFICATION_BUTTON_ICON, dataMap, icon); - putDataOnSerializedMap(Definitions.NOTIFICATION_BUTTON_LABEL, dataMap, label); - putDataOnSerializedMap(Definitions.NOTIFICATION_COLOR, dataMap, color); - putDataOnSerializedMap(Definitions.NOTIFICATION_ACTION_TYPE, dataMap, actionType); - putDataOnSerializedMap(Definitions.NOTIFICATION_ENABLED, dataMap, enabled); - putDataOnSerializedMap(Definitions.NOTIFICATION_REQUIRE_INPUT_TEXT, dataMap, requireInputText); - putDataOnSerializedMap(Definitions.NOTIFICATION_AUTO_DISMISSIBLE, dataMap, autoDismissible); - putDataOnSerializedMap(Definitions.NOTIFICATION_SHOW_IN_COMPACT_VIEW, dataMap, showInCompactView); - putDataOnSerializedMap(Definitions.NOTIFICATION_IS_DANGEROUS_OPTION, dataMap, isDangerousOption); - - return dataMap; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationButtonModel fromJson(String json){ - return (NotificationButtonModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - if(stringUtils.isNullOrEmpty(key)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_MISSING_ARGUMENTS, - "Button key is required", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".button.actionKey"); - - if(stringUtils.isNullOrEmpty(label)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_MISSING_ARGUMENTS, - "Button label is required", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".button.label"); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationCalendarModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationCalendarModel.java deleted file mode 100644 index 141ad0da..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationCalendarModel.java +++ /dev/null @@ -1,194 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Calendar; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.utils.CronUtils; -import me.carda.awesome_notifications.core.utils.IntegerUtils; - -public class NotificationCalendarModel extends NotificationScheduleModel { - - private static final String TAG = "NotificationCalendarModel"; - - /// Field number for get and set indicating the era, e.g., AD or BC in the Julian calendar - public Integer era; - /// Field number for get and set indicating the year. - public Integer year; - /// Field number for get and set indicating the month. - public Integer month; - /// Field number for get and set indicating the day number within the current year (1-12). - public Integer day; - /// Field number for get and set indicating the hour of the day (0-23). - public Integer hour; - /// Field number for get and set indicating the minute within the hour (0-59). - public Integer minute; - /// Field number for get and set indicating the second within the minute (0-59). - public Integer second; - /// Field number for get and set indicating the millisecond within the second. - public Integer millisecond; - /// Field number for get and set indicating the day of the week. - public Integer weekday; - /// Field number for get and set indicating the count of weeks of the month. - public Integer weekOfMonth; - /// Field number for get and set indicating the weeks of the year. - public Integer weekOfYear; - - private String weekdayName; - - @Override - @SuppressWarnings("unchecked") - public NotificationCalendarModel fromMap(Map arguments) { - super.fromMap(arguments); - - era = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_ERA, Integer.class, null); - year = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_YEAR, Integer.class, null); - month = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_MONTH, Integer.class, null); - day = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_DAY, Integer.class, null); - hour = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_HOUR, Integer.class, null); - minute = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_MINUTE, Integer.class, null); - second = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_SECOND, Integer.class, null); - millisecond = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_MILLISECOND, Integer.class, null); - weekday = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_WEEKDAY, Integer.class, null); - weekOfMonth = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_WEEKOFMONTH, Integer.class, null); - weekOfYear = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_WEEKOFYEAR, Integer.class, null); - weekday = weekDayISO8601ToStandard(weekday); - - return this; - } - - @Override - public Map toMap() { - Map dataMap = super.toMap(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_ERA, dataMap, era); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_YEAR, dataMap, year); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_MONTH, dataMap, month); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_DAY, dataMap, day); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_HOUR, dataMap, hour); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_MINUTE, dataMap, minute); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_SECOND, dataMap, second); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_MILLISECOND, dataMap, millisecond); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_WEEKOFMONTH, dataMap, weekOfMonth); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_WEEKOFYEAR, dataMap, weekOfYear); - - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_WEEKDAY, dataMap, - weekDayStandardToISO8601(weekday)); - - return dataMap; - } - - static Integer weekDayISO8601ToStandard(@Nullable Integer weekdayISOValue) { - if (weekdayISOValue == null) return null; - if (weekdayISOValue <= 0) return weekdayISOValue; - if (weekdayISOValue > 7) return weekdayISOValue; - - if (weekdayISOValue == 7) return 1; - return weekdayISOValue + 1; - } - - static Integer weekDayStandardToISO8601(@Nullable Integer weekdayValue) { - if (weekdayValue == null) return null; - if (weekdayValue <= 0) return weekdayValue; - if (weekdayValue > 7) return weekdayValue; - - if (weekdayValue == 1) return 7; - return weekdayValue - 1; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationCalendarModel fromJson(String json) { - return (NotificationCalendarModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - - if ( - era == null && - year == null && - month == null && - day == null && - hour == null && - minute == null && - second == null && - millisecond == null && - weekday == null && - weekOfMonth == null && - weekOfYear == null - ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "At least one time condition is required", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationCalendar"); - - if ( - era != null && !IntegerUtils.isBetween(era, 0, Integer.MAX_VALUE) || - year != null && !IntegerUtils.isBetween(year, 0, Integer.MAX_VALUE) || - month != null && !IntegerUtils.isBetween(month, 1, 12) || - day != null && !IntegerUtils.isBetween(day, 1, 31) || - hour != null && !IntegerUtils.isBetween(hour, 0, 23) || - minute != null && !IntegerUtils.isBetween(minute, 0, 59) || - second != null && !IntegerUtils.isBetween(second, 0, 59) || - millisecond != null && !IntegerUtils.isBetween(millisecond, 0, 999) || - weekday != null && !IntegerUtils.isBetween(weekday, 1, 7) || - weekOfMonth != null && !IntegerUtils.isBetween(weekOfMonth, 1, 6) || - weekOfYear != null && !IntegerUtils.isBetween(weekOfYear, 1, 53) - ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "The time conditions are invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationCalendar"); - } - - @Override - @Nullable - public Calendar getNextValidDate( - @NonNull Calendar fixedNowDate - ) throws AwesomeNotificationsException { - - if (timeZone == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid time zone", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationCalendar.timeZone"); - - String cronExpression = - (second == null ? "*" : second.toString()) + " " + - (minute == null ? "*" : minute.toString()) + " " + - (hour == null ? "*" : hour.toString()) + " " + - (weekday != null ? "?" : (day == null ? "*" : day.toString())) + " " + - (month == null ? "*" : month.toString()) + " " + - (weekday == null ? "?" : weekday.toString()) + " " + - (year == null ? "*" : year.toString()); - - return CronUtils - .getNextCalendar( - fixedNowDate, - cronExpression, - timeZone); - } -} - diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationChannelGroupModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationChannelGroupModel.java deleted file mode 100644 index ac2b15b2..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationChannelGroupModel.java +++ /dev/null @@ -1,67 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import java.util.HashMap; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; - -public class NotificationChannelGroupModel extends AbstractModel { - - private static final String TAG = "NotificationChannelGroupModel"; - - public String channelGroupName; - public String channelGroupKey; - - @Override - public NotificationChannelGroupModel fromMap(Map arguments) { - channelGroupName = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_GROUP_NAME, String.class, null); - channelGroupKey = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_GROUP_KEY, String.class, null); - - return this; - } - - public Map toMap(){ - Map dataMap = new HashMap<>(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_GROUP_NAME, dataMap, this.channelGroupName); - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_GROUP_KEY, dataMap, this.channelGroupKey); - - return dataMap; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationChannelGroupModel fromJson(String json){ - return (NotificationChannelGroupModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - if(stringUtils.isNullOrEmpty(channelGroupName)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel group name cannot be null or empty", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channelGroup.name"); - - if(stringUtils.isNullOrEmpty(channelGroupKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel group key cannot be null or empty", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channelGroup.key"); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationChannelModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationChannelModel.java deleted file mode 100644 index 370953fe..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationChannelModel.java +++ /dev/null @@ -1,308 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import androidx.annotation.Nullable; - -import java.util.HashMap; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.enumerators.DefaultRingtoneType; -import me.carda.awesome_notifications.core.enumerators.GroupAlertBehaviour; -import me.carda.awesome_notifications.core.enumerators.GroupSort; -import me.carda.awesome_notifications.core.enumerators.MediaSource; -import me.carda.awesome_notifications.core.enumerators.NotificationImportance; -import me.carda.awesome_notifications.core.enumerators.NotificationPrivacy; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.utils.AudioUtils; -import me.carda.awesome_notifications.core.utils.BitmapUtils; -import me.carda.awesome_notifications.core.utils.BooleanUtils; -import me.carda.awesome_notifications.core.utils.CompareUtils; - -public class NotificationChannelModel extends AbstractModel { - - private static final String TAG = "NotificationChannelModel"; - - public String channelKey; - public String channelName; - public String channelDescription; - public Boolean channelShowBadge; - - public String channelGroupKey; - - public NotificationImportance importance; - - public Boolean playSound; - public String soundSource; - public DefaultRingtoneType defaultRingtoneType; - - public Boolean enableVibration; - public long[] vibrationPattern; - - public Boolean enableLights; - public Integer ledColor; - public Integer ledOnMs; - public Integer ledOffMs; - - public String groupKey; - public GroupSort groupSort; - public GroupAlertBehaviour groupAlertBehavior; - - // Note: this is set on the Android to save details about the icon that should be used when re-hydrating delayed notifications when a device has been restarted. - public Integer iconResourceId; - - public String icon; - public Long defaultColor; - - public Boolean locked; - public Boolean onlyAlertOnce; - - public Boolean criticalAlerts; - - public NotificationPrivacy defaultPrivacy; - - public void refreshIconResource(Context context){ - if(iconResourceId == null && icon != null){ - if(BitmapUtils.getInstance().getMediaSourceType(icon) == MediaSource.Resource) { - - int resourceIndex = BitmapUtils.getInstance().getDrawableResourceId(context, icon); - if (resourceIndex > 0) { - iconResourceId = resourceIndex; - } else { - iconResourceId = null; - } - } - } - } - - public boolean isChannelEnabled(){ - return importance != null && importance != NotificationImportance.None; - } - - public String getChannelHashKey(Context context, boolean fullHashObject){ - - refreshIconResource(context); - - if(fullHashObject){ - String jsonData = this.toJson(); - return stringUtils.digestString(jsonData); - } - - NotificationChannelModel channelCopied = this.clone(); - channelCopied.channelName = ""; - channelCopied.channelDescription = ""; - channelCopied.groupKey = null; - - String jsonData = channelCopied.toJson(); - return channelKey + "_" + stringUtils.digestString(jsonData); - } - - @Override - public boolean equals(@Nullable Object obj) { - if(super.equals(obj)) return true; - if(!(obj instanceof NotificationChannelModel)) return false; - NotificationChannelModel other = (NotificationChannelModel) obj; - - return - CompareUtils.assertEqualObjects(other.iconResourceId, this.iconResourceId) && - CompareUtils.assertEqualObjects(other.defaultColor, this.defaultColor) && - CompareUtils.assertEqualObjects(other.channelKey, this.channelKey) && - CompareUtils.assertEqualObjects(other.channelName, this.channelName) && - CompareUtils.assertEqualObjects(other.channelDescription, this.channelDescription) && - CompareUtils.assertEqualObjects(other.channelShowBadge, this.channelShowBadge) && - CompareUtils.assertEqualObjects(other.importance, this.importance) && - CompareUtils.assertEqualObjects(other.playSound, this.playSound) && - CompareUtils.assertEqualObjects(other.soundSource, this.soundSource) && - CompareUtils.assertEqualObjects(other.enableVibration, this.enableVibration) && - CompareUtils.assertEqualObjects(other.vibrationPattern, this.vibrationPattern) && - CompareUtils.assertEqualObjects(other.enableLights, this.enableLights) && - CompareUtils.assertEqualObjects(other.ledColor, this.ledColor) && - CompareUtils.assertEqualObjects(other.ledOnMs, this.ledOnMs) && - CompareUtils.assertEqualObjects(other.ledOffMs, this.ledOffMs) && - CompareUtils.assertEqualObjects(other.groupKey, this.groupKey) && - CompareUtils.assertEqualObjects(other.locked, this.locked) && - CompareUtils.assertEqualObjects(other.criticalAlerts, this.criticalAlerts) && - CompareUtils.assertEqualObjects(other.onlyAlertOnce, this.onlyAlertOnce) && - CompareUtils.assertEqualObjects(other.defaultPrivacy, this.defaultPrivacy) && - CompareUtils.assertEqualObjects(other.defaultRingtoneType, this.defaultRingtoneType) && - CompareUtils.assertEqualObjects(other.groupSort, this.groupSort) && - CompareUtils.assertEqualObjects(other.groupAlertBehavior, this.groupAlertBehavior); - } - - @Override - public NotificationChannelModel fromMap(Map arguments) { - - iconResourceId = getValueOrDefault(arguments, Definitions.NOTIFICATION_ICON_RESOURCE_ID, Integer.class, null); - icon = getValueOrDefault(arguments, Definitions.NOTIFICATION_ICON, String.class, null); - defaultColor = getValueOrDefault(arguments, Definitions.NOTIFICATION_DEFAULT_COLOR, Long.class, 0xFF000000L); - channelKey = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_KEY, String.class, "miscellaneous"); - channelName = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_NAME, String.class, "Notifications"); - channelDescription = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_DESCRIPTION, String.class, "Notifications"); - channelShowBadge = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_SHOW_BADGE, Boolean.class, false); - channelGroupKey = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_GROUP_KEY, String.class, null); - playSound = getValueOrDefault(arguments, Definitions.NOTIFICATION_PLAY_SOUND, Boolean.class, true); - soundSource = getValueOrDefault(arguments, Definitions.NOTIFICATION_SOUND_SOURCE, String.class, null); - criticalAlerts = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_CRITICAL_ALERTS, Boolean.class, false); - enableVibration = getValueOrDefault(arguments, Definitions.NOTIFICATION_ENABLE_VIBRATION, Boolean.class, true); - vibrationPattern = getValueOrDefault(arguments, Definitions.NOTIFICATION_VIBRATION_PATTERN, long[].class, null); - ledColor = getValueOrDefault(arguments, Definitions.NOTIFICATION_LED_COLOR, Integer.class, 0xFFFFFFFF); - enableLights = getValueOrDefault(arguments, Definitions.NOTIFICATION_ENABLE_LIGHTS, Boolean.class, true); - ledOnMs = getValueOrDefault(arguments, Definitions.NOTIFICATION_LED_ON_MS, Integer.class, 300); - ledOffMs = getValueOrDefault(arguments, Definitions.NOTIFICATION_LED_OFF_MS, Integer.class, 700); - importance = getValueOrDefault(arguments, Definitions.NOTIFICATION_IMPORTANCE, NotificationImportance.class, NotificationImportance.Default); - groupSort = getValueOrDefault(arguments, Definitions.NOTIFICATION_GROUP_SORT, GroupSort.class, GroupSort.Desc); - groupAlertBehavior = getValueOrDefault(arguments, Definitions.NOTIFICATION_GROUP_ALERT_BEHAVIOR, GroupAlertBehaviour.class, GroupAlertBehaviour.All); - defaultPrivacy = getValueOrDefault(arguments, Definitions.NOTIFICATION_DEFAULT_PRIVACY, NotificationPrivacy.class, NotificationPrivacy.Private); - defaultRingtoneType = getValueOrDefault(arguments, Definitions.NOTIFICATION_DEFAULT_RINGTONE_TYPE, DefaultRingtoneType.class, DefaultRingtoneType.Notification); - groupKey = getValueOrDefault(arguments, Definitions.NOTIFICATION_GROUP_KEY, String.class, null); - locked = getValueOrDefault(arguments, Definitions.NOTIFICATION_LOCKED, Boolean.class, false); - onlyAlertOnce = getValueOrDefault(arguments, Definitions.NOTIFICATION_ONLY_ALERT_ONCE, Boolean.class, false); - - return this; - } - - public Map toMap(){ - Map dataMap = new HashMap<>(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_ICON_RESOURCE_ID, dataMap, this.iconResourceId); - putDataOnSerializedMap(Definitions.NOTIFICATION_ICON, dataMap, this.icon); - putDataOnSerializedMap(Definitions.NOTIFICATION_DEFAULT_COLOR, dataMap, this.defaultColor); - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_KEY, dataMap, this.channelKey); - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_NAME, dataMap, this.channelName); - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_DESCRIPTION, dataMap, this.channelDescription); - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_SHOW_BADGE, dataMap, this.channelShowBadge); - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_GROUP_KEY, dataMap, this.channelGroupKey); - putDataOnSerializedMap(Definitions.NOTIFICATION_PLAY_SOUND, dataMap, this.playSound); - putDataOnSerializedMap(Definitions.NOTIFICATION_SOUND_SOURCE, dataMap, this.soundSource); - putDataOnSerializedMap(Definitions.NOTIFICATION_ENABLE_VIBRATION, dataMap, this.enableVibration); - putDataOnSerializedMap(Definitions.NOTIFICATION_VIBRATION_PATTERN, dataMap, this.vibrationPattern); - putDataOnSerializedMap(Definitions.NOTIFICATION_ENABLE_LIGHTS, dataMap, this.enableLights); - putDataOnSerializedMap(Definitions.NOTIFICATION_LED_COLOR, dataMap, this.ledColor); - putDataOnSerializedMap(Definitions.NOTIFICATION_LED_ON_MS, dataMap, this.ledOnMs); - putDataOnSerializedMap(Definitions.NOTIFICATION_LED_OFF_MS, dataMap, this.ledOffMs); - putDataOnSerializedMap(Definitions.NOTIFICATION_GROUP_KEY, dataMap, this.groupKey); - putDataOnSerializedMap(Definitions.NOTIFICATION_GROUP_SORT, dataMap, this.groupSort); - putDataOnSerializedMap(Definitions.NOTIFICATION_IMPORTANCE, dataMap, this.importance); - putDataOnSerializedMap(Definitions.NOTIFICATION_GROUP_ALERT_BEHAVIOR, dataMap, this.groupAlertBehavior); - putDataOnSerializedMap(Definitions.NOTIFICATION_DEFAULT_PRIVACY, dataMap, this.defaultPrivacy); - putDataOnSerializedMap(Definitions.NOTIFICATION_DEFAULT_RINGTONE_TYPE, dataMap, this.defaultRingtoneType); - putDataOnSerializedMap(Definitions.NOTIFICATION_LOCKED, dataMap, this.locked); - putDataOnSerializedMap(Definitions.NOTIFICATION_ONLY_ALERT_ONCE, dataMap, this.onlyAlertOnce); - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_CRITICAL_ALERTS, dataMap, this.criticalAlerts); - - return dataMap; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationChannelModel fromJson(String json){ - return (NotificationChannelModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - - if(icon != null) - if(BitmapUtils.getInstance().getMediaSourceType(icon) != MediaSource.Resource) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Icon is not a Resource media type", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationContent"); - - if(stringUtils.isNullOrEmpty(channelKey)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel key cannot be null or empty", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.key"); - - if(stringUtils.isNullOrEmpty(channelName)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel name cannot be null or empty", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.name"); - - if(stringUtils.isNullOrEmpty(channelDescription)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel description cannot be null or empty", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.description"); - - if(playSound == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Play sound selector cannot be null or empty", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.playSound"); - - if (ledColor != null && (ledOnMs == null || ledOffMs == null)) { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Standard led on and off times cannot be null or empty", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.led"); - } - - if(BooleanUtils.getInstance().getValue(playSound) && !stringUtils.isNullOrEmpty(soundSource)) - if(!AudioUtils.getInstance().isValidAudio(context, soundSource)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Audio media is not valid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.sound"); - } - - public NotificationChannelModel clone() { - NotificationChannelModel cloned = new NotificationChannelModel(); - - cloned.iconResourceId = this.iconResourceId; - cloned.defaultColor = this.defaultColor; - cloned.channelKey = this.channelKey; - cloned.channelName = this.channelName; - cloned.channelDescription = this.channelDescription; - cloned.channelShowBadge = this.channelShowBadge; - cloned.importance = this.importance; - cloned.playSound = this.playSound; - cloned.soundSource = this.soundSource; - cloned.enableVibration = this.enableVibration; - cloned.vibrationPattern = this.vibrationPattern; - cloned.enableLights = this.enableLights; - cloned.ledColor = this.ledColor; - cloned.ledOnMs = this.ledOnMs; - cloned.ledOffMs = this.ledOffMs; - cloned.groupKey = this.groupKey; - cloned.locked = this.locked; - cloned.onlyAlertOnce = this.onlyAlertOnce; - cloned.defaultPrivacy = this.defaultPrivacy; - cloned.defaultRingtoneType = this.defaultRingtoneType; - cloned.groupSort = this.groupSort; - cloned.groupAlertBehavior = this.groupAlertBehavior; - cloned.criticalAlerts = this.criticalAlerts; - - return cloned; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationContentModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationContentModel.java deleted file mode 100644 index 78950160..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationContentModel.java +++ /dev/null @@ -1,333 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.enumerators.ActionType; -import me.carda.awesome_notifications.core.enumerators.MediaSource; -import me.carda.awesome_notifications.core.enumerators.NotificationCategory; -import me.carda.awesome_notifications.core.enumerators.NotificationLayout; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.enumerators.NotificationPrivacy; -import me.carda.awesome_notifications.core.enumerators.NotificationSource; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.managers.ChannelManager; -import me.carda.awesome_notifications.core.utils.BitmapUtils; -import me.carda.awesome_notifications.core.utils.CalendarUtils; -import me.carda.awesome_notifications.core.utils.ListUtils; - -@SuppressWarnings("unchecked") -public class NotificationContentModel extends AbstractModel { - - private static final String TAG = "NotificationContentModel"; - - public boolean isRefreshNotification = false; - public boolean isRandomId = false; - - public Integer id; - public String channelKey; - public String title; - public String body; - public String summary; - public Boolean showWhen; - public List messages; - public Map payload; - public String groupKey; - public String customSound; - public Boolean playSound; - public String icon; - public String largeIcon; - public Boolean locked; - public String bigPicture; - public Boolean wakeUpScreen; - public Boolean fullScreenIntent; - public Boolean hideLargeIconOnExpand; - public Boolean autoDismissible; - public Boolean displayOnForeground; - public Boolean displayOnBackground; - public Integer color; - public Integer backgroundColor; - public Integer progress; - public Integer badge; - public String ticker; - - public Boolean roundedLargeIcon; - public Boolean roundedBigPicture; - - public ActionType actionType; - - public NotificationPrivacy privacy; - public String privateMessage; - - public NotificationLayout notificationLayout; - - public NotificationSource createdSource; - public NotificationLifeCycle createdLifeCycle; - public Calendar createdDate; - - public NotificationLifeCycle displayedLifeCycle; - public Calendar displayedDate; - - public NotificationCategory category; - - public boolean registerCreatedEvent(NotificationLifeCycle lifeCycle, NotificationSource createdSource){ - - // Creation register can only happen once - if(this.createdDate == null){ - - this.createdDate = CalendarUtils.getInstance().getCurrentCalendar(); - this.createdLifeCycle = lifeCycle; - this.createdSource = createdSource; - - return true; - } - return false; - } - - public boolean registerDisplayedEvent(NotificationLifeCycle lifeCycle){ - this.displayedDate = CalendarUtils.getInstance().getCurrentCalendar(); - this.displayedLifeCycle = lifeCycle; - return true; - } - - @Override - public NotificationContentModel fromMap(Map arguments) { - if(arguments == null || arguments.isEmpty()) - return null; - - processRetroCompatibility(arguments); - - id = getValueOrDefault(arguments, Definitions.NOTIFICATION_ID, Integer.class, 0); - actionType = getValueOrDefault(arguments, Definitions.NOTIFICATION_ACTION_TYPE, ActionType.class, ActionType.Default); - createdDate = getValueOrDefault(arguments, Definitions.NOTIFICATION_CREATED_DATE, Calendar.class, null); - displayedDate = getValueOrDefault(arguments, Definitions.NOTIFICATION_DISPLAYED_DATE, Calendar.class, null); - createdLifeCycle = getValueOrDefault(arguments, Definitions.NOTIFICATION_CREATED_LIFECYCLE, NotificationLifeCycle.class, null); - displayedLifeCycle = getValueOrDefault(arguments, Definitions.NOTIFICATION_DISPLAYED_LIFECYCLE, NotificationLifeCycle.class, null); - createdSource = getValueOrDefault(arguments, Definitions.NOTIFICATION_CREATED_SOURCE, NotificationSource.class, NotificationSource.Local); - channelKey = getValueOrDefault(arguments, Definitions.NOTIFICATION_CHANNEL_KEY, String.class, "miscellaneous"); - color = getValueOrDefault(arguments, Definitions.NOTIFICATION_COLOR, Integer.class, null); - backgroundColor = getValueOrDefault(arguments, Definitions.NOTIFICATION_BACKGROUND_COLOR, Integer.class, null); - title = getValueOrDefault(arguments, Definitions.NOTIFICATION_TITLE, String.class, null); - body = getValueOrDefault(arguments, Definitions.NOTIFICATION_BODY, String.class, null); - summary = getValueOrDefault(arguments, Definitions.NOTIFICATION_SUMMARY, String.class, null); - playSound = getValueOrDefault(arguments, Definitions.NOTIFICATION_PLAY_SOUND, Boolean.class, true); - customSound = getValueOrDefault(arguments, Definitions.NOTIFICATION_CUSTOM_SOUND, String.class, null); - wakeUpScreen = getValueOrDefault(arguments, Definitions.NOTIFICATION_WAKE_UP_SCREEN, Boolean.class, false); - fullScreenIntent = getValueOrDefault(arguments, Definitions.NOTIFICATION_FULL_SCREEN_INTENT, Boolean.class, false); - showWhen = getValueOrDefault(arguments, Definitions.NOTIFICATION_SHOW_WHEN, Boolean.class, true); - locked = getValueOrDefault(arguments, Definitions.NOTIFICATION_LOCKED, Boolean.class, false); - displayOnForeground = getValueOrDefault(arguments, Definitions.NOTIFICATION_DISPLAY_ON_FOREGROUND, Boolean.class, true); - displayOnBackground = getValueOrDefault(arguments, Definitions.NOTIFICATION_DISPLAY_ON_BACKGROUND, Boolean.class, true); - hideLargeIconOnExpand = getValueOrDefault(arguments, Definitions.NOTIFICATION_HIDE_LARGE_ICON_ON_EXPAND, Boolean.class, false); - notificationLayout = getValueOrDefault(arguments, Definitions.NOTIFICATION_LAYOUT, NotificationLayout.class, NotificationLayout.Default); - privacy = getValueOrDefault(arguments, Definitions.NOTIFICATION_PRIVACY, NotificationPrivacy.class, NotificationPrivacy.Private); - category = getValueOrDefault(arguments, Definitions.NOTIFICATION_CATEGORY, NotificationCategory.class, null); - privateMessage = getValueOrDefault(arguments, Definitions.NOTIFICATION_PRIVATE_MESSAGE, String.class, null); - icon = getValueOrDefault(arguments, Definitions.NOTIFICATION_ICON, String.class, null); - largeIcon = getValueOrDefault(arguments, Definitions.NOTIFICATION_LARGE_ICON, String.class, null); - bigPicture = getValueOrDefault(arguments, Definitions.NOTIFICATION_BIG_PICTURE, String.class, null); - payload = getValueOrDefault(arguments, Definitions.NOTIFICATION_PAYLOAD, Map.class, null); - autoDismissible = getValueOrDefault(arguments, Definitions.NOTIFICATION_AUTO_DISMISSIBLE, Boolean.class, true); - progress = getValueOrDefault(arguments, Definitions.NOTIFICATION_PROGRESS, Integer.class, null); - badge = getValueOrDefault(arguments, Definitions.NOTIFICATION_BADGE, Integer.class, null); - groupKey = getValueOrDefault(arguments, Definitions.NOTIFICATION_GROUP_KEY, String.class, null); - ticker = getValueOrDefault(arguments, Definitions.NOTIFICATION_TICKER, String.class, null); - roundedLargeIcon = getValueOrDefault(arguments, Definitions.NOTIFICATION_ROUNDED_LARGE_ICON, Boolean.class, false); - roundedBigPicture = getValueOrDefault(arguments, Definitions.NOTIFICATION_ROUNDED_BIG_PICTURE, Boolean.class, false); - - messages = mapToMessages(getValueOrDefault(arguments, Definitions.NOTIFICATION_MESSAGES, List.class, null)); - - return this; - } - - // Retro-compatibility with 0.6.X - public void processRetroCompatibility(Map arguments){ - - if (arguments.containsKey("autoCancel")) { - Logger.i("AwesomeNotifications", "autoCancel is deprecated. Please use autoDismissible instead."); - autoDismissible = getValueOrDefault(arguments, "autoCancel", Boolean.class, true); - } - } - - @Override - public Map toMap(){ - Map returnedObject = new HashMap<>(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_ID, returnedObject, this.id); - putDataOnSerializedMap(Definitions.NOTIFICATION_RANDOM_ID, returnedObject, this.isRandomId); - putDataOnSerializedMap(Definitions.NOTIFICATION_TITLE, returnedObject, this.title); - putDataOnSerializedMap(Definitions.NOTIFICATION_BODY, returnedObject, this.body); - putDataOnSerializedMap(Definitions.NOTIFICATION_SUMMARY, returnedObject, this.summary); - putDataOnSerializedMap(Definitions.NOTIFICATION_SHOW_WHEN, returnedObject, this.showWhen); - putDataOnSerializedMap(Definitions.NOTIFICATION_WAKE_UP_SCREEN, returnedObject, this.wakeUpScreen); - putDataOnSerializedMap(Definitions.NOTIFICATION_FULL_SCREEN_INTENT, returnedObject, this.fullScreenIntent); - putDataOnSerializedMap(Definitions.NOTIFICATION_ACTION_TYPE, returnedObject, this.actionType); - putDataOnSerializedMap(Definitions.NOTIFICATION_LOCKED, returnedObject, this.locked); - putDataOnSerializedMap(Definitions.NOTIFICATION_PLAY_SOUND, returnedObject, this.playSound); - putDataOnSerializedMap(Definitions.NOTIFICATION_CUSTOM_SOUND, returnedObject, this.customSound); - putDataOnSerializedMap(Definitions.NOTIFICATION_TICKER, returnedObject, this.ticker); - putDataOnSerializedMap(Definitions.NOTIFICATION_PAYLOAD, returnedObject, this.payload); - putDataOnSerializedMap(Definitions.NOTIFICATION_AUTO_DISMISSIBLE, returnedObject, this.autoDismissible); - putDataOnSerializedMap(Definitions.NOTIFICATION_LAYOUT, returnedObject, this.notificationLayout); - putDataOnSerializedMap(Definitions.NOTIFICATION_CREATED_SOURCE, returnedObject, this.createdSource); - putDataOnSerializedMap(Definitions.NOTIFICATION_CREATED_LIFECYCLE, returnedObject, this.createdLifeCycle); - putDataOnSerializedMap(Definitions.NOTIFICATION_DISPLAYED_LIFECYCLE, returnedObject, this.displayedLifeCycle); - putDataOnSerializedMap(Definitions.NOTIFICATION_DISPLAYED_DATE, returnedObject, this.displayedDate); - putDataOnSerializedMap(Definitions.NOTIFICATION_CREATED_DATE, returnedObject,this.createdDate); - putDataOnSerializedMap(Definitions.NOTIFICATION_CHANNEL_KEY, returnedObject, this.channelKey); - putDataOnSerializedMap(Definitions.NOTIFICATION_CATEGORY, returnedObject, this.category); - putDataOnSerializedMap(Definitions.NOTIFICATION_AUTO_DISMISSIBLE, returnedObject, this.autoDismissible); - putDataOnSerializedMap(Definitions.NOTIFICATION_DISPLAY_ON_FOREGROUND, returnedObject, this.displayOnForeground); - putDataOnSerializedMap(Definitions.NOTIFICATION_DISPLAY_ON_BACKGROUND, returnedObject, this.displayOnBackground); - putDataOnSerializedMap(Definitions.NOTIFICATION_COLOR, returnedObject, this.color); - putDataOnSerializedMap(Definitions.NOTIFICATION_BACKGROUND_COLOR, returnedObject, this.backgroundColor); - putDataOnSerializedMap(Definitions.NOTIFICATION_ICON, returnedObject, this.icon); - putDataOnSerializedMap(Definitions.NOTIFICATION_LARGE_ICON, returnedObject, this.largeIcon); - putDataOnSerializedMap(Definitions.NOTIFICATION_BIG_PICTURE, returnedObject, this.bigPicture); - putDataOnSerializedMap(Definitions.NOTIFICATION_PROGRESS, returnedObject, this.progress); - putDataOnSerializedMap(Definitions.NOTIFICATION_BADGE, returnedObject, this.badge); - putDataOnSerializedMap(Definitions.NOTIFICATION_GROUP_KEY, returnedObject, this.groupKey); - putDataOnSerializedMap(Definitions.NOTIFICATION_PRIVACY, returnedObject, this.privacy); - putDataOnSerializedMap(Definitions.NOTIFICATION_PRIVATE_MESSAGE, returnedObject, this.privateMessage); - putDataOnSerializedMap(Definitions.NOTIFICATION_ROUNDED_LARGE_ICON, returnedObject, this.roundedLargeIcon); - putDataOnSerializedMap(Definitions.NOTIFICATION_ROUNDED_BIG_PICTURE, returnedObject, this.roundedBigPicture); - - putDataOnSerializedMap(Definitions.NOTIFICATION_MESSAGES, returnedObject, this.messages); - - return returnedObject; - } - - public static List messagesToMap(List messages){ - List returnedMessages = new ArrayList<>(); - if(!ListUtils.isNullOrEmpty(messages)){ - for (NotificationMessageModel messageModel : messages) { - returnedMessages.add(messageModel.toMap()); - } - } - return returnedMessages; - } - - public static List mapToMessages(List messagesData){ - List messages = new ArrayList<>(); - if(!ListUtils.isNullOrEmpty(messagesData)) - for(Map messageData : messagesData){ - NotificationMessageModel messageModel = - new NotificationMessageModel().fromMap(messageData); - messages.add(messageModel); - } - return messages; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationContentModel fromJson(String json){ - return (NotificationContentModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - if(id == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_MISSING_ARGUMENTS, - "Notification id is required", - ExceptionCode.DETAILED_REQUIRED_ARGUMENTS+".notificationContent.id"); - - if(ChannelManager - .getInstance() - .getChannelByKey(context, channelKey) == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Notification channel '"+channelKey+"' does not exist.", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationContent."+channelKey); - - validateIcon(context); - - if(notificationLayout == null) { - notificationLayout = NotificationLayout.Default; - } else { - if(notificationLayout == NotificationLayout.BigPicture) { - validateRequiredImages(context); - } - } - - validateBigPicture(context); - validateLargeIcon(context); - - } - - private void validateIcon(Context context) throws AwesomeNotificationsException { - - if(!stringUtils.isNullOrEmpty(icon)){ - if( - BitmapUtils.getInstance().getMediaSourceType(icon) != MediaSource.Resource || - !BitmapUtils.getInstance().isValidBitmap(context, icon) - ){ - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Small icon ('"+icon+"') must be a valid media native resource type.", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".smallIcon"); - } - } - } - - private void validateRequiredImages(Context context) throws AwesomeNotificationsException { - if(stringUtils.isNullOrEmpty(largeIcon) && stringUtils.isNullOrEmpty(bigPicture)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_MISSING_ARGUMENTS, - "bigPicture or largeIcon is required", - ExceptionCode.DETAILED_REQUIRED_ARGUMENTS+".image.required"); - } - - private void validateBigPicture(Context context) throws AwesomeNotificationsException { - if( - !stringUtils.isNullOrEmpty(bigPicture) && - !BitmapUtils.getInstance().isValidBitmap(context, bigPicture) - ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "bigPicture is invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".invalid.bigPicture"); - } - - private void validateLargeIcon(Context context) throws AwesomeNotificationsException { - if( - !stringUtils.isNullOrEmpty(largeIcon) && - !BitmapUtils.getInstance().isValidBitmap(context, largeIcon) - ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "largeIcon is invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".invalid.largeIcon"); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationCrontabModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationCrontabModel.java deleted file mode 100644 index b4b966c7..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationCrontabModel.java +++ /dev/null @@ -1,183 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import java.util.Calendar; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.externalLibs.CronExpression; -import me.carda.awesome_notifications.core.utils.CalendarUtils; -import me.carda.awesome_notifications.core.utils.CronUtils; -import me.carda.awesome_notifications.core.utils.ListUtils; - -public class NotificationCrontabModel extends NotificationScheduleModel { - - private static final String TAG = "NotificationCrontabModel"; - - public Calendar initialDateTime; - public Calendar expirationDateTime; - public String crontabExpression; - public List preciseSchedules; - - @Override - @SuppressWarnings("unchecked") - public NotificationCrontabModel fromMap(Map arguments) { - super.fromMap(arguments); - - initialDateTime = getValueOrDefault(arguments, Definitions.NOTIFICATION_INITIAL_DATE_TIME, Calendar.class, null); - expirationDateTime = getValueOrDefault(arguments, Definitions.NOTIFICATION_EXPIRATION_DATE_TIME, Calendar.class, null); - crontabExpression = getValueOrDefault(arguments, Definitions.NOTIFICATION_CRONTAB_EXPRESSION, String.class, null); - preciseSchedules = getValueOrDefault(arguments, Definitions.NOTIFICATION_PRECISE_SCHEDULES, List.class, null); - - return this; - } - - @Override - public Map toMap(){ - Map dataMap = super.toMap(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_INITIAL_DATE_TIME, dataMap, initialDateTime); - putDataOnSerializedMap(Definitions.NOTIFICATION_EXPIRATION_DATE_TIME, dataMap, expirationDateTime); - putDataOnSerializedMap(Definitions.NOTIFICATION_CRONTAB_EXPRESSION, dataMap, crontabExpression); - putDataOnSerializedMap(Definitions.NOTIFICATION_PRECISE_SCHEDULES, dataMap, preciseSchedules); - - return dataMap; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationCalendarModel fromJson(String json){ - return (NotificationCalendarModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - - if( - stringUtils.isNullOrEmpty(crontabExpression) && - ListUtils.isNullOrEmpty(preciseSchedules) - ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "At least one schedule parameter is required", - ExceptionCode.DETAILED_REQUIRED_ARGUMENTS); - - try { - if(initialDateTime != null && expirationDateTime != null){ - if( - initialDateTime.equals(expirationDateTime) || - initialDateTime.after(expirationDateTime) - ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Expiration date must be greater than initial date", - ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".periodOrder"); - } - - if(crontabExpression != null && !CronExpression.isValidExpression(crontabExpression)) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Schedule cron expression is invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".crontabExpression"); - - } catch (AwesomeNotificationsException e){ - throw e; - } catch (Exception e){ - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Schedule time is invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".scheduleTime"); - } - } - - @Override - public Calendar getNextValidDate(@Nullable Calendar fixedNowDate) throws AwesomeNotificationsException { - - try { - CalendarUtils calendarUtils = CalendarUtils.getInstance(); - - if (fixedNowDate == null) - fixedNowDate = calendarUtils.getCurrentCalendar(timeZone); - - if ( - expirationDateTime != null && - fixedNowDate.after(expirationDateTime) || - fixedNowDate.equals(expirationDateTime) - ) return null; - - Calendar preciseCalendar = null, crontabCalendar = null; - - if (!ListUtils.isNullOrEmpty(preciseSchedules)){ - for (Calendar preciseSchedule : preciseSchedules) { - if( - initialDateTime != null && - preciseSchedule.before(preciseSchedule) - ) - continue; - - if(preciseSchedule.before(fixedNowDate)) - continue; - - if( - preciseCalendar == null || - preciseCalendar.after(preciseSchedule) - ) - preciseCalendar = preciseSchedule; - } - } - - if(!stringUtils.isNullOrEmpty(crontabExpression)) - crontabCalendar = CronUtils - .getNextCalendar( - initialDateTime != null ? - initialDateTime : fixedNowDate, - crontabExpression, - timeZone); - - if (preciseCalendar == null) - return crontabCalendar; - - if (crontabCalendar == null) - return preciseCalendar; - - if (preciseCalendar.before(crontabCalendar)) - return preciseCalendar; - - return crontabCalendar; - - } catch (AwesomeNotificationsException e){ - throw e; - } catch (Exception e){ - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Schedule time is invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationCrontab"); - } - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationIntervalModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationIntervalModel.java deleted file mode 100644 index e3282902..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationIntervalModel.java +++ /dev/null @@ -1,116 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import androidx.annotation.Nullable; - -import java.util.Calendar; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.utils.BooleanUtils; -import me.carda.awesome_notifications.core.utils.CalendarUtils; - -public class NotificationIntervalModel extends NotificationScheduleModel { - - private static final String TAG = "NotificationIntervalModel"; - - public Integer interval; - - @Override - @SuppressWarnings("unchecked") - public NotificationIntervalModel fromMap(Map arguments) { - super.fromMap(arguments); - - interval = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_INTERVAL, Integer.class, null); - - return this; - } - - @Override - public Map toMap(){ - Map dataMap = super.toMap(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_INTERVAL, dataMap, interval); - - return dataMap; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationIntervalModel fromJson(String json){ - return (NotificationIntervalModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - - if(interval == null || interval < 5) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Interval is required and must be greater than 5", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationInterval.interval"); - - if(repeats && interval < 60) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "time interval must be at least 60 if repeating", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationInterval.interval"); - } - - @Override - public Calendar getNextValidDate(@Nullable Calendar fixedNowDate) throws AwesomeNotificationsException { - - CalendarUtils calendarUtils = CalendarUtils.getInstance(); - BooleanUtils booleanUtils = BooleanUtils.getInstance(); - - fixedNowDate = - (fixedNowDate == null) ? - calendarUtils.getCurrentCalendar(timeZone) : - fixedNowDate; - - Calendar initialDate = createdDate == null ? - fixedNowDate: - createdDate; - - Calendar finalDate; - if(booleanUtils.getValueOrDefault(this.repeats, false)) - { - Long initialEpoch = initialDate.getTimeInMillis(); - Long currentEpoch = fixedNowDate.getTimeInMillis(); - Long missingSeconds = Math.abs(initialEpoch - currentEpoch)/1000 % interval; - - finalDate = initialDate.after(fixedNowDate) ? - (Calendar) initialDate.clone() : - (Calendar) fixedNowDate.clone(); - - finalDate.add(Calendar.SECOND, missingSeconds.intValue()); - } - else { - finalDate = (Calendar) initialDate.clone(); - finalDate.add(Calendar.SECOND, interval); - } - - if( - finalDate.after(fixedNowDate) || - finalDate.equals(fixedNowDate) - ) - return finalDate; - - return null; - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationMessageModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationMessageModel.java deleted file mode 100644 index 7a1dcf75..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationMessageModel.java +++ /dev/null @@ -1,65 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import java.util.HashMap; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; - -public class NotificationMessageModel extends AbstractModel { - - public String title; - public String message; - public String largeIcon; - public Long timestamp; - - public NotificationMessageModel(){} - - public NotificationMessageModel(String title, String message, String largeIcon){ - - this.title = title; - this.message = message; - this.largeIcon = largeIcon; - this.timestamp = System.currentTimeMillis(); - } - - @Override - public NotificationMessageModel fromMap(Map arguments) { - - title = getValueOrDefault(arguments, Definitions.NOTIFICATION_TITLE, String.class, null); - message = getValueOrDefault(arguments, Definitions.NOTIFICATION_MESSAGES, String.class, null); - largeIcon = getValueOrDefault(arguments, Definitions.NOTIFICATION_LARGE_ICON, String.class, null); - timestamp = getValueOrDefault(arguments, Definitions.NOTIFICATION_TIMESTAMP, Long.class, null); - - return this; - } - - @Override - public Map toMap() { - Map dataMap = new HashMap<>(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_TITLE, dataMap, title); - putDataOnSerializedMap(Definitions.NOTIFICATION_MESSAGES, dataMap, message); - putDataOnSerializedMap(Definitions.NOTIFICATION_LARGE_ICON, dataMap, largeIcon); - putDataOnSerializedMap(Definitions.NOTIFICATION_TIMESTAMP, dataMap, timestamp); - - return dataMap; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationButtonModel fromJson(String json){ - return (NotificationButtonModel) super.templateFromJson(json); - } - - @Override - public void validate(Context context) throws AwesomeNotificationsException { - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationModel.java deleted file mode 100644 index 017e6241..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationModel.java +++ /dev/null @@ -1,149 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; - -public class NotificationModel extends AbstractModel { - - private static final String TAG = "NotificationModel"; - - public boolean groupSummary = false; - public String remoteHistory; - - public NotificationContentModel content; - public NotificationScheduleModel schedule; - public List actionButtons; - - public NotificationModel(){} - - public NotificationModel ClonePush(){ - return new NotificationModel().fromMap(this.toMap()); - } - - @Override - @Nullable - public NotificationModel fromMap(Map parameters){ - content = extractNotificationContent(parameters); - if(content == null) return null; - - schedule = extractNotificationSchedule(parameters); - actionButtons = extractNotificationButtons(parameters); - - return this; - } - - @Override - public Map toMap(){ - if(content == null) return null; - Map dataMap = new HashMap<>(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_MODEL_CONTENT, dataMap, content); - putDataOnSerializedMap(Definitions.NOTIFICATION_MODEL_SCHEDULE, dataMap, schedule); - putDataOnSerializedMap(Definitions.NOTIFICATION_MODEL_BUTTONS, dataMap, actionButtons); - - return dataMap; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationModel fromJson(String json){ - return (NotificationModel) super.templateFromJson(json); - } - - private static NotificationContentModel extractNotificationContent( - @NonNull Map parameters - ){ - if(!parameters.containsKey(Definitions.NOTIFICATION_MODEL_CONTENT)) return null; - Object obj = parameters.get(Definitions.NOTIFICATION_MODEL_CONTENT); - - if(!(obj instanceof Map)) return null; - - @SuppressWarnings("unchecked") - Map map = (Map) obj; - - if(map.isEmpty()) return null; - else return new NotificationContentModel().fromMap(map); - } - - private static NotificationScheduleModel extractNotificationSchedule( - @NonNull Map parameters - ){ - if(!parameters.containsKey(Definitions.NOTIFICATION_MODEL_SCHEDULE)) return null; - Object obj = parameters.get(Definitions.NOTIFICATION_MODEL_SCHEDULE); - - if(!(obj instanceof Map)) return null; - - @SuppressWarnings("unchecked") - Map map = (Map) obj; - - return NotificationScheduleModel.getScheduleModelFromMap(map); - } - - @SuppressWarnings("unchecked") - private static List extractNotificationButtons( - @NonNull Map parameters - ){ - if(!parameters.containsKey(Definitions.NOTIFICATION_MODEL_BUTTONS)) return null; - Object obj = parameters.get(Definitions.NOTIFICATION_MODEL_BUTTONS); - - if(!(obj instanceof List)) return null; - List actionButtonsData = (List) obj; - - List actionButtons = new ArrayList<>(); - - for (Object objButton: actionButtonsData) { - if(!(objButton instanceof Map)) return null; - - Map map = (Map) objButton; - if(map.isEmpty()) continue; - - NotificationButtonModel button = new NotificationButtonModel().fromMap(map); - actionButtons.add(button); - } - - if(actionButtons.isEmpty()) return null; - - return actionButtons; - } - - public void validate( - Context context - ) throws AwesomeNotificationsException { - if(this.content == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Notification content is required", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationContent"); - - this.content.validate(context); - - if(this.schedule != null) - this.schedule.validate(context); - - if(this.actionButtons != null){ - for(NotificationButtonModel button : this.actionButtons){ - button.validate(context); - } - } - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationScheduleModel.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationScheduleModel.java deleted file mode 100644 index f682036c..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/NotificationScheduleModel.java +++ /dev/null @@ -1,115 +0,0 @@ -package me.carda.awesome_notifications.core.models; - -import androidx.annotation.NonNull; - -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; -import java.util.TimeZone; - -import javax.annotation.Nullable; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.utils.BooleanUtils; -import me.carda.awesome_notifications.core.utils.CalendarUtils; - -public abstract class NotificationScheduleModel extends AbstractModel { - - public TimeZone timeZone; - public Calendar createdDate; - - /// Specify false to deliver the notification one time. Specify true to reschedule the notification request each time the notification is delivered. - public Boolean repeats; - public Boolean allowWhileIdle; - public Boolean preciseAlarm; - - public NotificationScheduleModel() {} - - @NonNull - public NotificationScheduleModel fromMap(Map arguments) { - timeZone = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_TIMEZONE, TimeZone.class, TimeZone.getDefault()); - createdDate = getValueOrDefault(arguments, Definitions.NOTIFICATION_CREATED_DATE, Calendar.class, null); - repeats = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_REPEATS, Boolean.class, false); - allowWhileIdle = getValueOrDefault(arguments, Definitions.NOTIFICATION_ALLOW_WHILE_IDLE, Boolean.class, false); - preciseAlarm = getValueOrDefault(arguments, Definitions.NOTIFICATION_SCHEDULE_PRECISE_ALARM, Boolean.class, false); - - return this; - } - - @Override - public Map toMap(){ - Map dataMap = new HashMap<>(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_TIMEZONE, dataMap, timeZone); - putDataOnSerializedMap(Definitions.NOTIFICATION_CREATED_DATE, dataMap, createdDate); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_REPEATS, dataMap, repeats); - putDataOnSerializedMap(Definitions.NOTIFICATION_ALLOW_WHILE_IDLE, dataMap, allowWhileIdle); - putDataOnSerializedMap(Definitions.NOTIFICATION_SCHEDULE_PRECISE_ALARM, dataMap, preciseAlarm); - - return dataMap; - } - - @Nullable - public abstract Calendar getNextValidDate(@Nullable Calendar fixedNowDate) throws AwesomeNotificationsException; - - @NonNull - public Boolean hasNextValidDate() throws AwesomeNotificationsException { - - CalendarUtils calendarUtils = CalendarUtils.getInstance(); - - repeats = BooleanUtils.getInstance().getValue(repeats); - if(createdDate == null && !repeats) - return false; - - return hasNextValidDate(calendarUtils.getCurrentCalendar()); - } - - @NonNull - public Boolean hasNextValidDate(Calendar referenceDate) throws AwesomeNotificationsException { - - Calendar nextSchedule = getNextValidDate(referenceDate); - - return nextSchedule != null && - ( - nextSchedule.after(referenceDate) || - nextSchedule.equals(referenceDate) - ); - } - - @Nullable - public static NotificationScheduleModel getScheduleModelFromMap(Map map){ - - if(map == null || map.isEmpty()) return null; - - if( - map.containsKey(Definitions.NOTIFICATION_CRONTAB_EXPRESSION) || - map.containsKey(Definitions.NOTIFICATION_PRECISE_SCHEDULES) || - map.containsKey(Definitions.NOTIFICATION_EXPIRATION_DATE_TIME) - ){ - return new NotificationCrontabModel().fromMap(map); - } - - if( - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_SECOND) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_MINUTE) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_HOUR) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_DAY) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_MONTH) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_YEAR) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_ERA) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_MILLISECOND) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_WEEKDAY) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_WEEKOFMONTH) || - map.containsKey(Definitions.NOTIFICATION_SCHEDULE_WEEKOFYEAR) - ){ - return new NotificationCalendarModel().fromMap(map); - } - - if(map.containsKey(Definitions.NOTIFICATION_SCHEDULE_INTERVAL)){ - return new NotificationIntervalModel().fromMap(map); - } - - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/returnedData/ActionReceived.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/returnedData/ActionReceived.java deleted file mode 100644 index 67eb12ac..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/returnedData/ActionReceived.java +++ /dev/null @@ -1,97 +0,0 @@ -package me.carda.awesome_notifications.core.models.returnedData; - -import android.content.Intent; - -import java.util.Calendar; -import java.util.Map; - -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.models.NotificationContentModel; -import me.carda.awesome_notifications.core.utils.CalendarUtils; - -public class ActionReceived extends NotificationReceived { - - public String buttonKeyPressed; - public String buttonKeyInput; - - // The value autoDismiss must return as original. Because - // of that, this variable is being used as temporary - public boolean shouldAutoDismiss = true; - - public NotificationLifeCycle actionLifeCycle; - public NotificationLifeCycle dismissedLifeCycle; - public Calendar actionDate; - public Calendar dismissedDate; - - public ActionReceived(){} - - public ActionReceived(NotificationContentModel contentModel, Intent originalIntent){ - super(contentModel, originalIntent); - - this.shouldAutoDismiss = this.autoDismissible; - } - - public void registerUserActionEvent(NotificationLifeCycle lifeCycle){ - CalendarUtils calendarUtils = CalendarUtils.getInstance(); - try { - this.actionLifeCycle = lifeCycle; - this.actionDate = - calendarUtils.getCurrentCalendar( - calendarUtils.getUtcTimeZone()); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } - - public void registerDismissedEvent(NotificationLifeCycle lifeCycle){ - CalendarUtils calendarUtils = CalendarUtils.getInstance(); - try { - this.dismissedLifeCycle = lifeCycle; - this.dismissedDate = - calendarUtils.getCurrentCalendar( - calendarUtils.getUtcTimeZone()); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } - - @Override - public Map toMap(){ - Map dataMap = super.toMap(); - - putDataOnSerializedMap(Definitions.NOTIFICATION_ACTION_LIFECYCLE, dataMap, actionLifeCycle); - putDataOnSerializedMap(Definitions.NOTIFICATION_DISMISSED_LIFECYCLE, dataMap, dismissedLifeCycle); - putDataOnSerializedMap(Definitions.NOTIFICATION_BUTTON_KEY_PRESSED, dataMap, buttonKeyPressed); - putDataOnSerializedMap(Definitions.NOTIFICATION_BUTTON_KEY_INPUT, dataMap, buttonKeyInput); - putDataOnSerializedMap(Definitions.NOTIFICATION_ACTION_DATE, dataMap, actionDate); - putDataOnSerializedMap(Definitions.NOTIFICATION_DISMISSED_DATE, dataMap, dismissedDate); - - return dataMap; - } - - @Override - public ActionReceived fromMap(Map arguments) { - super.fromMap(arguments); - - buttonKeyPressed = getValueOrDefault(arguments, Definitions.NOTIFICATION_BUTTON_KEY_PRESSED, String.class, null); - buttonKeyInput = getValueOrDefault(arguments, Definitions.NOTIFICATION_BUTTON_KEY_INPUT, String.class, null); - actionDate = getValueOrDefault(arguments, Definitions.NOTIFICATION_ACTION_DATE, Calendar.class, null); - dismissedDate = getValueOrDefault(arguments, Definitions.NOTIFICATION_DISMISSED_DATE, Calendar.class, null); - actionLifeCycle = getValueOrDefault(arguments, Definitions.NOTIFICATION_ACTION_LIFECYCLE, NotificationLifeCycle.class, null); - dismissedLifeCycle = getValueOrDefault(arguments, Definitions.NOTIFICATION_DISMISSED_LIFECYCLE, NotificationLifeCycle.class, null); - - return this; - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public ActionReceived fromJson(String json){ - return (ActionReceived) super.templateFromJson(json); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/returnedData/NotificationReceived.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/returnedData/NotificationReceived.java deleted file mode 100644 index 4f77baf8..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/models/returnedData/NotificationReceived.java +++ /dev/null @@ -1,74 +0,0 @@ -package me.carda.awesome_notifications.core.models.returnedData; - -import android.content.Intent; - -import java.util.Map; - -import me.carda.awesome_notifications.core.models.NotificationContentModel; - -// Just created because of Json process -public class NotificationReceived extends NotificationContentModel { - - public Intent originalIntent; - - public NotificationReceived(){} - - public NotificationReceived(NotificationContentModel contentModel, Intent originalIntent){ - - this.originalIntent = originalIntent; - - this.id = contentModel.id; - this.channelKey = contentModel.channelKey; - this.groupKey = contentModel.groupKey; - this.title = contentModel.title; - this.body = contentModel.body; - this.summary = contentModel.summary; - this.showWhen = contentModel.showWhen; - this.payload = contentModel.payload; - this.largeIcon = contentModel.largeIcon; - this.bigPicture = contentModel.bigPicture; - this.hideLargeIconOnExpand = contentModel.hideLargeIconOnExpand; - this.autoDismissible = contentModel.autoDismissible; - this.color = contentModel.color; - this.backgroundColor = contentModel.backgroundColor; - this.progress = contentModel.progress; - this.ticker = contentModel.ticker; - this.locked = contentModel.locked; - - this.fullScreenIntent = contentModel.fullScreenIntent; - this.wakeUpScreen = contentModel.wakeUpScreen; - this.category = contentModel.category; - - this.notificationLayout = contentModel.notificationLayout; - - this.displayOnBackground = contentModel.displayOnBackground; - this.displayOnForeground = contentModel.displayOnForeground; - - this.displayedLifeCycle = contentModel.displayedLifeCycle; - this.displayedDate = contentModel.displayedDate; - - this.createdSource = contentModel.createdSource; - this.createdLifeCycle = contentModel.createdLifeCycle; - this.createdDate = contentModel.createdDate; - } - - @Override - public NotificationReceived fromMap(Map parameters){ - return (NotificationReceived) super.fromMap(parameters); - } - - @Override - public Map toMap(){ - return super.toMap(); - } - - @Override - public String toJson() { - return templateToJson(); - } - - @Override - public NotificationReceived fromJson(String json){ - return (NotificationReceived) super.templateFromJson(json); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/AutoCancelService.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/AutoCancelService.java deleted file mode 100644 index 96baed52..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/AutoCancelService.java +++ /dev/null @@ -1,30 +0,0 @@ -package me.carda.awesome_notifications.core.services; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -import androidx.annotation.Nullable; - -import me.carda.awesome_notifications.core.logs.Logger; - -public class AutoCancelService extends Service { - - String TAG = "AutoCancelService"; - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - public void onTaskRemoved(Intent rootIntent) { - Logger.d(TAG, "TASK END"); - //unregister listeners - //do any other cleanup if required - - //stop service - stopSelf(); - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/AwesomeBackgroundService.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/AwesomeBackgroundService.java deleted file mode 100644 index 9e45c3fe..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/AwesomeBackgroundService.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.carda.awesome_notifications.core.services; - -import android.content.Context; -import android.content.Intent; - -import androidx.annotation.NonNull; -import androidx.core.app.JobIntentService; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.background.BackgroundExecutor; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.managers.DefaultsManager; - -public abstract class AwesomeBackgroundService extends JobIntentService { - private static final String TAG = "BackgroundService"; - - public abstract void initializeExternalPlugins(Context context) throws Exception; - - @Override - protected void onHandleWork(@NonNull final Intent intent) { - Logger.d(TAG, "A new Dart background service has started"); - try { - initializeExternalPlugins(this); - AwesomeNotifications.initialize(this); - - Long dartCallbackHandle = getDartCallbackDispatcher(this); - if (dartCallbackHandle == 0L) { - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.CODE_BACKGROUND_EXECUTION_EXCEPTION, - "A background message could not be handled in Dart" + - " because there is no onActionReceivedMethod handler registered.", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".dartCallback"); - return; - } - - Long silentCallbackHandle = getSilentCallbackDispatcher(this); - if (silentCallbackHandle == 0L) { - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.CODE_BACKGROUND_EXECUTION_EXCEPTION, - "A background message could not be handled in Dart" + - " because there is no dart background handler registered.", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".silentCallback"); - return; - } - - BackgroundExecutor.runBackgroundExecutor( - this, - intent, - dartCallbackHandle, - silentCallbackHandle); - - } catch (AwesomeNotificationsException ignored) { - } catch (Exception e) { - ExceptionFactory - .getInstance() - .registerNewAwesomeException( - TAG, - ExceptionCode.CODE_BACKGROUND_EXECUTION_EXCEPTION, - "A new Dart background service could not be executed", - ExceptionCode.DETAILED_INVALID_ARGUMENTS, - e); - } - } - - public static Long getDartCallbackDispatcher(Context context) throws AwesomeNotificationsException { - return DefaultsManager.getDartCallbackDispatcher(context); - } - - public static Long getSilentCallbackDispatcher(Context context) throws AwesomeNotificationsException { - return DefaultsManager.getSilentCallbackDispatcher(context); - } -} \ No newline at end of file diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/ForegroundService.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/ForegroundService.java deleted file mode 100644 index 0100e7b9..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/services/ForegroundService.java +++ /dev/null @@ -1,155 +0,0 @@ -package me.carda.awesome_notifications.core.services; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.IBinder; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.builders.NotificationBuilder; -import me.carda.awesome_notifications.core.completion_handlers.NotificationThreadCompletionHandler; -import me.carda.awesome_notifications.core.enumerators.ForegroundServiceType; -import me.carda.awesome_notifications.core.enumerators.ForegroundStartMode; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.managers.LifeCycleManager; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.threads.NotificationForegroundSender; - -public class ForegroundService extends Service { - - private static final String TAG = "ForegroundService"; - - private static final Map serviceStack = new HashMap<>(); - private static final Map serviceIntentStack = new HashMap<>(); - - public static void startNewForegroundService( - @NonNull Context applicationContext, - @NonNull NotificationModel notificationModel, - @NonNull ForegroundStartMode foregroundStartMode, - @NonNull ForegroundServiceType foregroundServiceType - ){ - ForegroundServiceIntent foregroundServiceIntent = - new ForegroundServiceIntent( - notificationModel, - foregroundStartMode, - foregroundServiceType); - - int id = notificationModel.content.id;//IntegerUtils.generateNextRandomId(); - serviceIntentStack.put(id, foregroundServiceIntent); - - Intent intent = new Intent(applicationContext, ForegroundService.class); - intent.putExtra(Definitions.AWESOME_FOREGROUND_ID, id); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Android 8*/) - applicationContext.startForegroundService(intent); - else - applicationContext.startService(intent); - } - - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - int id = intent.getIntExtra(Definitions.AWESOME_FOREGROUND_ID, -1); - ForegroundServiceIntent serviceIntent = serviceIntentStack.remove(id); - - if(!(id == -1 || serviceIntent == null)){ - - final int notificationId = serviceIntent.notificationModel.content.id; - ForegroundService foregroundService = serviceStack.remove(notificationId); - if(foregroundService != null) - foregroundService.stopSelf(); - - serviceStack.put(notificationId, this); - - try { - NotificationForegroundSender.start( - this, - NotificationBuilder.getNewBuilder(), - serviceIntent, - LifeCycleManager.getApplicationLifeCycle(), - new NotificationThreadCompletionHandler() { - @Override - public void handle( - boolean success, - @Nullable AwesomeNotificationsException exception) { - if(!success) - serviceStack.remove(notificationId); - } - }); - return serviceIntent.startMode.toAndroidStartMode(); - - } catch (AwesomeNotificationsException ignore) { - } - } - - stopSelf(); - return ForegroundStartMode.notStick.toAndroidStartMode(); - - } - - public static boolean serviceIsRunning( - @NonNull Integer notificationId - ){ - return serviceStack.containsKey(notificationId); - } - - public static void stop( - @NonNull Integer notificationId - ){ - ForegroundService foregroundService = serviceStack.remove(notificationId); - if(foregroundService != null) { - foregroundService.stopSelf(); - if(AwesomeNotifications.debug) - Logger.d(TAG, "Foreground service "+ notificationId +" id stopped"); - } - else { - if(AwesomeNotifications.debug) - Logger.d(TAG, "Foreground service "+ notificationId +" id not found"); - } - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - // This class is used to transport parameters from the platform channel through an intent to the service - public static class ForegroundServiceIntent implements Serializable { - - // Explicitly use HashMap here since it is serializable - public final NotificationModel notificationModel; - public final ForegroundStartMode startMode; - public final ForegroundServiceType foregroundServiceType; - - public ForegroundServiceIntent( - @NonNull NotificationModel notificationModel, - @NonNull ForegroundStartMode foregroundStartMode, - @NonNull ForegroundServiceType foregroundServiceType - ){ - this.notificationModel = notificationModel; - this.startMode = foregroundStartMode; - this.foregroundServiceType = foregroundServiceType; - } - - @Override - public @NonNull - String toString() { - return "StartParameter{" + - "notification=" + notificationModel + - ", startMode=" + startMode + - ", foregroundServiceType=" + foregroundServiceType + - '}'; - } - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationForegroundSender.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationForegroundSender.java deleted file mode 100644 index a186d831..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationForegroundSender.java +++ /dev/null @@ -1,218 +0,0 @@ -package me.carda.awesome_notifications.core.threads; - -import android.app.Notification; -import android.app.Service; -import android.content.Context; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.lang.ref.WeakReference; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.broadcasters.senders.BroadcastSender; -import me.carda.awesome_notifications.core.builders.NotificationBuilder; -import me.carda.awesome_notifications.core.completion_handlers.NotificationThreadCompletionHandler; -import me.carda.awesome_notifications.core.enumerators.ForegroundServiceType; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.enumerators.NotificationSource; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; -import me.carda.awesome_notifications.core.services.ForegroundService; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class NotificationForegroundSender extends NotificationThread { - - public static String TAG = "NotificationSender"; - - private final WeakReference wContextReference; - - private final NotificationBuilder notificationBuilder; - private final ForegroundService.ForegroundServiceIntent foregroundServiceIntent; - private final NotificationSource createdSource; - private final NotificationLifeCycle appLifeCycle; - private final NotificationThreadCompletionHandler threadCompletionHandler; - - private long startTime = 0L, endTime = 0L; - - private final StringUtils stringUtils; - - public static void start( - @NonNull Context applicationContext, - @NonNull NotificationBuilder notificationBuilder, - @NonNull ForegroundService.ForegroundServiceIntent foregroundServiceIntent, - @NonNull NotificationLifeCycle appLifeCycle, - @NonNull NotificationThreadCompletionHandler threadCompletionHandler - ) throws AwesomeNotificationsException { - - if(foregroundServiceIntent.notificationModel == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Notification model is required", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".foreground.notificationModel"); - - foregroundServiceIntent - .notificationModel - .validate(applicationContext); - - new NotificationForegroundSender( - applicationContext, - StringUtils.getInstance(), - foregroundServiceIntent, - notificationBuilder, - appLifeCycle, - threadCompletionHandler - ).execute( - foregroundServiceIntent.notificationModel); - } - - private NotificationForegroundSender( - Context context, - StringUtils stringUtils, - ForegroundService.ForegroundServiceIntent foregroundServiceIntent, - NotificationBuilder notificationBuilder, - NotificationLifeCycle appLifeCycle, - NotificationThreadCompletionHandler threadCompletionHandler - ) throws AwesomeNotificationsException { - - if(foregroundServiceIntent == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Foreground service intent is invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".foreground.intent"); - - this.wContextReference = new WeakReference<>(context); - this.foregroundServiceIntent = foregroundServiceIntent; - this.threadCompletionHandler = threadCompletionHandler; - this.notificationBuilder = notificationBuilder; - this.appLifeCycle = appLifeCycle; - this.createdSource = NotificationSource.ForegroundService; - this.startTime = System.nanoTime(); - - this.stringUtils = stringUtils; - } - - /// AsyncTask METHODS BEGIN ********************************* - - @Override - protected NotificationModel doInBackground() throws Exception { - - NotificationModel notificationModel - = foregroundServiceIntent.notificationModel; - - notificationModel.content.registerCreatedEvent(appLifeCycle, createdSource); - notificationModel.content.registerDisplayedEvent(appLifeCycle); - - if ( - stringUtils.isNullOrEmpty(notificationModel.content.title) && - stringUtils.isNullOrEmpty(notificationModel.content.body) - ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "A foreground service requires at least the title or body", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".foreground.intent"); - - return showForegroundNotification(wContextReference.get(), notificationModel); - } - - @Override - protected NotificationModel onPostExecute( - NotificationModel notificationModel - ) throws AwesomeNotificationsException { - - // Only broadcast if notificationModel is valid - if(notificationModel != null){ - - NotificationReceived receivedNotification = - new NotificationReceived( - notificationModel.content, - null); - - receivedNotification.displayedLifeCycle = receivedNotification.displayedLifeCycle == null ? - appLifeCycle : receivedNotification.displayedLifeCycle; - - BroadcastSender.sendBroadcastNotificationCreated( - wContextReference.get(), - receivedNotification); - - - BroadcastSender.sendBroadcastNotificationDisplayed( - wContextReference.get(), - receivedNotification); - } - - if(this.endTime == 0L) - this.endTime = System.nanoTime(); - - if(AwesomeNotifications.debug){ - long elapsed = (endTime - startTime)/1000000; - Logger.d(TAG, "Notification displayed in "+elapsed+"ms"); - } - - return notificationModel; - } - - @Override - protected void whenComplete( - @Nullable NotificationModel notificationModel, - @Nullable AwesomeNotificationsException exception - ) throws AwesomeNotificationsException { - if (threadCompletionHandler != null) - threadCompletionHandler.handle(notificationModel != null, exception); - } - - /// AsyncTask METHODS END ********************************* - - public NotificationModel showForegroundNotification(Context context, NotificationModel notificationModel) { - - try { - - NotificationLifeCycle lifeCycle = AwesomeNotifications.getApplicationLifeCycle(); - - if( - (lifeCycle == NotificationLifeCycle.AppKilled) || - (lifeCycle == NotificationLifeCycle.Foreground && notificationModel.content.displayOnForeground) || - (lifeCycle == NotificationLifeCycle.Background && notificationModel.content.displayOnBackground) - ){ - - Notification androidNotification = notificationBuilder - .createNewAndroidNotification(context, null, notificationModel); - - if ( - androidNotification != null && - Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && - foregroundServiceIntent.foregroundServiceType != ForegroundServiceType.none - ){ - ((Service) context).startForeground( - notificationModel.content.id, - androidNotification, - foregroundServiceIntent.foregroundServiceType.toAndroidServiceType()); - } else { - ((Service) context).startForeground( - notificationModel.content.id, - androidNotification); - } - } - - return notificationModel; - - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationScheduler.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationScheduler.java deleted file mode 100644 index 21da4e8b..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationScheduler.java +++ /dev/null @@ -1,485 +0,0 @@ -package me.carda.awesome_notifications.core.threads; - -import android.annotation.SuppressLint; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.AlarmManagerCompat; - -import java.lang.ref.WeakReference; -import java.util.Calendar; -import java.util.List; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.Definitions; -import me.carda.awesome_notifications.core.broadcasters.senders.BroadcastSender; -import me.carda.awesome_notifications.core.completion_handlers.NotificationThreadCompletionHandler; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.enumerators.NotificationSource; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.managers.ChannelManager; -import me.carda.awesome_notifications.core.managers.ScheduleManager; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; -import me.carda.awesome_notifications.core.utils.BooleanUtils; -import me.carda.awesome_notifications.core.utils.CalendarUtils; -import me.carda.awesome_notifications.core.utils.IntegerUtils; - -public class NotificationScheduler extends NotificationThread { - - public static String TAG = "NotificationScheduler"; - - private final WeakReference wContextReference; - - private final NotificationSource createdSource; - private final NotificationLifeCycle appLifeCycle; - private NotificationModel notificationModel; - private final Intent originalIntent; - - private Boolean scheduled = false; - private Boolean rescheduled = false; - private long startTime = 0L, endTime = 0L; - private final Calendar initialDate; - - private final NotificationThreadCompletionHandler threadCompletionHandler; - - public static void schedule( - Context context, - NotificationModel notificationModel, - Intent originalIntent, - NotificationThreadCompletionHandler threadCompletionHandler - ) throws AwesomeNotificationsException { - - if (notificationModel == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid notification content", - ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".notificationModel"); - - notificationModel.validate(context); - - new NotificationScheduler( - context, - AwesomeNotifications.getApplicationLifeCycle(), - notificationModel.content.createdSource, - notificationModel, - originalIntent, - true, - threadCompletionHandler - ).execute(notificationModel); - } - - public static void schedule( - Context context, - NotificationSource createdSource, - NotificationModel notificationModel, - NotificationThreadCompletionHandler threadCompletionHandler - ) throws AwesomeNotificationsException { - - if (notificationModel == null) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid notification content", - ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".notificationModel"); - - notificationModel.validate(context); - - new NotificationScheduler( - context, - AwesomeNotifications.getApplicationLifeCycle(), - createdSource, - notificationModel, - null, - false, - threadCompletionHandler - ).execute(notificationModel); - } - - private NotificationScheduler( - Context context, - NotificationLifeCycle appLifeCycle, - NotificationSource createdSource, - NotificationModel notificationModel, - Intent originalIntent, - boolean isReschedule, - NotificationThreadCompletionHandler threadCompletionHandler - ) throws AwesomeNotificationsException { - this.wContextReference = new WeakReference<>(context); - this.rescheduled = isReschedule; - this.createdSource = createdSource; - this.appLifeCycle = appLifeCycle; - this.notificationModel = notificationModel; - this.startTime = System.nanoTime(); - this.originalIntent = originalIntent; - this.threadCompletionHandler = threadCompletionHandler; - - this.initialDate = - CalendarUtils - .getInstance() - .getCurrentCalendar( - notificationModel - .schedule - .timeZone); - - // Only generate randomly for first time to avoid collisions - if(notificationModel.content.id == null || notificationModel.content.id < 0) - notificationModel.content.id = IntegerUtils.generateNextRandomId(); - } - - /// AsyncTask METHODS BEGIN ********************************* - - @Override - protected Calendar doInBackground() throws Exception { - Calendar nextValidDate = null; - - if(notificationModel != null){ - - if (!ChannelManager - .getInstance() - .isChannelEnabled( - wContextReference.get(), - notificationModel.content.channelKey) - ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Channel '" + notificationModel.content.channelKey + - "' do not exist or is disabled", - ExceptionCode.DETAILED_INSUFFICIENT_PERMISSIONS+ - ".channel."+notificationModel.content.channelKey); - - if(notificationModel.schedule == null) - return null; - - scheduled = notificationModel - .content - .registerCreatedEvent( - appLifeCycle, - createdSource); - - nextValidDate = notificationModel - .schedule - .getNextValidDate(initialDate); - - if(nextValidDate != null){ - - notificationModel = scheduleNotification( - wContextReference.get(), - notificationModel, - nextValidDate); - - if(notificationModel != null){ - scheduled = true; - } - - return nextValidDate; - } - else { - cancelSchedule( - wContextReference.get(), - notificationModel); - - String now = CalendarUtils.getInstance().getNowStringCalendar(); - String msg = "Date is not more valid. ("+now+")"; - Logger.d(TAG, msg); - } - } - - return null; - } - - @Override - protected Calendar onPostExecute(Calendar nextValidDate) throws AwesomeNotificationsException { - - // Only fire ActionReceived if notificationModel is valid - if(notificationModel != null){ - - if(nextValidDate != null) { - - if(scheduled){ - - ScheduleManager.saveSchedule(wContextReference.get(), notificationModel); - if (!rescheduled) { - BroadcastSender.sendBroadcastNotificationCreated( - wContextReference.get(), - new NotificationReceived( - notificationModel.content, - originalIntent) - ); - Logger.d(TAG, "Scheduled created"); - } - - ScheduleManager.commitChanges(wContextReference.get()); - - if(this.endTime == 0L) - this.endTime = System.nanoTime(); - - if(AwesomeNotifications.debug){ - long elapsed = (endTime - startTime)/1000000; - Logger.d(TAG, "Notification "+( - rescheduled ? "rescheduled" : "scheduled" - )+" in "+elapsed+"ms"); - } - return nextValidDate; - } - } - - ScheduleManager.removeSchedule(wContextReference.get(), notificationModel); - _removeFromAlarm(wContextReference.get(), notificationModel.content.id); - - Logger.d(TAG, "Scheduled removed"); - ScheduleManager.commitChanges(wContextReference.get()); - } - - if(this.endTime == 0L) - this.endTime = System.nanoTime(); - - if(AwesomeNotifications.debug){ - long elapsed = (endTime - startTime)/1000000; - Logger.d(TAG, "Notification schedule removed in "+elapsed+"ms"); - } - - return null; - } - - @Override - protected void whenComplete( - @Nullable Calendar calendar, - @Nullable AwesomeNotificationsException exception - ) throws AwesomeNotificationsException { - if(threadCompletionHandler != null) - threadCompletionHandler.handle(exception != null, exception); - } - - /// AsyncTask METHODS END ********************************* - - private NotificationModel scheduleNotification(Context context, NotificationModel notificationModel, Calendar nextValidDate) { - - if(nextValidDate != null){ - - String notificationDetailsJson = notificationModel.toJson(); - Intent notificationIntent = new Intent(context, AwesomeNotifications.scheduleReceiverClass); - notificationIntent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - - notificationIntent.putExtra(Definitions.NOTIFICATION_ID, notificationModel.content.id); - notificationIntent.putExtra(Definitions.NOTIFICATION_JSON, notificationDetailsJson); - - @SuppressLint("WrongConstant") PendingIntent pendingIntent = - PendingIntent - .getBroadcast( - context, - notificationModel.content.id, - notificationIntent, - Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : - PendingIntent.FLAG_UPDATE_CURRENT ); - - //scheduleNotificationWithWorkManager(context, notificationModel, nextValidDate); - scheduleNotificationWithAlarmManager( - context, - notificationModel, - nextValidDate, - pendingIntent); - - return notificationModel; - } - return null; - } - - // WorkManager does not not meet the requirements to be used in scheduling process -// private void scheduleNotificationWithWorkManager(Context context, NotificationModel notificationModel, Calendar nextValidDate) { -// Constraints myConstraints = new Constraints.Builder() -// .setRequiresDeviceIdle(!notificationModel.schedule.allowWhileIdle) -// .setRequiresBatteryNotLow(!notificationModel.schedule.allowWhileIdle) -// .setRequiresStorageNotLow(false) -// .build(); -// -// OneTimeWorkRequest notificationWork = new OneTimeWorkRequest.Builder(ScheduleWorker.class) -// .setInitialDelay(calculateDelay(nextValidDate), TimeUnit.MILLISECONDS) -// .addTag(notificationModel.content.id.toString()) -// .setConstraints(myConstraints) -// .build(); -// -// WorkManager.getInstance(context).enqueue(notificationWork); -// } - - private void scheduleNotificationWithAlarmManager(Context context, NotificationModel notificationModel, Calendar nextValidDate, PendingIntent pendingIntent) { - AlarmManager alarmManager = ScheduleManager.getAlarmManager(context); - long timeMillis = nextValidDate.getTimeInMillis(); - - if ( - BooleanUtils.getInstance().getValue(notificationModel.schedule.preciseAlarm) && - ScheduleManager.isPreciseAlarmGloballyAllowed(alarmManager) - ) { - AlarmManager.AlarmClockInfo info = new AlarmManager.AlarmClockInfo(timeMillis, pendingIntent); - alarmManager.setAlarmClock(info, pendingIntent); - return; - } - - if (BooleanUtils.getInstance().getValue(notificationModel.schedule.allowWhileIdle)) { - AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeMillis, pendingIntent); - return; - } - - AlarmManagerCompat.setExact(alarmManager, AlarmManager.RTC_WAKEUP, timeMillis, pendingIntent); - } - - public static void refreshScheduledNotifications( - Context context - ) throws AwesomeNotificationsException { - - List notificationIds = ScheduleManager.listScheduledIds(context); - if (notificationIds.isEmpty()) return; - - for (String id : notificationIds) { - - if(isScheduleActiveOnAlarmManager( - context, - Integer.parseInt(id))) - continue; - - NotificationModel notificationModel = ScheduleManager.getScheduleById(context, id); - if(notificationModel == null){ - ScheduleManager.cancelScheduleById(context, id); - } - else if(notificationModel.schedule.hasNextValidDate()){ - // TODO save original intents to be restored later - schedule(context, notificationModel, null, null); - } - else { - ScheduleManager.removeSchedule(context, notificationModel); - } - } - } - - public static void cancelScheduleById( - @NonNull Context context, @NonNull Integer id - ) throws AwesomeNotificationsException { - _removeFromAlarm(context, id); - ScheduleManager.cancelScheduleById(context, id.toString()); - ScheduleManager.commitChanges(context); - } - - public static void cancelSchedule( - @NonNull Context context, @NonNull NotificationModel notificationModel - ) throws AwesomeNotificationsException { - _removeFromAlarm(context, notificationModel.content.id); - ScheduleManager.removeSchedule(context, notificationModel); - ScheduleManager.commitChanges(context); - } - - public static void cancelSchedulesByChannelKey( - @NonNull Context context, @NonNull String channelKey - ) throws AwesomeNotificationsException { - List ids = ScheduleManager.listScheduledIdsFromChannel(context, channelKey); - _removeAllFromAlarm(context, ids); - ScheduleManager.cancelSchedulesByChannelKey(context, channelKey); - ScheduleManager.commitChanges(context); - } - - public static void cancelSchedulesByGroupKey( - @NonNull Context context, @NonNull String groupKey - ) throws AwesomeNotificationsException { - List ids = ScheduleManager.listScheduledIdsFromGroup(context, groupKey); - _removeAllFromAlarm(context, ids); - ScheduleManager.cancelSchedulesByGroupKey(context, groupKey); - ScheduleManager.commitChanges(context); - } - - public static void cancelAllSchedules(@NonNull Context context) throws AwesomeNotificationsException { - List ids = ScheduleManager.listScheduledIds(context); - _removeAllFromAlarm(context, ids); - ScheduleManager.cancelAllSchedules(context); - ScheduleManager.commitChanges(context); - } - - private static void _removeFromAlarm( - @NonNull Context context, @NonNull Integer id - ){ - Intent intent = new Intent(context, AwesomeNotifications.scheduleReceiverClass); - - @SuppressLint("WrongConstant") - PendingIntent pendingIntent = - PendingIntent - .getBroadcast( - context, - id, - intent, - Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : - PendingIntent.FLAG_UPDATE_CURRENT ); - - AlarmManager alarmManager = ScheduleManager.getAlarmManager(context); - alarmManager.cancel(pendingIntent); - } - - private static void _removeAllFromAlarm( - @NonNull Context context, @NonNull List ids - ){ - AlarmManager alarmManager = ScheduleManager.getAlarmManager(context); - Intent intent = new Intent(context, AwesomeNotifications.scheduleReceiverClass); - int flags = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : - PendingIntent.FLAG_UPDATE_CURRENT; - - for(String id : ids){ - @SuppressLint("WrongConstant") - PendingIntent pendingIntent = - PendingIntent - .getBroadcast( - context, - Integer.parseInt(id), - intent, - flags); - - alarmManager.cancel(pendingIntent); - } - } - - public static boolean isScheduleActiveOnAlarmManager( - @NonNull Context context, - @NonNull Integer notificationId - ) throws AwesomeNotificationsException { - - if(notificationId < 0) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Scheduled notification Id is invalid", - ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".notificationId"); - - Intent notificationIntent = new Intent(context, AwesomeNotifications.scheduleReceiverClass); - - @SuppressLint("WrongConstant") - PendingIntent pendingIntent1 = - PendingIntent - .getBroadcast( - context, - notificationId, - notificationIntent, - Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? - PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_NO_CREATE : - PendingIntent.FLAG_NO_CREATE ); - - return pendingIntent1 != null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationSender.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationSender.java deleted file mode 100644 index b816768f..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationSender.java +++ /dev/null @@ -1,278 +0,0 @@ -package me.carda.awesome_notifications.core.threads; - -import android.app.Notification; -import android.content.Context; -import android.content.Intent; -import android.os.Build; - -import androidx.annotation.Nullable; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.broadcasters.senders.BroadcastSender; -import me.carda.awesome_notifications.core.builders.NotificationBuilder; -import me.carda.awesome_notifications.core.completion_handlers.NotificationThreadCompletionHandler; -import me.carda.awesome_notifications.core.enumerators.NotificationLayout; -import me.carda.awesome_notifications.core.enumerators.NotificationLifeCycle; -import me.carda.awesome_notifications.core.enumerators.NotificationSource; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.logs.Logger; -import me.carda.awesome_notifications.core.managers.ScheduleManager; -import me.carda.awesome_notifications.core.managers.StatusBarManager; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.models.returnedData.NotificationReceived; -import me.carda.awesome_notifications.core.utils.IntegerUtils; -import me.carda.awesome_notifications.core.utils.StringUtils; - -public class NotificationSender extends NotificationThread { - - public static String TAG = "NotificationSender"; - - private final WeakReference wContextReference; - - private final NotificationBuilder notificationBuilder; - private final NotificationSource createdSource; - private final NotificationLifeCycle appLifeCycle; - private final Intent originalIntent; - - private NotificationModel notificationModel; - private final NotificationThreadCompletionHandler threadCompletionHandler; - - private Boolean created = false; - private Boolean displayed = false; - - private long startTime = 0L, endTime = 0L; - - private final StringUtils stringUtils; - - /// FACTORY METHODS BEGIN ********************************* - - public static void send( - Context context, - NotificationBuilder notificationBuilder, - NotificationLifeCycle appLifeCycle, - NotificationModel notificationModel, - NotificationThreadCompletionHandler threadCompletionHandler - ) throws AwesomeNotificationsException { - - NotificationSender.send( - context, - notificationBuilder, - notificationModel.content.createdSource, - appLifeCycle, - notificationModel, - null, - threadCompletionHandler); - } - - public static void send( - Context context, - NotificationBuilder notificationBuilder, - NotificationSource createdSource, - NotificationLifeCycle appLifeCycle, - NotificationModel notificationModel, - Intent originalIntent, - NotificationThreadCompletionHandler threadCompletionHandler - ) throws AwesomeNotificationsException { - - if (notificationModel == null ) - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Notification cannot be empty or null", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".sender.notificationModel"); - - new NotificationSender( - context, - StringUtils.getInstance(), - notificationBuilder, - appLifeCycle, - createdSource, - notificationModel, - originalIntent, - threadCompletionHandler - ).execute(notificationModel); - } - - private NotificationSender( - Context context, - StringUtils stringUtils, - NotificationBuilder notificationBuilder, - NotificationLifeCycle appLifeCycle, - NotificationSource createdSource, - NotificationModel notificationModel, - Intent originalIntent, - NotificationThreadCompletionHandler threadCompletionHandler - ){ - this.wContextReference = new WeakReference<>(context); - this.notificationBuilder = notificationBuilder; - this.createdSource = createdSource; - this.appLifeCycle = appLifeCycle; - this.notificationModel = notificationModel; - this.originalIntent = originalIntent; - this.threadCompletionHandler = threadCompletionHandler; - - this.startTime = System.nanoTime(); - this.stringUtils = stringUtils; - } - - /// AsyncTask METHODS BEGIN ********************************* - - @Override - protected NotificationReceived doInBackground() throws Exception { - if (notificationModel != null){ - - created = notificationModel - .content - .registerCreatedEvent( - appLifeCycle, - createdSource); - - if ( - !stringUtils.isNullOrEmpty(notificationModel.content.title) || - !stringUtils.isNullOrEmpty(notificationModel.content.body) - ){ - displayed = notificationModel - .content - .registerDisplayedEvent( - appLifeCycle); - - notificationModel = showNotification( - wContextReference.get(), - notificationModel, - originalIntent); - } - - // Only save DisplayedMethods if notificationModel was created - // and displayed successfully - if(notificationModel != null) - return new NotificationReceived( - notificationModel.content, - originalIntent); - } - - return null; - } - - @Override - protected NotificationReceived onPostExecute( - NotificationReceived receivedNotification - ) throws AwesomeNotificationsException { - - // Only broadcast if notificationModel is valid - if(receivedNotification != null){ - - if(created) { - ScheduleManager.cancelScheduleById( - wContextReference.get(), - String.valueOf(receivedNotification.id)); - - BroadcastSender.sendBroadcastNotificationCreated( - wContextReference.get(), - receivedNotification); - } - - if(displayed) - BroadcastSender.sendBroadcastNotificationDisplayed( - wContextReference.get(), - receivedNotification); - } - - if(this.endTime == 0L) - this.endTime = System.nanoTime(); - - if(AwesomeNotifications.debug){ - long elapsed = (endTime - startTime)/1000000; - - List actionsTookList = new ArrayList<>(); - if(created) actionsTookList.add("created"); - if(displayed) actionsTookList.add("displayed"); - - Logger.d(TAG, "Notification "+stringUtils.join(actionsTookList.iterator(), " and ")+" in "+elapsed+"ms"); - } - - return receivedNotification; - } - - @Override - protected void whenComplete( - @Nullable NotificationReceived notificationReceived, - @Nullable AwesomeNotificationsException exception - ) throws AwesomeNotificationsException { - if (threadCompletionHandler != null) - threadCompletionHandler.handle(notificationReceived != null, exception); - } - - /// AsyncTask METHODS END ********************************* - - public NotificationModel showNotification( - Context context, - NotificationModel notificationModel, - Intent originalIntent - ) throws Exception { - - NotificationLifeCycle lifeCycle = AwesomeNotifications.getApplicationLifeCycle(); - - boolean shouldDisplay = true; - switch (lifeCycle){ - - case Background: - shouldDisplay = notificationModel.content.displayOnBackground; - break; - - case Foreground: - shouldDisplay = notificationModel.content.displayOnForeground; - break; - } - - if(shouldDisplay){ - - Notification notification = - notificationBuilder - .createNewAndroidNotification(context, originalIntent, notificationModel); - - if( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && - notificationModel.content.notificationLayout == NotificationLayout.Default && - StatusBarManager - .getInstance(context) - .isFirstActiveOnGroupKey(notificationModel.content.groupKey) - ){ - NotificationModel pushSummary = _buildSummaryGroupNotification(notificationModel); - Notification summaryNotification = - notificationBuilder - .createNewAndroidNotification(context, originalIntent, pushSummary); - - StatusBarManager - .getInstance(context) - .showNotificationOnStatusBar(context, pushSummary, summaryNotification); - } - - StatusBarManager - .getInstance(context) - .showNotificationOnStatusBar(context, notificationModel, notification); - } - - return notificationModel; - } - - private NotificationModel _buildSummaryGroupNotification(NotificationModel original){ - - NotificationModel pushSummary = notificationModel.ClonePush(); - - pushSummary.content.id = IntegerUtils.generateNextRandomId(); - pushSummary.content.notificationLayout = NotificationLayout.Default; - pushSummary.content.largeIcon = null; - pushSummary.content.bigPicture = null; - pushSummary.groupSummary = true; - - return pushSummary; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationThread.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationThread.java deleted file mode 100644 index 87bba285..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/threads/NotificationThread.java +++ /dev/null @@ -1,178 +0,0 @@ -package me.carda.awesome_notifications.core.threads; - -import android.os.Handler; -import android.os.Looper; - -import androidx.annotation.Nullable; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import me.carda.awesome_notifications.core.enumerators.MediaSource; -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; -import me.carda.awesome_notifications.core.models.NotificationModel; -import me.carda.awesome_notifications.core.utils.BitmapUtils; - -public abstract class NotificationThread{ - - private final String TAG = "NotificationThread"; - - protected abstract T doInBackground() throws Exception; - protected abstract T onPostExecute(@Nullable T received) throws AwesomeNotificationsException; - protected abstract void whenComplete(@Nullable T returnedValue, @Nullable AwesomeNotificationsException exception) throws AwesomeNotificationsException; - - public void execute(){ - runOnBackgroundThread(); - } - - public void execute(NotificationModel notificationModel){ - if(itMustRunOnBackgroundThread(notificationModel)) - runOnBackgroundThread(); - else - runOnForegroundThread(); - } - - private void runOnBackgroundThread() { - final ExecutorService executor = Executors.newSingleThreadExecutor(); - final Handler handler = new Handler(Looper.getMainLooper()); - final NotificationThread threadReference = this; - - executor.execute(new Runnable() { - @Override - public void run() { - try{ - final T response = threadReference.doInBackground(); - handler.post(new Runnable() { - @Override - public void run() { - T returnedValue = null; - try{ - returnedValue = threadReference.onPostExecute(response); - whenComplete(returnedValue, null); - } catch (AwesomeNotificationsException awesomeException) { - try { - whenComplete(null, awesomeException); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } catch (Exception exception){ - try { - whenComplete( - null, - ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_NOTIFICATION_THREAD_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - exception)); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } - } - }); - } catch (AwesomeNotificationsException awesomeException) { - try { - whenComplete(null, awesomeException); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } catch (Exception exception) { - try { - whenComplete( - null, - ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_NOTIFICATION_THREAD_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - exception)); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } - } - }); - } - - private void runOnForegroundThread() { - if(Looper.myLooper() == Looper.getMainLooper()) { - T returnedValue = null; - try{ - returnedValue = onPostExecute(doInBackground()); - whenComplete(returnedValue, null); - } catch (AwesomeNotificationsException awesomeException) { - try { - whenComplete(null, awesomeException); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } catch (Exception exception){ - try { - whenComplete( - null, - ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_NOTIFICATION_THREAD_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - exception)); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } - } - else { - final Handler handler = new Handler(Looper.getMainLooper()); - final NotificationThread threadReference = this; - - handler.post(new Runnable() { - @Override - public void run() { - T returnedValue = null; - try { - final T response = threadReference.doInBackground(); - returnedValue = threadReference.onPostExecute(response); - whenComplete(returnedValue, null); - } catch (AwesomeNotificationsException awesomeException) { - try { - whenComplete(null, awesomeException); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } catch (Exception exception){ - try { - whenComplete( - null, - ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_NOTIFICATION_THREAD_EXCEPTION, - ExceptionCode.DETAILED_UNEXPECTED_ERROR, - exception)); - } catch (AwesomeNotificationsException e) { - e.printStackTrace(); - } - } - } - }); - } - } - - private boolean itMustRunOnBackgroundThread(NotificationModel notificationModel){ - BitmapUtils bitmapUtils = BitmapUtils.getInstance(); - return - MediaSource.Network == bitmapUtils - .getMediaSourceType(notificationModel.content.bigPicture) - || - MediaSource.Network == bitmapUtils - .getMediaSourceType(notificationModel.content.largeIcon); - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/AudioUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/AudioUtils.java deleted file mode 100644 index fbb67190..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/AudioUtils.java +++ /dev/null @@ -1,91 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_ASSET; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_FILE; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_NETWORK; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_RESOURCE; - -import android.content.Context; - -import me.carda.awesome_notifications.core.AwesomeNotifications; - -public class AudioUtils extends MediaUtils { - - // ************** SINGLETON PATTERN *********************** - - protected static AudioUtils instance; - - protected AudioUtils(){ - } - - public static AudioUtils getInstance() { - if (instance == null) - instance = new AudioUtils(); - return instance; - } - - // ******************************************************** - - public int getAudioResourceId(Context context, String audioReference){ - audioReference = this.cleanMediaPath(audioReference); - String[] reference = audioReference.split("\\/"); - - try { - int resId = 0; - - String type = reference[0]; - String label = reference[1]; - - // Resources protected from obfuscation - // https://developer.android.com/studio/build/shrink-code#strict-reference-checks - String name = String.format("res_%1s", label); - resId = context.getResources().getIdentifier(name, type, AwesomeNotifications.getPackageName(context)); - - if(resId == 0){ - resId = context.getResources().getIdentifier(label, type, AwesomeNotifications.getPackageName(context)); - } - - return resId; - - } catch (Exception e) { - e.printStackTrace(); - } - - return 0; - } - - public Boolean isValidAudio(Context context, String mediaPath) { - - if (mediaPath != null) { - - if (matchMediaType(MEDIA_VALID_RESOURCE, mediaPath)) { - return isValidAudioResource(context, mediaPath); - } - - if (matchMediaType(MEDIA_VALID_NETWORK, mediaPath, false)) { - // TODO MISSING IMPLEMENTATION - return false; - } - - if (matchMediaType(MEDIA_VALID_FILE, mediaPath)) { - // TODO MISSING IMPLEMENTATION - return false; - } - - if (matchMediaType(MEDIA_VALID_ASSET, mediaPath)) { - // TODO MISSING IMPLEMENTATION - return false; - } - - } - return false; - } - - private Boolean isValidAudioResource(Context context, String name) { - if(name != null){ - int resourceId = getAudioResourceId(context, name); - return resourceId > 0; - } - return false; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/BitmapUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/BitmapUtils.java deleted file mode 100644 index 0f10bb27..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/BitmapUtils.java +++ /dev/null @@ -1,271 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_ASSET; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_FILE; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_NETWORK; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_RESOURCE; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; - -import androidx.annotation.NonNull; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.util.regex.Pattern; - -import javax.annotation.Nullable; - -import me.carda.awesome_notifications.core.AwesomeNotifications; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; - -public class BitmapUtils extends MediaUtils { - - public static final String TAG = "BitmapUtils"; - - // ************** SINGLETON PATTERN *********************** - - protected static BitmapUtils instance; - - protected BitmapUtils(){ - stringUtils = StringUtils.getInstance(); - } - - public static BitmapUtils getInstance() { - if (instance == null) - instance = new BitmapUtils(); - return instance; - } - - // ******************************************************** - - public Bitmap getBitmapFromSource(Context context, String bitmapPath, boolean roundedBitmap) { - - Bitmap returnedBitmap = null; - switch (getMediaSourceType(bitmapPath)){ - - case Resource: - returnedBitmap = getBitmapFromResource(context, bitmapPath); - break; - - case File: - returnedBitmap = getBitmapFromFile(bitmapPath); - break; - - case Asset: - returnedBitmap = getBitmapFromAsset(context, bitmapPath); - break; - - case Network: - returnedBitmap = getBitmapFromUrl(this.cleanMediaPath(bitmapPath)); - - case Unknown: - break; - } - - if(returnedBitmap != null && roundedBitmap){ - returnedBitmap = roundBitmap(returnedBitmap); - } - - return returnedBitmap; - } - - public String cleanMediaPath(String mediaPath) { - if (mediaPath != null) { - Pattern pattern = Pattern.compile("^https?:\\/\\/", Pattern.CASE_INSENSITIVE); - Pattern pattern2 = Pattern.compile("^(asset:\\/\\/)(.*)", Pattern.CASE_INSENSITIVE); - Pattern pattern3 = Pattern.compile("^(file:\\/\\/)(.*)", Pattern.CASE_INSENSITIVE); - Pattern pattern4 = Pattern.compile("^(resource:\\/\\/)(.*)", Pattern.CASE_INSENSITIVE); - - if(pattern.matcher(mediaPath).find()){ - return mediaPath; - } - - if(pattern2.matcher(mediaPath).find()){ - return pattern2.matcher(mediaPath).replaceAll("$2"); - } - - if(pattern3.matcher(mediaPath).find()){ - return pattern3.matcher(mediaPath).replaceAll("/$2"); - } - - if(pattern4.matcher(mediaPath).find()){ - return pattern4.matcher(mediaPath).replaceAll("$2"); - } - } - return null; - } - - public int getDrawableResourceId(Context context, String bitmapReference){ - bitmapReference = this.cleanMediaPath(bitmapReference); - String[] reference = bitmapReference.split("\\/"); - - try { - int resId; - - String type = reference[0]; - String label = reference[1]; - - // Resources protected from obfuscation - // https://developer.android.com/studio/build/shrink-code#strict-reference-checks - String name = String.format("res_%1s", label); - resId = context.getResources().getIdentifier(name, type, AwesomeNotifications.getPackageName(context)); - - if(resId == 0){ - resId = context.getResources().getIdentifier(label, type, AwesomeNotifications.getPackageName(context)); - } - - return resId; - - } catch (Exception ignore) { - } - - return 0; - } - - public Bitmap getBitmapFromResource(Context context, String bitmapReference){ - int resourceId = getDrawableResourceId(context, bitmapReference); - if(resourceId <= 0) return null; - return BitmapFactory.decodeResource(context.getResources(), resourceId); - } - - public Bitmap getBitmapFromAsset(Context context, String bitmapPath) { - return null; - } - - public Bitmap roundBitmap(@NonNull Bitmap bitmap) { - Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), - bitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(output); - - final int color = 0xff424242; - final Paint paint = new Paint(); - final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); - - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - paint.setColor(color); - - float bitmapWidth = bitmap.getWidth(); - float bitmapHeight = bitmap.getHeight(); - - if(bitmapWidth <= 0 || bitmapHeight <= 0) - return bitmap; - else{ - bitmapWidth /= 2; - bitmapHeight /= 2; - } - - canvas.drawCircle(bitmapWidth, bitmapHeight, bitmapWidth, paint); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(bitmap, rect, rect, paint); - - return output; - } - - private Bitmap getBitmapFromFile(String bitmapPath){ - bitmapPath = this.cleanMediaPath(bitmapPath); - Bitmap bitmap = null; - - try { - File imageFile = new File(bitmapPath); - bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath()); - } catch (Exception originalException) { - ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "The image file '"+bitmapPath+"' is invalid", - originalException); - } - - return bitmap; - } - - private Bitmap getBitmapFromUrl(String bitmapUri) { - bitmapUri = this.cleanMediaPath(bitmapUri); - Bitmap bitmap = null; - InputStream inputStream = null; - BufferedInputStream bufferedInputStream = null; - - try { - URLConnection conn = new URL(bitmapUri).openConnection(); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - conn.connect(); - - inputStream = conn.getInputStream(); - bufferedInputStream = new BufferedInputStream(inputStream, 8192); - bitmap = BitmapFactory.decodeStream(bufferedInputStream); - } - catch (Exception e){ - e.printStackTrace(); - } - finally { - if (bufferedInputStream != null) - { - try - { - bufferedInputStream.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - if (inputStream != null) - { - try - { - inputStream.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - } - - return bitmap; - } - - public Boolean isValidBitmap(@NonNull Context context, @Nullable String mediaPath) { - - if (!stringUtils.isNullOrEmpty(mediaPath)) { - - if (matchMediaType(MEDIA_VALID_NETWORK, mediaPath, false)) { - return true; - } - - if (matchMediaType(MEDIA_VALID_FILE, mediaPath)) { - // TODO MISSING IMPLEMENTATION - return true; - } - - if (matchMediaType(MEDIA_VALID_RESOURCE, mediaPath)) { - return isValidDrawableResource(context, mediaPath); - } - - return matchMediaType(MEDIA_VALID_ASSET, mediaPath); - - } - return false; - } - - private Boolean isValidDrawableResource(@NonNull Context context, @NonNull String name) { - int resourceId = getDrawableResourceId(context, name); - return resourceId > 0; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/BooleanUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/BooleanUtils.java deleted file mode 100644 index 3fa2f440..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/BooleanUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -public class BooleanUtils { - - // ************** SINGLETON PATTERN *********************** - - private static BooleanUtils instance; - - private BooleanUtils(){ - } - - public static BooleanUtils getInstance() { - if (instance == null) - instance = new BooleanUtils(); - return instance; - } - - // ******************************************************** - - public boolean getValue(Boolean booleanObject){ - return booleanObject != null && booleanObject; - } - public boolean getValueOrDefault(Boolean booleanObject, Boolean defaultValue){ - return booleanObject == null ? getValue(defaultValue) : getValue(booleanObject); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CalendarUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CalendarUtils.java deleted file mode 100644 index 36b0eb05..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CalendarUtils.java +++ /dev/null @@ -1,202 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import androidx.annotation.NonNull; - -import java.util.Calendar; -import java.util.Date; -import java.util.Locale; -import java.util.Objects; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.annotation.Nullable; - -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.exceptions.ExceptionCode; -import me.carda.awesome_notifications.core.exceptions.ExceptionFactory; - -public class CalendarUtils { - - private static final String TAG = "CalendarUtils"; - - // ***************************** SINGLETON PATTERN ***************************** - - protected static final CalendarUtils instance = new CalendarUtils(); - - private CalendarUtils(){} - - public static CalendarUtils getInstance(){ - return instance; - } - - // ******************************************************************************* - - static TimeZone utcTimeZone = TimeZone.getTimeZone("GMT"); - public TimeZone getUtcTimeZone() { - return utcTimeZone; - } - - static TimeZone localTimeZone = TimeZone.getDefault(); - public TimeZone getLocalTimeZone() { - return localTimeZone; - } - - protected Calendar getNowCalendar(){ - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.setTime(new Date()); - calendar.set(Calendar.MILLISECOND, 0); - return calendar; - } - - public String getNowStringCalendar(){ - return calendarToString(getNowCalendar()); - } - - @Nullable - public String calendarToString(@Nullable Calendar calendar){ - if(calendar == null) - return null; - - return String - .format( - Locale.US, - "%04d-%02d-%02d %02d:%02d:%02d %s", - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH)+1, - calendar.get(Calendar.DAY_OF_MONTH), - calendar.get(Calendar.HOUR_OF_DAY), - calendar.get(Calendar.MINUTE), - calendar.get(Calendar.SECOND), - calendar.getTimeZone().getID()); - } - - @Nullable - public Calendar shiftTimeZone(@Nullable Calendar calendar, @Nullable String timeZoneId) throws AwesomeNotificationsException { - if(calendar == null) - return null; - - TimeZone timeZone = TimeZoneUtils.getInstance().getValidTimeZone(timeZoneId); - - if (timeZone != null){ - Calendar shiftedCalendar = (Calendar) calendar.clone(); - shiftedCalendar.setTimeZone(timeZone); - return shiftedCalendar; - } - else { - throw ExceptionFactory - .getInstance() - .createNewAwesomeException( - TAG, - ExceptionCode.CODE_INVALID_ARGUMENTS, - "Invalid time zone", - ExceptionCode.DETAILED_INVALID_ARGUMENTS+".calendar.timeZone"); - } - } - - public Calendar shiftTimeZone(@NonNull Calendar calendar, @NonNull TimeZone timeZone) { - Date date = calendar.getTime(); - long msFromEpochGmt = date.getTime(); - - int offsetFromUTC = - calendar - .getTimeZone() - .getOffset(msFromEpochGmt); - - Calendar shiftedCalendar = Calendar.getInstance(timeZone); - shiftedCalendar.setTime(date); - shiftedCalendar.add(Calendar.MILLISECOND, offsetFromUTC); - - return shiftedCalendar; - } - - @Nullable - public Calendar calendarFromString(@Nullable String date) { - if(StringUtils.getInstance().isNullOrEmpty(date)) - return null; - assert date != null; - - final String regex = "^((\\d+)-(\\d+)-(\\d+) (\\d+):(\\d+):(\\d+))( (\\S+))?$"; - - final Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); - final Matcher matcher = pattern.matcher(date); - - if(matcher.matches()) - return calendarFromString(Objects.requireNonNull(matcher.group(1)), matcher.group(9)); - else - return null; - } - - @Nullable - public Calendar calendarFromString(@Nullable String date, @Nullable String timeZoneId) { - - if(StringUtils.getInstance().isNullOrEmpty(date)) - return null; - - TimeZone timeZone; - if(StringUtils.getInstance().isNullOrEmpty(timeZoneId)) - timeZone = - CalendarUtils - .getInstance() - .getUtcTimeZone(); - else - timeZone = - TimeZoneUtils - .getInstance() - .getValidTimeZone(timeZoneId); - - if (timeZone == null) - timeZone = - TimeZoneUtils - .getInstance() - .getValidTimeZone(timeZoneId); - - return calendarFromString(date, timeZone); - } - - @Nullable - public Calendar calendarFromString(@Nullable String date, @Nullable TimeZone timeZone) { - - if(StringUtils.getInstance().isNullOrEmpty(date)) - return null; - - final String regex = "^(\\d+)-(\\d+)-(\\d+) (\\d+):(\\d+):(\\d+)$"; - - final Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); - final Matcher matcher = pattern.matcher(date); - - if(matcher.matches()){ - - final Calendar calendar = getNowCalendar(); - shiftTimeZone(calendar, timeZone); - - calendar.set(Calendar.YEAR, Integer.parseInt(matcher.group(1))); - calendar.set(Calendar.MONTH, Integer.parseInt(matcher.group(2))-1); - calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(matcher.group(3))); - calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(matcher.group(4))); - calendar.set(Calendar.MINUTE, Integer.parseInt(matcher.group(5))); - calendar.set(Calendar.SECOND, Integer.parseInt(matcher.group(6))); - - return calendar; - } - - return null; - } - - @NonNull - public Calendar getCurrentCalendar() { - return getNowCalendar(); - } - - @NonNull - public Calendar getCurrentCalendar(@NonNull String fromTimeZone) throws AwesomeNotificationsException { - return shiftTimeZone(getNowCalendar(), fromTimeZone); - } - - @NonNull - public Calendar getCurrentCalendar(@NonNull TimeZone fromTimeZone) throws AwesomeNotificationsException { - Calendar originalCalendar = getNowCalendar(); - Calendar shiftedCalendar = shiftTimeZone(originalCalendar, fromTimeZone); - return shiftedCalendar; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CompareUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CompareUtils.java deleted file mode 100644 index ac808195..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CompareUtils.java +++ /dev/null @@ -1,105 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class CompareUtils { - - public static boolean assertEqualObjects(Object object1, Object object2){ - if(object1 == object2) - return true; - - if(object1 == null || object2 == null) - return false; - - if(object1.equals(object2)) - return true; - - if(object1 instanceof Number) - if(object2 instanceof Number) - return assertEqualNumbers(object1, object2); - else - return false; - - if(object1 instanceof List) - if(object2 instanceof List) - return assertEqualLists(object1, object2); - else - return false; - - if(object1 instanceof Map) - if(object2 instanceof Map) - return assertEqualMaps(object1, object2); - else - return false; - - return false; - } - - public static boolean assertEqualNumbers(Object object1, Object object2) { - if(!(object1 instanceof Number)) return false; - if(!(object2 instanceof Number)) return false; - - BigDecimal b1 = BigDecimal.valueOf(((Number) object1).doubleValue()); - BigDecimal b2 = BigDecimal.valueOf(((Number) object2).doubleValue()); - return b1.compareTo(b2) == 0; - } - - @SuppressWarnings("unchecked") - public static boolean assertEqualMaps(Object object1, Object object2) { - if(object1 == null || object2 == null) - return false; - - if(!(object1 instanceof Map)) return false; - if(!(object2 instanceof Map)) return false; - - Map map1 = (Map) object1; - Map map2 = (Map) object2; - - if (map1.size() != map2.size()) - return false; - - for (Map.Entry entry : map1.entrySet()) { - Object key1 = entry.getKey(); - if (map2.containsKey(key1) && assertEqualObjects(entry.getValue(), map2.get(key1))) - continue; - return false; - } - - return true; - } - - @SuppressWarnings("unchecked") - public static boolean assertEqualLists(Object object1, Object object2) { - if(object1 == null || object2 == null) - return false; - - if(!(object1 instanceof List)) return false; - if(!(object2 instanceof List)) return false; - - List list1 = (List) object1; - List list2 = (List) object2; - - if (list1.size() != list2.size()) - return false; - - List cloned = new ArrayList<>(list2); - for(Object value1 : list1) { - boolean foundEqual = false; - for (Object value2 : cloned) { - if (CompareUtils.assertEqualObjects(value1, value2)) { - foundEqual = true; - break; - } - } - if(!foundEqual) - return false; - else - cloned.remove(value1); - } - - return true; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CronUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CronUtils.java deleted file mode 100644 index 98721058..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/CronUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - -import me.carda.awesome_notifications.core.exceptions.AwesomeNotificationsException; -import me.carda.awesome_notifications.core.externalLibs.CronExpression; - -public final class CronUtils { - - public static String validDateFormat = "yyyy-MM-dd HH:mm:ss"; - - /// https://www.baeldung.com/cron-expressions - /// - @Nullable - public static Calendar getNextCalendar( - @NonNull Calendar fixedNowDate, - @NonNull String crontabRule, - @NonNull TimeZone timeZone - ) throws AwesomeNotificationsException { - - if( - fixedNowDate == null || - timeZone == null || - StringUtils.getInstance().isNullOrEmpty(crontabRule) - ) return null; - - if(CronExpression.isValidExpression(crontabRule)) { - try { - CronExpression cronExpression = new CronExpression(crontabRule); - cronExpression.setTimeZone(timeZone); - Date nextSchedule = - cronExpression - .getNextValidTimeAfter(fixedNowDate.getTime()); - - Calendar delayedNow = applyToleranceDate(fixedNowDate); - if (nextSchedule != null && nextSchedule.compareTo(delayedNow.getTime()) >= 0) { - - Calendar calendar = Calendar.getInstance(); - calendar.setTimeZone(timeZone); - calendar.setTime(nextSchedule); - return calendar; - - } else { - // if there is no more valid dates, remove the repetitions - return null; - } - - } catch (ParseException e) { - e.printStackTrace(); - } - } - - return null; - } - - /// Processing time tolerance - public static Calendar applyToleranceDate(Calendar initialScheduleDay) { - Calendar shifted = (Calendar) initialScheduleDay.clone(); - shifted.set(Calendar.MILLISECOND, 0); - return shifted; - } -} - diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/EnumUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/EnumUtils.java deleted file mode 100644 index 460e4916..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/EnumUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -public class EnumUtils { - - public static final String TAG = "EnumUtils"; - - // ************** SINGLETON PATTERN *********************** - - protected static EnumUtils instance; - - protected EnumUtils(){} - - public static EnumUtils getInstance() { - if (instance == null) - instance = new EnumUtils(); - return instance; - } - - // ******************************************************** -/* - public > String enumToString( - @Nullable T enumeratorValue - ){ - if (enumeratorValue == null) return null; - return enumeratorValue.toString(); - } - - public > String enumToString( - @Nullable T enumeratorValue, - @Nullable T defaultValue - ){ - if (enumeratorValue == null) - return enumToString(defaultValue); - else - return enumToString(enumeratorValue); - } - - public > SafeEnum stringToEnum( - @Nullable String text, - @NonNull Class> type - ){ - Enum[] valueList = type.getEnumConstants(); - if(valueList == null) return null; - - for (SafeEnum candidate : valueList) { - if (candidate.getSafeName().equalsIgnoreCase(text)) { - return candidate; - } - } - - return null; - }*/ -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/HtmlUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/HtmlUtils.java deleted file mode 100644 index 717faa80..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/HtmlUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import android.os.Build; -import android.text.Html; -import android.text.Spanned; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class HtmlUtils { - - @SuppressWarnings("deprecation") - public static Spanned fromHtml(String html) { - if (StringUtils.getInstance().isNullOrEmpty(html)) { - return null; - } - - html = adaptFlutterColorsToJava(html); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); - } else { - return Html.fromHtml(html); - } - } - - public static String adaptFlutterColorsToJava(String htmlText){ - if(!StringUtils.getInstance().isNullOrEmpty(htmlText)){ - final String regex = "(<(\\S+\\s+)*)(color=)('|\")(0x|#)?(\\d+)('|\")((\\s+[^\\s>]+)*\\/?>)"; - - final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); - final Matcher matcher = pattern.matcher(htmlText); - - Boolean converted = false; - StringBuffer stringBuffer = new StringBuffer(); - - while (matcher.find()) { - try { - String beforeBody = matcher.group(1); - String tag = matcher.group(3); - String quoteTypeStart = matcher.group(4); - String colorValue = matcher.group(6); - Long parsedLog = Long.parseLong(colorValue); - String quoteTypeEnd = matcher.group(7); - String afterBody = matcher.group(8); - Integer parsedInt = parsedLog.intValue(); - matcher.appendReplacement(stringBuffer, - beforeBody + tag + quoteTypeStart + parsedInt.toString() + quoteTypeEnd + afterBody); - converted = true; - } catch (Exception ignored) { - - } - } - - if(converted){ - matcher.appendTail(stringBuffer); - return stringBuffer.toString(); - } - } - - return htmlText; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/IntegerUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/IntegerUtils.java deleted file mode 100644 index d5eb77b0..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/IntegerUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import java.security.SecureRandom; - -public class IntegerUtils { - - public static Integer convertToInt(Object object){ - int intValue = 0; - if(object != null) { - - if (object instanceof Number) { - intValue = ((Number) object).intValue(); - } else - if (object instanceof Enum) { - intValue = ((Enum) object).ordinal(); - } else - if (object instanceof String) { - try { - intValue = Integer.parseInt((String) object); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - return intValue; - } - - public static Boolean isBetween(Integer value, Integer min, Integer max){ - return value >= min && value <= max; - } - - // Note: sometimes Json parser converts Integer into Double objects - public static Integer extractInteger(Object value, Object defaultValue){ - if(value == null){ - return convertToInt(defaultValue); - } - return convertToInt(value); - } - - private static final SecureRandom random = new SecureRandom(); - public static int generateNextRandomId() { - int randomValue = random.nextInt(); - return randomValue < 0 ? randomValue * -1 : randomValue; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/JsonUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/JsonUtils.java deleted file mode 100644 index dc044b61..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/JsonUtils.java +++ /dev/null @@ -1,125 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - - -public class JsonUtils { - - public static Map fromJson(String jsonData){ - Gson gson = new Gson(); - Type type = new TypeToken>(){}.getType(); - return gson.fromJson(jsonData, type); - } - - public static String toJson(Map model){ - return chainOfResponsibilityToJson(model); - } - - // ************** CHAIN OF RESPONSIBILITY PATTERN *********************************** - - @SuppressWarnings("unchecked") - static String chainOfResponsibilityToJson(@Nullable Object object){ - - if(object == null) return "null"; - - StringBuilder text = new StringBuilder(); - - if(object instanceof Map){ - text.append(chainOfResponsibilityMapToJson((Map) object)); - } else - if(object instanceof List){ - text.append(chainOfResponsibilityListToJson((List) object)); - } else { - text.append(chainOfResponsibilityGenericsToJson(object)); - } - - return text.toString(); - } - - static String chainOfResponsibilityMapToJson(@Nullable Map map){ - - if(map == null) return "null"; - - List parameters = new ArrayList<>(); - - List sortedKeys = new ArrayList<>(map.keySet()); - Collections.sort(sortedKeys); - - for (String key : sortedKeys) { - Object value = map.get(key); - - StringBuilder text = new StringBuilder(); - text.append("\"").append(key).append("\":"); - - if(value == null) - text.append("null"); - else - text.append(chainOfResponsibilityToJson(value)); - - parameters.add(text.toString()); - } - - return "{"+ joinList(parameters) + "}"; - } - - static String chainOfResponsibilityListToJson(@Nullable List list){ - - if(list == null) return "null"; - - List parameters = new ArrayList<>(); - for (Object parameter : list) - parameters.add(chainOfResponsibilityGenericsToJson(parameter)); - - return "["+ joinList(parameters) + "]"; - } - - static String chainOfResponsibilityGenericsToJson(@Nullable Object generic){ - - if(generic == null) return "null"; - - // Gson is only trustable at this lower level - Gson gson = new Gson(); - - if((generic instanceof Map) || (generic instanceof List)){ - return chainOfResponsibilityToJson(generic); - } else - if(generic instanceof Boolean){ - return gson.toJson(generic); - } else - if(generic instanceof Number){ - return gson.toJson(generic); - } else - if(generic instanceof String){ - return gson.toJson(generic); - } - - return "null"; - } - - static String joinList(@Nullable List input) { - - if (input == null || input.size() <= 0) return ""; - - StringBuilder stringBuilder = new StringBuilder(); - - int cursor = 0; int size = input.size() - 1; - for (cursor = 0; cursor < size; cursor++) - stringBuilder - .append(input.get(cursor)) - .append(","); - - stringBuilder - .append(input.get(cursor)); - - return stringBuilder.toString(); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/ListUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/ListUtils.java deleted file mode 100644 index eec117a5..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/ListUtils.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import java.util.List; - -import javax.annotation.Nullable; - -public class ListUtils { - public static boolean isNullOrEmpty(@Nullable List list){ - return list == null || list.isEmpty(); - } - - public static boolean listHasDuplicates(List list){ - for(int position = 0; position < list.size(); position++) - for(int searchPointer = position + 1; searchPointer < list.size(); searchPointer++) - if(CompareUtils.assertEqualObjects(list.get(position), list.get(searchPointer))) - return true; - return false; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/MapUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/MapUtils.java deleted file mode 100644 index e2fe7dba..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/MapUtils.java +++ /dev/null @@ -1,203 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.common.base.Optional; -import com.google.common.primitives.Bytes; -import com.google.common.primitives.Doubles; -import com.google.common.primitives.Floats; -import com.google.common.primitives.Ints; -import com.google.common.primitives.Longs; -import com.google.common.primitives.Shorts; - -import java.util.Calendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import me.carda.awesome_notifications.core.logs.Logger; - -public class MapUtils { - - private static String TAG = "MapUtils"; - - public static Boolean isNullOrEmptyKey(Map map, String key){ - - if(map == null || map.isEmpty() || !map.containsKey(key)) - return true; - - Object value = map.get(key); - - if(value == null) return true; - - if (value instanceof String){ - if(((String) value).isEmpty()) return true; - } - - if (value instanceof Map){ - if(((Map) value).isEmpty()) return true; - } - - if (value instanceof List){ - return ((List) value).isEmpty(); - } - - return false; - } - - public static Boolean isNullOrEmpty(Map map){ - return map == null || map.isEmpty(); - } - - public static Optional extractArgument( - @Nullable Object object, - @NonNull Class expectedClass){ - if(object == null) - return Optional.absent(); - - try { - if(expectedClass.isInstance(object)){ - return Optional.of(expectedClass.cast(object)); - } - } - catch (Exception e){ - Logger.d(TAG,"Argument is not a type of " + Optional.class.getSimpleName()); - } - - return Optional.absent(); - } - - @SuppressWarnings("unchecked") - public static Optional extractValue(Map map, String key, Class expectedClass){ - if(MapUtils.isNullOrEmptyKey(map, key)) - return Optional.absent(); - - try { - - Object value = map.get(key); - - if(expectedClass == TimeZone.class){ - if(value == null) - return Optional.absent(); - - if (!(value instanceof String)) - return Optional.absent(); - - TimeZone finalTimeZone = - TimeZoneUtils - .getInstance() - .getValidTimeZone((String) value); - - if(finalTimeZone == null) - return Optional.absent(); - - return Optional.fromNullable(expectedClass.cast(finalTimeZone)); - } - - if(expectedClass == Calendar.class){ - if(value == null) - return Optional.absent(); - - Calendar finalCalendar = - CalendarUtils - .getInstance() - .calendarFromString((String) value); - - return Optional.fromNullable(expectedClass.cast(finalCalendar)); - } - - if(Number.class.isAssignableFrom(expectedClass)){ - - // Hexadecimal color conversion - if(expectedClass == Long.class && value instanceof String){ - Pattern pattern = Pattern.compile("(0x|#)(\\w{2})?(\\w{6})", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher((String) value); - - // 0x000000 hexadecimal color conversion - if(matcher.find()) { - String transparency = matcher.group(2); - String textValue = (transparency == null ? "FF" : transparency) + matcher.group(3); - long finalValue = 0L; - if(!StringUtils.getInstance().isNullOrEmpty(textValue)){ - finalValue += Long.parseLong(textValue, 16); - } - return Optional.fromNullable(expectedClass.cast(finalValue)); - } - } - - if(value instanceof Number){ - String expectedClassName = expectedClass.getSimpleName().toLowerCase(); - switch (expectedClassName){ - case "integer": value = ((Number)value).intValue(); break; - case "double": value = ((Number)value).doubleValue(); break; - case "float": value = ((Number)value).floatValue(); break; - case "long": value = ((Number)value).longValue(); break; - case "short": value = ((Number)value).shortValue(); break; - case "byte": value = ((Number)value).byteValue(); break; - } - } - } - - if(value instanceof List && expectedClass.isArray()){ - String expectedClassName = expectedClass.getSimpleName().toLowerCase(); - switch (expectedClassName){ - case "double[]": value = Doubles.toArray((List)value); break; - case "long[]": value = Longs.toArray((List)value); break; - case "short[]": value = Shorts.toArray((List)value); break; - case "int[]": value = Ints.toArray((List)value); break; - case "byte[]": value = Bytes.toArray((List)value); break; - case "float[]": value = Floats.toArray((List)value); break; - } - return Optional.fromNullable(expectedClass.cast(value)); - } - - if(expectedClass.isInstance(value)){ - return Optional.fromNullable(expectedClass.cast(value)); - } - - // TODO REGRESSION TO PRIMITIVES. IS NOT SO NECESSARY, DUE MAPS AND GSON DO NOT USE THEN. ITS A OVERKILL SOLUTION - /*if(expectedClass.isPrimitive()) { - Class objectClass = value.getClass(); - if (!objectClass.isPrimitive()) { - Class primitiveObjectClass = (Class) objectClass.getField("TYPE").get(null); - if(expectedClass.equals(primitiveObjectClass)){ - primitiveObjectClass. - return Optional.of(T.cast(value)); - } - } - }*/ - } - catch (Exception e){ - Logger.d(TAG, key + " is not a type of " + Optional.class.getSimpleName()); - } - - return Optional.absent(); - } - - // This is fancier than Map.putAll(Map) - @SuppressWarnings("rawtypes") - public static Map deepMerge(@Nullable Map original, @Nullable Map newMap) { - if (original == null) { - if (newMap == null) return null; - return new HashMap<>(newMap); - } - if (newMap == null) return new HashMap<>(original); - - original = new HashMap<>(original); - for (Object key : newMap.keySet()) { - if (newMap.get(key) instanceof Map && original.get(key) instanceof Map) { - Map originalChild = (Map) original.get(key); - Map newChild = (Map) newMap.get(key); - original.put(key, deepMerge(originalChild, newChild)); - } else { - original.put(key, newMap.get(key)); - } - } - return original; - } - -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/MediaUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/MediaUtils.java deleted file mode 100644 index 70254fba..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/MediaUtils.java +++ /dev/null @@ -1,77 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_ASSET; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_FILE; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_NETWORK; -import static me.carda.awesome_notifications.core.Definitions.MEDIA_VALID_RESOURCE; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import me.carda.awesome_notifications.core.enumerators.MediaSource; - -public abstract class MediaUtils { - - protected StringUtils stringUtils; - - protected Boolean matchMediaType(String regex, String mediaPath){ - return matchMediaType(regex, mediaPath, true); - } - - protected Boolean matchMediaType(String regex, String mediaPath, Boolean filterEmpty){ - Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); - Matcher m = p.matcher(mediaPath); - String s = mediaPath.replaceFirst(regex, ""); - return m.find() && (!filterEmpty || !s.isEmpty()); - } - - public MediaSource getMediaSourceType(String mediaPath) { - - if (mediaPath != null) { - - if (matchMediaType(MEDIA_VALID_NETWORK, mediaPath, false)) { - return MediaSource.Network; - } - - if (matchMediaType(MEDIA_VALID_FILE, mediaPath)) { - return MediaSource.File; - } - - if (matchMediaType(MEDIA_VALID_RESOURCE, mediaPath)) { - return MediaSource.Resource; - } - - if (matchMediaType(MEDIA_VALID_ASSET, mediaPath)) { - return MediaSource.Asset; - } - - } - return MediaSource.Unknown; - } - - public String cleanMediaPath(String mediaPath) { - if (mediaPath != null) { - Pattern pattern = Pattern.compile("^https?:\\/\\/", Pattern.CASE_INSENSITIVE); - Pattern pattern2 = Pattern.compile("^(asset:\\/\\/)(.*)", Pattern.CASE_INSENSITIVE); - Pattern pattern3 = Pattern.compile("^(file:\\/\\/)(.*)", Pattern.CASE_INSENSITIVE); - Pattern pattern4 = Pattern.compile("^(resource:\\/\\/)(.*)", Pattern.CASE_INSENSITIVE); - - if(pattern.matcher(mediaPath).find()){ - return mediaPath; - } - - if(pattern2.matcher(mediaPath).find()){ - return pattern2.matcher(mediaPath).replaceAll("$2"); - } - - if(pattern3.matcher(mediaPath).find()){ - return pattern3.matcher(mediaPath).replaceAll("/$2"); - } - - if(pattern4.matcher(mediaPath).find()){ - return pattern4.matcher(mediaPath).replaceAll("$2"); - } - } - return null; - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/SerializableUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/SerializableUtils.java deleted file mode 100644 index 4f19c3a4..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/SerializableUtils.java +++ /dev/null @@ -1,60 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import androidx.annotation.NonNull; - -import java.util.Calendar; -import java.util.TimeZone; - -public class SerializableUtils { - - public static final String TAG = "SerializableUtils"; - - protected final EnumUtils enumUtils; - protected final StringUtils stringUtils; - protected final CalendarUtils calendarUtils; - protected final TimeZoneUtils timeZoneUtils; - - // ************** SINGLETON PATTERN *********************** - - protected SerializableUtils( - @NonNull EnumUtils enumUtils, - @NonNull StringUtils stringUtils, - @NonNull CalendarUtils calendarUtils, - @NonNull TimeZoneUtils timeZoneUtils - ){ - this.enumUtils = enumUtils; - this.stringUtils = stringUtils; - this.calendarUtils = calendarUtils; - this.timeZoneUtils = timeZoneUtils; - } - - protected static SerializableUtils instance; - public static SerializableUtils getInstance() { - if (instance == null) - instance = new SerializableUtils( - EnumUtils.getInstance(), - StringUtils.getInstance(), - CalendarUtils.getInstance(), - TimeZoneUtils.getInstance() - ); - return instance; - } - - // *********************** SERIALIZATION METHODS ********************************* - - public Object serializeCalendar(T value) { - return calendarUtils.calendarToString(value); - } - - public Object serializeTimeZone(T value) { - return timeZoneUtils.timeZoneToString(value); - } - - public Calendar deserializeCalendar(String value) { - return calendarUtils.calendarFromString(value); - } - - public TimeZone deserializeTimeZone(String value) { - return timeZoneUtils.getValidTimeZone(value); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/StringUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/StringUtils.java deleted file mode 100644 index 94a56e6c..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/StringUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.util.Iterator; -import java.util.Objects; - -public class StringUtils { - - public static final String TAG = "StringUtils"; - - // ************** SINGLETON PATTERN *********************** - - protected static StringUtils instance; - - protected StringUtils(){} - - public static StringUtils getInstance() { - if (instance == null) - instance = new StringUtils(); - return instance; - } - - // ******************************************************** - - public Boolean isNullOrEmpty(String string){ - return string == null || string.trim().isEmpty(); - } - - public String getValueOrDefault(String value, String defaultValue){ - return isNullOrEmpty(value) ? defaultValue : value; - } - - public String digestString(String reference){ - - MessageDigest md = null; - final String MD5 = "MD5"; - - if(reference == null) - reference = ""; - - try { - reference = reference.replaceAll("\\W+", ""); - - byte[] bytes = reference.getBytes(StandardCharsets.UTF_8); - - md = MessageDigest.getInstance(MD5); - md.reset(); - md.update(bytes); - - final BigInteger bigInt = new BigInteger(1, md.digest()); - return String.format("%032x", bigInt); - - } catch (Exception ex) { - //("MD5 Cryptography Not Supported"); - return reference; - } - } - - public > T getEnumFromString(Class clazz, String string) { - if( clazz != null && string != null ) { - try { - return Enum.valueOf(clazz, string.trim()); - } catch(IllegalArgumentException ignored) { - } - } - return null; - } - - public String join(final Iterator iterator, final String separator) { - - // handle null, zero and one elements before building a buffer - if (iterator == null) { - return null; - } - if (!iterator.hasNext()) { - return ""; - } - final Object first = iterator.next(); - if (!iterator.hasNext()) { - return Objects.toString(first, ""); - } - - // two or more elements - final StringBuilder buf = new StringBuilder(256); - if (first != null) { - buf.append(first); - } - - while (iterator.hasNext()) { - if (separator != null) { - buf.append(separator); - } - final Object obj = iterator.next(); - if (obj != null) { - buf.append(obj); - } - } - return buf.toString(); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/ThreadUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/ThreadUtils.java deleted file mode 100644 index 8756dc33..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/ThreadUtils.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import android.os.Looper; - -public class ThreadUtils { - - // ************** SINGLETON PATTERN *********************** - - private static ThreadUtils instance; - - private ThreadUtils(){} - public static ThreadUtils getInstance() { - if (instance == null) - instance = new ThreadUtils(); - return instance; - } - - // ******************************************************** - - public boolean isMainThread(){ - return Looper.myLooper() == Looper.getMainLooper(); - } -} diff --git a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/TimeZoneUtils.java b/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/TimeZoneUtils.java deleted file mode 100644 index 3985a53f..00000000 --- a/example/android/awn_core/src/main/java/me/carda/awesome_notifications/core/utils/TimeZoneUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.annotation.Nullable; - -public class TimeZoneUtils { - - // ***************************** SINGLETON PATTERN ***************************** - - protected static final TimeZoneUtils instance = new TimeZoneUtils(); - - private TimeZoneUtils(){} - - public static TimeZoneUtils getInstance(){ - return instance; - } - - // ******************************************************************************* - - @Nullable - public TimeZone getValidTimeZone(@Nullable String timeZoneId){ - if(timeZoneId == null) - return null; - - final String regex = "^((\\-|\\+)?(\\d{2}(:\\d{2})?))|(\\S+)$"; - - final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); - final Matcher matcher = pattern.matcher(timeZoneId); - - TimeZone finalTimeZone; - if(matcher.matches()){ - if(matcher.group(1) != null) { - finalTimeZone = TimeZone.getTimeZone( - "GMT"+ - (matcher.group(2) == null ? "+" : matcher.group(2)) + - matcher.group(3) + - (matcher.group(4) == null ? ":00" : matcher.group(4))); - } - else { - finalTimeZone = TimeZone.getTimeZone(matcher.group(5)); - } - } - else { - finalTimeZone = TimeZone.getDefault(); - } - - if(finalTimeZone.getID().contains(timeZoneId)) - return finalTimeZone; - - return null; - } - - @Nullable - public String timeZoneToString(@Nullable TimeZone timeZone){ - if(timeZone == null) - return null; - - return timeZone.getID(); - } -} diff --git a/example/android/awn_core/src/test/java/me/carda/awesome_notifications/core/ExampleUnitTest.java b/example/android/awn_core/src/test/java/me/carda/awesome_notifications/core/ExampleUnitTest.java deleted file mode 100644 index 7e810d0e..00000000 --- a/example/android/awn_core/src/test/java/me/carda/awesome_notifications/core/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.carda.awesome_notifications.core; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/example/android/awn_core/src/test/java/me/carda/awesome_notifications/core/utils/MapUtilsTest.java b/example/android/awn_core/src/test/java/me/carda/awesome_notifications/core/utils/MapUtilsTest.java deleted file mode 100644 index 7f81e668..00000000 --- a/example/android/awn_core/src/test/java/me/carda/awesome_notifications/core/utils/MapUtilsTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package me.carda.awesome_notifications.core.utils; - -import static org.junit.Assert.*; - -import android.util.ArrayMap; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class MapUtilsTest { - - @Before - public void setUp() throws Exception { - } - - @After - public void tearDown() throws Exception { - } - - @Test - public void isNullOrEmptyKey() { - } - - @Test - public void isNullOrEmpty() { - } - - @Test - public void extractArgument() { - } - - @Test - public void extractValue() { - } - - @Test - public void deepMerge() { - Map test1 = new HashMap() {{ - put("title", "title 1"); - put("body", "body 1"); - put("payload", new HashMap() {{ - put("key 1", "value 1"); - put("key 2", "value 2"); - }}); - put("actionButtons", Arrays.asList( - new HashMap() {{ - put("Button 1", "label 1"); - }}, - new HashMap() {{ - put("Button 2", "label 2"); - }} - )); - }}; - Map test2 = new HashMap() {{ - put("title", "Modified 1"); - put("body", "body 1"); - put("payload", new HashMap() {{ - put("key 1", "value 1"); - put("key 2", "modified 2"); - put("key 3", "value 3"); - }}); - put("actionButtons", Arrays.asList( - new HashMap() {{ - put("Button 1", "label test"); - }} - )); - }}; - - assertNotNull(MapUtils.deepMerge(test1, null)); - assertNotNull(MapUtils.deepMerge(null, test2)); - - Map result = MapUtils.deepMerge(test1, test2); - - assertNotNull(result); - assertEquals("Modified 1", result.get("title")); - assertEquals("body 1", result.get("body")); - assertEquals("value 1", ((Map) result.get("payload")).get("key 1")); - assertEquals("modified 2", ((Map) result.get("payload")).get("key 2")); - assertEquals("value 3", ((Map) result.get("payload")).get("key 3")); - assertNotNull(result.get("actionButtons")); - assertEquals(1, ((List) result.get("actionButtons")).size()); - - } -} \ No newline at end of file diff --git a/example/android/build.gradle b/example/android/build.gradle index 83ae2200..3cdaac95 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 0e7d46d6..efa37ac1 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,5 +1,12 @@ include ':app' -//include ':awn_core' + +def useAwnLocalReferences = false +if (useAwnLocalReferences) { + include ':awn_core' + def localAndroidCoreFolder = new File(rootDir, "../../../AndroidAwnCore/core") + assert localAndroidCoreFolder.exists() + project(":awn_core").projectDir=localAndroidCoreFolder +} def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/example/assets/images/american.jpg b/example/assets/images/american.jpg new file mode 100644 index 00000000..5d871774 Binary files /dev/null and b/example/assets/images/american.jpg differ diff --git a/example/assets/images/awn-rocks-de.jpg b/example/assets/images/awn-rocks-de.jpg new file mode 100644 index 00000000..124b2b9c Binary files /dev/null and b/example/assets/images/awn-rocks-de.jpg differ diff --git a/example/assets/images/awn-rocks-en.jpg b/example/assets/images/awn-rocks-en.jpg new file mode 100644 index 00000000..ce121c36 Binary files /dev/null and b/example/assets/images/awn-rocks-en.jpg differ diff --git a/example/assets/images/awn-rocks-es.jpg b/example/assets/images/awn-rocks-es.jpg new file mode 100644 index 00000000..54d4dc33 Binary files /dev/null and b/example/assets/images/awn-rocks-es.jpg differ diff --git a/example/assets/images/awn-rocks-ko.jpg b/example/assets/images/awn-rocks-ko.jpg new file mode 100644 index 00000000..9e7008ca Binary files /dev/null and b/example/assets/images/awn-rocks-ko.jpg differ diff --git a/example/assets/images/awn-rocks-pt-br.jpg b/example/assets/images/awn-rocks-pt-br.jpg new file mode 100644 index 00000000..17f01130 Binary files /dev/null and b/example/assets/images/awn-rocks-pt-br.jpg differ diff --git a/example/assets/images/awn-rocks-pt.jpg b/example/assets/images/awn-rocks-pt.jpg new file mode 100644 index 00000000..17f01130 Binary files /dev/null and b/example/assets/images/awn-rocks-pt.jpg differ diff --git a/example/assets/images/awn-rocks-zh.jpg b/example/assets/images/awn-rocks-zh.jpg new file mode 100644 index 00000000..39147858 Binary files /dev/null and b/example/assets/images/awn-rocks-zh.jpg differ diff --git a/example/assets/images/awn-rocks.psd b/example/assets/images/awn-rocks.psd new file mode 100644 index 00000000..f6de2c27 Binary files /dev/null and b/example/assets/images/awn-rocks.psd differ diff --git a/example/assets/images/brazilian.jpg b/example/assets/images/brazilian.jpg new file mode 100644 index 00000000..346afa8d Binary files /dev/null and b/example/assets/images/brazilian.jpg differ diff --git a/example/assets/images/chinese.jpg b/example/assets/images/chinese.jpg new file mode 100644 index 00000000..912e701d Binary files /dev/null and b/example/assets/images/chinese.jpg differ diff --git a/example/assets/images/german.jpg b/example/assets/images/german.jpg new file mode 100644 index 00000000..0893645d Binary files /dev/null and b/example/assets/images/german.jpg differ diff --git a/example/assets/images/korean.jpg b/example/assets/images/korean.jpg new file mode 100644 index 00000000..1f0611f2 Binary files /dev/null and b/example/assets/images/korean.jpg differ diff --git a/example/assets/images/portuguese.jpg b/example/assets/images/portuguese.jpg new file mode 100644 index 00000000..9922ae82 Binary files /dev/null and b/example/assets/images/portuguese.jpg differ diff --git a/example/assets/images/spanish.jpg b/example/assets/images/spanish.jpg new file mode 100644 index 00000000..17cef29e Binary files /dev/null and b/example/assets/images/spanish.jpg differ diff --git a/example/assets/images/test_image.png b/example/assets/images/test_image.png new file mode 100644 index 00000000..43e413d2 Binary files /dev/null and b/example/assets/images/test_image.png differ diff --git a/example/assets/readme/buy-me-a-coffee.jpeg b/example/assets/readme/buy-me-a-coffee.jpeg new file mode 100644 index 00000000..05c4846c Binary files /dev/null and b/example/assets/readme/buy-me-a-coffee.jpeg differ diff --git a/example/assets/readme/open-source-175x29.png b/example/assets/readme/open-source-175x29.png new file mode 100644 index 00000000..206bdd03 Binary files /dev/null and b/example/assets/readme/open-source-175x29.png differ diff --git a/example/assets/readme/stripe.png b/example/assets/readme/stripe.png new file mode 100644 index 00000000..8b5fc2b8 Binary files /dev/null and b/example/assets/readme/stripe.png differ diff --git a/example/ios/Podfile b/example/ios/Podfile index 687da38c..d06eaa03 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -31,13 +31,18 @@ target 'Runner' do use_frameworks! use_modular_headers! - #pod 'IosAwnCore', '0.7.0-alpha.3' + #pod 'IosAwnCore', '0.7.5-dev.3' #pod 'IosAwnCore', :path => '../../../IosAwnCore/' flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] == '8.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' + end + end flutter_additional_ios_build_settings(target) end end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 26e7f069..22d7f341 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,18 +1,20 @@ PODS: - - awesome_notifications (0.0.5): + - awesome_notifications (0.7.5): - Flutter - - IosAwnCore (= 0.7.3) - - device_info (0.0.1): + - IosAwnCore (= 0.7.5) + - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) - fluttertoast (0.0.2): - Flutter - Toast - - IosAwnCore (0.7.3) - - path_provider_ios (0.0.1): + - IosAwnCore (0.7.5) + - path_provider_foundation (0.0.1): - Flutter - - shared_preferences_ios (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): - Flutter + - FlutterMacOS - Toast (4.0.0) - url_launcher_ios (0.0.1): - Flutter @@ -21,11 +23,11 @@ PODS: DEPENDENCIES: - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`) - - device_info (from `.symlinks/plugins/device_info/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - vibration (from `.symlinks/plugins/vibration/ios`) @@ -37,33 +39,33 @@ SPEC REPOS: EXTERNAL SOURCES: awesome_notifications: :path: ".symlinks/plugins/awesome_notifications/ios" - device_info: - :path: ".symlinks/plugins/device_info/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - path_provider_ios: - :path: ".symlinks/plugins/path_provider_ios/ios" - shared_preferences_ios: - :path: ".symlinks/plugins/shared_preferences_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" vibration: :path: ".symlinks/plugins/vibration/ios" SPEC CHECKSUMS: - awesome_notifications: d63d9a25f126860f9a600850d99772237895b3ba - device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 + awesome_notifications: 866bda0a8fcf8f83cb0fe76762f730c6b77083db + device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037 - IosAwnCore: 6494e0e174d49f04f513e8a002187be226889a37 - path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 - shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c + IosAwnCore: 29cb6224ff81d33529b0055556897b63471de801 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 - url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 vibration: 7d883d141656a1c1a6d8d238616b2042a51a1241 -PODFILE CHECKSUM: 8f69bd6e30716cc51439574c7edb9fdc3d779b52 +PODFILE CHECKSUM: 75def7c0fdb0efe5a8b7599c2e7d9302906d572f -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 6ee5a7ef..cc284496 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,20 +3,18 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ - 06133C53BCB23C8EE09E7525 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05380AFC25F5C29A001D5D0A /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 47CF3D5FF75CE3A5279A143F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05380B2325F5C441001D5D0A /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 93991FC2CC0A2008236BBBCF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49B82A9F0A400AD5BC1E2AC2 /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; C75A3FCF27A6EF7A000DC28D /* res_morph_power_rangers.aiff in Resources */ = {isa = PBXBuildFile; fileRef = C75A3FCE27A6EF7A000DC28D /* res_morph_power_rangers.aiff */; }; - EB9EDE2CFFEA74D255CD1DC7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 449D413CE332323BB49015B0 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -33,20 +31,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 05380AFC25F5C29A001D5D0A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 05380B1625F5C311001D5D0A /* awesome_notifications.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = awesome_notifications.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 05380B1A25F5C324001D5D0A /* awesome_notifications.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = awesome_notifications.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 05380B1F25F5C438001D5D0A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 05380B2325F5C441001D5D0A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 05C0EE5025F2DA0100DC0E72 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; 05C0EE5225F2DA0100DC0E72 /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; }; 05C0EE8525F2DF0200DC0E72 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 308CB41F8BE9487F2766792A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 449D413CE332323BB49015B0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 658F28815C0BBD624B481175 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 6C4FE32154B402C6DE2CFB72 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 49B82A9F0A400AD5BC1E2AC2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -57,8 +51,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A20569149C466242957DE3AD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; C75A3FCE27A6EF7A000DC28D /* res_morph_power_rangers.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = res_morph_power_rangers.aiff; sourceTree = ""; }; - EB53628D31E8A9D73BFF0E7D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + DC2D97CBB1102812A360FA09 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -66,9 +61,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - EB9EDE2CFFEA74D255CD1DC7 /* Pods_Runner.framework in Frameworks */, - 06133C53BCB23C8EE09E7525 /* Pods_Runner.framework in Frameworks */, - 47CF3D5FF75CE3A5279A143F /* Pods_Runner.framework in Frameworks */, + 93991FC2CC0A2008236BBBCF /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -78,14 +71,11 @@ 05C0EE4F25F2DA0100DC0E72 /* Frameworks */ = { isa = PBXGroup; children = ( - 05380B2325F5C441001D5D0A /* Pods_Runner.framework */, - 05380B1F25F5C438001D5D0A /* Pods_Runner.framework */, 05380B1A25F5C324001D5D0A /* awesome_notifications.framework */, 05380B1625F5C311001D5D0A /* awesome_notifications.framework */, - 05380AFC25F5C29A001D5D0A /* Pods_Runner.framework */, 05C0EE5025F2DA0100DC0E72 /* UserNotifications.framework */, 05C0EE5225F2DA0100DC0E72 /* UserNotificationsUI.framework */, - 449D413CE332323BB49015B0 /* Pods_Runner.framework */, + 49B82A9F0A400AD5BC1E2AC2 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -140,9 +130,9 @@ C63CE89E4FB0ED8AB1ED2C36 /* Pods */ = { isa = PBXGroup; children = ( - 6C4FE32154B402C6DE2CFB72 /* Pods-Runner.debug.xcconfig */, - 658F28815C0BBD624B481175 /* Pods-Runner.release.xcconfig */, - EB53628D31E8A9D73BFF0E7D /* Pods-Runner.profile.xcconfig */, + A20569149C466242957DE3AD /* Pods-Runner.debug.xcconfig */, + 308CB41F8BE9487F2766792A /* Pods-Runner.release.xcconfig */, + DC2D97CBB1102812A360FA09 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -154,14 +144,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - B4EF8673CC2DD6973A9C2A26 /* [CP] Check Pods Manifest.lock */, + 94C6F8AF2F4745FB96FCED08 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 05C0EE4425F2D9E700DC0E72 /* Embed App Extensions */, - D206D6193D36967F987FAB57 /* [CP] Embed Pods Frameworks */, + B3E47F495DBB68B75BD73794 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -179,7 +169,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1240; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -224,10 +214,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -236,43 +228,43 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 94C6F8AF2F4745FB96FCED08 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Run Script"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; }; - B4EF8673CC2DD6973A9C2A26 /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( ); + name = "Run Script"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - D206D6193D36967F987FAB57 /* [CP] Embed Pods Frameworks */ = { + B3E47F495DBB68B75BD73794 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -364,7 +356,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -386,7 +378,7 @@ DEVELOPMENT_TEAM = 4R89PK952Q; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -446,7 +438,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -495,7 +487,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -519,7 +511,7 @@ DEVELOPMENT_TEAM = 4R89PK952Q; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -546,7 +538,7 @@ DEVELOPMENT_TEAM = 4R89PK952Q; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6e..b52b2e69 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + AwnAppGroupName + group.awn.fixedAppGroup CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -50,5 +52,7 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements index 903def2a..127fedee 100644 --- a/example/ios/Runner/Runner.entitlements +++ b/example/ios/Runner/Runner.entitlements @@ -4,5 +4,7 @@ aps-environment development + com.apple.security.application-groups + diff --git a/example/lib/main.dart b/example/lib/main.dart index bf60d4b7..a4bc2bb2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,19 @@ +import 'dart:async'; +import 'dart:isolate'; +import 'dart:ui'; + import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; +import 'package:palette_generator/palette_generator.dart'; Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + // Always initialize Awesome Notifications await NotificationController.initializeLocalNotifications(); + await NotificationController.initializeIsolateReceivePort(); runApp(const MyApp()); } @@ -42,6 +51,17 @@ class NotificationController { .getInitialNotificationAction(removeFromActionEvents: false); } + static ReceivePort? receivePort; + static Future initializeIsolateReceivePort() async { + receivePort = ReceivePort('Notification action port in main isolate') + ..listen( + (silentData) => onActionReceivedImplementationMethod(silentData)); + + // This initialization only happens on main isolate + IsolateNameServer.registerPortWithName( + receivePort!.sendPort, 'notification_action_port'); + } + /// ********************************************* /// NOTIFICATION EVENTS LISTENER /// ********************************************* @@ -58,22 +78,40 @@ class NotificationController { @pragma('vm:entry-point') static Future onActionReceivedMethod( ReceivedAction receivedAction) async { - - if( - receivedAction.actionType == ActionType.SilentAction || - receivedAction.actionType == ActionType.SilentBackgroundAction - ){ + if (receivedAction.actionType == ActionType.SilentAction || + receivedAction.actionType == ActionType.SilentBackgroundAction) { // For background actions, you must hold the execution until the end - print('Message sent via notification input: "${receivedAction.buttonKeyInput}"'); + print( + 'Message sent via notification input: "${receivedAction.buttonKeyInput}"'); await executeLongTaskInBackground(); + } else { + // this process is only necessary when you need to redirect the user + // to a new page or use a valid context, since parallel isolates do not + // have valid context, so you need redirect the execution to main isolate + if (receivePort == null) { + print( + 'onActionReceivedMethod was called inside a parallel dart isolate.'); + SendPort? sendPort = + IsolateNameServer.lookupPortByName('notification_action_port'); + + if (sendPort != null) { + print('Redirecting the execution to main isolate process.'); + sendPort.send(receivedAction); + return; + } + } + + return onActionReceivedImplementationMethod(receivedAction); } - else { - MyApp.navigatorKey.currentState?.pushNamedAndRemoveUntil( - '/notification-page', - (route) => - (route.settings.name != '/notification-page') || route.isFirst, - arguments: receivedAction); - } + } + + static Future onActionReceivedImplementationMethod( + ReceivedAction receivedAction) async { + MyApp.navigatorKey.currentState?.pushNamedAndRemoveUntil( + '/notification-page', + (route) => + (route.settings.name != '/notification-page') || route.isFirst, + arguments: receivedAction); } /// ********************************************* @@ -96,7 +134,7 @@ class NotificationController { children: [ Expanded( child: Image.asset( - 'assets/animated-bell.gif', + 'assets/images/animated-bell.gif', height: MediaQuery.of(context).size.height * 0.3, fit: BoxFit.fitWidth, ), @@ -178,8 +216,7 @@ class NotificationController { key: 'REPLY', label: 'Reply Message', requireInputText: true, - actionType: ActionType.SilentAction - ), + actionType: ActionType.SilentAction), NotificationActionButton( key: 'DISMISS', label: 'Dismiss', @@ -193,30 +230,14 @@ class NotificationController { if (!isAllowed) isAllowed = await displayNotificationRationale(); if (!isAllowed) return; - await AwesomeNotifications().createNotification( - content: NotificationContent( - id: -1, // -1 is replaced by a random number - channelKey: 'alerts', - title: "Huston! The eagle has landed!", - body: - "A small step for a man, but a giant leap to Flutter's community!", - bigPicture: 'https://storage.googleapis.com/cms-storage-bucket/d406c736e7c4c57f5f61.png', - largeIcon: 'https://storage.googleapis.com/cms-storage-bucket/0dbfcc7a59cd1cf16282.png', - //'asset://assets/images/balloons-in-sky.jpg', - notificationLayout: NotificationLayout.BigPicture, - payload: { - 'notificationId': '1234567890' - }), - actionButtons: [ - NotificationActionButton(key: 'REDIRECT', label: 'Redirect'), - NotificationActionButton( - key: 'DISMISS', - label: 'Dismiss', - actionType: ActionType.DismissAction, - isDangerousOption: true) - ], - schedule: NotificationCalendar.fromDate( - date: DateTime.now().add(const Duration(seconds: 10)))); + await myNotifyScheduleInHours( + title: 'test', + msg: 'test message', + heroThumbUrl: + 'https://storage.googleapis.com/cms-storage-bucket/d406c736e7c4c57f5f61.png', + hoursFromNow: 5, + username: 'test user', + repeatNotif: false); } static Future resetBadgeCounter() async { @@ -228,6 +249,52 @@ class NotificationController { } } +Future myNotifyScheduleInHours({ + required int hoursFromNow, + required String heroThumbUrl, + required String username, + required String title, + required String msg, + bool repeatNotif = false, +}) async { + var nowDate = DateTime.now().add(Duration(hours: hoursFromNow, seconds: 5)); + await AwesomeNotifications().createNotification( + schedule: NotificationCalendar( + //weekday: nowDate.day, + hour: nowDate.hour, + minute: 0, + second: nowDate.second, + repeats: repeatNotif, + //allowWhileIdle: true, + ), + // schedule: NotificationCalendar.fromDate( + // date: DateTime.now().add(const Duration(seconds: 10))), + content: NotificationContent( + id: -1, + channelKey: 'basic_channel', + title: '${Emojis.food_bowl_with_spoon} $title', + body: '$username, $msg', + bigPicture: heroThumbUrl, + notificationLayout: NotificationLayout.BigPicture, + //actionType : ActionType.DismissAction, + color: Colors.black, + backgroundColor: Colors.black, + // customSound: 'resource://raw/notif', + payload: {'actPag': 'myAct', 'actType': 'food', 'username': username}, + ), + actionButtons: [ + NotificationActionButton( + key: 'NOW', + label: 'btnAct1', + ), + NotificationActionButton( + key: 'LATER', + label: 'btnAct2', + ), + ], + ); +} + /// ********************************************* /// MAIN WIDGET /// ********************************************* @@ -371,118 +438,212 @@ class _MyHomePageState extends State { /// ********************************************* /// NOTIFICATION PAGE /// ********************************************* -/// -class NotificationPage extends StatelessWidget { - const NotificationPage({Key? key, required this.receivedAction}) - : super(key: key); +class NotificationPage extends StatefulWidget { + const NotificationPage({ + Key? key, + required this.receivedAction, + }) : super(key: key); final ReceivedAction receivedAction; + @override + NotificationPageState createState() => NotificationPageState(); +} + +class NotificationPageState extends State { + bool get hasTitle => widget.receivedAction.title?.isNotEmpty ?? false; + bool get hasBody => widget.receivedAction.body?.isNotEmpty ?? false; + bool get hasLargeIcon => widget.receivedAction.largeIconImage != null; + bool get hasBigPicture => widget.receivedAction.bigPictureImage != null; + + double bigPictureSize = 0.0; + double largeIconSize = 0.0; + bool isTotallyCollapsed = false; + bool bigPictureIsPredominantlyWhite = true; + + ScrollController scrollController = ScrollController(); + + Future isImagePredominantlyWhite(ImageProvider imageProvider) async { + final paletteGenerator = + await PaletteGenerator.fromImageProvider(imageProvider); + final dominantColor = + paletteGenerator.dominantColor?.color ?? Colors.transparent; + return dominantColor.computeLuminance() > 0.5; + } + + @override + void initState() { + super.initState(); + scrollController.addListener(_scrollListener); + + if (hasBigPicture) { + isImagePredominantlyWhite(widget.receivedAction.bigPictureImage!) + .then((isPredominantlyWhite) => setState(() { + bigPictureIsPredominantlyWhite = isPredominantlyWhite; + })); + } + } + + void _scrollListener() { + bool pastScrollLimit = scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 240; + + if (!hasBigPicture) { + isTotallyCollapsed = true; + return; + } + + if (isTotallyCollapsed) { + if (!pastScrollLimit) { + setState(() { + isTotallyCollapsed = false; + }); + } + } else { + if (pastScrollLimit) { + setState(() { + isTotallyCollapsed = true; + }); + } + } + } + @override Widget build(BuildContext context) { - bool hasLargeIcon = receivedAction.largeIconImage != null; - bool hasBigPicture = receivedAction.bigPictureImage != null; - double bigPictureSize = MediaQuery.of(context).size.height * .4; - double largeIconSize = - MediaQuery.of(context).size.height * (hasBigPicture ? .12 : .2); + bigPictureSize = MediaQuery.of(context).size.height * .4; + largeIconSize = + MediaQuery.of(context).size.height * (hasBigPicture ? .16 : .2); + + if (!hasBigPicture) { + isTotallyCollapsed = true; + } return Scaffold( - appBar: AppBar( - title: Text(receivedAction.title ?? receivedAction.body ?? ''), - ), - body: SingleChildScrollView( - padding: EdgeInsets.zero, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: - hasBigPicture ? bigPictureSize + 40 : largeIconSize + 60, - child: hasBigPicture - ? Stack( - children: [ - if (hasBigPicture) - FadeInImage( - placeholder: const NetworkImage( - 'https://cdn.syncfusion.com/content/images/common/placeholder.gif'), - //AssetImage('assets/images/placeholder.gif'), - height: bigPictureSize, - width: MediaQuery.of(context).size.width, - image: receivedAction.bigPictureImage!, - fit: BoxFit.cover, - ), - if (hasLargeIcon) - Positioned( - bottom: 15, - left: 20, + body: CustomScrollView( + controller: scrollController, + physics: const BouncingScrollPhysics(), + slivers: [ + SliverAppBar( + elevation: 0, + centerTitle: true, + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon( + Icons.arrow_back_ios_rounded, + color: isTotallyCollapsed || bigPictureIsPredominantlyWhite + ? Colors.black + : Colors.white, + ), + ), + systemOverlayStyle: + isTotallyCollapsed || bigPictureIsPredominantlyWhite + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, + expandedHeight: hasBigPicture + ? bigPictureSize + (hasLargeIcon ? 40 : 0) + : (hasLargeIcon) + ? largeIconSize + 10 + : MediaQuery.of(context).padding.top + 28, + backgroundColor: Colors.transparent, + stretch: true, + flexibleSpace: FlexibleSpaceBar( + stretchModes: const [StretchMode.zoomBackground], + centerTitle: true, + expandedTitleScale: 1, + collapseMode: CollapseMode.pin, + title: (!hasLargeIcon) + ? null + : Stack(children: [ + Positioned( + bottom: 0, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: hasBigPicture + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + SizedBox( + height: largeIconSize, + width: largeIconSize, child: ClipRRect( borderRadius: BorderRadius.all( Radius.circular(largeIconSize)), child: FadeInImage( placeholder: const NetworkImage( 'https://cdn.syncfusion.com/content/images/common/placeholder.gif'), - //AssetImage('assets/images/placeholder.gif'), - height: largeIconSize, - width: largeIconSize, - image: receivedAction.largeIconImage!, + image: widget.receivedAction.largeIconImage!, fit: BoxFit.cover, ), ), - ) - ], - ) - : Center( - child: ClipRRect( - borderRadius: - BorderRadius.all(Radius.circular(largeIconSize)), - child: FadeInImage( - placeholder: const NetworkImage( - 'https://cdn.syncfusion.com/content/images/common/placeholder.gif'), - //AssetImage('assets/images/placeholder.gif'), - height: largeIconSize, - width: largeIconSize, - image: receivedAction.largeIconImage!, - fit: BoxFit.cover, - ), + ), + ], ), - )), - Padding( - padding: const EdgeInsets.only(bottom: 20.0, left: 20, right: 20), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan(children: [ - if (receivedAction.title?.isNotEmpty ?? false) - TextSpan( - text: receivedAction.title!, - style: Theme.of(context).textTheme.titleLarge, - ), - if ((receivedAction.title?.isNotEmpty ?? false) && - (receivedAction.body?.isNotEmpty ?? false)) - TextSpan( - text: '\n\n', - style: Theme.of(context).textTheme.bodyMedium, ), - if (receivedAction.body?.isNotEmpty ?? false) - TextSpan( - text: receivedAction.body!, - style: Theme.of(context).textTheme.bodyMedium, + ]), + background: hasBigPicture + ? Padding( + padding: EdgeInsets.only(bottom: hasLargeIcon ? 60 : 20), + child: FadeInImage( + placeholder: const NetworkImage( + 'https://cdn.syncfusion.com/content/images/common/placeholder.gif'), + height: bigPictureSize, + width: MediaQuery.of(context).size.width, + image: widget.receivedAction.bigPictureImage!, + fit: BoxFit.cover, ), - ])) - ], - ), + ) + : null, ), - Container( - color: Colors.black12, - padding: const EdgeInsets.all(20), - width: MediaQuery.of(context).size.width, - child: Text(receivedAction.toString()), + ), + SliverList( + delegate: SliverChildListDelegate( + [ + Padding( + padding: + const EdgeInsets.only(bottom: 20.0, left: 20, right: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + text: TextSpan(children: [ + if (hasTitle) + TextSpan( + text: widget.receivedAction.title!, + style: Theme.of(context).textTheme.titleLarge, + ), + if (hasBody) + WidgetSpan( + child: Padding( + padding: EdgeInsets.only( + top: hasTitle ? 16.0 : 0.0, + ), + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: Text( + widget.receivedAction.bodyWithoutHtml ?? + '', + style: Theme.of(context) + .textTheme + .bodyText2)), + ), + ), + ]), + ), + ], + ), + ), + Container( + color: Colors.black12, + padding: const EdgeInsets.all(20), + width: MediaQuery.of(context).size.width, + child: Text(widget.receivedAction.toString()), + ), + ], ), - ], - ), + ), + ], ), ); } diff --git a/example/lib/main_complete.dart b/example/lib/main_complete.dart index 106a9910..4a1f4f50 100644 --- a/example/lib/main_complete.dart +++ b/example/lib/main_complete.dart @@ -3,7 +3,6 @@ // import 'package:firebase_messaging/firebase_messaging.dart'; // import 'package:awesome_notifications_example/notifications/firebase_controller.dart'; -import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:flutter/material.dart'; import 'package:awesome_notifications_example/routes/routes.dart'; @@ -67,4 +66,4 @@ class _AppState extends State { theme: ThemesController.currentTheme, ); } -} +} \ No newline at end of file diff --git a/example/lib/notifications/notifications_controller.dart b/example/lib/notifications/notifications_controller.dart index 29000c86..6f27d627 100644 --- a/example/lib/notifications/notifications_controller.dart +++ b/example/lib/notifications/notifications_controller.dart @@ -1,3 +1,6 @@ +import 'dart:isolate'; +import 'dart:ui'; + import 'package:awesome_notifications_example/main_complete.dart'; import 'package:awesome_notifications_example/routes/routes.dart'; import 'package:awesome_notifications_example/utils/common_functions.dart' if (dart.library.html) @@ -12,6 +15,8 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; +//ignore_for_file: avoid_print + class NotificationsController { static ReceivedAction? initialCallAction; @@ -19,6 +24,7 @@ class NotificationsController { // INITIALIZATIONS // *************************************************************** static Future initializeLocalNotifications() async { + await initializeIsolateReceivePort(); await AwesomeNotifications().initialize( 'resource://drawable/res_app_icon', [ @@ -256,19 +262,32 @@ class NotificationsController { } // *************************************************************** - // NOTIFICATIONS EVENT LISTENERS + // ON ACTION EVENT REDIRECTION TO MAIN ISOLATE // *************************************************************** - static String _toSimpleEnum(NotificationLifeCycle lifeCycle) => - lifeCycle.toString().split('.').last; + static ReceivePort? receivePort; + static Future initializeIsolateReceivePort() async { + receivePort = ReceivePort('Notification action port in main isolate'); + receivePort!.listen((silentData) => onActionReceivedMethodImpl(silentData)); + + // This initialization only happens on main isolate + IsolateNameServer.registerPortWithName( + receivePort!.sendPort, + 'notification_action_port'); + } + + // *************************************************************** + // NOTIFICATIONS EVENT LISTENERS + // *************************************************************** /// Use this method to detect when a new notification or a schedule is created @pragma("vm:entry-point") static Future onNotificationCreatedMethod( ReceivedNotification receivedNotification) async { + var message = 'Notification created on ${receivedNotification.createdLifeCycle?.name}'; + print(message); Fluttertoast.showToast( - msg: - 'Notification created on ${_toSimpleEnum(receivedNotification.createdLifeCycle!)}', + msg: message, toastLength: Toast.LENGTH_SHORT, backgroundColor: Colors.green, gravity: ToastGravity.BOTTOM); @@ -278,9 +297,13 @@ class NotificationsController { @pragma("vm:entry-point") static Future onNotificationDisplayedMethod( ReceivedNotification receivedNotification) async { + var message1 = 'Notification displayed on ${receivedNotification.displayedLifeCycle?.name}'; + var message2 = 'Notification displayed at ${receivedNotification.displayedDate}'; + + print(message1); + print(message2); Fluttertoast.showToast( - msg: - 'Notification displayed on ${_toSimpleEnum(receivedNotification.displayedLifeCycle!)}', + msg: message1, toastLength: Toast.LENGTH_SHORT, backgroundColor: Colors.blue, gravity: ToastGravity.BOTTOM); @@ -290,9 +313,9 @@ class NotificationsController { @pragma("vm:entry-point") static Future onDismissActionReceivedMethod( ReceivedAction receivedAction) async { + var message = 'Notification dismissed on ${receivedAction.dismissedLifeCycle?.name}'; Fluttertoast.showToast( - msg: - 'Notification dismissed on ${_toSimpleEnum(receivedAction.dismissedLifeCycle!)}', + msg: message, toastLength: Toast.LENGTH_SHORT, backgroundColor: Colors.orange, gravity: ToastGravity.BOTTOM); @@ -302,6 +325,30 @@ class NotificationsController { @pragma("vm:entry-point") static Future onActionReceivedMethod( ReceivedAction receivedAction) async { + if (receivePort == null) { + print( + 'onActionReceivedMethod was called inside a parallel dart isolate.'); + SendPort? sendPort = + IsolateNameServer.lookupPortByName('notification_action_port'); + + if (sendPort != null) { + print('Redirecting the execution to main isolate process...'); + sendPort.send(receivedAction); + return; + } + } + + await onActionReceivedMethodImpl(receivedAction); + } + + + + static Future onActionReceivedMethodImpl( + ReceivedAction receivedAction) async { + var message = 'Action ${receivedAction.actionType?.name} received on ${ + receivedAction.actionLifeCycle?.name}'; + print(message); + // Always ensure that all plugins was initialized WidgetsFlutterBinding.ensureInitialized(); @@ -314,7 +361,8 @@ class NotificationsController { if (receivedAction.actionType != ActionType.SilentBackgroundAction) { Fluttertoast.showToast( msg: - '${isSilentAction ? 'Silent action' : 'Action'} received on ${_toSimpleEnum(receivedAction.actionLifeCycle!)}', + '${isSilentAction ? 'Silent action' : 'Action'}' + ' received on ${receivedAction.actionLifeCycle?.name}', toastLength: Toast.LENGTH_SHORT, backgroundColor: isSilentAction ? Colors.blueAccent : App.mainColor, gravity: ToastGravity.BOTTOM); @@ -322,7 +370,7 @@ class NotificationsController { switch (receivedAction.channelKey) { case 'call_channel': - if (receivedAction.actionLifeCycle != NotificationLifeCycle.AppKilled){ + if (receivedAction.actionLifeCycle != NotificationLifeCycle.Terminated){ await receiveCallNotificationAction(receivedAction); } break; @@ -459,4 +507,4 @@ class NotificationsController { initialCallAction = receivedAction; } } -} +} \ No newline at end of file diff --git a/example/lib/notifications/notifications_util.dart b/example/lib/notifications/notifications_util.dart index 70d46cea..979a9e85 100644 --- a/example/lib/notifications/notifications_util.dart +++ b/example/lib/notifications/notifications_util.dart @@ -80,8 +80,8 @@ class NotificationUtils { await showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Color(0xfffbfbfb), - title: Text('Get Notified!', + backgroundColor: const Color(0xfffbfbfb), + title: const Text('Get Notified!', maxLines: 2, textAlign: TextAlign.center, style: @@ -94,7 +94,7 @@ class NotificationUtils { height: MediaQuery.of(context).size.height * 0.3, fit: BoxFit.fitWidth, ), - Text( + const Text( 'Allow Awesome Notifications to send you beautiful notifications!', maxLines: 4, textAlign: TextAlign.center, @@ -106,7 +106,7 @@ class NotificationUtils { onPressed: () { Navigator.pop(context); }, - child: Text( + child: const Text( 'Later', style: TextStyle(color: Colors.grey, fontSize: 18), )), @@ -116,7 +116,7 @@ class NotificationUtils { .requestPermissionToSendNotifications(); Navigator.pop(context); }, - child: Text( + child: const Text( 'Allow', style: TextStyle( color: Colors.deepPurple, @@ -180,8 +180,8 @@ class NotificationUtils { await showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Color(0xfffbfbfb), - title: Text( + backgroundColor: const Color(0xfffbfbfb), + title: const Text( 'Awesome Notificaitons needs your permission', textAlign: TextAlign.center, maxLines: 2, @@ -204,7 +204,7 @@ class NotificationUtils { maxLines: 2, textAlign: TextAlign.center, ), - SizedBox(height: 5), + const SizedBox(height: 5), Text( lockedPermissions .join(', ') @@ -212,7 +212,7 @@ class NotificationUtils { maxLines: 2, textAlign: TextAlign.center, style: - TextStyle(fontSize: 14, fontWeight: FontWeight.w600), + const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), ], ), @@ -221,7 +221,7 @@ class NotificationUtils { onPressed: () { Navigator.pop(context); }, - child: Text( + child: const Text( 'Deny', style: TextStyle(color: Colors.red, fontSize: 18), )), @@ -241,7 +241,7 @@ class NotificationUtils { Navigator.pop(context); }, - child: Text( + child: const Text( 'Allow', style: TextStyle( color: Colors.deepPurple, @@ -337,10 +337,10 @@ class NotificationUtils { id: id, channelKey: 'basic_channel', category: NotificationCategory.Social, - title: 'Emojis are awesome too! ' + - Emojis.smile_face_with_tongue + - Emojis.smile_smiling_face + - Emojis.smile_smiling_face_with_heart_eyes, + title: 'Emojis are awesome too! ' + '${Emojis.smile_face_with_tongue}' + '${Emojis.smile_smiling_face}' + '${Emojis.smile_smiling_face_with_heart_eyes}', body: 'Simple body with a bunch of Emojis! ${Emojis.transport_police_car} ${Emojis.animals_dog} ${Emojis.flag_UnitedStates} ${Emojis.person_baby}', largeIcon: 'https://tecnoblog.net/wp-content/uploads/2019/09/emoji.jpg', @@ -356,6 +356,7 @@ class NotificationUtils { id: id, channelKey: 'basic_channel', title: 'Simple notification', + summary: 'Simple subtitle', body: 'Only a simple notification', payload: {'uuid': 'uuid-test'})); } @@ -443,6 +444,132 @@ class NotificationUtils { await AwesomeNotifications().resetGlobalBadge(); } + /* ********************************************* + TIMEOUT NOTIFICATIONS + ************************************************ */ + + static Future showNotificationWithTimeout(int id) async { + await AwesomeNotifications().createNotification( + content: NotificationContent( + id: id, + channelKey: 'basic_channel', + title: 'This notification will expire', + body: 'This notification will expire in 10 seconds', + summary: 'Timeout After', + notificationLayout: NotificationLayout.BigPicture, + bigPicture: 'asset://assets/images/melted-clock.png', + timeoutAfter: const Duration(seconds: 10), + chronometer: Duration.zero, // starts from 0 seconds + payload: {'uuid': 'user-profile-uuid'}), + actionButtons: [ + NotificationActionButton( + key: 'AGREED1', label: 'I agree', autoDismissible: true), + NotificationActionButton( + key: 'AGREED2', label: 'I agree too', autoDismissible: true), + ] + ); + } + + /* ********************************************* + TRANSLATED NOTIFICATIONS + ************************************************ */ + + + static Future showTranslatedNotification(int id, {required languageCode}) async { + await AwesomeNotifications().setLocalization(languageCode: languageCode); + await AwesomeNotifications().createNotification( + content: NotificationContent( + id: id, + channelKey: 'basic_channel', + title: 'This title is written in english', + body: 'Now it is really easy to translate a notification content, ' + 'including images and buttons!', + summary: 'Awesome Notifications Translations', + notificationLayout: NotificationLayout.BigPicture, + bigPicture: 'asset://assets/images/awn-rocks-en.jpg', + largeIcon: 'asset://assets/images/american.jpg', + payload: {'uuid': 'user-profile-uuid'}), + actionButtons: [ + NotificationActionButton( + key: 'AGREED1', label: 'I agree', autoDismissible: true), + NotificationActionButton( + key: 'AGREED2', label: 'I agree too', autoDismissible: true), + ], + localizations: { + 'pt-br' : NotificationLocalization( + title: 'Este título está escrito em português do Brasil!', + body: 'Agora é muito fácil traduzir o conteúdo das notificações, ' + 'incluindo imagens e botões!', + summary: 'Traduções Awesome Notifications', + bigPicture: 'asset://assets/images/awn-rocks-pt-br.jpg', + largeIcon: 'asset://assets/images/brazilian.jpg', + buttonLabels: { + 'AGREED1': 'Eu concordo!', + 'AGREED2': 'Eu concordo também!' + } + ), + 'zh': NotificationLocalization( + title: '这个标题是用中文写的', + body: '现在,轻松翻译通知内容,包括图像和按钮!', + summary: '', + bigPicture: 'asset://assets/images/awn-rocks-zh.jpg', + largeIcon: 'asset://assets/images/chinese.jpg', + buttonLabels: { + 'AGREED1': '我同意', + 'AGREED2': '我也同意' + } + ), + 'ko': NotificationLocalization( + title: '이 타이틀은 한국어로 작성되었습니다', + body: '이제 이미지 및 버튼을 포함한 알림 콘텐츠를 쉽게 번역할 수 있습니다!', + summary: '', + bigPicture: 'asset://assets/images/awn-rocks-ko.jpg', + largeIcon: 'asset://assets/images/korean.jpg', + buttonLabels: { + 'AGREED1': '동의합니다', + 'AGREED2': '저도 동의합니다' + } + ), + 'de': NotificationLocalization( + title: 'Dieser Titel ist in Deutsch geschrieben', + body: 'Jetzt ist es wirklich einfach, den Inhalt einer Benachrichtigung zu übersetzen, ' + 'einschließlich Bilder und Schaltflächen!', + summary: '', + bigPicture: 'asset://assets/images/awn-rocks-de.jpg', + largeIcon: 'asset://assets/images/german.jpg', + buttonLabels: { + 'AGREED1': 'Ich stimme zu', + 'AGREED2': 'Ich stimme auch zu' + } + ), + 'pt': NotificationLocalization( + title: 'Este título está escrito em português de Portugal!', + body: 'Agora é muito fácil traduzir o conteúdo das notificações, ' + 'incluindo imagens e botões!', + summary: 'Traduções Awesome Notifications', + bigPicture: 'asset://assets/images/awn-rocks-pt.jpg', + largeIcon: 'asset://assets/images/portuguese.jpg', + buttonLabels: { + 'AGREED1': 'Eu concordo!', + 'AGREED2': 'Eu concordo também!' + } + ), + 'es': NotificationLocalization( + title: 'Este título está escrito en español!', + body: 'Ahora es muy fácil traducir el contenido de las notificaciones, ' + 'incluyendo imágenes y botones.', + summary: 'Traducciones de Awesome Notifications', + bigPicture: 'asset://assets/images/awn-rocks-es.jpg', + largeIcon: 'asset://assets/images/spanish.jpg', + buttonLabels: { + 'AGREED1': 'Estoy de acuerdo', + 'AGREED2': 'También estoy de acuerdo' + } + ), + }); + await AwesomeNotifications().setLocalization(languageCode: null); + } + /* ********************************************* ACTION BUTTONS NOTIFICATIONS ************************************************ */ @@ -1545,6 +1672,7 @@ class NotificationUtils { static Future listScheduledNotifications(BuildContext context) async { List activeSchedules = await AwesomeNotifications().listScheduledNotifications(); + for (NotificationModel schedule in activeSchedules) { debugPrint('pending notification: [' 'id: ${schedule.content!.id}, ' @@ -1559,7 +1687,7 @@ class NotificationUtils { content: Text('${activeSchedules.length} schedules founded'), actions: [ TextButton( - child: Text('OK'), + child: const Text('OK'), onPressed: () { Navigator.of(context).pop(); }, @@ -1570,6 +1698,75 @@ class NotificationUtils { ); } + + static Future pickScheduleDate(BuildContext context, + {required bool isUtc}) async { + TimeOfDay? timeOfDay; + DateTime now = isUtc ? DateTime.now().toUtc() : DateTime.now(); + DateTime? newDate = await showDatePicker( + context: context, + initialDate: now, + firstDate: now, + lastDate: now.add(const Duration(days: 365))); + + if (newDate != null) { + timeOfDay = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(now.add(const Duration(minutes: 1))), + ); + + if (timeOfDay != null) { + return isUtc + ? DateTime.utc(newDate.year, newDate.month, newDate.day, + timeOfDay.hour, timeOfDay.minute) + : DateTime(newDate.year, newDate.month, newDate.day, timeOfDay.hour, + timeOfDay.minute); + } + } + return null; + } + + static Future getNextValidMonday(BuildContext context) async { + DateTime? referenceDate = + await pickScheduleDate(context, isUtc: false); + + NotificationSchedule schedule = NotificationCalendar( + weekday: DateTime.monday, + hour: 0, + minute: 0, + second: 0, + timeZone: AwesomeNotifications.localTimeZoneIdentifier); + //NotificationCalendar.fromDate(date: expectedDate); + + DateTime? nextValidDate = await AwesomeNotifications() + .getNextDate(schedule, fixedDate: referenceDate); + + late String response; + if (nextValidDate == null) { + response = 'There is no more valid date for this schedule'; + } else { + response = utils.AwesomeDateUtils.parseDateToString( + nextValidDate.toUtc(), + format: 'dd/MM/yyyy')!; + } + + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("Next valid schedule"), + content: SizedBox( + height: 50, child: Center(child: Text(response))), + actions: [ + TextButton( + child: const Text("OK"), + onPressed: () { + Navigator.of(context).pop(null); + }, + ) + ], + )); + } + static Future getCurrentTimeZone() { return AwesomeNotifications().getLocalTimeZoneIdentifier(); } @@ -1599,6 +1796,7 @@ class NotificationUtils { } static Future repeatMultiple5Crontab() async { + var nowDate = DateTime.now(); String localTimeZone = await AwesomeNotifications().getLocalTimeZoneIdentifier(); await AwesomeNotifications().createNotification( @@ -1609,15 +1807,18 @@ class NotificationUtils { body: 'This notification was schedule to repeat at every 5 seconds.'), schedule: NotificationAndroidCrontab( - initialDateTime: DateTime.now().add(Duration(seconds: 10)).toUtc(), + initialDateTime: nowDate.copyWith().add(const Duration(seconds: 10)).toUtc(), expirationDateTime: - DateTime.now().add(Duration(seconds: 10, minutes: 1)).toUtc(), + nowDate.copyWith().add(const Duration(seconds: 10, minutes: 1)).toUtc(), crontabExpression: '/5 * * * * ? *', timeZone: localTimeZone, - repeats: true)); + repeats: true, + preciseAlarm: true + )); } static Future repeatPreciseThreeTimes() async { + var nowDate = DateTime.now(); await AwesomeNotifications().createNotification( content: NotificationContent( id: -1, @@ -1625,12 +1826,17 @@ class NotificationUtils { title: 'Notification scheduled to play precisely 3 times', body: 'This notification was schedule to play precisely 3 times.', notificationLayout: NotificationLayout.BigPicture, + category: NotificationCategory.Alarm, bigPicture: 'asset://assets/images/melted-clock.png'), - schedule: NotificationAndroidCrontab(preciseSchedules: [ - DateTime.now().add(Duration(seconds: 10)).toUtc(), - DateTime.now().add(Duration(seconds: 25)).toUtc(), - DateTime.now().add(Duration(seconds: 45)).toUtc() - ], repeats: true)); + schedule: NotificationAndroidCrontab( + preciseSchedules: [ + nowDate.copyWith().add(const Duration(seconds: 1)).toUtc(), + nowDate.copyWith().add(const Duration(seconds: 25)).toUtc(), + nowDate.copyWith().add(const Duration(seconds: 45)).toUtc() + ], + repeats: true, + preciseAlarm: true + )); } static Future repeatMinuteNotificationOClock() async { @@ -1641,8 +1847,7 @@ class NotificationUtils { id: -1, channelKey: 'scheduled', title: 'Notification at exactly every single minute', - body: - 'This notification was schedule to repeat at every single minute at clock.', + body: 'This notification was schedule to repeat at every single minute at clock.', notificationLayout: NotificationLayout.BigPicture, bigPicture: 'asset://assets/images/melted-clock.png'), schedule: NotificationCalendar( diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 937ac2ec..6ea4ebb9 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -58,33 +58,6 @@ class _HomePageState extends State { String packageName = 'me.carda.awesome_notifications_example'; - Future pickScheduleDate(BuildContext context, - {required bool isUtc}) async { - TimeOfDay? timeOfDay; - DateTime now = isUtc ? DateTime.now().toUtc() : DateTime.now(); - DateTime? newDate = await showDatePicker( - context: context, - initialDate: now, - firstDate: now, - lastDate: now.add(const Duration(days: 365))); - - if (newDate != null) { - timeOfDay = await showTimePicker( - context: context, - initialTime: TimeOfDay.fromDateTime(now.add(const Duration(minutes: 1))), - ); - - if (timeOfDay != null) { - return isUtc - ? DateTime.utc(newDate.year, newDate.month, newDate.day, - timeOfDay.hour, timeOfDay.minute) - : DateTime(newDate.year, newDate.month, newDate.day, timeOfDay.hour, - timeOfDay.minute); - } - } - return null; - } - int _pickAmount = 50; Future pickBadgeCounter(BuildContext context, int initialAmount) async { setState(() => _pickAmount = initialAmount); @@ -184,12 +157,10 @@ class _HomePageState extends State { Widget build(BuildContext context) { MediaQueryData mediaQuery = MediaQuery.of(context); ThemeData themeData = Theme.of(context); - + bool isAndroid = Theme.of(context).platform == TargetPlatform.android; return Scaffold( appBar: AppBar( centerTitle: false, - // ignore: deprecated_member_use - brightness: Brightness.light, title: Image.asset( 'assets/images/awesome-notifications-logo-color.png', width: mediaQuery.size.width * @@ -482,10 +453,10 @@ class _HomePageState extends State { TextDivisor( title: - 'Emojis ${Emojis.smile_alien}${Emojis.transport_air_rocket}'), + 'Emojis ${Emojis.smile_alien}${Emojis.transport_air_rocket}'), const TextNote( 'To send local and push notifications with emojis, use the class Emoji concatenated with your text.\n\n' - 'Attention: not all Emojis work with all platforms. Please, test the specific emoji before using it in production.'), + 'Attention: not all Emojis work with all platforms. Please, test the specific emoji before using it in production.'), SimpleButton('Show notification with emojis', onPressed: () => NotificationUtils.showEmojiNotification(1)), SimpleButton( @@ -496,6 +467,78 @@ class _HomePageState extends State { /* ******************************************************************** */ + TextDivisor(title: 'Timeout Notification (Android)'), + const TextNote( + 'To set a timeout for notification, making it auto dismiss as it get expired, set the "timeoutAfter" property with an duration interval.\n\n' + "Attention: to use this property from json payloads, use an integer positive value to represent seconds."), + SimpleButton('Create notification with 10 seconds timeout', + onPressed: () => NotificationUtils.showNotificationWithTimeout(2)), + SimpleButton('Cancel notification', + backgroundColor: Colors.red, + labelColor: Colors.white, + onPressed: () => NotificationUtils.cancelNotification(2)), + + /* ******************************************************************** */ + + TextDivisor( + title: + 'Translations 🈳🈂️'), + const TextNote( + 'Notification translations allow developers to show notification ' + 'content in different languages based on the user\'s language preference. ' + 'The NotificationModel provides a localizations field, which is a ' + 'Map that contains a list of ' + 'NotificationLocalization instances for each available language, ' + 'indexed by their respective language code (e.g., "en", "pt-br"). ' + 'If the current language, set by the setLocalization methods or ' + 'the current language in system settings, matches with a defined ' + 'translation in the notification, all contents defined in the translation ' + 'will replace the original content. Otherwise, the original ' + 'content will be preserved.' + ), + SimpleButton('Show notification using system default', + onPressed: () => NotificationUtils.showTranslatedNotification( + 1, languageCode: null + ) + ), + SimpleButton('Show notification in english 🇺🇸', + onPressed: () => NotificationUtils.showTranslatedNotification( + 1, languageCode: "en" + ) + ), + SimpleButton('Show notification in brazilian portuguese 🇧🇷', + onPressed: () => NotificationUtils.showTranslatedNotification( + 1, languageCode: "pt-br" + ) + ), + SimpleButton('Show notification in portuguese 🇵🇹', + onPressed: () => NotificationUtils.showTranslatedNotification( + 1, languageCode: "pt" + ) + ), + SimpleButton('Show notification in chinese 🇨🇳', + onPressed: () => NotificationUtils.showTranslatedNotification( + 1, languageCode: "zh" + ) + ), + SimpleButton('Show notification in Korean 🇰🇷', + onPressed: () => NotificationUtils.showTranslatedNotification( + 1, languageCode: "ko" + ) + ), + SimpleButton('Show notification in Spanish 🇪🇸', + onPressed: () => NotificationUtils.showTranslatedNotification( + 1, languageCode: "es" + ) + ), + SimpleButton('Show notification in Germany 🇩🇪', + onPressed: () => NotificationUtils.showTranslatedNotification( + 1, languageCode: "de" + ) + ), + + /* ******************************************************************** */ + TextDivisor(title: 'Locked Notifications (onGoing - Android)'), const TextNote( 'To send local or push locked notification, that users cannot dismiss it swiping it, set the "locked" property to true.\n\n' @@ -794,7 +837,7 @@ class _HomePageState extends State { SimpleButton('Schedule notification with local time zone', onPressed: () async { DateTime? pickedDate = - await pickScheduleDate(context, isUtc: false); + await NotificationUtils.pickScheduleDate(context, isUtc: false); if (pickedDate != null) { NotificationUtils.showNotificationAtSchedulePreciseDate( pickedDate); @@ -803,7 +846,7 @@ class _HomePageState extends State { SimpleButton('Schedule notification with utc time zone', onPressed: () async { DateTime? pickedDate = - await pickScheduleDate(context, isUtc: true); + await NotificationUtils.pickScheduleDate(context, isUtc: true); if (pickedDate != null) { NotificationUtils.showNotificationAtSchedulePreciseDate( pickedDate); @@ -815,11 +858,15 @@ class _HomePageState extends State { ), SimpleButton( 'Show notifications repeatedly in 10 sec, spaced 5 sec from each other for 1 minute (only for Android)', - onPressed: () => NotificationUtils.repeatMultiple5Crontab(), + onPressed: isAndroid + ? () => NotificationUtils.repeatMultiple5Crontab() + : null, ), SimpleButton( 'Show notification with 3 precise times (only for Android)', - onPressed: () => NotificationUtils.repeatPreciseThreeTimes(), + onPressed: isAndroid + ? () => NotificationUtils.repeatPreciseThreeTimes() + : null, ), SimpleButton( 'Show notification at every single minute o\'clock', @@ -891,46 +938,10 @@ class _HomePageState extends State { 'This is a simple example to show how to query the next valid ' 'schedule date. The date components follow the ISO 8601 ' 'standard.'), - SimpleButton('Get next Monday after date', onPressed: () async { - DateTime? referenceDate = - await pickScheduleDate(context, isUtc: false); - - NotificationSchedule schedule = NotificationCalendar( - weekday: DateTime.monday, - hour: 0, - minute: 0, - second: 0, - timeZone: AwesomeNotifications.localTimeZoneIdentifier); - //NotificationCalendar.fromDate(date: expectedDate); - - DateTime? nextValidDate = await AwesomeNotifications() - .getNextDate(schedule, fixedDate: referenceDate); - - late String response; - if (nextValidDate == null) { - response = 'There is no more valid date for this schedule'; - } else { - response = AwesomeDateUtils.parseDateToString( - nextValidDate.toUtc(), - format: 'dd/MM/yyyy')!; - } - - showDialog( - context: context, - builder: (_) => AlertDialog( - title: const Text("Next valid schedule"), - content: SizedBox( - height: 50, child: Center(child: Text(response))), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () { - Navigator.of(context).pop(null); - }, - ) - ], - )); - }), + SimpleButton( + 'Get next Monday after date', + onPressed: () => NotificationUtils.getNextValidMonday(context) + ), /* ******************************************************************** */ @@ -956,11 +967,16 @@ class _HomePageState extends State { /* ******************************************************************** */ TextDivisor(title: 'Progress Notifications'), - SimpleButton('Show indeterminate progress notification', - onPressed: () => - NotificationUtils.showIndeterminateProgressNotification(9)), + SimpleButton('Show indeterminate progress notification (Only for Android)', + onPressed: isAndroid + ? () => NotificationUtils.showIndeterminateProgressNotification(9) + : null + ), SimpleButton('Show progress notification - updates every second', - onPressed: () => NotificationUtils.showProgressNotification(9)), + onPressed: isAndroid + ? () => NotificationUtils.showProgressNotification(9) + : null + ), SimpleButton('Cancel notification', backgroundColor: Colors.red, labelColor: Colors.white, diff --git a/example/lib/pages/media_details_page.dart b/example/lib/pages/media_details_page.dart index aa3723b7..d9a18996 100644 --- a/example/lib/pages/media_details_page.dart +++ b/example/lib/pages/media_details_page.dart @@ -198,7 +198,7 @@ class _MediaDetailsPageState extends State { isLighten = // ignore: deprecated_member_use - isLighten ?? themeData.accentColorBrightness == Brightness.light; + isLighten ?? themeData.brightness == Brightness.light; mainColor = mainColor ?? themeData.backgroundColor; contrastColor = contrastColor ?? (isLighten! ? Colors.black : Colors.white); @@ -211,7 +211,7 @@ class _MediaDetailsPageState extends State { data: Theme.of(context).copyWith( primaryColor: mainColor, // ignore: deprecated_member_use - accentColor: contrastColor, + secondaryHeaderColor: contrastColor, scaffoldBackgroundColor: mainColor, disabledColor: contrastColor?.withOpacity(0.25), textTheme: Theme.of(context) diff --git a/example/lib/routes/routes.dart b/example/lib/routes/routes.dart index b3181a2d..66cb4b8e 100644 --- a/example/lib/routes/routes.dart +++ b/example/lib/routes/routes.dart @@ -7,7 +7,6 @@ import 'package:awesome_notifications_example/pages/media_details_page.dart'; import 'package:awesome_notifications_example/pages/notification_details_page.dart'; import 'package:awesome_notifications_example/pages/home_page.dart'; -import '../main.dart'; import '../notifications/notifications_controller.dart'; const String PAGE_HOME = '/'; diff --git a/example/lib/themes/themes_controller.dart b/example/lib/themes/themes_controller.dart index b9fc10a3..8973e614 100644 --- a/example/lib/themes/themes_controller.dart +++ b/example/lib/themes/themes_controller.dart @@ -2,16 +2,18 @@ import 'package:flutter/material.dart'; import '../main_complete.dart'; -enum Themes { Light, Dark } +enum Themes { light, dark } class ThemesController { static final appThemeData = { - Themes.Light: ThemeData( + Themes.light: ThemeData( brightness: Brightness.light, primaryColor: App.mainColor, // ignore: deprecated_member_use - accentColor: Colors.blueGrey, + colorScheme: const ColorScheme.light().copyWith( + secondary: Colors.blueGrey, + ), canvasColor: Colors.white, focusColor: Colors.blueAccent, disabledColor: Colors.grey, @@ -19,23 +21,18 @@ class ThemesController { backgroundColor: Colors.blueGrey.shade400, appBarTheme: AppBarTheme( - // ignore: deprecated_member_use - brightness: Brightness.dark, color: Colors.white, elevation: 0, iconTheme: IconThemeData( color: App.mainColor, - ), - // ignore: deprecated_member_use - textTheme: TextTheme( - headline6: TextStyle(color: App.mainColor, fontSize: 18), - )), + ) + ), fontFamily: 'Robot', // Define the default TextTheme. Use this to specify the default // text styling for headlines, titles, bodies of text, and more. - textTheme: TextTheme( + textTheme: const TextTheme( headline1: TextStyle(fontSize: 64.0, height: 1.5, fontWeight: FontWeight.w500), headline2: @@ -59,9 +56,9 @@ class ThemesController { buttonTheme: ButtonThemeData( buttonColor: Colors.grey.shade200, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(5))), - padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), textTheme: ButtonTextTheme.accent, ), ) @@ -70,14 +67,12 @@ class ThemesController { static ThemeData? _currentTheme; ThemesController(bool isLight) { - _currentTheme = appThemeData[isLight ? Themes.Light : Themes.Dark]!; + _currentTheme = appThemeData[isLight ? Themes.light : Themes.dark]!; } /// Use this method on UI to get selected theme. static ThemeData get currentTheme { - if (_currentTheme == null) { - _currentTheme = appThemeData[Themes.Light]; - } + _currentTheme ??= appThemeData[Themes.light]; return _currentTheme!; } diff --git a/example/lib/utils/common_functions.dart b/example/lib/utils/common_functions.dart index c78c1002..0f515393 100644 --- a/example/lib/utils/common_functions.dart +++ b/example/lib/utils/common_functions.dart @@ -6,7 +6,7 @@ import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; -import 'package:device_info/device_info.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:path_provider/path_provider.dart'; Future saveAssetOnDisk(ImageProvider image, String fileName) async { @@ -15,7 +15,7 @@ Future saveAssetOnDisk(ImageProvider image, String fileName) async { File newFile = File(filePath); if (!await newFile.exists()) { - BitmapHelper bitmapHelper = await BitmapHelper.fromProvider(image); + BitmapHelper bitmapHelper = await BitmapHelper.fromImageProvider(image); await newFile.writeAsBytes(bitmapHelper.content); } diff --git a/example/lib/utils/common_web_functions.dart b/example/lib/utils/common_web_functions.dart index becb4f93..f68592a1 100644 --- a/example/lib/utils/common_web_functions.dart +++ b/example/lib/utils/common_web_functions.dart @@ -6,7 +6,7 @@ import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; -import 'package:device_info/device_info.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:path_provider/path_provider.dart'; Future saveAssetOnDisk(ImageProvider image, String fileName) async { diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 3e151d44..59c2d0ec 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,12 +6,14 @@ import FlutterMacOS import Foundation import awesome_notifications -import path_provider_macos -import shared_preferences_macos +import device_info_plus +import path_provider_foundation +import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AwesomeNotificationsPlugin.register(with: registry.registrar(forPlugin: "AwesomeNotificationsPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/example/pubspec.lock b/example/pubspec.lock index 3fdffaa4..f5948201 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,121 +5,153 @@ packages: dependency: transitive description: name: archive - url: "https://pub.dartlang.org" + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.3.7" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.11.0" awesome_notifications: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.7.3" + version: "0.7.5" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 + url: "https://pub.dev" + source: hosted + version: "0.4.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "3.1.1" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" source: hosted - version: "1.0.5" - device_info: + version: "1.0.6" + device_info_plus: dependency: "direct main" description: - name: device_info - url: "https://pub.dartlang.org" + name: device_info_plus + sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" + url: "https://pub.dev" source: hosted - version: "2.0.3" - device_info_platform_interface: + version: "9.0.3" + device_info_plus_platform_interface: dependency: transitive description: - name: device_info_platform_interface - url: "https://pub.dartlang.org" + name: device_info_plus_platform_interface + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "7.0.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted version: "6.1.4" - flare_dart: - dependency: transitive - description: - name: flare_dart - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.4" flare_flutter: dependency: transitive description: name: flare_flutter - url: "https://pub.dartlang.org" + sha256: "99d63c60f00fac81249ce6410ee015d7b125c63d8278a30da81edf3317a1f6a0" + url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "3.0.2" flutter: dependency: "direct main" description: flutter @@ -129,16 +161,18 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - url: "https://pub.dartlang.org" + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.13.1" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter @@ -153,247 +187,282 @@ packages: dependency: "direct main" description: name: fluttertoast - url: "https://pub.dartlang.org" + sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" + url: "https://pub.dev" source: hosted - version: "8.0.9" + version: "8.2.2" font_awesome_flutter: dependency: "direct main" description: name: font_awesome_flutter - url: "https://pub.dartlang.org" + sha256: "5fb789145cae1f4c3245c58b3f8fb287d055c26323879eab57a7bf0cfd1e45f3" + url: "https://pub.dev" source: hosted - version: "9.2.0" - giffy_dialog: + version: "10.5.0" + giff_dialog: dependency: "direct main" description: - name: giffy_dialog - url: "https://pub.dartlang.org" + name: giff_dialog + sha256: "0fb2189a1d8902ce4e3858a7917f1e53d887fad791a9153f0711736d31e00af1" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.0.1" http: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "1.1.0" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" image: dependency: transitive description: name: image - url: "https://pub.dartlang.org" + sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.17" infinite_listview: dependency: transitive description: name: infinite_listview - url: "https://pub.dartlang.org" + sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6 + url: "https://pub.dev" source: hosted version: "1.1.0" intl: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" source: hosted - version: "0.17.0" + version: "0.18.1" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "4.8.1" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.5.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" numberpicker: dependency: "direct main" description: name: numberpicker - url: "https://pub.dartlang.org" + sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" palette_generator: dependency: "direct main" description: name: palette_generator - url: "https://pub.dartlang.org" + sha256: eb7082b4b97487ebc65b3ad3f6f0b7489b96e76840381ed0e06a46fe7ffd4068 + url: "https://pub.dev" source: hosted - version: "0.3.3+2" + version: "0.3.3+3" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" path_provider: dependency: "direct main" description: name: path_provider - url: "https://pub.dartlang.org" + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.1.1" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.dartlang.org" + sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" + url: "https://pub.dev" source: hosted - version: "2.0.20" - path_provider_ios: + version: "2.2.0" + path_provider_foundation: dependency: transitive description: - name: path_provider_ios - url: "https://pub.dartlang.org" + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.3.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" source: hosted - version: "2.1.7" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.1.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.1" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.4.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + url: "https://pub.dev" + source: hosted + version: "2.1.6" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "3.7.3" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac + url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.2.1" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" + url: "https://pub.dev" source: hosted - version: "2.0.13" - shared_preferences_ios: + version: "2.1.4" + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_ios - url: "https://pub.dartlang.org" + name: shared_preferences_foundation + sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.2" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" + sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.2.0" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.3.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.1.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" sky_engine: dependency: transitive description: flutter @@ -403,156 +472,194 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.6.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" universal_io: dependency: "direct main" description: name: universal_io - url: "https://pub.dartlang.org" + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.2.2" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" + url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.1.14" url_launcher_android: dependency: transitive description: name: url_launcher_android - url: "https://pub.dartlang.org" + sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd" + url: "https://pub.dev" source: hosted - version: "6.0.19" + version: "6.0.31" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - url: "https://pub.dartlang.org" + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + url: "https://pub.dev" source: hosted - version: "6.0.17" + version: "6.1.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.5" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.5" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + url: "https://pub.dev" source: hosted - version: "2.0.13" + version: "2.0.16" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.6" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" vibration: dependency: "direct main" description: name: vibration - url: "https://pub.dartlang.org" + sha256: d81f665bcb201f586c295a21f3fe8f1cb6dc32c81a213a99e9c714ec8e811ce5 + url: "https://pub.dev" source: hosted - version: "1.7.6" + version: "1.8.1" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + url: "https://pub.dev" + source: hosted + version: "4.1.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "1.1.0" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" source: hosted - version: "0.2.0+2" + version: "1.0.0" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.3.0" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=3.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 21ac8c75..62df5410 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,7 +6,7 @@ description: Demonstrates how to use the awesome_notifications plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: '>=2.18.0 <3.0.0' + sdk: '>=2.19.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -28,21 +28,21 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - universal_io: ^2.0.4 + cupertino_icons: ^1.0.6 + universal_io: ^2.2.2 - font_awesome_flutter: ^9.0.0 - fluttertoast: ^8.0.3 - http: ^0.13.1 - path_provider: ^2.0.11 - shared_preferences: ^2.0.5 - numberpicker: ^2.0.1 - giffy_dialog: ^1.8.0 - vibration: ^1.7.4-nullsafety.0 - device_info: ^2.0.3 - url_launcher: ^6.0.3 - intl: ^0.17.0 - palette_generator: ^0.3.0 + font_awesome_flutter: ^10.5.0 + fluttertoast: ^8.2.2 + http: ^1.1.0 + path_provider: ^2.1.1 + shared_preferences: ^2.2.1 + numberpicker: ^2.1.2 + giff_dialog: ^1.0.1 + vibration: ^1.8.1 + device_info_plus: ^9.0.3 + url_launcher: ^6.1.14 + intl: ^0.18.1 + palette_generator: ^0.3.3+3 dev_dependencies: flutter_test: @@ -53,9 +53,9 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^2.0.3 - flutter_launcher_icons: ^0.9.0 + flutter_launcher_icons: ^0.13.1 flutter_icons: ios: true @@ -96,4 +96,17 @@ flutter: - assets/images/dj-disc.jpg - assets/images/80s-disc.jpg - assets/images/remix-disc.jpg - - assets/images/old-disc.jpg + - assets/images/american.jpg + - assets/images/chinese.jpg + - assets/images/portuguese.jpg + - assets/images/brazilian.jpg + - assets/images/german.jpg + - assets/images/korean.jpg + - assets/images/spanish.jpg + - assets/images/awn-rocks-en.jpg + - assets/images/awn-rocks-pt-br.jpg + - assets/images/awn-rocks-pt.jpg + - assets/images/awn-rocks-ko.jpg + - assets/images/awn-rocks-zh.jpg + - assets/images/awn-rocks-es.jpg + - assets/images/awn-rocks-de.jpg diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 619c9be1..447c12a9 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -1,27 +1,27 @@ -// This is a basic Flutter widget test. +// // This is a basic Flutter widget test. +// // +// // To perform an interaction with a widget in your test, use the WidgetTester +// // utility in the flutter_test package. For example, you can send tap and scroll +// // gestures. You can also use WidgetTester to find child widgets in the widget +// // tree, read text, and verify that the values of widget properties are correct. +// +// import 'package:flutter/material.dart'; +// import 'package:flutter_test/flutter_test.dart'; +// +// import 'package:awesome_notifications_example/main_complete.dart'; // -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:awesome_notifications_example/main_complete.dart'; - void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const App()); - - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); - }); +// testWidgets('Verify Platform version', (WidgetTester tester) async { +// // Build our app and trigger a frame. +// await tester.pumpWidget(const App()); +// +// // Verify that platform version is retrieved. +// expect( +// find.byWidgetPredicate( +// (Widget widget) => widget is Text && +// widget.data!.startsWith('Running on:'), +// ), +// findsOneWidget, +// ); +// }); } diff --git a/flutter_01.log b/flutter_01.log new file mode 100644 index 00000000..cfa1e40c --- /dev/null +++ b/flutter_01.log @@ -0,0 +1,96 @@ +Flutter crash report. +Please report a bug at https://github.com/flutter/flutter/issues. + +## command + +flutter --no-color pub get + +## exception + +FileSystemException: FileSystemException: Cannot create link, path = 'D:\GitHub\plugins\awesome_notifications\example\windows\flutter\ephemeral\.plugin_symlinks\awesome_notifications' (OS Error: Função incorreta. +, errno = 1) + +``` +#0 _Link.throwIfError (dart:io/link.dart:254:7) +#1 _Link.createSync (dart:io/link.dart:182:5) +#2 ForwardingLink.createSync (package:file/src/forwarding/forwarding_link.dart:20:16) +#3 ForwardingLink.createSync (package:file/src/forwarding/forwarding_link.dart:20:16) +#4 _createPlatformPluginSymlinks (package:flutter_tools/src/flutter_plugins.dart:1057:12) +#5 createPluginSymlinks (package:flutter_tools/src/flutter_plugins.dart:987:5) +#6 refreshPluginsList (package:flutter_tools/src/flutter_plugins.dart:1089:5) + +#7 FlutterProject.ensureReadyForPlatformSpecificTooling (package:flutter_tools/src/project.dart:357:5) + +#8 PackagesGetCommand.runCommand (package:flutter_tools/src/commands/packages.dart:182:7) + +#9 FlutterCommand.run. (package:flutter_tools/src/runner/flutter_command.dart:1257:27) + +#10 AppContext.run. (package:flutter_tools/src/base/context.dart:150:19) + +#11 CommandRunner.runCommand (package:args/command_runner.dart:209:13) + +#12 FlutterCommandRunner.runCommand. (package:flutter_tools/src/runner/flutter_command_runner.dart:283:9) + +#13 AppContext.run. (package:flutter_tools/src/base/context.dart:150:19) + +#14 FlutterCommandRunner.runCommand (package:flutter_tools/src/runner/flutter_command_runner.dart:229:5) + +#15 run.. (package:flutter_tools/runner.dart:64:9) + +#16 AppContext.run. (package:flutter_tools/src/base/context.dart:150:19) + +#17 main (package:flutter_tools/executable.dart:91:3) + +``` + +## flutter doctor + +``` +[✓] Flutter (Channel stable, 3.7.12, on Microsoft Windows [versÆo 10.0.19044.2846], locale pt-BR) + • Flutter version 3.7.12 on channel stable at D:\flutter + • Upstream repository https://github.com/flutter/flutter.git + • Framework revision 4d9e56e694 (2 weeks ago), 2023-04-17 21:47:46 -0400 + • Engine revision 1a65d409c7 + • Dart version 2.19.6 + • DevTools version 2.20.1 + +[✗] Windows Version (Unable to confirm if installed Windows version is 10 or greater) + +[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2) + • Android SDK at C:\Users\rafae\AppData\Local\Android\sdk + • Platform android-33, build-tools 33.0.2 + • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java + • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694) + • All Android licenses accepted. + +[✓] Chrome - develop for the web + • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe + +[✓] Visual Studio - develop for Windows (Visual Studio Community 2022 17.5.5) + • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community + • Visual Studio Community 2022 version 17.5.33627.172 + • Windows 10 SDK version 10.0.22000.0 + +[✓] Android Studio (version 2022.2) + • Android Studio at C:\Program Files\Android\Android Studio + • Flutter plugin can be installed from: + 🔨 https://plugins.jetbrains.com/plugin/9212-flutter + • Dart plugin can be installed from: + 🔨 https://plugins.jetbrains.com/plugin/6351-dart + • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694) + +[✓] VS Code (version 1.78.0) + • VS Code at C:\Users\rafae\AppData\Local\Programs\Microsoft VS Code + • Flutter extension version 3.62.0 + +[✓] Connected device (4 available) + • 2201117TG (mobile) • 16d1392f • android-arm64 • Android 12 (API 31) + • Windows (desktop) • windows • windows-x64 • Microsoft Windows [versÆo 10.0.19044.2846] + • Chrome (web) • chrome • web-javascript • Google Chrome 113.0.5672.64 + • Edge (web) • edge • web-javascript • Microsoft Edge 112.0.1722.68 + +[✓] HTTP Host Availability + • All required HTTP hosts are available + +! Doctor found issues in 1 category. +``` diff --git a/ios/AwnCore/Classes/AwesomeNotifications.swift b/ios/AwnCore/Classes/AwesomeNotifications.swift index 367da117..8d3cbb02 100644 --- a/ios/AwnCore/Classes/AwesomeNotifications.swift +++ b/ios/AwnCore/Classes/AwesomeNotifications.swift @@ -35,6 +35,9 @@ public class AwesomeNotifications: public static var awesomeExtensions:AwesomeNotificationsExtension? public static var backgroundClassType:BackgroundExecutor.Type? + public static var didFinishLaunch:Bool = false + public static var removeFromEvents:Bool = false + public static var completionHandlerGetInitialAction:((ActionReceived?) -> Void)? = nil // ************************** CONSTRUCTOR *********************************** @@ -51,6 +54,14 @@ public class AwesomeNotifications: } activateiOSNotifications() + + DefaultsManager + .shared + .setDefaultGroupTest() + + BadgeManager + .shared + .syncBadgeAmount() } static var areDefaultsLoaded = false @@ -177,8 +188,9 @@ public class AwesomeNotifications: if ( DefaultsManager .shared - .actionCallback != 0){ - try recoverNotificationsDisplayed( + .actionCallback != 0 + ){ + try recoverLostEvents( withReferenceLifeCycle: .Background ) } @@ -283,7 +295,7 @@ public class AwesomeNotifications: return nil } - return UIImage.pngData(image)() + return image.pngData() } // *************************************************************************************** @@ -295,13 +307,13 @@ public class AwesomeNotifications: .actionCallback = actionHandle if actionHandle != 0 { - try recoverLostEvents() + try recoverLostEvents(withReferenceLifeCycle: .AppKilled) } } - public func recoverLostEvents() throws { + public func recoverLostEvents(withReferenceLifeCycle referencedLifeCycle: NotificationLifeCycle) throws { try recoverNotificationsCreated() - try recoverNotificationsDisplayed(withReferenceLifeCycle: .AppKilled) + try recoverNotificationsDisplayed(withReferenceLifeCycle: referencedLifeCycle) try recoverNotificationsDismissed() try recoverNotificationActions() } @@ -422,7 +434,7 @@ public class AwesomeNotifications: } private func recoverNotificationsDisplayed( - withReferenceLifeCycle lifeCycle:NotificationLifeCycle + withReferenceLifeCycle referenceLifeCycle:NotificationLifeCycle ) throws { let lastRecoveredDate:RealDateTime = @@ -442,7 +454,7 @@ public class AwesomeNotifications: if(lastRecoveredDate < displayedDate){ try displayedNotification.validate() - displayedNotification.displayedLifeCycle = lifeCycle + displayedNotification.displayedLifeCycle = referenceLifeCycle notifyNotificationEvent( eventName: Definitions.EVENT_NOTIFICATION_DISPLAYED, @@ -487,20 +499,26 @@ public class AwesomeNotifications: } } - // ***************************** IOS NOTIFICATION CENTER METHODS ********************************** -#if !ACTION_EXTENSION private var _originalNotificationCenterDelegate: UNUserNotificationCenterDelegate? @objc public func didFinishLaunch(_ application: UIApplication) { UNUserNotificationCenter.current().delegate = self - UIApplication.shared.registerForRemoteNotifications() RefreshSchedulesReceiver() .refreshSchedules() + AwesomeNotifications.didFinishLaunch = true + if AwesomeNotifications.completionHandlerGetInitialAction != nil { + AwesomeNotifications + .completionHandlerGetInitialAction!( + ActionManager.getInitialAction( + removeFromEvents: AwesomeNotifications.removeFromEvents)) + } + + if AwesomeNotifications.debug { Logger.d(TAG, "Awesome Notifications attached for iOS") } @@ -638,7 +656,7 @@ public class AwesomeNotifications: } do { - try recoverLostEvents() + try recoverLostEvents(withReferenceLifeCycle: .Foreground) } catch { if !(error is AwesomeNotificationsException) { ExceptionFactory @@ -681,8 +699,6 @@ public class AwesomeNotifications: return jsonMap } -#endif - // ***************************** NOTIFICATION METHODS ********************************** @@ -785,6 +801,15 @@ public class AwesomeNotifications: .decrementGlobalBadgeCounter() } + public func getInitialAction(removeFromEvents:Bool, completionHandler: @escaping (ActionReceived?) -> Void) { + if AwesomeNotifications.didFinishLaunch { + completionHandler(ActionManager.getInitialAction(removeFromEvents: removeFromEvents)) + return + } + AwesomeNotifications.removeFromEvents = removeFromEvents + AwesomeNotifications.completionHandlerGetInitialAction = completionHandler + } + // ***************************** CANCELATION METHODS ********************************** public func dismissNotification(byId id: Int) -> Bool { @@ -975,4 +1000,17 @@ public class AwesomeNotifications: whenUserReturns: completionHandler) } + + public func setLocalization(languageCode:String?) -> Bool { + return LocalizationManager + .shared + .setLocalization( + languageCode: languageCode) + } + + public func getLocalization() -> String { + return LocalizationManager + .shared + .getLocalization() + } } diff --git a/ios/AwnCore/Classes/Definitions.swift b/ios/AwnCore/Classes/Definitions.swift index c788b0d8..63ff778a 100644 --- a/ios/AwnCore/Classes/Definitions.swift +++ b/ios/AwnCore/Classes/Definitions.swift @@ -1,9 +1,11 @@ public enum Definitions { - public static let USER_DEFAULT_TAG = "group.AwesomeNotifications." + Bundle.main.getBundleName() + public static let USER_DEFAULT_TAG = "group.awn." + Bundle.main.getBundleName().md5.prefix(8) + public static let TEST_APP_GROUP = "AAA" public static let NOTIFICATION_MODEL_CONTENT = "content" public static let NOTIFICATION_MODEL_SCHEDULE = "schedule" public static let NOTIFICATION_MODEL_BUTTONS = "actionButtons" + public static let NOTIFICATION_MODEL_LOCALIZATIONS = "localizations" public static let MEDIA_VALID_NETWORK = "^https?:\\/\\/"//(www)?(\\.?[a-zA-Z0-9@:%.\\-_\\+~#=]{2,256}\\/?)+(\\?\\S+)$ public static let MEDIA_VALID_FILE = "^file:\\/\\/" @@ -65,6 +67,7 @@ public enum Definitions { public static let CHANNEL_METHOD_INITIALIZE = "initialize" public static let CHANNEL_METHOD_REGISTER_PLUGIN_BY_NAME = "registerPluginByName" public static let CHANNEL_METHOD_PUSH_NEXT = "pushNext" + public static let CHANNEL_METHOD_GET_INITIAL_ACTION = "getInitialAction" public static let CHANNEL_METHOD_GET_DRAWABLE_DATA = "getDrawableData" public static let CHANNEL_METHOD_GET_PLATFORM_VERSION = "getPlatformVersion" public static let CHANNEL_METHOD_CREATE_NOTIFICATION = "createNewNotification" @@ -85,6 +88,9 @@ public enum Definitions { public static let CHANNEL_METHOD_GET_NEXT_DATE = "getNextDate" public static let CHANNEL_METHOD_RESET_BADGE = "resetBadge" + public static let CHANNEL_METHOD_SET_LOCALIZATION = "setLocalization" + public static let CHANNEL_METHOD_GET_LOCALIZATION = "getLocalization" + public static let CHANNEL_METHOD_SHOW_NOTIFICATION_PAGE = "showNotificationPage" public static let CHANNEL_METHOD_SHOW_ALARM_PAGE = "showAlarmPage" public static let CHANNEL_METHOD_SHOW_GLOBAL_DND_PAGE = "showGlobalDndPage" @@ -147,6 +153,9 @@ public enum Definitions { public static let NOTIFICATION_DISPLAYED_DATE = "displayedDate" public static let NOTIFICATION_ACTION_DATE = "actionDate" public static let NOTIFICATION_DISMISSED_DATE = "dismissedDate" + + public static let NOTIFICATION_MODEL_ANDROID = "Android" + public static let NOTIFICATION_MODEL_IOS = "iOS" public static let NOTIFICATION_ID = "id" public static let NOTIFICATION_LAYOUT = "notificationLayout" @@ -158,7 +167,9 @@ public enum Definitions { public static let NOTIFICATION_BUTTON_KEY_PRESSED = "buttonKeyPressed" public static let NOTIFICATION_BUTTON_KEY_INPUT = "buttonKeyInput" public static let NOTIFICATION_JSON = "notificationJson" + public static let NOTIFICATION_TIMEOUT_AFTER = "timeoutAfter" + public static let NOTIFICATION_BUTTON_LABELS = "buttonLabels" public static let NOTIFICATION_ACTION_BUTTONS = "actionButtons" public static let NOTIFICATION_BUTTON_KEY = "key" public static let NOTIFICATION_BUTTON_ICON = "icon" @@ -209,6 +220,7 @@ public enum Definitions { public static let NOTIFICATION_BIG_PICTURE = "bigPicture" public static let NOTIFICATION_HIDE_LARGE_ICON_ON_EXPAND = "hideLargeIconOnExpand" public static let NOTIFICATION_PROGRESS = "progress" + public static let NOTIFICATION_BADGE = "badge" public static let NOTIFICATION_ENABLE_LIGHTS = "enableLights" public static let NOTIFICATION_LED_COLOR = "ledColor" public static let NOTIFICATION_LED_ON_MS = "ledOnMs" @@ -249,7 +261,7 @@ public enum Definitions { Definitions.NOTIFICATION_PLAY_SOUND: true, Definitions.NOTIFICATION_AUTO_DISMISSIBLE: true, Definitions.NOTIFICATION_LOCKED: false, - Definitions.NOTIFICATION_TICKER: "ticker", + Definitions.NOTIFICATION_TICKER: "", Definitions.NOTIFICATION_ALLOW_WHILE_IDLE: false, Definitions.NOTIFICATION_ONLY_ALERT_ONCE: false, Definitions.NOTIFICATION_IS_DANGEROUS_OPTION: false, diff --git a/ios/AwnCore/Classes/broadcasters/receivers/NotificationActionReceiver.swift b/ios/AwnCore/Classes/broadcasters/receivers/NotificationActionReceiver.swift index d9dd1661..50aa88e2 100644 --- a/ios/AwnCore/Classes/broadcasters/receivers/NotificationActionReceiver.swift +++ b/ios/AwnCore/Classes/broadcasters/receivers/NotificationActionReceiver.swift @@ -37,12 +37,13 @@ public class NotificationActionReceiver { var notificationModel:NotificationModel? = nil - if let jsonData:String = - response + let userInfo = response .notification .request .content - .userInfo[Definitions.NOTIFICATION_JSON] as? String + .userInfo + + if let jsonData:String = userInfo[Definitions.NOTIFICATION_JSON] as? String { notificationModel = NotificationBuilder @@ -51,56 +52,100 @@ public class NotificationActionReceiver { jsonData: jsonData) } else { - if response - .notification - .request - .content - .userInfo["gcm.message_id"] == nil - { - throw ExceptionFactory - .shared - .createNewAwesomeException( - className: TAG, - code: ExceptionCode.CODE_INVALID_ARGUMENTS, - message: "The action content doesn't contain any awesome information", - detailedCode: ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".addNewActionEvent.jsonData") + if userInfo[Definitions.NOTIFICATION_MODEL_CONTENT] != nil { + var mapData:[String:Any?] = [:] + + var contentData = JsonUtils.fromJson(userInfo[Definitions.NOTIFICATION_MODEL_CONTENT] as? String) + if contentData?[Definitions.NOTIFICATION_TITLE] == nil { + contentData?[Definitions.NOTIFICATION_TITLE] = response.notification.request.content.title + } + if contentData?[Definitions.NOTIFICATION_BODY] == nil { + contentData?[Definitions.NOTIFICATION_BODY] = response.notification.request.content.body + } + + mapData[Definitions.NOTIFICATION_MODEL_CONTENT] = contentData + mapData[Definitions.NOTIFICATION_MODEL_SCHEDULE] = JsonUtils.fromJson(userInfo[Definitions.NOTIFICATION_MODEL_SCHEDULE] as? String) + mapData[Definitions.NOTIFICATION_MODEL_BUTTONS] = JsonUtils.fromJsonArr(userInfo[Definitions.NOTIFICATION_MODEL_BUTTONS] as? String) + + if (userInfo[Definitions.NOTIFICATION_MODEL_IOS] != nil) { + let iosCustomData:[String:Any?]? = JsonUtils.fromJson(userInfo[Definitions.NOTIFICATION_MODEL_IOS] as? String) + if iosCustomData != nil { + mapData = MapUtils<[String:Any?]>.deepMerge(mapData, iosCustomData!) + } + } + + notificationModel = NotificationModel() + if notificationModel?.fromMap(arguments: mapData) == nil { + throw ExceptionFactory + .shared + .createNewAwesomeException( + className: TAG, + code: ExceptionCode.CODE_INVALID_ARGUMENTS, + message: "\(TAG) received a invalid awesome notification content", + detailedCode: ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationModel.invalid") + } } - - let title:String? = response.notification.request.content.title - let body:String? = response.notification.request.content.body - - if StringUtils.shared.isNullOrEmpty(title) && StringUtils.shared.isNullOrEmpty(body) { - throw ExceptionFactory - .shared - .createNewAwesomeException( - className: TAG, - code: ExceptionCode.CODE_INVALID_ARGUMENTS, - message: "The action content doesn't contain any awesome information", - detailedCode: ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".addNewActionEvent.jsonData") + else{ + if userInfo["gcm.message_id"] == nil { + throw ExceptionFactory + .shared + .createNewAwesomeException( + className: TAG, + code: ExceptionCode.CODE_INVALID_ARGUMENTS, + message: "The action content doesn't contain any awesome information", + detailedCode: ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".addNewActionEvent.jsonData") + } + + let title:String? = response.notification.request.content.title + let body:String? = response.notification.request.content.body + + notificationModel = NotificationModel() + notificationModel!.content = NotificationContentModel() + notificationModel!.content!.id = -1 + notificationModel!.content!.title = title + notificationModel!.content!.body = body + + if StringUtils.shared.isNullOrEmpty(title) && StringUtils.shared.isNullOrEmpty(body) { + throw ExceptionFactory + .shared + .createNewAwesomeException( + className: TAG, + code: ExceptionCode.CODE_INVALID_ARGUMENTS, + message: "The action content doesn't contain any awesome information", + detailedCode: ExceptionCode.DETAILED_INVALID_ARGUMENTS + ".addNewActionEvent.jsonData") + } } - let date:Date = response.notification.date - let image:String? = (response - .notification - .request - .content - .userInfo["fcm_options"] as? [String : Any?])?["image"] as? String - - notificationModel = NotificationModel() - notificationModel!.content = NotificationContentModel() - notificationModel!.content!.id = -1 - notificationModel!.content!.title = title - notificationModel!.content!.body = body - notificationModel!.content!.createdDate = RealDateTime - .init(fromDate: date, inTimeZone: DateUtils.shared.utcTimeZone) - notificationModel!.content!.displayedDate = - notificationModel!.content!.createdDate + if notificationModel!.content!.createdDate == nil { + let date:Date = response.notification.date + notificationModel!.content!.createdDate = RealDateTime + .init(fromDate: date, inTimeZone: DateUtils.shared.utcTimeZone) + notificationModel!.content!.displayedDate = + notificationModel!.content!.createdDate + } + let image:String? = (userInfo["fcm_options"] as? [String : Any?])?["image"] as? String if image != nil { notificationModel!.content!.notificationLayout = .BigPicture notificationModel!.content!.bigPicture = image } } + + if !userInfo.isEmpty && notificationModel!.content!.payload == nil { + notificationModel!.content!.payload = [:] + } + for (key, value) in userInfo { + guard let key:String = key as? String + else { continue } + if key == Definitions.NOTIFICATION_JSON { continue } + if key == Definitions.NOTIFICATION_MODEL_CONTENT { continue } + if key == Definitions.NOTIFICATION_MODEL_BUTTONS { continue } + if key == Definitions.NOTIFICATION_MODEL_SCHEDULE { continue } + if key == Definitions.NOTIFICATION_MODEL_ANDROID { continue } + if key == Definitions.NOTIFICATION_MODEL_IOS { continue } + + notificationModel!.content!.payload![key] = value as? String + } guard let actionReceived:ActionReceived = NotificationBuilder diff --git a/ios/AwnCore/Classes/broadcasters/senders/BroadcastSender.swift b/ios/AwnCore/Classes/broadcasters/senders/BroadcastSender.swift index f9a5e8bc..9c0ee2a2 100644 --- a/ios/AwnCore/Classes/broadcasters/senders/BroadcastSender.swift +++ b/ios/AwnCore/Classes/broadcasters/senders/BroadcastSender.swift @@ -79,7 +79,7 @@ class BroadcastSender { .addActionEvent( named: Definitions.BROADCAST_DEFAULT_ACTION, with: actionReceived) - Logger.i(TAG, "action broadcasted") + Logger.d(TAG, "action broadcasted") } completionHandler(true, nil) diff --git a/ios/AwnCore/Classes/builders/NotificationBuilder.swift b/ios/AwnCore/Classes/builders/NotificationBuilder.swift index ecc31375..c84ab1fc 100644 --- a/ios/AwnCore/Classes/builders/NotificationBuilder.swift +++ b/ios/AwnCore/Classes/builders/NotificationBuilder.swift @@ -120,7 +120,9 @@ public class NotificationBuilder { completion(nil) return } - + + setCurrentTranslation(notificationModel: notificationModel) + let content = content ?? buildNotificationContentFromModel(notificationModel: notificationModel) setTitle(notificationModel: notificationModel, channel: channel, content: content) @@ -231,7 +233,67 @@ public class NotificationBuilder { content.userInfo[Definitions.NOTIFICATION_CHANNEL_KEY] = notificationModel.content!.channelKey! content.userInfo[Definitions.NOTIFICATION_GROUP_KEY] = notificationModel.content!.groupKey } + + private func setCurrentTranslation(notificationModel: NotificationModel) { + guard let localizations = notificationModel.localizations, !localizations.isEmpty else { return } + + let languageCode = LocalizationManager.shared.getLocalization() + guard let matchedTranslationCode = getMatchedLanguageCode(localizations, languageCode: languageCode) + else { return } + + guard let localizationModel:NotificationLocalizationModel = localizations[matchedTranslationCode] + else { return } + + if !StringUtils.shared.isNullOrEmpty(localizationModel.title) { + notificationModel.content!.title = localizationModel.title + } + if !StringUtils.shared.isNullOrEmpty(localizationModel.body) { + notificationModel.content!.body = localizationModel.body + } + if !StringUtils.shared.isNullOrEmpty(localizationModel.summary) { + notificationModel.content!.summary = localizationModel.summary + } + if !StringUtils.shared.isNullOrEmpty(localizationModel.largeIcon) { + notificationModel.content!.largeIcon = localizationModel.largeIcon + } + if !StringUtils.shared.isNullOrEmpty(localizationModel.bigPicture) { + notificationModel.content!.bigPicture = localizationModel.bigPicture + } + guard + let buttonLabels:[String:String] = localizationModel.buttonLabels, + let actionButtons:[NotificationButtonModel] = notificationModel.actionButtons + else { return } + + for buttonModel in actionButtons { + if let label:String = buttonLabels[buttonModel.key!] { + buttonModel.label = label + } + } + } + + private func getMatchedLanguageCode(_ localizations: [String: NotificationLocalizationModel], languageCode: String) -> String? { + let lowercaseLanguageCode = languageCode.lowercased(with: Locale(identifier: "en")) + if localizations.keys.contains(lowercaseLanguageCode) { + return lowercaseLanguageCode + } + + let sortedCodeKeys = localizations.sorted(by: { $0.key < $1.key }) + for (laguangeCode, _) in sortedCodeKeys { + let lowercaseKey = laguangeCode.lowercased(with: Locale(identifier: "en")) + if lowercaseKey == lowercaseLanguageCode { + return laguangeCode + } + if lowercaseKey.hasPrefix("\(lowercaseLanguageCode)-") { + return laguangeCode + } + if lowercaseLanguageCode.hasPrefix("\(lowercaseKey)-") { + return laguangeCode + } + } + return nil + } + private func setTitle(notificationModel:NotificationModel, channel:NotificationChannelModel, content:UNMutableNotificationContent){ content.title = notificationModel.content!.title?.withoutHtmlTags() ?? "" } @@ -247,6 +309,17 @@ public class NotificationBuilder { } private func setBadgeIndicator(notificationModel:NotificationModel, channel:NotificationChannelModel, content:UNMutableNotificationContent){ + if notificationModel.content?.badge != nil { + let badgeAmount:Int = max(notificationModel.content!.badge!, 0) + content.badge = NSNumber(value: badgeAmount) + if SwiftUtils.isRunningOnExtension() { + BadgeManager + .shared + .setGlobalBadgeCounterInStorage( + newValue: badgeAmount) + } + return + } if(channel.channelShowBadge!){ content.badge = NSNumber(value: BadgeManager.shared.incrementGlobalBadgeCounter()) } diff --git a/ios/AwnCore/Classes/databases/SQLitePrimitivesDB.swift b/ios/AwnCore/Classes/databases/SQLitePrimitivesDB.swift new file mode 100644 index 00000000..26df89f0 --- /dev/null +++ b/ios/AwnCore/Classes/databases/SQLitePrimitivesDB.swift @@ -0,0 +1,623 @@ +// +// UserDefaultPrimitivesDB.swift +// IosAwnCore +// +// Created by Rafael Setragni on 18/02/23. +// + +import Foundation +import SQLite3 + +public enum SQLiteError: Error { + case openFailed(String) + case queryFailed(String) + case invalidParameterType + case notFound +} + +public class SQLitePrimitivesDB { + + private var DATABASE_VERSION = 1 + private var DATABASE_NAME = "AwesomePrimitivesDB.db" + + private var TABLE_STRING = "string_prefs" + private var TABLE_INT = "int_prefs" + private var TABLE_FLOAT = "float_prefs" + private var TABLE_BOOLEAN = "boolean_prefs" + private var TABLE_LONG = "long_prefs" + private let COLUMN_TAG = "tag" + private let COLUMN_KEY = "key" + private let COLUMN_VALUE = "value" + + private let databasePath:URL + private init() { + databasePath = try! FileManager.default + .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + .appendingPathComponent(DATABASE_NAME) + + switch openDatabase(writable: true) { + + case .success(let db): + onCreate(writableDb: db) + + case .failure(let error): + print("Error opening database: \(error.localizedDescription)") + } + } + static let shared = SQLitePrimitivesDB() + + private func openDatabase(writable: Bool) -> Result { + var db: OpaquePointer? + let flags = writable ? SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE : SQLITE_OPEN_READONLY + + return withUnsafeMutablePointer(to: &db) { dbPointer in + let status = sqlite3_open_v2(databasePath.path, dbPointer, flags, nil) + + guard status == SQLITE_OK, let database = dbPointer.pointee else { + let errorMessage = String(cString: sqlite3_errmsg(dbPointer.pointee)) + return .failure(SQLiteError.openFailed(errorMessage)) + } + + let copy = database + return .success(copy) + } + } + + private func getWritableDatabase() -> Result { + openDatabase(writable: true) + } + + private func getReadableDatabase() -> Result { + openDatabase(writable: false) + } + + private func execute(db: OpaquePointer, query: String, parameters: (() -> [Any])? = nil) -> Result { + var statement: OpaquePointer? + + guard sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK else { + return .failure(SQLiteError.queryFailed(String(cString: sqlite3_errmsg(db)))) + } + + if let parameters = parameters { + let values = parameters() + + for (index, value) in values.enumerated() { + let statementIndex = Int32(index + 1) + + switch value { + case let int as Int: + sqlite3_bind_int(statement, statementIndex, Int32(int)) + + case let int64 as Int64: + sqlite3_bind_int64(statement, statementIndex, int64) + + case let double as Double: + sqlite3_bind_double(statement, statementIndex, double) + + case let bool as Bool: + let intValue = bool ? 1 : 0 + sqlite3_bind_int(statement, statementIndex, Int32(intValue)) + + case let string as String: + let itemNSString = string as NSString + sqlite3_bind_text(statement, statementIndex, itemNSString.utf8String, -1, nil) + + default: + return .failure(SQLiteError.invalidParameterType) + } + } + } + + let result = sqlite3_step(statement) + + if result == SQLITE_NOTFOUND { + return .failure(SQLiteError.notFound) + } + + if result == SQLITE_ROW { + return .success(statement!) + } + + if result == SQLITE_DONE { + return .success(statement!) + } + + return .failure(SQLiteError.queryFailed(String(cString: sqlite3_errmsg(db)))) + } + + + private func onCreate(writableDb: OpaquePointer) { + let createStringTable = """ + CREATE TABLE IF NOT EXISTS \(TABLE_STRING) ( + \(COLUMN_TAG) TEXT, + \(COLUMN_KEY) TEXT, + \(COLUMN_VALUE) TEXT, + PRIMARY KEY (\(COLUMN_TAG), \(COLUMN_KEY))); + """ + _ = execute(db: writableDb, query:createStringTable) + + let createIntTable = """ + CREATE TABLE IF NOT EXISTS \(TABLE_INT) ( + \(COLUMN_TAG) TEXT, + \(COLUMN_KEY) TEXT, + \(COLUMN_VALUE) INTEGER, + PRIMARY KEY (\(COLUMN_TAG), \(COLUMN_KEY))); + """ + _ = execute(db: writableDb, query:createIntTable) + + let createFloatTable = """ + CREATE TABLE IF NOT EXISTS \(TABLE_FLOAT) ( + \(COLUMN_TAG) TEXT, + \(COLUMN_KEY) TEXT, + \(COLUMN_VALUE) REAL, + PRIMARY KEY (\(COLUMN_TAG), \(COLUMN_KEY))); + """ + _ = execute(db: writableDb, query:createFloatTable) + + let createBooleanTable = """ + CREATE TABLE IF NOT EXISTS \(TABLE_BOOLEAN) ( + \(COLUMN_TAG) TEXT, + \(COLUMN_KEY) TEXT, + \(COLUMN_VALUE) INTEGER, + PRIMARY KEY (\(COLUMN_TAG), \(COLUMN_KEY))); + """ + _ = execute(db: writableDb, query:createBooleanTable) + + let createLongTable = """ + CREATE TABLE IF NOT EXISTS \(TABLE_LONG) ( + \(COLUMN_TAG) TEXT, + \(COLUMN_KEY) TEXT, + \(COLUMN_VALUE) INTEGER, + PRIMARY KEY (\(COLUMN_TAG), \(COLUMN_KEY))); + """ + _ = execute(db: writableDb, query:createLongTable) + } + + private func commit(db: OpaquePointer){ + _ = execute(db: db, query: "COMMIT") + close(db: db) + } + + private func rollback(db: OpaquePointer){ + _ = execute(db: db, query: "ROLLBACK") + close(db: db) + } + + private func close(db: OpaquePointer){ + sqlite3_close(db) + } + + private func setValue(tableName: String, tag: String, key: String, value: T) -> Result { + let query = """ + INSERT OR REPLACE INTO \(tableName) (\(COLUMN_TAG), \(COLUMN_KEY), \(COLUMN_VALUE)) + VALUES (?, ?, ?) + """ + + let parameters = { [tag, key, value] as [Any] } + + switch getWritableDatabase() { + case .success(let db): + switch execute(db: db, query: query, parameters: parameters) { + case .success: + _ = execute(db: db, query: "COMMIT") + close(db: db) + return .success(()) + + case .failure(let error): + _ = execute(db: db, query: "ROLLBACK") + close(db: db) + print("Error setting value: \(error.localizedDescription)") + return .failure(error) + } + + case .failure(let error): + print("Error getting writable database: \(error.localizedDescription)") + return .failure(error) + } + } + + public func setBoolean(tag: String, key: String, value: Bool) -> Result { + return setValue(tableName: TABLE_BOOLEAN, tag: tag, key: key, value: value ? 1 : 0) + } + public func setInt(tag: String, key: String, value: Int) -> Result { + return setValue(tableName: TABLE_INT, tag: tag, key: key, value: value) + } + public func setLong(tag: String, key: String, value: Int64) -> Result { + return setValue(tableName: TABLE_LONG, tag: tag, key: key, value: value) + } + public func setFloat(tag: String, key: String, value: Float) -> Result { + return setValue(tableName: TABLE_FLOAT, tag: tag, key: key, value: value) + } + public func setString(tag: String, key: String, value: String) -> Result { + return setValue(tableName: TABLE_STRING, tag: tag, key: key, value: value) + } + + private func getValue(tag: String, key: String, type: T.Type) -> Result { + let tableName: String + switch type { + case is Int.Type: + tableName = TABLE_STRING + + case is Int64.Type: + tableName = TABLE_STRING + + case is Double.Type: + tableName = TABLE_STRING + + case is Float.Type: + tableName = TABLE_STRING + + case is Bool.Type: + tableName = TABLE_STRING + + case is String.Type: + tableName = TABLE_STRING + + default: + return .failure(SQLiteError.notFound) + } + + let query = """ + SELECT \(COLUMN_VALUE) FROM \(tableName) + WHERE \(COLUMN_TAG) = ? AND \(COLUMN_KEY) = ? + """ + + + let parameters = { [tag, key] as [Any] } + + switch getReadableDatabase() { + case .success(let db): + defer { close(db: db) } + + switch execute(db: db, query: query, parameters: parameters) { + case .success(let statement): + defer { sqlite3_finalize(statement) } + + switch type { + case is Int.Type: + let value = sqlite3_column_int(statement, 0) + return .success(value as? T) + + case is Int64.Type: + let value = sqlite3_column_int64(statement, 0) + return .success(value as? T) + + case is Double.Type: + let value = sqlite3_column_double(statement, 0) + return .success(value as? T) + + case is Float.Type: + let value = sqlite3_column_double(statement, 0) + return .success(value as? T) + + case is Bool.Type: + let value = sqlite3_column_int(statement, 0) + return .success((value == 1) as? T) + + case is String.Type: + let value = String(cString: sqlite3_column_text(statement, 0)) + return .success(value as? T) + + default: + return .failure(SQLiteError.invalidParameterType) + } + + case .failure(let error): + switch error { + case .notFound: + return .success(nil) + default: + return .failure(error) + } + + } + + case .failure(let error): + return .failure(error) + } + } + + public func getBoolean(tag: String, key: String) -> Result { + return getValue(tag: tag, key: key, type: Bool.self) + } + + public func getInt(tag: String, key: String) -> Result { + return getValue(tag: tag, key: key, type: Int.self) + } + + public func getLong(tag: String, key: String) -> Result { + return getValue(tag: tag, key: key, type: Int64.self) + } + + public func getFloat(tag: String, key: String) -> Result { + return getValue(tag: tag, key: key, type: Float.self) + } + + public func getString(tag: String, key: String) -> Result { + return getValue(tag: tag, key: key, type: String.self) + } + + + private func count(tableName: String, tag: String) -> Result { + let query = """ + SELECT COUNT(*) + FROM \(tableName) + WHERE \(COLUMN_TAG) = ? + """ + + var count = 0 + switch getReadableDatabase() { + case .success(let db): + defer { close(db: db)} + + switch execute(db: db, query: query, parameters: { [tag] as [Any] }) { + case .success(let statement): + defer { sqlite3_finalize(statement) } + + while sqlite3_step(statement) == SQLITE_ROW { + count = Int(sqlite3_column_int(statement, 0)) + } + return .success(count) + + case .failure(let error): + return .failure(error) + } + + case .failure(let error): + return .failure(error) + } + } + + public func stringCount(tag: String) -> Result { + return count(tableName: TABLE_STRING, tag: tag) + } + + public func floatCount(tag: String) -> Result { + return count(tableName: TABLE_FLOAT, tag: tag) + } + + public func intCount(tag: String) -> Result { + return count(tableName: TABLE_INT, tag: tag) + } + + public func booleanCount(tag: String) -> Result { + return count(tableName: TABLE_BOOLEAN, tag: tag) + } + + public func longCount(tag: String) -> Result { + return count(tableName: TABLE_LONG, tag: tag) + } + + + private func getAllValues(tableName: String, tag: String, valueGetter: (OpaquePointer) -> T) -> Result<[String: T], SQLiteError> { + var values: [String: T] = [:] + + let query = """ + SELECT \(COLUMN_KEY), \(COLUMN_VALUE) FROM \(tableName) + WHERE \(COLUMN_TAG) = '\(tag)' + """ + + switch getReadableDatabase() { + case .success(let db): + defer { close(db: db)} + + switch execute(db: db, query: query, parameters: { [tag] as [Any] }) { + case .success(let statement): + defer { sqlite3_finalize(statement) } + + while sqlite3_step(statement) == SQLITE_ROW { + let key = String(cString: sqlite3_column_text(statement, 0)) + let value = valueGetter(statement) + + values[key] = value + } + return .success(values) + + case .failure(let error): + return .failure(error) + } + + case .failure(let error): + return .failure(error) + } + } + + public func getAllStringValues(tag: String) -> Result<[String: String], SQLiteError> { + getAllValues(tableName: TABLE_STRING, tag: tag) { statement in + String(cString: sqlite3_column_text(statement, 1)) + } + } + + public func getAllFloatValues(tag: String) -> Result<[String: Float], SQLiteError> { + return getAllValues(tableName: TABLE_FLOAT, tag: tag) { statement in + Float(sqlite3_column_double(statement, 1)) + } + } + + public func getAllIntValues(tag: String) -> Result<[String: Int], SQLiteError> { + return getAllValues(tableName: TABLE_INT, tag: tag) { statement in + Int(sqlite3_column_int(statement, 1)) + } + } + + public func getAllBooleanValues(tag: String) -> Result<[String: Bool], SQLiteError> { + return getAllValues(tableName: TABLE_BOOLEAN, tag: tag) { statement in + sqlite3_column_int(statement, 1) != 0 + } + } + + public func getAllLongValues(tag: String) -> Result<[String: Int64], SQLiteError> { + return getAllValues(tableName: TABLE_LONG, tag: tag) { statement in + sqlite3_column_int64(statement, 1) + } + } + + private func setAllValues(tableName: String, tag: String, values: [String: T]) -> Result where T: Any { + let query = """ + INSERT OR REPLACE INTO \(tableName) (\(COLUMN_TAG), \(COLUMN_KEY), \(COLUMN_VALUE)) + VALUES (?, ?, ?) + """ + + switch getWritableDatabase() { + case .success(let db): + defer { close(db: db)} + + _ = execute(db: db, query: "BEGIN TRANSACTION") + + for (key, value) in values { + let parameters = [tag, key, value] as [Any] + let _ = execute(db: db, query: query, parameters: { parameters }) + } + + _ = execute(db: db, query: "COMMIT") + return .success(()) + + case .failure(let error): + return .failure(SQLiteError.queryFailed( + "Error getting writable database: \(error.localizedDescription)" + )) + } + } + + public func setAllIntValues(tag: String, intValues: [String: Int]) -> Result { + return setAllValues(tableName: TABLE_INT, tag: tag, values: intValues) + } + + public func setAllStringValues(tag: String, stringValues: [String: String]) -> Result { + return setAllValues(tableName: TABLE_STRING, tag: tag, values: stringValues) + } + + public func setAllFloatValues(tag: String, floatValues: [String: Float]) -> Result { + return setAllValues(tableName: TABLE_FLOAT, tag: tag, values: floatValues) + } + + public func setAllBooleanValues(tag: String, booleanValues: [String: Bool]) -> Result { + return setAllValues(tableName: TABLE_BOOLEAN, tag: tag, values: booleanValues) + } + + public func setAllLongValues(tag: String, longValues: [String: Int64]) -> Result { + return setAllValues(tableName: TABLE_LONG, tag: tag, values: longValues) + } + + private func removeValue(tableName: String, tag: String, key: String) -> Result { + let query = """ + DELETE FROM \(tableName) + WHERE \(COLUMN_TAG) = ? AND \(COLUMN_KEY) = ? + """ + + let parameters = { [tag, key] as [Any] } + + switch getWritableDatabase() { + case .success(let db): + defer { close(db: db)} + + switch execute(db: db, query: query, parameters: parameters) { + case .success: + _ = execute(db: db, query: "COMMIT") + return .success(()) + + case .failure(let error): + _ = execute(db: db, query: "ROLLBACK") + return .failure(SQLiteError.queryFailed( + "Error removing value: \(error.localizedDescription)" + )) + } + + case .failure(let error): + return .failure(SQLiteError.queryFailed( + "Error getting writable database: \(error.localizedDescription)" + )) + } + } + + public func removeString(tag: String, key: String) -> Result { + return removeValue(tableName: TABLE_STRING, tag: tag, key: key) + } + + public func removeFloat(tag: String, key: String) -> Result { + return removeValue(tableName: TABLE_FLOAT, tag: tag, key: key) + } + + public func removeInt(tag: String, key: String) -> Result { + return removeValue(tableName: TABLE_INT, tag: tag, key: key) + } + + public func removeBoolean(tag: String, key: String) -> Result { + return removeValue(tableName: TABLE_BOOLEAN, tag: tag, key: key) + } + + public func removeLong(tag: String, key: String) -> Result { + return removeValue(tableName: TABLE_LONG, tag: tag, key: key) + } + + private func removeAllValues(tableName: String, tag: String) -> Result { + let query = "DELETE FROM \(tableName) WHERE \(COLUMN_TAG) = ?" + let parameters = { [tag] as [Any] } + + switch getWritableDatabase() { + case .success(let db): + defer { close(db: db)} + + switch execute(db: db, query: query, parameters: parameters) { + case .success: + _ = execute(db: db, query: "COMMIT") + return .success(()) + + case .failure(let error): + _ = execute(db: db, query: "ROLLBACK") + return .failure(SQLiteError.queryFailed( + "Error removing values: \(error.localizedDescription)" + )) + } + + case .failure(let error): + return .failure(SQLiteError.queryFailed( + "Error getting writable database: \(error.localizedDescription)" + )) + } + } + + public func removeAllString(tag: String) -> Result { + return removeAllValues(tableName: TABLE_STRING, tag: tag) + } + + public func removeAllFloat(tag: String) -> Result { + return removeAllValues(tableName: TABLE_FLOAT, tag: tag) + } + + public func removeAllInt(tag: String) -> Result { + return removeAllValues(tableName: TABLE_INT, tag: tag) + } + + public func removeAllBoolean(tag: String) -> Result { + return removeAllValues(tableName: TABLE_BOOLEAN, tag: tag) + } + + public func removeAllLong(tag: String) -> Result { + return removeAllValues(tableName: TABLE_LONG, tag: tag) + } + + private func removeAll(tableName: String, tag: String) -> Result { + let results: [Result] = [ + removeAllString(tag: tag), + removeAllInt(tag: tag), + removeAllLong(tag: tag), + removeAllFloat(tag: tag), + removeAllBoolean(tag: tag) + ] + + let firstFailure = results.first(where: { result in + if case let .failure(error) = result { + print("Error removing all values: \(error.localizedDescription)") + return true + } + return false + }) + + if let firstFailure = firstFailure { + return firstFailure + } + + return .success(()) + } +} diff --git a/ios/AwnCore/Classes/extensions/StringExtension.swift b/ios/AwnCore/Classes/extensions/StringExtension.swift index 5900c3c1..47795d9d 100644 --- a/ios/AwnCore/Classes/extensions/StringExtension.swift +++ b/ios/AwnCore/Classes/extensions/StringExtension.swift @@ -1,7 +1,5 @@ -import var CommonCrypto.CC_MD5_DIGEST_LENGTH -import func CommonCrypto.CC_MD5 -import typealias CommonCrypto.CC_LONG +import CommonCrypto extension String { diff --git a/ios/AwnCore/Classes/managers/ActionManager.swift b/ios/AwnCore/Classes/managers/ActionManager.swift index 16f569f2..180c00c2 100644 --- a/ios/AwnCore/Classes/managers/ActionManager.swift +++ b/ios/AwnCore/Classes/managers/ActionManager.swift @@ -14,31 +14,46 @@ public class ActionManager { // Cache is necessary due user preferences are not aways ready for return data // if the respective value is request too fast. static var actionCache:[Int:ActionReceived] = [:] + static var initialAction:ActionReceived? + static var removeInitialActionFromCache:Bool = false public static func removeAction(id:Int) -> Bool { return actionCache.removeValue(forKey: id) != nil } public static func recoverActions() -> [ActionReceived] { - Logger.i(TAG, "action recovered") + if recovered { return [] } recovered = true return Array(actionCache.values) } public static func saveAction(received:ActionReceived) { + if received.actionLifeCycle == .AppKilled { + initialAction = received + if removeInitialActionFromCache { return } + } actionCache[received.id!] = received } public static func getActionByKey(id:Int) -> ActionReceived? { return actionCache[id] } - + public static func removeAllActions() { actionCache.removeAll() } - - public static func removeAction(id:Int) { - actionCache.removeValue(forKey: id) + + public static func getInitialAction(removeFromEvents:Bool) -> ActionReceived? { + if initialAction == nil { return nil } + if removeFromEvents { + removeInitialActionFromCache = true + _ = removeAction(id: initialAction!.id!) + } + return initialAction } + + //public static func removeAction(id:Int) { + // actionCache.removeValue(forKey: id) + //} } diff --git a/ios/AwnCore/Classes/managers/BadgeManager.swift b/ios/AwnCore/Classes/managers/BadgeManager.swift index cb3f984a..a52b0e2c 100644 --- a/ios/AwnCore/Classes/managers/BadgeManager.swift +++ b/ios/AwnCore/Classes/managers/BadgeManager.swift @@ -7,8 +7,7 @@ import Foundation -public class BadgeManager { - +public class BadgeManager : AwesomeLifeCycleEventListener { let TAG:String = "BadgeManager" // ************** SINGLETON PATTERN *********************** @@ -21,7 +20,14 @@ public class BadgeManager { return BadgeManager.instance! } } - private init(){} + + private init(){ + _ = LifeCycleManager.shared.subscribe(listener: self) + } + + public func onNewLifeCycleEvent(lifeCycle: NotificationLifeCycle) { + syncBadgeAmount() + } // ******************************************************** @@ -49,11 +55,18 @@ public class BadgeManager { else{ _badgeAmount = NSNumber(value: newValue) } - - let userDefaults = UserDefaults(suiteName: Definitions.USER_DEFAULT_TAG) - userDefaults!.set(newValue, forKey: Definitions.BADGE_COUNT) + setGlobalBadgeCounterInStorage(newValue: newValue) } } + + public func syncBadgeAmount(){ + setGlobalBadgeCounterInStorage(newValue: globalBadgeCounter) + } + + public func setGlobalBadgeCounterInStorage(newValue:Int) { + let userDefaults = UserDefaults(suiteName: Definitions.USER_DEFAULT_TAG) + userDefaults!.set(newValue, forKey: Definitions.BADGE_COUNT) + } public func resetGlobalBadgeCounter() { globalBadgeCounter = 0 diff --git a/ios/AwnCore/Classes/managers/DefaultsManager.swift b/ios/AwnCore/Classes/managers/DefaultsManager.swift index f0c3cd73..e1887786 100644 --- a/ios/AwnCore/Classes/managers/DefaultsManager.swift +++ b/ios/AwnCore/Classes/managers/DefaultsManager.swift @@ -7,9 +7,9 @@ import Foundation -class DefaultsManager { +public class DefaultsManager { - static let TAG = "DefaultsManager" + let TAG = "DefaultsManager" let userDefaults:UserDefaults = UserDefaults(suiteName: Definitions.USER_DEFAULT_TAG)! // ************** SINGLETON PATTERN *********************** @@ -24,6 +24,10 @@ class DefaultsManager { } private init(){} + public func setDefaultGroupTest() { + userDefaults.setValue("pass", forKey: Definitions.TEST_APP_GROUP) + } + // ******************************************************** public var debug:Bool { @@ -55,14 +59,11 @@ class DefaultsManager { get { let dateText:String? = userDefaults.object(forKey: Definitions.AWESOME_LAST_DISPLAYED_DATE) as? String - Logger.d(DefaultsManager.TAG, "Awesome Notifications - UTC timezone : \(RealDateTime.utcTimeZone)") - Logger.d(DefaultsManager.TAG, "Awesome Notifications - Local timezone : \(DateUtils.shared.localTimeZone)") - guard let dateText:String = dateText else { return RealDateTime.init(fromTimeZone: RealDateTime.utcTimeZone) } - Logger.d(DefaultsManager.TAG, "Awesome Notifications - last displayed date : \(dateText)") + Logger.d(TAG, "Awesome Notifications - last displayed date : \(dateText)") guard let lastDate:RealDateTime = RealDateTime.init( @@ -81,4 +82,11 @@ class DefaultsManager { public func registerLastDisplayedDate(){ self.lastDisplayedDate = RealDateTime.init(fromTimeZone: RealDateTime.utcTimeZone) } + + public func checkIfAppGroupConnected() { + let valueRestored:String? = userDefaults.object(forKey: Definitions.TEST_APP_GROUP) as? String + if valueRestored?.isEmpty ?? true { + Logger.e(TAG, "App Groups are not successfully connected. Please, use '\(Definitions.USER_DEFAULT_TAG)' group name in your App Groups Capabilities.") + } + } } diff --git a/ios/AwnCore/Classes/managers/LocalizationManager.swift b/ios/AwnCore/Classes/managers/LocalizationManager.swift new file mode 100644 index 00000000..ff52dd1e --- /dev/null +++ b/ios/AwnCore/Classes/managers/LocalizationManager.swift @@ -0,0 +1,65 @@ +// +// LocalizationManager.swift +// IosAwnCore +// +// Created by Rafael Setragni on 18/02/23. +// + +import Foundation + +class LocalizationManager { + static let shared = LocalizationManager() + + func setLocalization(languageCode: String? = nil) -> Bool { + let langCode = + languageCode ?? + Locale.preferredLanguages[0] + + switch SQLitePrimitivesDB.shared.setString( + tag: "localization", + key: "languageCode", + value: langCode + .lowercased() + .replacingOccurrences(of: "_", with: "-") + ) { + case .success(): + return true + case .failure(let error): + let awnError = ExceptionFactory.shared.createNewAwesomeException( + className: "LocalizationManager", + code: ExceptionCode.CODE_INSUFFICIENT_PERMISSIONS, + message: "SQLitePrimitivesDB is not available \(error.localizedDescription)", + detailedCode: ExceptionCode.DETAILED_INSUFFICIENT_PERMISSIONS+".setLocalization" + ) + print(awnError) + return false + } + } + + func getLocalization() -> String { + let appLangCode = Locale.preferredLanguages[0] + + switch SQLitePrimitivesDB.shared.getString( + tag: "localization", + key: "languageCode" + ) { + + case .success(let value): + return (value ?? appLangCode) + .lowercased() + .replacingOccurrences(of: "_", with: "-") + + case .failure(let error): + let error = ExceptionFactory.shared.createNewAwesomeException( + className: "LocalizationManager", + code: ExceptionCode.CODE_INSUFFICIENT_PERMISSIONS, + message: "SQLitePrimitivesDB is not available \(error.localizedDescription)", + detailedCode: ExceptionCode.DETAILED_INSUFFICIENT_PERMISSIONS+".getLocalization" + ) + print(error) + return appLangCode + .lowercased() + .replacingOccurrences(of: "_", with: "-") + } + } +} diff --git a/ios/AwnCore/Classes/managers/PermissionManager.swift b/ios/AwnCore/Classes/managers/PermissionManager.swift index 8c9362e2..aa74aab0 100644 --- a/ios/AwnCore/Classes/managers/PermissionManager.swift +++ b/ios/AwnCore/Classes/managers/PermissionManager.swift @@ -624,7 +624,7 @@ public class PermissionManager { guard let settingsUrl = URL(string: url) else { return false } - + if UIApplication.shared.canOpenURL(settingsUrl) { DispatchQueue.main.async { UIApplication.shared.open(settingsUrl) @@ -634,7 +634,6 @@ public class PermissionManager { return false } - public func handlePermissionResult() { fireActivityCompletionHandle() } diff --git a/ios/AwnCore/Classes/models/NotificationContentModel.swift b/ios/AwnCore/Classes/models/NotificationContentModel.swift index 855e9aec..de2a881f 100644 --- a/ios/AwnCore/Classes/models/NotificationContentModel.swift +++ b/ios/AwnCore/Classes/models/NotificationContentModel.swift @@ -36,6 +36,7 @@ public class NotificationContentModel : AbstractModel { public var color: Int64? public var backgroundColor: Int64? public var progress: Int? + public var badge: Int? public var ticker: String? public var roundedLargeIcon: Bool? @@ -139,6 +140,7 @@ public class NotificationContentModel : AbstractModel { self.color = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_COLOR, arguments: arguments) self.backgroundColor = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_BACKGROUND_COLOR, arguments: arguments) self.progress = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_PROGRESS, arguments: arguments) + self.badge = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_BADGE, arguments: arguments) self.ticker = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_TICKER, arguments: arguments) self.roundedLargeIcon = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_ROUNDED_LARGE_ICON, arguments: arguments) @@ -159,6 +161,13 @@ public class NotificationContentModel : AbstractModel { self.payload = MapUtils<[String:String?]>.getValueOrDefault(reference: Definitions.NOTIFICATION_PAYLOAD, arguments: arguments) + if StringUtils.shared.isNullOrEmpty(self.bigPicture, considerWhiteSpaceAsEmpty: true) { + self.largeIcon = nil + } + if StringUtils.shared.isNullOrEmpty(self.bigPicture, considerWhiteSpaceAsEmpty: true) { + self.bigPicture = nil + } + return self } @@ -189,6 +198,7 @@ public class NotificationContentModel : AbstractModel { if(self.color != nil){ mapData[Definitions.NOTIFICATION_COLOR] = self.color } if(self.backgroundColor != nil){ mapData[Definitions.NOTIFICATION_BACKGROUND_COLOR] = self.backgroundColor } if(self.progress != nil){ mapData[Definitions.NOTIFICATION_PROGRESS] = self.progress } + if(self.badge != nil){ mapData[Definitions.NOTIFICATION_BADGE] = self.badge } if(self.ticker != nil){ mapData[Definitions.NOTIFICATION_TICKER] = self.ticker } if(self.privacy != nil){ mapData[Definitions.NOTIFICATION_PRIVACY] = self.privacy?.rawValue } if(self.privateMessage != nil){ mapData[Definitions.NOTIFICATION_PRIVATE_MESSAGE] = self.privateMessage } diff --git a/ios/AwnCore/Classes/models/NotificationIntervalModel.swift b/ios/AwnCore/Classes/models/NotificationIntervalModel.swift index f02532cb..073f8a89 100644 --- a/ios/AwnCore/Classes/models/NotificationIntervalModel.swift +++ b/ios/AwnCore/Classes/models/NotificationIntervalModel.swift @@ -58,13 +58,13 @@ public class NotificationIntervalModel : NotificationScheduleModel { public func validate() throws { - if(IntUtils.isNullOrEmpty(interval) || interval! <= 5){ + if(IntUtils.isNullOrEmpty(interval) || interval! < 5){ throw ExceptionFactory .shared .createNewAwesomeException( className: NotificationIntervalModel.TAG, code: ExceptionCode.CODE_INVALID_ARGUMENTS, - message: "Interval is required and must be greater than 5", + message: "Interval is required and must be equal or greater than 5", detailedCode: ExceptionCode.DETAILED_INVALID_ARGUMENTS+".notificationInterval.interval") } diff --git a/ios/AwnCore/Classes/models/NotificationLocalizationModel.swift b/ios/AwnCore/Classes/models/NotificationLocalizationModel.swift new file mode 100644 index 00000000..a5f1aea1 --- /dev/null +++ b/ios/AwnCore/Classes/models/NotificationLocalizationModel.swift @@ -0,0 +1,55 @@ +// +// NotificationLocalization.swift +// IosAwnCore +// +// Created by Rafael Setragni on 19/02/23. +// + +import Foundation + +public class NotificationLocalizationModel : AbstractModel { + + private static let TAG = "NotificationLocalization" + + public var content:NotificationContentModel? + public var actionButtons:[NotificationButtonModel]? + public var schedule:NotificationScheduleModel? + public var importance:NotificationImportance? + + var title:String? + var body:String? + var summary:String? + var largeIcon:String? + var bigPicture:String? + var buttonLabels:[String:String]? + + public init(){} + + public func fromMap(arguments: [String : Any?]?) -> AbstractModel? { + + self.title = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_TITLE, arguments: arguments) + self.body = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_BODY, arguments: arguments) + self.summary = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_SUMMARY, arguments: arguments) + self.largeIcon = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_LARGE_ICON, arguments: arguments) + self.bigPicture = MapUtils.getValueOrDefault(reference: Definitions.NOTIFICATION_BIG_PICTURE, arguments: arguments) + self.buttonLabels = MapUtils<[String:String]>.getValueOrDefault(reference: Definitions.NOTIFICATION_BUTTON_LABELS, arguments: arguments) + + return self + } + + public func toMap() -> [String : Any?] { + var mapData:[String: Any?] = [:] + + if(title != nil) {mapData[Definitions.NOTIFICATION_TITLE] = title} + if(body != nil) {mapData[Definitions.NOTIFICATION_BODY] = body} + if(summary != nil) {mapData[Definitions.NOTIFICATION_SUMMARY] = summary} + if(largeIcon != nil) {mapData[Definitions.NOTIFICATION_LARGE_ICON] = largeIcon} + if(bigPicture != nil) {mapData[Definitions.NOTIFICATION_BIG_PICTURE] = bigPicture} + if(buttonLabels != nil) {mapData[Definitions.NOTIFICATION_BUTTON_LABELS] = buttonLabels} + + return mapData + } + + public func validate() throws { + } +} diff --git a/ios/AwnCore/Classes/models/NotificationModel.swift b/ios/AwnCore/Classes/models/NotificationModel.swift index 07f08e41..2ccdee1d 100644 --- a/ios/AwnCore/Classes/models/NotificationModel.swift +++ b/ios/AwnCore/Classes/models/NotificationModel.swift @@ -14,6 +14,7 @@ public class NotificationModel : AbstractModel { public var content:NotificationContentModel? public var actionButtons:[NotificationButtonModel]? public var schedule:NotificationScheduleModel? + public var localizations: [String:NotificationLocalizationModel]? public var importance:NotificationImportance? public var nextValidDate:RealDateTime? @@ -29,6 +30,7 @@ public class NotificationModel : AbstractModel { self.schedule = try extractNotificationSchedule(Definitions.NOTIFICATION_MODEL_SCHEDULE, arguments) self.actionButtons = extractNotificationButtons(Definitions.NOTIFICATION_MODEL_BUTTONS, arguments) + self.localizations = extractLocalizations(Definitions.NOTIFICATION_MODEL_LOCALIZATIONS, arguments) return self @@ -43,16 +45,21 @@ public class NotificationModel : AbstractModel { public func toMap() -> [String : Any?] { var mapData:[String: Any?] = [:] - mapData["content"] = self.content!.toMap() - if(self.schedule != nil){ mapData["schedule"] = self.schedule!.toMap() } - if(self.actionButtons != nil){ - var listButtons:[[String:Any?]] = [] - - for button in self.actionButtons! { - listButtons.append(button.toMap()) - } - - mapData["actionButtons"] = listButtons + mapData[Definitions.NOTIFICATION_MODEL_CONTENT] = self.content!.toMap() + if(self.schedule != nil){ + mapData[Definitions.NOTIFICATION_MODEL_SCHEDULE] = self.schedule!.toMap() + } + + if let actionButtons = self.actionButtons { + let listButtons = actionButtons.compactMap { $0.toMap() } + mapData[Definitions.NOTIFICATION_MODEL_BUTTONS] = listButtons + } + + if let localizations = self.localizations { + let localizationsData = Dictionary(uniqueKeysWithValues: localizations.map { key, value in + (key, value.toMap()) + }) + mapData[Definitions.NOTIFICATION_MODEL_LOCALIZATIONS] = localizationsData } return mapData @@ -106,6 +113,24 @@ public class NotificationModel : AbstractModel { return actionButtons } + func extractLocalizations(_ reference:String, _ arguments:[String:Any?]?) -> [String:NotificationLocalizationModel]? { + guard let localizationsData:[String:[String:Any?]] = arguments?[reference] as? [String:[String:Any?]] else { return nil } + if(localizationsData.isEmpty){ return nil } + + var localizations:[String:NotificationLocalizationModel] = [:] + + for (languageCode, localizationData) in localizationsData { + let localizationModel = NotificationLocalizationModel() + .fromMap(arguments: localizationData) as? NotificationLocalizationModel + + if(localizationModel == nil){ return nil } + localizations[languageCode] = localizationModel + } + + return localizations + + } + public func validate() throws { try self.content?.validate() try self.schedule?.validate() diff --git a/ios/AwnCore/Classes/threads/NotificationSenderAndScheduler.swift b/ios/AwnCore/Classes/threads/NotificationSenderAndScheduler.swift index e3042ffb..6c6e4094 100644 --- a/ios/AwnCore/Classes/threads/NotificationSenderAndScheduler.swift +++ b/ios/AwnCore/Classes/threads/NotificationSenderAndScheduler.swift @@ -211,71 +211,35 @@ public class NotificationSenderAndScheduler { receivedNotification:NotificationReceived?, completion: @escaping (Bool, UNMutableNotificationContent?, Error?) -> () ){ - if refreshNotification { - completion(true, content, nil) + defer { printElapsedTime(scheduled: false) } + + guard let receivedNotification = receivedNotification else { + completion(false, nil, nil) return } - // Only broadcast if notificationModel is valid - if(receivedNotification != nil){ + defer { + completion(true, content, nil) + } + + if refreshNotification { return } - if(created){ - BroadcastSender - .shared - .sendBroadcast( - notificationCreated: receivedNotification!, - whenFinished: { [self] (created:Bool) in - - if created && scheduled == nil && receivedNotification?.id != nil { - removePastSchedule(withId:receivedNotification!.id!) - } - - if scheduled == nil { - printElapsedTime(scheduled: false) - BroadcastSender - .shared - .sendBroadcast( - notificationDisplayed: receivedNotification!, - whenFinished: { [self] (created:Bool) in - completion(true, content, nil) - }) - } - else { - printElapsedTime(scheduled: true) - DisplayedManager - .saveScheduledToDisplay( - received: receivedNotification!) - completion(true, content, nil) - } - }) - } - else { - - if created && scheduled == nil && receivedNotification?.id != nil { - removePastSchedule(withId:receivedNotification!.id!) - } - - if scheduled == nil { - printElapsedTime(scheduled: false) - BroadcastSender - .shared - .sendBroadcast( - notificationDisplayed: receivedNotification!, - whenFinished: { [self] (created:Bool) in - completion(true, content, nil) - }) - } - else { - printElapsedTime(scheduled: true) - DisplayedManager - .saveScheduledToDisplay( - received: receivedNotification!) - completion(true, content, nil) - } - } + if(created){ + CreatedManager + .saveCreated( + received: receivedNotification) + } + + if scheduled == nil { + DisplayedManager + .saveDisplayed( + received: receivedNotification) } else { - completion(false, nil, nil) + printElapsedTime(scheduled: true) + DisplayedManager + .saveScheduledToDisplay( + received: receivedNotification) } } diff --git a/ios/AwnCore/Classes/utils/MapUtils.swift b/ios/AwnCore/Classes/utils/MapUtils.swift index 37903aab..8dd7dd9a 100644 --- a/ios/AwnCore/Classes/utils/MapUtils.swift +++ b/ios/AwnCore/Classes/utils/MapUtils.swift @@ -78,4 +78,19 @@ public class MapUtils { return defaultValue } + + public static func deepMerge(_ originalMap:[String:Any?], _ newMap:[String:Any?]) -> [String:Any?]{ + var result = [String:Any?]() + for (k1,v1) in originalMap { + result[k1] = v1 + } + for (k2,v2) in newMap { + if v2 is [String:Any?], let v1 = result[k2], v1 is [String:Any?] { + result[k2] = deepMerge(v1 as! [String:Any?],v2 as! [String:Any?]) + } else { + result[k2] = v2 + } + } + return result + } } diff --git a/ios/AwnCore/Classes/utils/SwiftUtils.swift b/ios/AwnCore/Classes/utils/SwiftUtils.swift index 30d95e1b..54d14f27 100644 --- a/ios/AwnCore/Classes/utils/SwiftUtils.swift +++ b/ios/AwnCore/Classes/utils/SwiftUtils.swift @@ -11,11 +11,6 @@ public class SwiftUtils{ private static var _isExtension:Bool? public static func isRunningOnExtension() -> Bool { -// #if ACTION_EXTENSION -// return true -// #else -// return false -// #endif if _isExtension == nil { _isExtension = Bundle.main.bundlePath.hasSuffix(".appex") } diff --git a/ios/Classes/lib/DartAwesomeNotificationsExtension.swift b/ios/Classes/lib/DartAwesomeNotificationsExtension.swift index 60bd4ed6..91ea417f 100644 --- a/ios/Classes/lib/DartAwesomeNotificationsExtension.swift +++ b/ios/Classes/lib/DartAwesomeNotificationsExtension.swift @@ -40,12 +40,13 @@ public class DartAwesomeNotificationsExtension: AwesomeNotificationsExtension { AwesomeNotifications.initialize() + FlutterAudioUtils.extendCapabilities( + usingFlutterRegistrar: DartAwesomeNotificationsExtension.registrar) + + FlutterBitmapUtils.extendCapabilities( + usingFlutterRegistrar: DartAwesomeNotificationsExtension.registrar) + if DartAwesomeNotificationsExtension.registrar != nil { - FlutterAudioUtils.extendCapabilities( - usingFlutterRegistrar: DartAwesomeNotificationsExtension.registrar!) - - FlutterBitmapUtils.extendCapabilities( - usingFlutterRegistrar: DartAwesomeNotificationsExtension.registrar!) DartBackgroundExecutor.extendCapabilities( usingFlutterRegistrar: DartAwesomeNotificationsExtension.registrar!) diff --git a/ios/Classes/lib/FlutterAudioUtils.swift b/ios/Classes/lib/FlutterAudioUtils.swift index 0d9fd1ee..2c0336b9 100644 --- a/ios/Classes/lib/FlutterAudioUtils.swift +++ b/ios/Classes/lib/FlutterAudioUtils.swift @@ -12,14 +12,14 @@ import IosAwnCore @available(iOS 10.0, *) public class FlutterAudioUtils : AudioUtils { - let registrar:FlutterPluginRegistrar + let registrar:FlutterPluginRegistrar? - public init(registrar:FlutterPluginRegistrar) { + public init(registrar:FlutterPluginRegistrar?) { self.registrar = registrar super.init() } - public static func extendCapabilities(usingFlutterRegistrar registrar:FlutterPluginRegistrar){ + public static func extendCapabilities(usingFlutterRegistrar registrar:FlutterPluginRegistrar?){ AudioUtils.instance = FlutterAudioUtils(registrar:registrar) } @@ -28,10 +28,17 @@ public class FlutterAudioUtils : AudioUtils { let mediaPath:String? = cleanMediaPath(mediaPath) if(StringUtils.shared.isNullOrEmpty(mediaPath)){ return nil } + + var topPath:String? + if registrar != nil { + let key = registrar!.lookupKey(forAsset: mediaPath!) + topPath = Bundle.main.path(forResource: key, ofType: nil) + } - let key = registrar.lookupKey(forAsset: mediaPath!) - let topPath = Bundle.main.path(forResource: key, ofType: nil)! + if SwiftUtils.isRunningOnExtension() && topPath?.isEmpty ?? true { + topPath = SwiftUtils.getFlutterAssetPath(forAsset: mediaPath!) + } - return getSoundFromFile(fromRealPath: topPath) + return topPath == nil ? nil : getSoundFromFile(fromRealPath: topPath!) } } diff --git a/ios/Classes/lib/FlutterBitmapUtils.swift b/ios/Classes/lib/FlutterBitmapUtils.swift index 541fecbf..76997e92 100644 --- a/ios/Classes/lib/FlutterBitmapUtils.swift +++ b/ios/Classes/lib/FlutterBitmapUtils.swift @@ -12,14 +12,14 @@ import IosAwnCore @available(iOS 10.0, *) public class FlutterBitmapUtils : BitmapUtils { - let registrar:FlutterPluginRegistrar + let registrar:FlutterPluginRegistrar? - public init(registrar:FlutterPluginRegistrar) { + public init(registrar:FlutterPluginRegistrar?) { self.registrar = registrar super.init() } - public static func extendCapabilities(usingFlutterRegistrar registrar:FlutterPluginRegistrar){ + public static func extendCapabilities(usingFlutterRegistrar registrar:FlutterPluginRegistrar?){ BitmapUtils.instance = FlutterBitmapUtils(registrar: registrar) } @@ -28,9 +28,16 @@ public class FlutterBitmapUtils : BitmapUtils { let mediaPath:String? = cleanMediaPath(mediaPath) if(StringUtils.shared.isNullOrEmpty(mediaPath)){ return nil } - - let key = registrar.lookupKey(forAsset: mediaPath!) - let topPath = Bundle.main.path(forResource: key, ofType: nil) + + var topPath:String? + if registrar != nil { + let key = registrar!.lookupKey(forAsset: mediaPath!) + topPath = Bundle.main.path(forResource: key, ofType: nil) + } + + if SwiftUtils.isRunningOnExtension() && topPath?.isEmpty ?? true { + topPath = SwiftUtils.getFlutterAssetPath(forAsset: mediaPath!) + } return topPath == nil ? nil : getBitmapFromFile(fromRealPath: topPath!) } diff --git a/ios/Classes/lib/SwiftAwesomeNotificationsPlugin.swift b/ios/Classes/lib/SwiftAwesomeNotificationsPlugin.swift index 03a64a41..4740f068 100644 --- a/ios/Classes/lib/SwiftAwesomeNotificationsPlugin.swift +++ b/ios/Classes/lib/SwiftAwesomeNotificationsPlugin.swift @@ -47,7 +47,7 @@ public class SwiftAwesomeNotificationsPlugin: ){ flutterChannel = channel - do { + do { DartAwesomeNotificationsExtension.registrar = registrar DartAwesomeNotificationsExtension.initialize() @@ -57,9 +57,17 @@ public class SwiftAwesomeNotificationsPlugin: registrar.addMethodCallDelegate(self, channel: self.flutterChannel!) registrar.addApplicationDelegate(self) - if AwesomeNotifications.debug { - Logger.d(SwiftAwesomeNotificationsPlugin.TAG, "Awesome Notifications plugin attached to iOS \(floor(NSFoundationVersionNumber))") - Logger.d(SwiftAwesomeNotificationsPlugin.TAG, "Awesome Notifications - App Group : \(Definitions.USER_DEFAULT_TAG)") + Logger.d(SwiftAwesomeNotificationsPlugin.TAG, "Awesome Notifications plugin attached to iOS \(floor(NSFoundationVersionNumber))") + Logger.d(SwiftAwesomeNotificationsPlugin.TAG, "Awesome Notifications - App Group : \(Definitions.USER_DEFAULT_TAG)") + + if !Definitions.USER_DEFAULT_TAG.starts(with: "group.") { + throw ExceptionFactory + .shared + .createNewAwesomeException( + className: SwiftAwesomeNotificationsPlugin.TAG, + code: ExceptionCode.CODE_INITIALIZATION_EXCEPTION, + message: "Your App Group name \"\(Definitions.USER_DEFAULT_TAG)\" is invalid. It must starts with \"group.\"", + detailedCode: ExceptionCode.DETAILED_REQUIRED_ARGUMENTS+".customAppGroup") } } catch { @@ -119,10 +127,10 @@ public class SwiftAwesomeNotificationsPlugin: case Definitions.CHANNEL_METHOD_INITIALIZE: try channelMethodInitialize(call: call, result: result) - return + return - case Definitions.CHANNEL_METHOD_SET_ACTION_HANDLE: - try channelMethodSetActionHandle(call: call, result: result) + case Definitions.CHANNEL_METHOD_SET_EVENTS_HANDLES: + try channelMethodSetEventsHandle(call: call, result: result) return case Definitions.CHANNEL_METHOD_GET_DRAWABLE_DATA: @@ -196,9 +204,13 @@ public class SwiftAwesomeNotificationsPlugin: case Definitions.CHANNEL_METHOD_DISMISS_NOTIFICATION: try channelMethodDismissNotification(call: call, result: result) return + + case Definitions.CHANNEL_METHOD_SET_LOCALIZATION: + try channelMethodSetLocalization(call: call, result: result) + return - case Definitions.CHANNEL_METHOD_CANCEL_SCHEDULE: - try channelMethodCancelSchedule(call: call, result: result) + case Definitions.CHANNEL_METHOD_GET_LOCALIZATION: + try channelMethodGetLocalization(call: call, result: result) return case Definitions.CHANNEL_METHOD_CANCEL_NOTIFICATION: @@ -311,8 +323,8 @@ public class SwiftAwesomeNotificationsPlugin: .init(bytes: data)) } - private func channelMethodSetChannel(call: FlutterMethodCall, result: @escaping FlutterResult) throws { - guard let channelData:[String:Any?] = call.arguments as? [String:Any?] + private func channelMethodSetChannel(call: FlutterMethodCall, result: @escaping FlutterResult) throws { + guard let channel = NotificationChannelModel(fromMap: call.arguments as? [String:Any?]) else { throw ExceptionFactory .shared @@ -322,11 +334,6 @@ public class SwiftAwesomeNotificationsPlugin: message: "Channel data is invalid", detailedCode: ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.data") } - - let channel:NotificationChannelModel = - NotificationChannelModel() - .fromMap( - arguments: channelData) as! NotificationChannelModel let updated = awesomeNotifications? .setChannel(channel: channel) ?? false @@ -421,6 +428,20 @@ public class SwiftAwesomeNotificationsPlugin: result(nil) } + private func channelMethodSetLocalization(call: FlutterMethodCall, result: @escaping FlutterResult) throws { + guard let languageCode:String? = call.arguments as? String? else { + result(false) + return + } + let success = awesomeNotifications?.setLocalization(languageCode: languageCode) ?? false + result(success) + } + + private func channelMethodGetLocalization(call: FlutterMethodCall, result: @escaping FlutterResult) throws { + let languageCode:String? = awesomeNotifications?.getLocalization() + result(languageCode) + } + private func channelMethodDismissNotification(call: FlutterMethodCall, result: @escaping FlutterResult) throws { let notificationId:Int? = call.arguments as? Int if notificationId == nil || notificationId! < 0 { @@ -708,8 +729,8 @@ public class SwiftAwesomeNotificationsPlugin: guard let scheduleModel:NotificationScheduleModel = (scheduleData[Definitions.NOTIFICATION_SCHEDULE_INTERVAL] != nil) ? - NotificationIntervalModel().fromMap(arguments: scheduleData) as? NotificationScheduleModel : - NotificationCalendarModel().fromMap(arguments: scheduleData) as? NotificationScheduleModel + NotificationIntervalModel(fromMap: scheduleData) : + NotificationCalendarModel(fromMap: scheduleData) else { result(nil) return @@ -899,7 +920,7 @@ public class SwiftAwesomeNotificationsPlugin: private func channelMethodCreateNotification(call: FlutterMethodCall, result: @escaping FlutterResult) throws { let pushData:[String:Any?] = call.arguments as? [String:Any?] ?? [:] - guard let notificationModel = NotificationModel().fromMap(arguments: pushData) as? NotificationModel + guard let notificationModel = NotificationModel(fromMap: pushData) else { throw ExceptionFactory .shared @@ -981,9 +1002,7 @@ public class SwiftAwesomeNotificationsPlugin: detailedCode: ExceptionCode.DETAILED_INVALID_ARGUMENTS+".channel.invalid.\(channelsData)") } - guard let channel:NotificationChannelModel = - NotificationChannelModel() - .fromMap(arguments: channelMap) as? NotificationChannelModel + guard let channel = NotificationChannelModel(fromMap: channelMap) else { throw ExceptionFactory .shared @@ -1008,16 +1027,21 @@ public class SwiftAwesomeNotificationsPlugin: result(awesomeNotifications != nil) } - private func channelMethodSetActionHandle(call: FlutterMethodCall, result: @escaping FlutterResult) throws { + private func channelMethodSetEventsHandle(call: FlutterMethodCall, result: @escaping FlutterResult) throws { let platformParameters:[String:Any?] = call.arguments as? [String:Any?] ?? [:] + + let createdHandle:Int64 = platformParameters[Definitions.CREATED_HANDLE] as? Int64 ?? 0 + let displayedHandle:Int64 = platformParameters[Definitions.DISPLAYED_HANDLE] as? Int64 ?? 0 let actionHandle:Int64 = platformParameters[Definitions.ACTION_HANDLE] as? Int64 ?? 0 - let getLostDisplayed:Bool = platformParameters[Definitions.RECOVER_DISPLAYED] as? Bool ?? false + let dismissedHandle:Int64 = platformParameters[Definitions.DISMISSED_HANDLE] as? Int64 ?? 0 awesomeNotifications?.attachAsMainInstance(usingAwesomeEventListener: self) try awesomeNotifications? - .setActionHandle( - actionHandle: actionHandle, - recoveringLostDisplayed: getLostDisplayed) + .setEventsHandle( + createdHandle: createdHandle, + displayedHandle: displayedHandle, + actionHandle: actionHandle, + dismissedHandle: dismissedHandle) let success = actionHandle != 0 if !success { diff --git a/ios/awesome_notifications.podspec b/ios/awesome_notifications.podspec index 023abddd..ff83f778 100644 --- a/ios/awesome_notifications.podspec +++ b/ios/awesome_notifications.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'awesome_notifications' - s.version = '0.0.5' - s.summary = 'A complete solution to create Local Notifications and Push Notifications, through Firebase or another services, using Flutter.' + s.version = '0.7.5' + s.summary = 'A complete solution to create Local and Push Notifications, through Firebase or another services, using Flutter.' s.description = <<-DESC A complete solution to create Local Notifications and Push Notifications, through Firebase or another services, using Flutter. DESC @@ -12,7 +12,7 @@ A complete solution to create Local Notifications and Push Notifications, throug s.source_files = 'Classes/**/*' s.static_framework = true s.dependency 'Flutter' - s.dependency 'IosAwnCore', '0.7.3' + s.dependency 'IosAwnCore', '0.7.5' s.platform = :ios, '11.0' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. diff --git a/lib/android_foreground_service.dart b/lib/android_foreground_service.dart index a03cfc9d..ec4d0043 100644 --- a/lib/android_foreground_service.dart +++ b/lib/android_foreground_service.dart @@ -2,8 +2,8 @@ /// while utilizing [awesome_notifications.dart `NotificationModel`](https://pub.dev/documentation/awesome_notifications/latest/awesome_notifications/NotificationModel-class.html). library android_foreground_service; +export 'src/android_foreground_service/android_foreground_service_web.dart' + if (dart.library.io) 'src/android_foreground_service/android_foreground_service_core.dart'; export 'src/enumerators/android_foreground_service_constants.dart'; export 'src/enumerators/foreground_service_type.dart'; export 'src/enumerators/foreground_start_mode.dart'; -export 'src/android_foreground_service/android_foreground_service_web.dart' - if (dart.library.io) 'src/android_foreground_service/android_foreground_service_core.dart'; diff --git a/lib/awesome_notifications.dart b/lib/awesome_notifications.dart index 96a2971a..cb98a538 100644 --- a/lib/awesome_notifications.dart +++ b/lib/awesome_notifications.dart @@ -1,5 +1,9 @@ import 'dart:typed_data'; +import 'package:awesome_notifications/src/models/notification_localization.dart'; + +import 'awesome_notifications_platform_interface.dart' + if (dart.library.html) 'awesome_notifications_web_interface.dart'; import 'i_awesome_notifications.dart'; import 'src/enumerators/notification_life_cycle.dart'; import 'src/enumerators/notification_permission.dart'; @@ -12,35 +16,37 @@ import 'src/models/notification_schedule.dart'; import 'src/models/received_models/received_action.dart'; import 'src/models/received_models/received_notification.dart'; +export 'src/definitions.dart'; export 'src/enumerators/action_type.dart'; +export 'src/enumerators/default_ringtone_type.dart'; +export 'src/enumerators/emojis.dart'; export 'src/enumerators/group_alert_behaviour.dart'; +export 'src/enumerators/group_sort.dart'; export 'src/enumerators/media_source.dart'; -export 'src/enumerators/emojis.dart'; -export 'src/enumerators/default_ringtone_type.dart'; +export 'src/enumerators/notification_category.dart'; export 'src/enumerators/notification_importance.dart'; export 'src/enumerators/notification_layout.dart'; export 'src/enumerators/notification_life_cycle.dart'; +export 'src/enumerators/notification_permission.dart'; export 'src/enumerators/notification_privacy.dart'; export 'src/enumerators/notification_source.dart'; -export 'src/enumerators/notification_category.dart'; export 'src/enumerators/time_and_date.dart'; -export 'src/enumerators/group_sort.dart'; -export 'src/enumerators/notification_permission.dart'; -export 'src/extensions/extension_navigator_state.dart'; export 'src/exceptions/awesome_exception.dart'; export 'src/exceptions/isolate_callback_exception.dart'; +export 'src/extensions/extension_navigator_state.dart'; export 'src/helpers/bitmap_helper.dart'; export 'src/helpers/cron_helper.dart'; +export 'src/models/notification_android_crontab.dart'; export 'src/models/notification_button.dart'; +export 'src/models/notification_calendar.dart'; export 'src/models/notification_channel.dart'; export 'src/models/notification_channel_group.dart'; export 'src/models/notification_content.dart'; -export 'src/models/notification_schedule.dart'; -export 'src/models/notification_calendar.dart'; export 'src/models/notification_interval.dart'; -export 'src/models/notification_android_crontab.dart'; -export 'src/models/received_models/push_notification.dart'; +export 'src/models/notification_localization.dart'; export 'src/models/notification_model.dart'; +export 'src/models/notification_schedule.dart'; +export 'src/models/received_models/push_notification.dart'; export 'src/models/received_models/received_action.dart'; export 'src/models/received_models/received_notification.dart'; export 'src/utils/assert_utils.dart'; @@ -49,10 +55,6 @@ export 'src/utils/date_utils.dart'; export 'src/utils/map_utils.dart'; export 'src/utils/resource_image_provider.dart'; export 'src/utils/string_utils.dart'; -export 'src/definitions.dart'; - -import 'awesome_notifications_platform_interface.dart' - if (dart.library.html) 'awesome_notifications_web_interface.dart'; /// Method structure to listen to an incoming action with dart typedef ActionHandler = Future Function(ReceivedAction receivedAction); @@ -132,12 +134,17 @@ class AwesomeNotifications implements IAwesomeNotifications { } @override - Future createNotification( - {required NotificationContent content, - NotificationSchedule? schedule, - List? actionButtons}) { + Future createNotification({ + required NotificationContent content, + NotificationSchedule? schedule, + List? actionButtons, + Map? localizations, + }) { return AwesomeNotificationsPlatform.instance.createNotification( - content: content, schedule: schedule, actionButtons: actionButtons); + content: content, + schedule: schedule, + actionButtons: actionButtons, + localizations: localizations); } @override @@ -189,7 +196,8 @@ class AwesomeNotifications implements IAwesomeNotifications { } @override - Future getInitialNotificationAction({bool removeFromActionEvents = false}) { + Future getInitialNotificationAction( + {bool removeFromActionEvents = false}) { return AwesomeNotificationsPlatform.instance.getInitialNotificationAction( removeFromActionEvents: removeFromActionEvents, ); @@ -224,11 +232,15 @@ class AwesomeNotifications implements IAwesomeNotifications { @override Future initialize( - String? defaultIcon, List channels, - {List? channelGroups, bool debug = false}) { + String? defaultIcon, + List channels, { + List? channelGroups, + bool debug = false, + String? languageCode, + }) { return AwesomeNotificationsPlatform.instance.initialize( defaultIcon, channels, - channelGroups: channelGroups, debug: debug); + channelGroups: channelGroups, languageCode: languageCode, debug: debug); } @override @@ -276,7 +288,7 @@ class AwesomeNotifications implements IAwesomeNotifications { } @override - Future setGlobalBadgeCounter(int? amount) { + Future setGlobalBadgeCounter(int amount) { return AwesomeNotificationsPlatform.instance.setGlobalBadgeCounter(amount); } @@ -325,4 +337,27 @@ class AwesomeNotifications implements IAwesomeNotifications { return AwesomeNotificationsPlatform.instance .showNotificationConfigPage(channelKey: channelKey); } + + @override + Future getLocalization() async { + return AwesomeNotificationsPlatform.instance.getLocalization(); + } + + @override + Future setLocalization({required String? languageCode}) async { + return AwesomeNotificationsPlatform.instance + .setLocalization(languageCode: languageCode); + } + + @override + Future isNotificationActiveOnStatusBar({required int id}) { + return AwesomeNotificationsPlatform.instance + .isNotificationActiveOnStatusBar(id: id); + } + + @override + Future> getAllActiveNotificationIdsOnStatusBar() { + return AwesomeNotificationsPlatform.instance + .getAllActiveNotificationIdsOnStatusBar(); + } } diff --git a/lib/awesome_notifications_empty.dart b/lib/awesome_notifications_empty.dart index ab6513ff..dc045578 100644 --- a/lib/awesome_notifications_empty.dart +++ b/lib/awesome_notifications_empty.dart @@ -45,10 +45,12 @@ class AwesomeNotificationsEmpty extends AwesomeNotificationsPlatform } @override - Future createNotification( - {required NotificationContent content, - NotificationSchedule? schedule, - List? actionButtons}) async { + Future createNotification({ + required NotificationContent content, + NotificationSchedule? schedule, + List? actionButtons, + Map? localizations, + }) async { return false; } @@ -77,7 +79,7 @@ class AwesomeNotificationsEmpty extends AwesomeNotificationsPlatform @override Future getAppLifeCycle() async { - return NotificationLifeCycle.AppKilled; + return NotificationLifeCycle.Terminated; } @override @@ -119,9 +121,12 @@ class AwesomeNotificationsEmpty extends AwesomeNotificationsPlatform @override Future initialize( - String? defaultIcon, List channels, - {List? channelGroups, - bool debug = false}) async { + String? defaultIcon, + List channels, { + List? channelGroups, + bool debug = false, + String? languageCode, + }) async { return true; } @@ -194,6 +199,26 @@ class AwesomeNotificationsEmpty extends AwesomeNotificationsPlatform @override Future showNotificationConfigPage({String? channelKey}) async {} + @override + Future getLocalization() async { + return ''; + } + + @override + Future setLocalization({required String? languageCode}) async { + return false; + } + + @override + Future isNotificationActiveOnStatusBar({required int id}) async { + return false; + } + + @override + Future> getAllActiveNotificationIdsOnStatusBar() async { + return []; + } + @override dispose() {} } diff --git a/lib/awesome_notifications_method_channel.dart b/lib/awesome_notifications_method_channel.dart index edec45bd..f216c393 100644 --- a/lib/awesome_notifications_method_channel.dart +++ b/lib/awesome_notifications_method_channel.dart @@ -7,7 +7,6 @@ import 'package:flutter/services.dart'; import 'awesome_notifications.dart'; import 'awesome_notifications_platform_interface.dart'; - import 'src/isolates/isolate_main.dart'; import 'src/logs/logger.dart'; @@ -17,7 +16,7 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { /// The method channel used to interact with the native platform. @visibleForTesting - final methodChannel = const MethodChannel('awesome_notifications'); + var methodChannel = const MethodChannel('awesome_notifications'); ActionHandler? actionHandler; ActionHandler? dismissedHandler; @@ -26,7 +25,7 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { @override Future cancel(int id) async { - _validateId(id); + validateId(id); await methodChannel.invokeMethod(CHANNEL_METHOD_CANCEL_NOTIFICATION, id); } @@ -54,7 +53,7 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { @override Future cancelSchedule(int id) async { - _validateId(id); + validateId(id); await methodChannel.invokeMethod(CHANNEL_METHOD_CANCEL_SCHEDULE, id); } @@ -92,18 +91,21 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { } @override - Future createNotification( - {required NotificationContent content, - NotificationSchedule? schedule, - List? actionButtons}) async { - _validateId(content.id!); + Future createNotification({ + required NotificationContent content, + NotificationSchedule? schedule, + List? actionButtons, + Map? localizations, + }) async { + validateId(content.id!); final bool wasCreated = await methodChannel.invokeMethod( CHANNEL_METHOD_CREATE_NOTIFICATION, NotificationModel( content: content, schedule: schedule, - actionButtons: actionButtons) + actionButtons: actionButtons, + localizations: localizations) .toMap()); return wasCreated; @@ -128,6 +130,11 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { json.decode(mapData[NOTIFICATION_BUTTONS]); } + if (mapData[NOTIFICATION_LOCALIZATIONS] is String) { + mapData[NOTIFICATION_LOCALIZATIONS] = + json.decode(mapData[NOTIFICATION_LOCALIZATIONS]); + } + // Invalid Notification NotificationModel? notificationModel = NotificationModel().fromMap(mapData); @@ -153,7 +160,7 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { @override Future dismiss(int id) async { - _validateId(id); + validateId(id); await methodChannel.invokeMethod(CHANNEL_METHOD_DISMISS_NOTIFICATION, id); } @@ -252,12 +259,15 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { @override Future initialize( - String? defaultIcon, List channels, - {List? channelGroups, - bool debug = false}) async { + String? defaultIcon, + List channels, { + List? channelGroups, + bool debug = false, + String? languageCode, + }) async { WidgetsFlutterBinding.ensureInitialized(); - methodChannel.setMethodCallHandler(_handleMethod); + methodChannel.setMethodCallHandler(handleMethod); List serializedChannels = []; for (NotificationChannel channel in channels) { @@ -275,7 +285,7 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { if (kIsWeb) { // For web release } else { - if (!AwesomeAssertUtils.isNullOrEmptyOrInvalid(defaultIcon, String)) { + if (!AwesomeAssertUtils.isNullOrEmptyOrInvalid(defaultIcon)) { // To set a icon on top of notification, is mandatory to user a native resource assert(AwesomeBitmapUtils().getMediaSource(defaultIcon!) == MediaSource.Resource); @@ -294,8 +304,13 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { BACKGROUND_HANDLE: dartCallbackReference!.toRawHandle() }); + if (languageCode != null) { + await setLocalization(languageCode: languageCode); + } + AwesomeNotifications.localTimeZoneIdentifier = await methodChannel .invokeMethod(CHANNEL_METHOD_GET_LOCAL_TIMEZONE_IDENTIFIER); + AwesomeNotifications.utcTimeZoneIdentifier = await methodChannel .invokeMethod(CHANNEL_METHOD_GET_UTC_TIMEZONE_IDENTIFIER); @@ -317,13 +332,11 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { if (returned != null) { for (Object object in returned) { if (object is Map) { - try { - NotificationModel notificationModel = - NotificationModel().fromMap(Map.from(object))!; - scheduledNotifications.add(notificationModel); - } catch (e) { - return []; - } + NotificationModel? notificationModel = + NotificationModel().fromMap(Map.from(object)); + if (notificationModel == null) continue; + + scheduledNotifications.add(notificationModel); } } } @@ -365,7 +378,7 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { @override Future resetGlobalBadge() async { - await methodChannel.invokeListMethod(CHANNEL_METHOD_RESET_BADGE); + await methodChannel.invokeMethod(CHANNEL_METHOD_RESET_BADGE); } @override @@ -398,13 +411,30 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { createdHandler = onNotificationCreatedMethod; displayedHandler = onNotificationDisplayedMethod; + final CallbackHandle? createdCallbackReference = + onNotificationCreatedMethod != null + ? PluginUtilities.getCallbackHandle(onNotificationCreatedMethod) + : null; + + final CallbackHandle? displayedCallbackReference = + onNotificationDisplayedMethod != null + ? PluginUtilities.getCallbackHandle(onNotificationDisplayedMethod) + : null; + final CallbackHandle? actionCallbackReference = PluginUtilities.getCallbackHandle(onActionReceivedMethod); + final CallbackHandle? dismissedCallbackReference = + onDismissActionReceivedMethod != null + ? PluginUtilities.getCallbackHandle(onDismissActionReceivedMethod) + : null; + bool result = - await methodChannel.invokeMethod(CHANNEL_METHOD_SET_ACTION_HANDLE, { + await methodChannel.invokeMethod(CHANNEL_METHOD_SET_EVENT_HANDLES, { + CREATED_HANDLE: createdCallbackReference?.toRawHandle(), + DISPLAYED_HANDLE: displayedCallbackReference?.toRawHandle(), ACTION_HANDLE: actionCallbackReference?.toRawHandle(), - RECOVER_DISPLAYED: displayedHandler != null + DISMISSED_HANDLE: dismissedCallbackReference?.toRawHandle() }); if (!result) { @@ -453,10 +483,37 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { CHANNEL_METHOD_SHOW_NOTIFICATION_PAGE, channelKey); } + @override + Future getLocalization() async { + return await methodChannel.invokeMethod(CHANNEL_METHOD_GET_LOCALIZATION); + } + + @override + Future setLocalization({required String? languageCode}) async { + var success = await methodChannel.invokeMethod( + CHANNEL_METHOD_SET_LOCALIZATION, languageCode); + return success; + } + + @override + Future isNotificationActiveOnStatusBar({required int id}) async { + var success = await methodChannel.invokeMethod( + CHANNEL_METHOD_IS_NOTIFICATION_ACTIVE, id); + return success; + } + + @override + Future> getAllActiveNotificationIdsOnStatusBar() async { + return await methodChannel + .invokeMethod(CHANNEL_METHOD_GET_ALL_ACTIVE_NOTIFICATION_IDS) ?? + []; + } + final String _silentBGActionTypeKey = AwesomeAssertUtils.toSimpleEnumString(ActionType.SilentBackgroundAction)!; - Future _handleMethod(MethodCall call) async { + @visibleForTesting + Future handleMethod(MethodCall call) async { Map arguments = (call.arguments as Map).cast(); @@ -483,9 +540,9 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { case EVENT_SILENT_ACTION: if (arguments[NOTIFICATION_ACTION_TYPE] == _silentBGActionTypeKey) { - compute(receiveSilentAction, arguments); + await compute(IsolateController().receiveSilentAction, arguments); } else { - receiveSilentAction(arguments); + await IsolateController().receiveSilentAction(arguments); } return; @@ -494,7 +551,8 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { } } - void _validateId(int id) { + @visibleForTesting + void validateId(int id) { if (id > 0x7FFFFFFF || id < -0x80000000) { throw ArgumentError( 'The id field must be the limited to 32-bit size integer'); @@ -525,8 +583,5 @@ class MethodChannelAwesomeNotifications extends AwesomeNotificationsPlatform { } @override - dispose() { - // TODO: implement dispose - throw UnimplementedError(); - } + dispose() {} } diff --git a/lib/awesome_notifications_platform_interface.dart b/lib/awesome_notifications_platform_interface.dart index 3e07fcf0..27a79d43 100644 --- a/lib/awesome_notifications_platform_interface.dart +++ b/lib/awesome_notifications_platform_interface.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:awesome_notifications/i_awesome_notifications.dart'; +import 'package:flutter/cupertino.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'awesome_notifications_empty.dart'; @@ -13,18 +14,33 @@ abstract class AwesomeNotificationsPlatform extends PlatformInterface static final Object _token = Object(); - static AwesomeNotificationsPlatform _instance = Platform.isIOS - ? MethodChannelAwesomeNotifications() - : Platform.isAndroid - ? MethodChannelAwesomeNotifications() - : - // TODO: Missing implementation - AwesomeNotificationsEmpty(); + static AwesomeNotificationsPlatform? _instance; + + @visibleForTesting + static String operatingSystem = Platform.operatingSystem; /// The default instance of [AwesomeNotificationsPlatform] to use. /// /// Defaults to [MethodChannelAwesomeNotifications]. - static AwesomeNotificationsPlatform get instance => _instance; + static AwesomeNotificationsPlatform get instance { + if (_instance == null) { + switch (operatingSystem) { + case "android": + case "ios": + _instance = MethodChannelAwesomeNotifications(); + break; + default: + _instance = AwesomeNotificationsEmpty(); + break; + } + } + return _instance!; + } + + @visibleForTesting + static void resetInstance() { + _instance = null; + } /// Platform-specific implementations should set this with their own /// platform-specific class that extends [AwesomeNotificationsPlatform] when diff --git a/lib/awesome_notifications_web.dart b/lib/awesome_notifications_web.dart index 19d2d2bc..36994554 100644 --- a/lib/awesome_notifications_web.dart +++ b/lib/awesome_notifications_web.dart @@ -57,10 +57,12 @@ class AwesomeNotificationsWeb extends AwesomeNotificationsPlatform { } @override - Future createNotification( - {required NotificationContent content, - NotificationSchedule? schedule, - List? actionButtons}) async { + Future createNotification({ + required NotificationContent content, + NotificationSchedule? schedule, + List? actionButtons, + Map? localizations, + }) async { return false; } @@ -89,7 +91,7 @@ class AwesomeNotificationsWeb extends AwesomeNotificationsPlatform { @override Future getAppLifeCycle() async { - return NotificationLifeCycle.AppKilled; + return NotificationLifeCycle.Terminated; } @override @@ -131,9 +133,12 @@ class AwesomeNotificationsWeb extends AwesomeNotificationsPlatform { @override Future initialize( - String? defaultIcon, List channels, - {List? channelGroups, - bool debug = false}) async { + String? defaultIcon, + List channels, { + List? channelGroups, + bool debug = false, + String? languageCode, + }) async { return false; } @@ -206,6 +211,26 @@ class AwesomeNotificationsWeb extends AwesomeNotificationsPlatform { @override Future showNotificationConfigPage({String? channelKey}) async {} + @override + Future getLocalization() async { + return ''; + } + + @override + Future setLocalization({required String? languageCode}) async { + return false; + } + + @override + Future isNotificationActiveOnStatusBar({required int id}) async { + return false; + } + + @override + Future> getAllActiveNotificationIdsOnStatusBar() async { + return []; + } + @override dispose() async {} } diff --git a/lib/awesome_notifications_web_interface.dart b/lib/awesome_notifications_web_interface.dart index dcd38d27..3a494be6 100644 --- a/lib/awesome_notifications_web_interface.dart +++ b/lib/awesome_notifications_web_interface.dart @@ -1,8 +1,8 @@ import 'package:awesome_notifications/i_awesome_notifications.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'awesome_notifications_web.dart'; import 'awesome_notifications_method_channel.dart'; +import 'awesome_notifications_web.dart'; abstract class AwesomeNotificationsPlatform extends PlatformInterface implements IAwesomeNotifications { diff --git a/lib/i_awesome_notifications.dart b/lib/i_awesome_notifications.dart index 14f95c5f..23b98282 100644 --- a/lib/i_awesome_notifications.dart +++ b/lib/i_awesome_notifications.dart @@ -7,23 +7,58 @@ abstract class IAwesomeNotifications { dispose(); - /// INITIALIZING METHODS ********************************************* - - /// Initializes the plugin, creating a default icon and the initial channels. Only needs - /// to be called at main.dart once. - /// OBS: [defaultIcon] needs to be a Resource media type - /// OBS 2: [channels] are updated if they already exists + /// Initializes the plugin by creating a default icon and setting up the initial + /// notification channels. This method only needs to be called once in the + /// `main.dart` file of your application. + /// + /// The [defaultIcon] parameter specifies the resource media type of the default + /// icon that will be used for notifications. This should be a `String` + /// representing the resource name. + /// + /// The [channels] parameter is a list of [NotificationChannel] objects that + /// represent the initial notification channels to be created. If any of the + /// channels already exist, they will be updated with the provided information. + /// + /// The optional [channelGroups] parameter is a list of [NotificationChannelGroup] + /// objects that are used to organize the channels visually in Android's + /// notification configuration page. + /// + /// The optional [debug] parameter enables verbose logging in Awesome + /// Notifications. + /// + /// The optional [languageCode] parameter is a `String` that represents the + /// localization code for translating notification content. If specified, this + /// code will be used to translate notification titles, bodies, and other + /// contents into the appropriate language. + /// + /// This method returns a [Future] that resolves to `true` if the initialization + /// was successful, or `false` if an error occurred. Future initialize( - String? defaultIcon, List channels, - {List? channelGroups, bool debug = false}); + String? defaultIcon, + List channels, { + List? channelGroups, + bool debug = false, + String? languageCode, + }); - /// Defines the global or static methods that gonna receive the notification - /// events. OBS: Only after set at least one method, the notification's events are delivered. + /// Defines the global or static methods that will receive notification events. + /// Only after set at least one method, the notification's events will be delivered. + /// These methods require to use the notation @pragma("vm:entry-point") + /// + /// The [onActionReceivedMethod] parameter is a function that receives all the + /// notification actions that are triggered by the user. /// - /// [onActionReceivedMethod] method that receives all the notification actions - /// [onNotificationCreatedMethod] method that gets called when a new notification or schedule is created on the system - /// [onNotificationDisplayedMethod] method that gets called when a new notification is displayed on status bar - /// [onDismissActionReceivedMethod] method that receives the notification dismiss actions + /// The optional [onNotificationCreatedMethod] parameter is a function that is + /// called when a new notification or schedule is created on the system. + /// + /// The optional [onNotificationDisplayedMethod] parameter is a function that is + /// called when a new notification is displayed on the status bar. + /// + /// The optional [onDismissActionReceivedMethod] parameter is a function that + /// receives the notification dismiss actions. + /// + /// This method returns a [Future] that resolves to `true` if the listeners were + /// successfully set, or `false` if an error occurred. Future setListeners( {required ActionHandler onActionReceivedMethod, NotificationHandler? onNotificationCreatedMethod, @@ -32,46 +67,231 @@ abstract class IAwesomeNotifications { /// NATIVE MEDIA METHODS ********************************************* - /// Decode a native drawable resource into a Uint8List to be used by Flutter widgets + /// Decodes a native drawable resource into a [Uint8List] that can be used by + /// Flutter widgets. + /// + /// The [drawablePath] parameter is a [String] that represents the path to the + /// drawable resource. + /// + /// This method returns a [Future] that resolves to a [Uint8List] containing the + /// decoded data, or `null` if an error occurred. + /// + /// This method is typically used to load native drawable resources, such as + /// notification icons and images, and convert them into a format that can be used by + /// Flutter widgets. The decoded data can then be used to create a [MemoryImage], + /// which can be used as the image source for a [CircleAvatar], [Image], or + /// other widget that accepts an [ImageProvider]. Future getDrawableData(String drawablePath); /// LOCAL NOTIFICATION METHODS ********************************************* - /// Creates a new notification. - /// If notification has no [body] or [title], it will only be created, but never displayed. (background notification) - /// [schedule] and [actionButtons] are optional + /// Creates a new notification with the specified content. + /// + /// The [content] parameter is a [NotificationContent] object that represents + /// the content of the notification, including the title, body, icon, and other + /// details. + /// + /// The optional [schedule] parameter is a [NotificationSchedule] object that + /// specifies when the notification should be delivered. + /// + /// The optional [actionButtons] parameter is a list of + /// [NotificationActionButton] objects that represent the action buttons to be + /// displayed on the notification. + /// + /// The optional [localizations] parameter is a [Map] of + /// [NotificationLocalization] objects that represent the localized content of + /// the notification, such as the title and body text in different languages. + /// + /// This method returns a [Future] that resolves to `true` if the notification + /// was successfully created, or `false` if an error occurred. + /// + /// If the notification has no [body] or [title], it will be created but not + /// displayed (i.e. it will be a "background" notification). + /// + /// This method is typically used to create a new notification with the + /// specified content and delivery schedule. If an action button or buttons are + /// provided, they will be displayed on the notification to allow the user to + /// take specific actions in response to the notification. + /// + /// The [localizations] parameter can be used to provide localized versions of + /// the notification content, such as the title and body text in different + /// languages. To provide localized content, create a [NotificationLocalization] + /// object for each language, and include them in a [Map] that maps the language + /// codes as keys (e.g. "en", "pt-br", "es", etc.) to their corresponding localizations. + /// When the notification is displayed, the appropriate localization will be selected + /// based on the user's language preferences or the default system language. Future createNotification({ required NotificationContent content, NotificationSchedule? schedule, List? actionButtons, + Map? localizations, }); - /// Creates a new notification based on a map similar to the map produced by - /// toMap method + /// Creates a new notification based on a map that is similar to the map + /// produced by the `toMap()` method of a [NotificationModel] object. + /// + /// The [mapData] parameter is a [Map] that represents the model of the + /// notification, including the content, schedule, buttons, and localizations. + /// + /// This method returns a [Future] that resolves to `true` if the notification + /// was successfully created, or `false` if an error occurred. + /// + /// This method is typically used to recreate a notification from data that was + /// previously saved or transmitted in a different format, such as JSON. + /// To use this method, you must first create a [Map] of the notification data, + /// using a format that is similar to the output of the `toMap()` method of a + /// [NotificationModel] object. Then, pass this map to the + /// `createNotificationFromJsonData()` method to create the notification. Future createNotificationFromJsonData(Map mapData); - /// Gets the notification action that launched the app. If the app - /// wasn't launched by a notification, so it returns null. This method does - /// not depend on setListeners being called first. - /// [removeFromActionEvents] when set to true, prevents the same action from - /// being delivered in the method onActionMethod, in case it hasn't already - /// happened - Future getInitialNotificationAction( - {bool removeFromActionEvents = false}); + /// Gets the notification action that launched the app, if any. + /// + /// This method returns a [Future] that resolves to a [ReceivedAction] object + /// if the app was launched by a notification action, or `null` if it wasn't. + /// + /// The optional [removeFromActionEvents] parameter is a boolean value that + /// indicates whether the same action should be prevented from being delivered + /// in the `onActionMethod()` function, in case it hasn't already happened. + /// + /// This method does not depend on the `setListeners()` method being called + /// first, and can be used to retrieve the initial notification action even + /// before the app is fully initialized. + /// + /// To prevent any delay in application initialization, you can use a timeout + /// with the returned [Future] to specify a maximum duration for the method to + /// wait for the initial notification action. If no action is received within + /// the timeout duration, the [Future] will resolve to `null`. For example: + /// + /// ```dart + /// final initialAction = await getInitialNotificationAction() + /// .timeout(Duration(seconds: 5)); + /// ``` + Future getInitialNotificationAction({ + bool removeFromActionEvents = false, + }); - /// Opens the app notifications page + /// Opens the notification configuration page for the app. + /// + /// The optional [channelKey] parameter is a [String] that represents the key + /// of the notification channel for which to show the configuration page. If + /// this parameter is omitted, the page for the default notification channel + /// will be shown. + /// + /// This method returns a [Future] that resolves when the notification + /// configuration page was opened. The user can then configure the notification + /// settings for the app, such as disabling or enabling notifications, changing + /// the notification sound or vibration pattern, and so on. + /// + /// On iOS, as there is no channels specification page, it always open the + /// default configuration page. + /// + /// This method can be used to provide a way for users to easily access and + /// manage the notification settings for your app. For example, you might + /// open this pages in case you need to require the user a special notification + /// feature activated, or add a button to your app's settings page that calls + /// this method when tapped, or include a prompt in your app to encourage users + /// to visit the notification settings page to enable notifications. Future showNotificationConfigPage({String? channelKey}); - /// Opens the app notifications page + /// Opens the system's notifications settings page for the app's alarms. + /// + /// This method returns a [Future] that resolves when the notification + /// settings page is opened. The user can then configure the alarm settings + /// for the app, such as setting a custom alarm sound, enabling or disabling + /// alarms, and so on. + /// + /// This method can be used to provide a way for users to easily access and + /// manage the alarm settings for your app. For example, you might add a button + /// to your app's settings page that calls this method when tapped, or include + /// a prompt in your app to encourage users to visit the alarm settings page to + /// configure their alarms. Future showAlarmPage(); - /// Opens the app page to allows to override device DnD + /// Opens the system settings page for overriding device Do Not Disturb mode. + /// + /// This method returns a [Future] that resolves when the system settings page + /// is opened. The user can then configure the app's behavior during Do Not + /// Disturb mode to play sounds and vibrate even if the app is in silent mode. + /// + /// On Android, this method opens the relevant system settings page directly. + /// + /// On iOS this requires additional setup and permission from Apple, and is + /// not enabled by default. + /// If your app has been granted permission to override Do Not Disturb mode on + /// iOS, calling this method will open the relevant system settings page. If + /// your app has not been granted permission, calling this method has no + /// effect and DnD modes will prevent your notification to play sounds and vibrates. + /// + /// This method can be used to provide a way for users to easily access and + /// manage the Do Not Disturb settings for your app. For example, you might add + /// a button to your app's settings page that calls this method when tapped, or + /// include a prompt in your app to encourage users to visit the Do Not Disturb + /// settings page to configure their preferences. Future showGlobalDndOverridePage(); - /// Check if the notifications are globally permitted + /// Checks whether notifications are currently allowed globally on the device. + /// + /// This method returns a [Future] that resolves to a [bool] value indicating + /// whether notifications are allowed or not. If notifications are allowed, + /// the value will be `true`. If notifications are not allowed, the value will + /// be `false`. + /// + /// This method can be used to check whether the user has globally disabled + /// notifications for the app. If notifications are not allowed, you can use + /// the `showNotificationConfigPage()` method to prompt the user to enable + /// notifications for the app. Future isNotificationAllowed(); - /// Prompts the user to enabled notifications + /// Checks whether a notification with the specified ID is currently active on the device's status bar. + /// + /// This method takes an integer id argument representing the ID of the notification + /// to be checked. It returns a [Future] that resolves to a [bool] value indicating + /// whether the specified notification is currently active on the device's status bar. + /// If the notification is active, the value will be true. If the notification is + /// not active, the value will be false. + /// + /// This method can be used to check whether a specific notification is currently being + /// displayed on the device's status bar. If the notification is not active, you may want + /// to take appropriate action, such as re-creating the notification or displaying a message + /// to the user. + /// + /// Note: In order to use this method, you need to have the appropriate permissions + /// set in your AndroidManifest.xml file. Specifically, you need to have the + /// [android.permission.ACCESS_NOTIFICATION_POLICY] permission declared in your manifest + /// in order to access the device's notification policy. + Future isNotificationActiveOnStatusBar({required int id}); + + Future> getAllActiveNotificationIdsOnStatusBar(); + + /// Requests permission from the user to send notifications from the app. + /// + /// The optional [channelKey] parameter is a [String] that represents the key + /// of the notification channel for which to request permission. If this + /// parameter is omitted, the permission will be set for all channels. + /// + /// The optional [permissions] parameter is a list of + /// [NotificationPermission] values that the app requests permission to use. + /// This parameter is optional and defaults to a list of permissions that + /// are commonly requested by apps, including [NotificationPermission.Alert], + /// [NotificationPermission.Sound], [NotificationPermission.Badge], + /// [NotificationPermission.Vibration], and [NotificationPermission.Light]. + /// + /// Some permissions may require explicit authorization from the user, such + /// as the [NotificationPermission.Sound] permission on iOS. Other permissions + /// may be granted automatically without user intervention. If a permission + /// requires explicit authorization, this method will try to show the + /// permission dialog without leaving the app. If the user has denied the + /// permission too many times previously, and the permission dialog is not + /// available, the user will be redirected to the app's notification settings + /// to manually grant the permission. + /// + /// This method returns a [Future] that resolves to a [bool] value indicating + /// whether the permission was granted or not. If the user grants the + /// permission, the value will be `true`. If the user denies the permission the + /// value will be `false`. + /// + /// In case the user is redirected to the app's notification settings to grant the + /// permission, the future will wait until the user returns to app in foreground. Future requestPermissionToSendNotifications( {String? channelKey, List permissions = const [ @@ -82,112 +302,415 @@ abstract class IAwesomeNotifications { NotificationPermission.Light, ]}); - /// Check each individual permission to send notifications and returns only the allowed permissions - Future> checkPermissionList( - {String? channelKey, - List permissions = const [ - NotificationPermission.Badge, - NotificationPermission.Alert, - NotificationPermission.Sound, - NotificationPermission.Vibration, - NotificationPermission.Light - ]}); + /// Checks which notification permissions have been granted to the app. + /// + /// The optional [channelKey] parameter is a [String] that represents the key + /// of the notification channel for which to check permissions. If this + /// parameter is omitted, permissions will be checked for all channels. + /// + /// The optional [permissions] parameter is a list of + /// [NotificationPermission] values to check. This parameter is optional and + /// defaults to a list of permissions that are commonly requested by apps, + /// including [NotificationPermission.Alert], [NotificationPermission.Sound], + /// [NotificationPermission.Badge], [NotificationPermission.Vibration], and + /// [NotificationPermission.Light]. + /// + /// This method returns a [Future] that resolves to a list of + /// [NotificationPermission] values indicating which permissions have been + /// granted to the app. If a permission has been granted, it will be included + /// in the list. If a permission has not been granted, it will not be included + /// in the list. + /// + /// This method can be used to check which notification permissions have been + /// granted by the user, so that the app can adjust its behavior accordingly. + Future> checkPermissionList({ + String? channelKey, + List permissions = const [ + NotificationPermission.Badge, + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Vibration, + NotificationPermission.Light + ], + }); - /// Check if the app must show some rationale before request the user's consent. Returns the - /// list of permissions that can only be changed via user's intervention. - Future> shouldShowRationaleToRequest( - {String? channelKey, - List permissions = const [ - NotificationPermission.Badge, - NotificationPermission.Alert, - NotificationPermission.Sound, - NotificationPermission.Vibration, - NotificationPermission.Light - ]}); + /// Checks whether the app should show a rationale to the user before requesting + /// notification permissions. + /// + /// The optional [channelKey] parameter is a [String] that represents the key + /// of the notification channel for which to check permissions. If this + /// parameter is omitted, permissions will be checked for all channels. + /// + /// The optional [permissions] parameter is a list of + /// [NotificationPermission] values to check. This parameter is optional and + /// defaults to a list of permissions that are commonly requested by apps, + /// including [NotificationPermission.Alert], [NotificationPermission.Sound], + /// [NotificationPermission.Badge], [NotificationPermission.Vibration], and + /// [NotificationPermission.Light]. + /// + /// This method returns a [Future] that resolves to a list of + /// [NotificationPermission] values indicating which permissions require user + /// intervention in order to be granted. If a permission requires user + /// intervention, it will be included in the list. If a permission does not + /// require user intervention, it will not be included in the list. If no + /// permissions require user intervention, the list will be empty. + /// + /// This method can be used to check whether the app should show a rationale to + /// the user before requesting notification permissions. If any permissions + /// require user intervention, the app should show a rationale to the user + /// explaining why the permission is needed before requesting it. + Future> shouldShowRationaleToRequest({ + String? channelKey, + List permissions = const [ + NotificationPermission.Badge, + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Vibration, + NotificationPermission.Light + ], + }); - /// List all active scheduled notifications. + /// Lists all active scheduled notifications. + /// + /// This method returns a [Future] that resolves to a list of + /// [NotificationModel] objects representing all active scheduled + /// notifications. If there are no active scheduled notifications, the list + /// will be empty. + /// + /// This method can be used to get a list of all scheduled notifications in + /// order to display them to the user or to cancel them programmatically. Future> listScheduledNotifications(); - /// Set a new notification channel or updates if already exists - /// [forceUpdate]: completely updates the channel on Android Oreo and above, but cancels all current notifications. + /// Sets a new notification channel or updates an existing channel. + /// + /// The [notificationChannel] parameter is a [NotificationChannel] object that + /// represents the channel to create or update. + /// + /// The optional [forceUpdate] parameter is a boolean value that determines + /// whether to completely update the channel on Android Oreo and above, which + /// requires cancelling all current notifications. If this parameter is set to + /// `false`, the channel will be updated without force the update changing the + /// channel key nor cancelling notifications. + /// + /// This method can be used to create or update notification channels for the + /// app. On Android Oreo and above, updating a channel requires cancelling all + /// current notifications associated with the channel. If the [forceUpdate] + /// parameter is set to `true`, the channel will be updated using a new key + /// managed by the plugin. This should only be used in emergency situations. Future setChannel( NotificationChannel notificationChannel, { bool forceUpdate = false, }); - /// Remove a notification channel + /// Removes a notification channel with the specified [channelKey]. + /// + /// The [channelKey] parameter is a [String] that represents the unique key of + /// the channel to remove. + /// + /// This method can be used to remove notification channels that are no longer + /// needed by the app. Note that removing a channel also removes all + /// notifications that were associated with the channel. If there are no + /// notifications associated with the channel, the channel will be removed + /// immediately. If there are notifications associated with the channel, they + /// will be cancelled before the channel is removed. + /// + /// This method returns a [Future] that resolves to a boolean value indicating + /// whether the channel was removed successfully. If the channel was removed + /// successfully, the value will be `true`. If the channel was not found or + /// could not be removed, the value will be `false`. Future removeChannel(String channelKey); - /// Get badge counter + /// Gets the global badge counter, which represents the number of unread + /// notifications that are currently pending for the app. + /// + /// This method can be used to retrieve the current value of the app's badge + /// counter, which can be displayed as a badge on the app's icon. Note that + /// the badge counter is specific to the app and is not shared across devices. + /// + /// It's important to note that the behavior of the badge counter can vary + /// across Android devices. For example, on Xiaomi devices, the badge counter + /// is always the number of notifications displayed in the status bar. On + /// Samsung devices, the badge counter is defined by the application, but is + /// only displayed if there is at least one active notification in the status + /// bar. However, the Awesome Notifications plugin handles these differences + /// transparently, so the developer does not need to worry about them or handle + /// them separately. Instead, the developer can treat the badge counter as it + /// is done in iOS devices. + /// + /// This method returns a [Future] that resolves to an integer value indicating + /// the current value of the app's badge counter. If there are no pending + /// notifications, the value will be `0`. Future getGlobalBadgeCounter(); - /// Set the badge counter to any value - Future setGlobalBadgeCounter(int? amount); - - /// Increment the badge counter + /// Sets the global badge counter to the specified value. This value will be + /// displayed on the app's icon badge (if supported by the device). If the + /// [amount] is 0, the badge counter will be cleared. + /// + /// ATTENTION: Developers should avoid mimicking increment and decrement functionality + /// using this method and instead use [incrementGlobalBadgeCounter] and + /// [decrementGlobalBadgeCounter], which are optimized for performance. + /// + /// Note that on some Android devices, such as those from Xiaomi or Samsung, + /// the way badge counters are handled may be different from other devices + /// and may not match the behavior on iOS devices. However, the Awesome + /// Notifications plugin handles these differences automatically, so the + /// developer should not need to worry about them. + Future setGlobalBadgeCounter(int amount); + + /// Increments the badge counter by 1 and returns the new value. If there is + /// no current value for the badge counter, it will be set to 1. This method is + /// the most performant way to increment the badge counter by a single unit. + /// + /// To increment the badge counter by a different value, the developer can get + /// the current value of the counter with the [getGlobalBadgeCounter] method, + /// add the desired amount, and then set the new value with the + /// [setGlobalBadgeCounter] method. + /// + /// This method returns a [Future] that resolves to an [int] value + /// representing the new value of the badge counter after it has been + /// incremented. Future incrementGlobalBadgeCounter(); - /// Decrement the badge counter + /// Decrements the global badge counter by 1. + /// + /// This method decrements the app's global badge counter by one. If the + /// badge counter is already zero, this method has no effect. The updated + /// badge counter value is returned as a [Future] that resolves to an [int]. + /// + /// To decrement the badge counter by a different value, the developer can get + /// the current value of the counter with the [getGlobalBadgeCounter] method, + /// add the desired amount, and then set the new value with the + /// [setGlobalBadgeCounter] method. + /// + /// This method returns a [Future] that resolves to an [int] value + /// representing the new value of the badge counter after it has been + /// decremented. Future decrementGlobalBadgeCounter(); - /// Resets the badge counter + /// Resets the global badge counter to zero. This removes any badge icon from + /// the app icon in the launcher. Note that resetting the badge counter does + /// not cancel any scheduled or active notifications. + /// + /// This method returns a [Future] that completes when the badge counter has + /// been reset. Future resetGlobalBadge(); - /// Get the next valid date for a notification schedule + /// Gets the next valid date for a notification schedule. The [schedule] + /// parameter is a valid [NotificationSchedule] model that specifies the + /// notification schedule. The optional [fixedDate] parameter is a [DateTime] + /// value that represents the reference date to simulate a schedule in a + /// different time. If this parameter is omitted, the reference date will be + /// set to the current date and time. + /// + /// This method returns a [Future] that resolves to a [DateTime] value that + /// represents the next valid date for the notification schedule. If the + /// notification schedule has expired or is invalid, the method will return + /// `null`. If the notification schedule is a one-time event, the method will + /// return the event's date and time. If the notification schedule is a + /// repeating event, the method will return the next valid date and time for + /// the event. Future getNextDate( - /// A valid Notification schedule model NotificationSchedule schedule, { - - /// reference date to simulate a schedule in different time. If null, the reference date will be now DateTime? fixedDate, }); - /// Get the current UTC time zone identifier + /// The [setLocalization] method is used to set the desired localization for + /// notifications. It takes a required [languageCode] parameter, which is an + /// optional, case-insensitive [String] that represents the language code for + /// the desired localization (e.g. "en" for English, "pt-br" for Brazilian + /// Portuguese, "es" for Spanish, etc.). If the [languageCode] parameter is + /// `null` or not provided, the default localization will be loaded from the + /// device system. + /// + /// This method returns a [Future] that resolves to [true] if the localization + /// was successfully set, or [false] if the localization could not be set for + /// any reason (e.g. the specified language is not supported). + /// + /// The translation value for the title or is defined on the parameter + Future setLocalization({required String? languageCode}); + + /// Gets the current localization code used by the plugin for notification content. + /// + /// This method returns a [Future] that resolves to a [String] representing + /// the current localization code. The localization code is a two-letter language + /// code (e.g. "en" for English, "pt" for Portuguese) or a language code combined + /// with a region code (e.g. "pt-br" for Brazilian Portuguese). If no localization + /// has been set, this method will return the system's default language code. + Future getLocalization(); + + /// Returns the identifier for the UTC time zone. + /// + /// The identifier for the UTC time zone is a string in the format "Etc/GMT[+/-]hh:mm", + /// where "[+/-]hh:mm" represents the time zone offset from UTC. For example, the + /// identifier for the UTC time zone commonly is "UTC". + /// + /// This method returns a [Future] that resolves to a [String] value containing the + /// UTC time zone identifier. Future getUtcTimeZoneIdentifier(); - /// Get the current Local time zone identifier + /// Returns the identifier for the device's local time zone. + /// + /// The identifier for a time zone is a string in the format "Area/Location", where + /// "Area" is a continent or ocean name, and "Location" is a city or region within + /// the area. For example, the identifier for the time zone of New York City is + /// "America/New_York". + /// + /// This method returns a [Future] that resolves to a [String] value containing the + /// identifier for the device's local time zone. Future getLocalTimeZoneIdentifier(); - /// Get the current Local time zone identifier + /// Returns the current state of the app lifecycle in regards to notifications. + /// + /// The returned value is an enumerator of type [NotificationLifeCycle]. The possible + /// values are: + /// + /// - [NotificationLifeCycle.Foreground]: The app is currently in the foreground and actively + /// being used by the user. + /// + /// - [NotificationLifeCycle.Background]: The app is currently in the background and not + /// actively being used by the user, but still running. + /// + /// - [NotificationLifeCycle.AppKilled]: The app has been killed and is not running. + /// + /// This method returns a [Future] that resolves to a [NotificationLifeCycle] value + /// representing the current state of the app in regards to notifications. Future getAppLifeCycle(); - /// Cancel a single notification and its respective schedule + /// Cancels a single notification and its respective schedule. + /// + /// The [id] parameter is an [int] that represents the unique identifier of + /// the notification to cancel. This method cancels both the notification and + /// its associated schedule. + /// + /// If the notification is already shown, it will be dismissed immediately. + /// If the notification has not been shown yet but is scheduled to be shown in + /// the future, the notification will be cancelled and the schedule will be + /// removed. + /// + /// This method returns a [Future] that resolves to `void`. Future cancel(int id); - /// Dismiss a single notification, without cancel its schedule + /// Dismisses a single notification without canceling its respective schedule. + /// + /// The [id] parameter is an [int] that represents the unique identifier of + /// the notification to dismiss. This method dismisses the notification only, + /// leaving its associated schedule intact. + /// + /// If the notification is currently shown, it will be dismissed immediately. + /// If the notification is not currently shown, this method has no effect. + /// + /// This method returns a [Future] that resolves to `void`. Future dismiss(int id); - /// Cancel a single scheduled notification, without dismiss the active notification + /// Cancels a single scheduled notification, without dismissing the active notification. + /// + /// The [id] parameter is an [int] that represents the unique identifier of + /// the scheduled notification to cancel. This method cancels only the + /// schedule associated with the notification, leaving the notification + /// itself intact. + /// + /// If the notification has not been shown yet but is scheduled to be shown in + /// the future, the schedule will be removed and the notification will not be + /// shown. If the notification is already shown, this method has no effect on + /// the currently active notification. + /// + /// This method returns a [Future] that resolves to `void`. Future cancelSchedule(int id); - /// Dismiss all active notifications with the same channel key on status bar, - /// without cancel the active respective schedules + /// Dismisses all active notifications with the specified [channelKey], without + /// cancelling their respective schedules. + /// + /// The [channelKey] parameter is a [String] that represents the unique key of + /// the channel that the notifications belong to. + /// + /// This method can be used to dismiss all active notifications that belong to + /// a specific channel, without cancelling their respective schedules. Note that + /// dismissing a notification does not remove it from the notification history. + /// + /// This method returns a [Future] that resolves when all active notifications + /// with the specified channel key have been dismissed. Future dismissNotificationsByChannelKey(String channelKey); - /// Cancel all active schedules with the same channel key, - /// without dismiss the respective notifications on status bar + /// Cancels all active schedules with the specified [channelKey], without + /// dismissing the respective notifications. + /// + /// The [channelKey] parameter is a [String] that represents the unique key of + /// the channel that the schedules belong to. + /// + /// This method can be used to cancel all active schedules that belong to + /// a specific channel, without dismissing the respective notifications. + /// + /// This method returns a [Future] that resolves when all active schedules + /// with the specified channel key have been cancelled. Future cancelSchedulesByChannelKey(String channelKey); - /// Cancel and dismiss all notifications and schedules with the same channel key + /// Cancels all active notifications and schedules with the specified [channelKey]. + /// + /// The [channelKey] parameter is a [String] that represents the unique key of + /// the channel that the notifications and schedules belong to. + /// + /// This method can be used to cancel all active notifications and schedules + /// that belong to a specific channel. + /// + /// This method returns a [Future] that resolves when all active notifications + /// and schedules with the specified channel key have been cancelled. Future cancelNotificationsByChannelKey(String channelKey); - /// Dismiss all active notifications with the same group key on status bar, - /// without cancel the active respective schedules + /// Dismisses all active notifications with the specified [groupKey], without + /// cancelling their respective schedules. + /// + /// The [groupKey] parameter is a [String] that represents the unique key of + /// the group that the notifications belong to. + /// + /// This method can be used to dismiss all active notifications that belong to + /// a specific group, without cancelling their respective schedules. Note that + /// dismissing a notification does not remove it from the notification history. + /// + /// This method returns a [Future] that resolves when all active notifications + /// with the specified group key have been dismissed. Future dismissNotificationsByGroupKey(String groupKey); - /// Cancel all active schedules with the same group key, - /// without dismiss the respective notifications on status bar + /// Cancels all active schedules with the specified [groupKey], without dismissing + /// the respective notifications. + /// + /// The [groupKey] parameter is a [String] that represents the unique key of + /// the group that the schedules belong to. + /// + /// This method can be used to cancel all active schedules that belong to + /// a specific group, without dismissing the respective notifications. + /// + /// This method returns a [Future] that resolves when all active schedules + /// with the specified group key have been cancelled. Future cancelSchedulesByGroupKey(String groupKey); - /// Cancel and dismiss all notifications and schedules with the same group key + /// Cancels all active notifications and schedules with the specified [groupKey]. + /// + /// The [groupKey] parameter is a [String] that represents the unique key of + /// the group that the notifications and schedules belong to. + /// + /// This method can be used to cancel all active notifications and schedules + /// that belong to a specific group. + /// + /// This method returns a [Future] that resolves when all active notifications + /// and schedules with the specified [groupKey] have been cancelled. Future cancelNotificationsByGroupKey(String groupKey); - /// Dismiss all active notifications, without cancel the active respective schedules + /// Dismisses all active notifications without cancelling their respective schedules. + /// Note that dismissing a notification does not remove it from the notification history. + /// + /// This method returns a [Future] that resolves when all active notifications + /// with the specified group key have been dismissed. Future dismissAllNotifications(); - /// Cancel all active notification schedules without dismiss the respective notifications + /// Cancels all active schedules, without dismissing the respective notifications. + /// + /// This method returns a [Future] that resolves when all active schedules + /// with the specified group key have been cancelled. Future cancelAllSchedules(); - /// Cancel and dismiss all notifications and the active schedules + /// Cancels all active notifications and schedules. + /// + /// This method returns a [Future] that resolves when all active notifications + /// and schedules have been cancelled. Future cancelAll(); } diff --git a/lib/src/android_foreground_service/android_foreground_service_core.dart b/lib/src/android_foreground_service/android_foreground_service_core.dart index d083d323..41d9587c 100644 --- a/lib/src/android_foreground_service/android_foreground_service_core.dart +++ b/lib/src/android_foreground_service/android_foreground_service_core.dart @@ -3,9 +3,9 @@ import 'dart:io' show Platform; import 'package:flutter/services.dart'; import '../definitions.dart'; -import '../enumerators/foreground_start_mode.dart'; -import '../enumerators/foreground_service_type.dart'; import '../enumerators/android_foreground_service_constants.dart'; +import '../enumerators/foreground_service_type.dart'; +import '../enumerators/foreground_start_mode.dart'; import '../models/notification_button.dart'; import '../models/notification_content.dart'; import '../models/notification_model.dart'; diff --git a/lib/src/definitions.dart b/lib/src/definitions.dart index 876d57cc..f299224e 100644 --- a/lib/src/definitions.dart +++ b/lib/src/definitions.dart @@ -26,6 +26,7 @@ const INITIALIZE_CHANNELS_GROUPS = "initializeChannelGroups"; const NOTIFICATION_CONTENT = "content"; const NOTIFICATION_SCHEDULE = "schedule"; const NOTIFICATION_BUTTONS = "actionButtons"; +const NOTIFICATION_LOCALIZATIONS = "localizations"; const FOREGROUND_NOTIFICATION_MODEL = "notificationModel"; const FOREGROUND_START_MODE = "startMode"; @@ -44,7 +45,10 @@ const SHARED_PREFERENCES_KEY = 'notification_plugin_cache'; const CHANNEL_FLUTTER_PLUGIN = 'awesome_notifications'; const DART_REVERSE_CHANNEL = 'awesome_notifications_reverse'; +const CREATED_HANDLE = 'createdHandle'; +const DISPLAYED_HANDLE = 'displayedHandle'; const ACTION_HANDLE = 'actionHandle'; +const DISMISSED_HANDLE = 'dismissedHandle'; const BACKGROUND_HANDLE = 'awesomeDartBGHandle'; const RECOVER_DISPLAYED = 'recoverScheduledDisplayed'; @@ -68,6 +72,9 @@ const CHANNEL_METHOD_IS_FCM_AVAILABLE = 'isFirebaseAvailable'; const CHANNEL_METHOD_GET_FCM_TOKEN = 'getFirebaseToken'; const CHANNEL_METHOD_NEW_FCM_TOKEN = 'newTokenReceived'; +const CHANNEL_METHOD_SET_LOCALIZATION = 'setLocalization'; +const CHANNEL_METHOD_GET_LOCALIZATION = 'getLocalization'; + const CHANNEL_METHOD_CREATE_NOTIFICATION = 'createNewNotification'; const EVENT_NOTIFICATION_CREATED = 'notificationCreated'; @@ -79,6 +86,9 @@ const EVENT_SILENT_ACTION = 'silentAction'; const CHANNEL_METHOD_NOTIFICATION_AT_LAUNCH = 'notificationAtLaunch'; const CHANNEL_METHOD_LIST_ALL_SCHEDULES = 'listAllSchedules'; +const CHANNEL_METHOD_IS_NOTIFICATION_ACTIVE = 'isNotificationActive'; +const CHANNEL_METHOD_GET_ALL_ACTIVE_NOTIFICATION_IDS = + 'getAllActiveNotificationIds'; const CHANNEL_METHOD_GET_BADGE_COUNT = 'getBadgeCount'; const CHANNEL_METHOD_SET_BADGE_COUNT = 'setBadgeCount'; @@ -86,7 +96,7 @@ const CHANNEL_METHOD_INCREMENT_BADGE_COUNT = 'incBadgeCount'; const CHANNEL_METHOD_DECREMENT_BADGE_COUNT = 'decBadgeCount'; const CHANNEL_METHOD_RESET_BADGE = 'resetBadge'; -const CHANNEL_METHOD_SET_ACTION_HANDLE = 'setActionHandle'; +const CHANNEL_METHOD_SET_EVENT_HANDLES = 'setEventHandles'; const CHANNEL_METHOD_DISMISS_NOTIFICATION = 'dismissNotification'; const CHANNEL_METHOD_CANCEL_NOTIFICATION = 'cancelNotification'; const CHANNEL_METHOD_CANCEL_SCHEDULE = 'cancelSchedule'; @@ -164,6 +174,7 @@ const NOTIFICATION_DISMISSED_LIFE_CYCLE = 'dismissedLifeCycle'; const NOTIFICATION_SCHEDULE_TIMEZONE = 'timeZone'; const NOTIFICATION_SCHEDULE_PRECISE_ALARM = 'preciseAlarm'; +const NOTIFICATION_SCHEDULE_DELAY_TOLERANCE = 'delayTolerance'; const NOTIFICATION_SCHEDULE_ERA = 'era'; const NOTIFICATION_SCHEDULE_YEAR = 'year'; const NOTIFICATION_SCHEDULE_MONTH = 'month'; @@ -237,10 +248,13 @@ const NOTIFICATION_IMPORTANCE = 'importance'; const NOTIFICATION_COLOR = 'color'; const NOTIFICATION_DEFAULT_COLOR = 'defaultColor'; const NOTIFICATION_BACKGROUND_COLOR = 'backgroundColor'; +const NOTIFICATION_CHRONOMETER = 'chronometer'; +const NOTIFICATION_TIMEOUT_AFTER = 'timeoutAfter'; const NOTIFICATION_LARGE_ICON = 'largeIcon'; const NOTIFICATION_BIG_PICTURE = 'bigPicture'; const NOTIFICATION_CUSTOM_SOUND = 'customSound'; const NOTIFICATION_HIDE_LARGE_ICON_ON_EXPAND = 'hideLargeIconOnExpand'; +const NOTIFICATION_BUTTON_LABELS = 'buttonLabels'; const NOTIFICATION_SHOW_PROGRESS = 'showProgress'; const NOTIFICATION_MAX_PROGRESS = 'maxProgress'; const NOTIFICATION_PROGRESS = 'progress'; @@ -271,7 +285,7 @@ const NOTIFICATION_ROUNDED_BIG_PICTURE = 'roundedBigPicture'; class Definitions { static Map initialValues = { - NOTIFICATION_ID: 0, + NOTIFICATION_ID: -1, NOTIFICATION_GROUP_SORT: GroupSort.Desc, NOTIFICATION_GROUP_ALERT_BEHAVIOR: GroupAlertBehavior.All, NOTIFICATION_IMPORTANCE: NotificationImportance.Default, @@ -282,8 +296,7 @@ class Definitions { NOTIFICATION_DEFAULT_RINGTONE_TYPE: DefaultRingtoneType.Notification, NOTIFICATION_DISPLAY_ON_FOREGROUND: true, NOTIFICATION_DISPLAY_ON_BACKGROUND: true, - NOTIFICATION_CHANNEL_DESCRIPTION: 'Notifications', - NOTIFICATION_CHANNEL_NAME: 'Notifications', + NOTIFICATION_REQUIRE_INPUT_TEXT: true, NOTIFICATION_SHOW_WHEN: true, NOTIFICATION_CHANNEL_SHOW_BADGE: false, NOTIFICATION_ENABLED: true, @@ -297,7 +310,6 @@ class Definitions { NOTIFICATION_PLAY_SOUND: true, NOTIFICATION_AUTO_DISMISSIBLE: true, NOTIFICATION_LOCKED: false, - NOTIFICATION_TICKER: 'ticker', NOTIFICATION_ALLOW_WHILE_IDLE: false, NOTIFICATION_ONLY_ALERT_ONCE: false, NOTIFICATION_SHOW_IN_COMPACT_VIEW: true, diff --git a/lib/src/enumerators/notification_life_cycle.dart b/lib/src/enumerators/notification_life_cycle.dart index 9095dd65..4d4150c3 100644 --- a/lib/src/enumerators/notification_life_cycle.dart +++ b/lib/src/enumerators/notification_life_cycle.dart @@ -1,2 +1,11 @@ /// Application life cycle at new notification change state -enum NotificationLifeCycle { Foreground, Background, AppKilled } +enum NotificationLifeCycle { + Foreground, + Background, + Terminated; + + @Deprecated('AppKilled is deprecated, use Terminated instead') + // ignore: non_constant_identifier_names + static NotificationLifeCycle get AppKilled => + NotificationLifeCycle.Terminated; +} diff --git a/lib/src/exceptions/awesome_exception.dart b/lib/src/exceptions/awesome_exception.dart index c88f9e7d..3734902f 100644 --- a/lib/src/exceptions/awesome_exception.dart +++ b/lib/src/exceptions/awesome_exception.dart @@ -1,4 +1,9 @@ class AwesomeNotificationsException implements Exception { final String message; const AwesomeNotificationsException({required this.message}); + + @override + String toString() { + return 'AwesomeNotificationsException{msg: $message}'; + } } diff --git a/lib/src/exceptions/isolate_callback_exception.dart b/lib/src/exceptions/isolate_callback_exception.dart index bb129ebf..6379b6f0 100644 --- a/lib/src/exceptions/isolate_callback_exception.dart +++ b/lib/src/exceptions/isolate_callback_exception.dart @@ -3,4 +3,7 @@ import 'dart:core'; class IsolateCallbackException implements Exception { String msg; IsolateCallbackException(this.msg); + + @override + String toString() => 'IsolateCallbackException: $msg'; } diff --git a/lib/src/helpers/bitmap_helper.dart b/lib/src/helpers/bitmap_helper.dart index 21fef08c..08b83930 100644 --- a/lib/src/helpers/bitmap_helper.dart +++ b/lib/src/helpers/bitmap_helper.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; import 'package:flutter/rendering.dart'; const int bitmapPixelLength = 4; @@ -40,7 +41,7 @@ class BitmapHelper { ); } - static Future fromProvider(ImageProvider provider) async { + static Future fromImageProvider(ImageProvider provider) async { final Completer completer = Completer(); final ImageStream stream = provider.resolve(const ImageConfiguration()); final listener = @@ -51,10 +52,11 @@ class BitmapHelper { }); stream.addListener(listener); final imageInfo = await completer.future; + stream.removeListener(listener); + final ui.Image image = imageInfo.image; final ByteData? byteData = await image.toByteData(); final Uint8List listInt = byteData!.buffer.asUint8List(); - return BitmapHelper.fromHeadless(image.width, image.height, listInt); } diff --git a/lib/src/helpers/cron_helper.dart b/lib/src/helpers/cron_helper.dart index bd0d1fdd..bd82d9a9 100644 --- a/lib/src/helpers/cron_helper.dart +++ b/lib/src/helpers/cron_helper.dart @@ -11,14 +11,12 @@ import 'package:intl/intl.dart'; /// OBS: Do not use and simultaneously. Chose one of /// then, marking the other one with ? tag to be ignored class CronHelper { - DateTime? _fixedNow; - /// FACTORY METHODS ********************************************* factory CronHelper() => instance; @visibleForTesting - CronHelper.private({DateTime? fixedNow}) : _fixedNow = fixedNow; + CronHelper.private(); static final CronHelper instance = CronHelper.private(); @@ -26,15 +24,6 @@ class CronHelper { String dateFormat = 'dd-MM-yyyy hh:mm'; - DateTime _getNow() { - return _fixedNow ?? DateTime.now(); - } - - /// Get the current UTC date - String get utc { - return DateFormat(dateFormat).format(_getNow().toUtc()); - } - /// Generates a Cron expression to be played at only exact time String atDate({required DateTime referenceDateTime}) { return DateFormat('s m H d M ? y').format(referenceDateTime); diff --git a/lib/src/isolates/isolate_main.dart b/lib/src/isolates/isolate_main.dart index 7989660f..354e8e00 100644 --- a/lib/src/isolates/isolate_main.dart +++ b/lib/src/isolates/isolate_main.dart @@ -6,6 +6,10 @@ import 'package:flutter/widgets.dart'; import '../../awesome_notifications.dart'; import '../logs/logger.dart'; +// We establish a new flutter native channel to be able to receive data in +// inverse direction +MethodChannel channel = const MethodChannel(DART_REVERSE_CHANNEL); + @pragma("vm:entry-point") void dartIsolateMain() { // Initialize state necessary for MethodChannels. @@ -14,19 +18,15 @@ void dartIsolateMain() { // DartPluginRegistrant.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized(); - // We establish a new flutter native channel to be able to receive data in - // inverse direction - const MethodChannel channel = MethodChannel(DART_REVERSE_CHANNEL); - // This is where we handle background silent events channel.setMethodCallHandler((MethodCall call) async { switch (call.method) { case CHANNEL_METHOD_SILENT_CALLBACK: - await channelMethodSilentCallbackHandle(call); + await IsolateController().channelMethodSilentCallbackHandle(call); break; case CHANNEL_METHOD_ISOLATE_SHUTDOWN: - await channelMethodIsolateShutdown(call); + await IsolateController().channelMethodIsolateShutdown(call); break; default: @@ -38,60 +38,70 @@ void dartIsolateMain() { channel.invokeMethod(CHANNEL_METHOD_PUSH_NEXT_DATA); } -/// This method handle the silent callback as a flutter plugin -@pragma("vm:entry-point") -Future channelMethodIsolateShutdown(MethodCall call) async { - try {} catch (error, stacktrace) { - Logger.e("channelMethodIsolateShutdown", - "An error occurred in your background messaging handler: $error"); - Logger.e("receiveSilentAction", stacktrace.toString()); +class IsolateController { + @visibleForTesting + static IsolateController singleton = IsolateController._internal(); + + factory IsolateController() { + return singleton; } -} -/// This method handle the silent callback as a flutter plugin -Future channelMethodSilentCallbackHandle(MethodCall call) async { - try { - bool success = await receiveSilentAction( - (call.arguments as Map).cast()); + IsolateController._internal(); - if (!success) { - throw const AwesomeNotificationsException( - message: 'Silent data could not be recovered'); + /// This method handle the silent callback as a flutter plugin + Future channelMethodIsolateShutdown(MethodCall call) async { + try {} catch (error, stacktrace) { + Logger.e("channelMethodIsolateShutdown", + "An error occurred in your background messaging handler: $error"); + Logger.e("receiveSilentAction", stacktrace.toString()); + } + } + + /// This method handle the silent callback as a flutter plugin + Future channelMethodSilentCallbackHandle(MethodCall call) async { + try { + bool success = await receiveSilentAction( + (call.arguments as Map).cast()); + + if (!success) { + throw const AwesomeNotificationsException( + message: 'Silent data could not be recovered'); + } + } on Exception catch (error, stacktrace) { + Logger.e("channelMethodSilentCallbackHandle", + "An error occurred in your background messaging handler: $error"); + Logger.e("receiveSilentAction", stacktrace.toString()); } - } on Exception catch (error, stacktrace) { - Logger.e("channelMethodSilentCallbackHandle", - "An error occurred in your background messaging handler: $error"); - Logger.e("receiveSilentAction", stacktrace.toString()); } -} -/// Calls the silent data method, if is a valid static one -Future receiveSilentAction(Map arguments) async { - final CallbackHandle actionCallbackHandle = - CallbackHandle.fromRawHandle(arguments[ACTION_HANDLE]); + /// Calls the silent data method, if is a valid static one + Future receiveSilentAction(Map arguments) async { + final CallbackHandle actionCallbackHandle = + CallbackHandle.fromRawHandle(arguments[ACTION_HANDLE]); - // PluginUtilities.getCallbackFromHandle performs a lookup based on the - // callback handle and returns a tear-off of the original callback. - final ActionHandler? onActionDataHandle = - PluginUtilities.getCallbackFromHandle(actionCallbackHandle) - as ActionHandler?; + // PluginUtilities.getCallbackFromHandle performs a lookup based on the + // callback handle and returns a tear-off of the original callback. + final ActionHandler? onActionDataHandle = + PluginUtilities.getCallbackFromHandle(actionCallbackHandle) + as ActionHandler?; - if (onActionDataHandle == null) { - throw IsolateCallbackException( - 'Could not find a valid action callback. Certifies that your action method is global and static.'); - } + if (onActionDataHandle == null) { + throw IsolateCallbackException( + 'Could not find a valid action callback. Certifies that your action method is global and static.'); + } - Map actionMap = Map.from(arguments); - final ReceivedAction receivedAction = ReceivedAction().fromMap(actionMap); + Map actionMap = Map.from(arguments); + final ReceivedAction receivedAction = ReceivedAction().fromMap(actionMap); - try { - await onActionDataHandle(receivedAction); - } catch (error, stacktrace) { - Logger.e("receiveSilentAction", - "Got an unknown Silent Action callback error: $error"); - Logger.e("receiveSilentAction", stacktrace.toString()); - return false; - } + try { + await onActionDataHandle(receivedAction); + } catch (error, stacktrace) { + Logger.e("receiveSilentAction", + "Got an unknown Silent Action callback error: $error"); + Logger.e("receiveSilentAction", stacktrace.toString()); + return false; + } - return true; + return true; + } } diff --git a/lib/src/logs/logger.dart b/lib/src/logs/logger.dart index 5208f1d3..8364cc9a 100644 --- a/lib/src/logs/logger.dart +++ b/lib/src/logs/logger.dart @@ -1,9 +1,7 @@ import 'package:flutter/foundation.dart'; // ignore_for_file: avoid_print -class Logger { - Logger._internal(); - +abstract class Logger { static RegExp stackTraceRegex = RegExp(r'#2\s+.*:(\d+):\d+\)'); static String _getLastLine() { String stack = StackTrace.current.toString(); diff --git a/lib/src/models/base_notification_content.dart b/lib/src/models/base_notification_content.dart index c7e09462..2ad2a1fa 100644 --- a/lib/src/models/base_notification_content.dart +++ b/lib/src/models/base_notification_content.dart @@ -1,4 +1,5 @@ import 'dart:developer' as developer; + import 'package:flutter/material.dart'; import '../definitions.dart'; @@ -32,6 +33,8 @@ class BaseNotificationContent extends Model { bool? _criticalAlert; Color? _color; Color? _backgroundColor; + Duration? _timeoutAfter; + Duration? _chronometer; NotificationPrivacy? _privacy; NotificationCategory? _category; @@ -47,34 +50,99 @@ class BaseNotificationContent extends Model { bool? _roundedLargeIcon; bool? _roundedBigPicture; + /// Returns the id of the notification. int? get id => _id; + + /// Returns the channel key of the notification. String? get channelKey => _channelKey; + + /// Returns the group key of the notification. String? get groupKey => _groupKey; + + /// Returns the title of the notification. String? get title => _title; + + /// Returns the body text of the notification. String? get body => _body; + + /// Returns the summary of the notification. String? get summary => _summary; + + /// Returns whether the notification should show a timestamp. bool? get showWhen => _showWhen; + + /// Returns the custom payload of the notification. Map? get payload => _payload; + + /// Returns the icon of the notification. String? get icon => _icon; + + /// Returns the large icon of the notification. String? get largeIcon => _largeIcon; + + /// Returns the big picture of the notification. String? get bigPicture => _bigPicture; + + /// Returns the custom sound of the notification. String? get customSound => _customSound; + + /// Returns whether the notification is auto-dismissible. bool? get autoDismissible => _autoDismissible; + + /// Returns whether the notification should wake up the screen. bool? get wakeUpScreen => _wakeUpScreen; + + /// Returns whether the notification should use a full screen intent. bool? get fullScreenIntent => _fullScreenIntent; + + /// Returns whether the notification is a critical alert. bool? get criticalAlert => _criticalAlert; + + /// Returns the color of the notification. Color? get color => _color; + + /// Returns the duration after which the notification should be timed out. + Duration? get timeoutAfter => _timeoutAfter; + + /// Returns the chronometer duration for the notification. + Duration? get chronometer => _chronometer; + + /// Returns the background color of the notification. Color? get backgroundColor => _backgroundColor; + + /// Returns the privacy setting of the notification. NotificationPrivacy? get privacy => _privacy; + + /// Returns the category of the notification. NotificationCategory? get category => _category; + + /// Returns the action type of the notification. ActionType? get actionType => _actionType; + + /// Returns whether the large icon should be rounded. bool? get roundedLargeIcon => _roundedLargeIcon; + + /// Returns whether the big picture should be rounded. bool? get roundedBigPicture => _roundedBigPicture; + /// Returns the date and time when the notification was displayed. DateTime? get displayedDate { return _displayedDate; } + @visibleForTesting + @protected + set privacy(newValue) { + _privacy = newValue; + } + + @visibleForTesting + @protected + set actionType(newValue) { + _actionType = newValue; + } + + @visibleForTesting @protected set displayedDate(newValue) { _displayedDate = newValue; @@ -84,6 +152,7 @@ class BaseNotificationContent extends Model { return _createdDate; } + @visibleForTesting @protected set createdDate(newValue) { _createdDate = newValue; @@ -93,6 +162,7 @@ class BaseNotificationContent extends Model { return _createdSource; } + @visibleForTesting @protected set createdSource(newValue) { _createdSource = newValue; @@ -102,6 +172,7 @@ class BaseNotificationContent extends Model { return _createdLifeCycle; } + @visibleForTesting @protected set createdLifeCycle(newValue) { _createdLifeCycle = newValue; @@ -111,6 +182,7 @@ class BaseNotificationContent extends Model { return _displayedLifeCycle; } + @visibleForTesting @protected set displayedLifeCycle(newValue) { _displayedLifeCycle = newValue; @@ -143,98 +215,132 @@ class BaseNotificationContent extends Model { bool autoDismissible = true, Color? color, Color? backgroundColor, + Duration? chronometer, + Duration? timeoutAfter, Map? payload, String? customSound, bool roundedLargeIcon = false, bool roundedBigPicture = false, bool autoCancel = true}) - : _id = id, - _channelKey = channelKey, - _groupKey = groupKey, - _actionType = actionType, - _title = title, - _body = body, - _summary = summary, - _showWhen = showWhen, - _icon = icon, - _largeIcon = largeIcon, - _bigPicture = bigPicture, - _wakeUpScreen = wakeUpScreen, - _fullScreenIntent = fullScreenIntent, - _criticalAlert = criticalAlert, - _category = category, - _color = color, - _backgroundColor = backgroundColor, - _payload = payload, - _customSound = customSound, - _roundedLargeIcon = roundedLargeIcon, - _roundedBigPicture = roundedBigPicture, - _autoDismissible = autoDismissible && autoCancel; + : _id = AwesomeAssertUtils.getValueOrDefault(NOTIFICATION_ID, id), + _channelKey = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CHANNEL_KEY, channelKey), + _groupKey = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_GROUP_KEY, groupKey), + _actionType = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_ACTION_TYPE, actionType), + _title = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_TITLE, title), + _body = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_BODY, body), + _summary = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_SUMMARY, summary), + _showWhen = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_SHOW_WHEN, showWhen), + _icon = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_ICON, icon), + _largeIcon = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_LARGE_ICON, largeIcon), + _bigPicture = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_BIG_PICTURE, bigPicture), + _wakeUpScreen = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_WAKE_UP_SCREEN, wakeUpScreen), + _fullScreenIntent = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_FULL_SCREEN_INTENT, fullScreenIntent), + _criticalAlert = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CRITICAL_ALERT, criticalAlert), + _category = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CATEGORY, category), + _color = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_COLOR, color), + _backgroundColor = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_BACKGROUND_COLOR, backgroundColor), + _chronometer = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CHRONOMETER, + (chronometer?.inSeconds ?? -1) < 0 ? null : chronometer), + _timeoutAfter = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_TIMEOUT_AFTER, + (timeoutAfter?.inSeconds ?? -1) < 0 ? null : timeoutAfter), + _payload = AwesomeAssertUtils.getValueOrDefault>( + NOTIFICATION_PAYLOAD, payload), + _customSound = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CUSTOM_SOUND, customSound), + _roundedLargeIcon = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_ROUNDED_LARGE_ICON, roundedLargeIcon), + _roundedBigPicture = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_ROUNDED_BIG_PICTURE, roundedBigPicture), + _autoDismissible = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_AUTO_DISMISSIBLE, autoDismissible && autoCancel); @override BaseNotificationContent? fromMap(Map mapData) { - _processRetroCompatibility(mapData); - - _id = AwesomeAssertUtils.extractValue(NOTIFICATION_ID, mapData, int); - _channelKey = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_KEY, mapData, String); - _groupKey = AwesomeAssertUtils.extractValue( - NOTIFICATION_GROUP_KEY, mapData, String); + mapData = processRetroCompatibility(mapData); + _id = AwesomeAssertUtils.extractValue(NOTIFICATION_ID, mapData); + _channelKey = AwesomeAssertUtils.extractValue( + NOTIFICATION_CHANNEL_KEY, mapData); + _groupKey = AwesomeAssertUtils.extractValue( + NOTIFICATION_GROUP_KEY, mapData); + _actionType = AwesomeAssertUtils.extractEnum( + NOTIFICATION_ACTION_TYPE, mapData, ActionType.values); _title = - AwesomeAssertUtils.extractValue(NOTIFICATION_TITLE, mapData, String); - _body = AwesomeAssertUtils.extractValue(NOTIFICATION_BODY, mapData, String); + AwesomeAssertUtils.extractValue(NOTIFICATION_TITLE, mapData); + _body = AwesomeAssertUtils.extractValue(NOTIFICATION_BODY, mapData); _summary = - AwesomeAssertUtils.extractValue(NOTIFICATION_SUMMARY, mapData, String); + AwesomeAssertUtils.extractValue(NOTIFICATION_SUMMARY, mapData); _showWhen = - AwesomeAssertUtils.extractValue(NOTIFICATION_SHOW_WHEN, mapData, bool); - _icon = AwesomeAssertUtils.extractValue(NOTIFICATION_ICON, mapData, String); - _largeIcon = AwesomeAssertUtils.extractValue( - NOTIFICATION_LARGE_ICON, mapData, String); - _bigPicture = AwesomeAssertUtils.extractValue( - NOTIFICATION_BIG_PICTURE, mapData, String); - _customSound = AwesomeAssertUtils.extractValue( - NOTIFICATION_CUSTOM_SOUND, mapData, String); - - _wakeUpScreen = AwesomeAssertUtils.extractValue( - NOTIFICATION_WAKE_UP_SCREEN, mapData, bool); - _fullScreenIntent = AwesomeAssertUtils.extractValue( - NOTIFICATION_FULL_SCREEN_INTENT, mapData, bool); - _criticalAlert = AwesomeAssertUtils.extractValue( - NOTIFICATION_CRITICAL_ALERT, mapData, bool); - _autoDismissible = AwesomeAssertUtils.extractValue( - NOTIFICATION_AUTO_DISMISSIBLE, mapData, bool); - - _roundedLargeIcon = AwesomeAssertUtils.extractValue( - NOTIFICATION_ROUNDED_LARGE_ICON, mapData, bool); - _roundedBigPicture = AwesomeAssertUtils.extractValue( - NOTIFICATION_ROUNDED_BIG_PICTURE, mapData, bool); - - _actionType = AwesomeAssertUtils.extractEnum( - NOTIFICATION_ACTION_TYPE, mapData, ActionType.values); - _privacy = AwesomeAssertUtils.extractEnum( - NOTIFICATION_PRIVACY, mapData, NotificationPrivacy.values); + AwesomeAssertUtils.extractValue(NOTIFICATION_SHOW_WHEN, mapData); + _icon = AwesomeAssertUtils.extractValue(NOTIFICATION_ICON, mapData); + _largeIcon = AwesomeAssertUtils.extractValue( + NOTIFICATION_LARGE_ICON, mapData); + _bigPicture = AwesomeAssertUtils.extractValue( + NOTIFICATION_BIG_PICTURE, mapData); + _wakeUpScreen = AwesomeAssertUtils.extractValue( + NOTIFICATION_WAKE_UP_SCREEN, mapData); + _fullScreenIntent = AwesomeAssertUtils.extractValue( + NOTIFICATION_FULL_SCREEN_INTENT, mapData); + _criticalAlert = AwesomeAssertUtils.extractValue( + NOTIFICATION_CRITICAL_ALERT, mapData); _category = AwesomeAssertUtils.extractEnum( NOTIFICATION_CATEGORY, mapData, NotificationCategory.values); - _color = - AwesomeAssertUtils.extractValue(NOTIFICATION_COLOR, mapData, Color); - _backgroundColor = AwesomeAssertUtils.extractValue( - NOTIFICATION_BACKGROUND_COLOR, mapData, Color); - + AwesomeAssertUtils.extractValue(NOTIFICATION_COLOR, mapData); + _backgroundColor = AwesomeAssertUtils.extractValue( + NOTIFICATION_BACKGROUND_COLOR, mapData); + _chronometer = AwesomeAssertUtils.extractValue( + NOTIFICATION_CHRONOMETER, mapData); + _timeoutAfter = AwesomeAssertUtils.extractValue( + NOTIFICATION_TIMEOUT_AFTER, mapData); _payload = AwesomeAssertUtils.extractMap( - mapData, NOTIFICATION_PAYLOAD); + NOTIFICATION_PAYLOAD, mapData); + _customSound = AwesomeAssertUtils.extractValue( + NOTIFICATION_CUSTOM_SOUND, mapData); + _roundedLargeIcon = AwesomeAssertUtils.extractValue( + NOTIFICATION_ROUNDED_LARGE_ICON, mapData); + _roundedBigPicture = AwesomeAssertUtils.extractValue( + NOTIFICATION_ROUNDED_BIG_PICTURE, mapData); + _autoDismissible = AwesomeAssertUtils.extractValue( + NOTIFICATION_AUTO_DISMISSIBLE, mapData); return this; } - void _processRetroCompatibility(Map dataMap) { - if (dataMap.containsKey("autoCancel")) { - developer - .log("autoCancel is deprecated. Please use autoDismissible instead."); - _autoDismissible = - AwesomeAssertUtils.extractValue("autoCancel", dataMap, bool); + @visibleForTesting + Map processRetroCompatibility(Map dataMap) { + if (dataMap.containsKey('autoCancel')) { + developer.log( + 'autoCancel is now deprecated. Please use autoDismissible instead.'); + dataMap[NOTIFICATION_AUTO_DISMISSIBLE] = + AwesomeAssertUtils.extractValue('autoCancel', dataMap); + } + for (MapEntry entry in dataMap.entries) { + if (entry.value == 'AppKilled') { + developer + .log('AppKilled is now deprecated. Please use Terminated instead.'); + dataMap[entry.key] = NotificationLifeCycle.Terminated.name; + } } + return dataMap; } @override @@ -267,7 +373,9 @@ class BaseNotificationContent extends Model { NOTIFICATION_CREATED_LIFECYCLE: createdLifeCycle?.name, NOTIFICATION_DISPLAYED_LIFECYCLE: displayedLifeCycle?.name, NOTIFICATION_CREATED_DATE: createdDate, - NOTIFICATION_DISPLAYED_DATE: displayedDate + NOTIFICATION_DISPLAYED_DATE: displayedDate, + NOTIFICATION_CHRONOMETER: _chronometer?.inSeconds, + NOTIFICATION_TIMEOUT_AFTER: _timeoutAfter?.inSeconds, }; } @@ -297,11 +405,11 @@ class BaseNotificationContent extends Model { @override void validate() { - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_id, int)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_id)) { throw const AwesomeNotificationsException( message: 'Property id is required'); } - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_channelKey, String)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_channelKey)) { throw const AwesomeNotificationsException( message: 'Channel Key is required'); } diff --git a/lib/src/models/model.dart b/lib/src/models/model.dart index 8ab00363..e627cd7f 100644 --- a/lib/src/models/model.dart +++ b/lib/src/models/model.dart @@ -10,10 +10,7 @@ abstract class Model { Model? fromMap(Map mapData); @override - String toString() { - Map mapData = toMap(); - return AwesomeMapUtils.printPrettyMap(mapData); - } + String toString() => AwesomeMapUtils.printPrettyMap(toMap()); /// Validates void validate(); diff --git a/lib/src/models/notification_android_crontab.dart b/lib/src/models/notification_android_crontab.dart index 7915aaac..971a26a9 100644 --- a/lib/src/models/notification_android_crontab.dart +++ b/lib/src/models/notification_android_crontab.dart @@ -44,11 +44,16 @@ class NotificationAndroidCrontab extends NotificationSchedule { String? timeZone, bool allowWhileIdle = false, bool repeats = false, - bool preciseAlarm = false}) - : _initialDateTime = initialDateTime, - _expirationDateTime = expirationDateTime, - _preciseSchedules = preciseSchedules, - _crontabExpression = crontabExpression, + bool preciseAlarm = true}) + : _initialDateTime = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_INITIAL_DATE_TIME, initialDateTime), + _expirationDateTime = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_EXPIRATION_DATE_TIME, expirationDateTime), + _preciseSchedules = + AwesomeAssertUtils.getValueOrDefault>( + NOTIFICATION_PRECISE_SCHEDULES, preciseSchedules), + _crontabExpression = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CRONTAB_EXPRESSION, crontabExpression), super( timeZone: timeZone ?? AwesomeNotifications.localTimeZoneIdentifier, allowWhileIdle: allowWhileIdle, @@ -106,7 +111,7 @@ class NotificationAndroidCrontab extends NotificationSchedule { allowWhileIdle: allowWhileIdle, repeats: false) { _crontabExpression = - CronHelper().monthly(referenceDateTime: referenceDateTime); + CronHelper().weekly(referenceDateTime: referenceDateTime); } /// Generates a Cron expression to be played only once at day based on a date reference @@ -119,7 +124,7 @@ class NotificationAndroidCrontab extends NotificationSchedule { allowWhileIdle: allowWhileIdle, repeats: false) { _crontabExpression = - CronHelper().monthly(referenceDateTime: referenceDateTime); + CronHelper().daily(referenceDateTime: referenceDateTime); } /// Generates a Cron expression to be played only once at hour based on a date reference @@ -132,7 +137,7 @@ class NotificationAndroidCrontab extends NotificationSchedule { allowWhileIdle: allowWhileIdle, repeats: false) { _crontabExpression = - CronHelper().monthly(referenceDateTime: referenceDateTime); + CronHelper().hourly(referenceDateTime: referenceDateTime); } /// Generates a Cron expression to be played only once at every minute based on a date reference @@ -158,7 +163,7 @@ class NotificationAndroidCrontab extends NotificationSchedule { allowWhileIdle: allowWhileIdle, repeats: false) { _crontabExpression = - CronHelper().monthly(referenceDateTime: referenceDateTime); + CronHelper().workweekDay(referenceDateTime: referenceDateTime); } /// Generates a Cron expression to be played only on weekend days based on a date reference @@ -171,19 +176,19 @@ class NotificationAndroidCrontab extends NotificationSchedule { allowWhileIdle: allowWhileIdle, repeats: false) { _crontabExpression = - CronHelper().monthly(referenceDateTime: referenceDateTime); + CronHelper().weekendDay(referenceDateTime: referenceDateTime); } @override NotificationAndroidCrontab? fromMap(Map mapData) { super.fromMap(mapData); - _crontabExpression = AwesomeAssertUtils.extractValue( - NOTIFICATION_CRONTAB_EXPRESSION, mapData, String); - _initialDateTime = AwesomeAssertUtils.extractValue( - NOTIFICATION_INITIAL_DATE_TIME, mapData, DateTime); - _expirationDateTime = AwesomeAssertUtils.extractValue( - NOTIFICATION_EXPIRATION_DATE_TIME, mapData, DateTime); + _crontabExpression = AwesomeAssertUtils.extractValue( + NOTIFICATION_CRONTAB_EXPRESSION, mapData); + _initialDateTime = AwesomeAssertUtils.extractValue( + NOTIFICATION_INITIAL_DATE_TIME, mapData); + _expirationDateTime = AwesomeAssertUtils.extractValue( + NOTIFICATION_EXPIRATION_DATE_TIME, mapData); if (mapData[NOTIFICATION_PRECISE_SCHEDULES] is List) { List schedules = diff --git a/lib/src/models/notification_button.dart b/lib/src/models/notification_button.dart index 3fb51a5f..3a36ceb3 100644 --- a/lib/src/models/notification_button.dart +++ b/lib/src/models/notification_button.dart @@ -1,5 +1,5 @@ -import 'dart:ui'; import 'dart:developer' as developer; +import 'dart:ui'; import 'package:awesome_notifications/src/definitions.dart'; import 'package:awesome_notifications/src/enumerators/action_type.dart'; @@ -9,6 +9,7 @@ import 'package:awesome_notifications/src/models/model.dart'; import 'package:awesome_notifications/src/utils/assert_utils.dart'; import 'package:awesome_notifications/src/utils/bitmap_utils.dart'; import 'package:awesome_notifications/src/utils/string_utils.dart'; +import 'package:flutter/foundation.dart'; /// Notification button to display inside a notification. /// Since Android 7, icons are displayed only for Media Layout Notifications @@ -87,41 +88,42 @@ class NotificationActionButton extends Model { _color = color, _actionType = actionType { // Adapting input type to 0.7.0 pattern - _adaptInputFieldToRequireText(); + adaptInputFieldToRequireText(); } @override NotificationActionButton? fromMap(Map mapData) { - _processRetroCompatibility(mapData); - _key = AwesomeAssertUtils.extractValue(NOTIFICATION_KEY, mapData, String); - _icon = AwesomeAssertUtils.extractValue(NOTIFICATION_ICON, mapData, String); - _label = AwesomeAssertUtils.extractValue( - NOTIFICATION_BUTTON_LABEL, mapData, String); + processRetroCompatibility(mapData); + _key = AwesomeAssertUtils.extractValue(NOTIFICATION_KEY, mapData); + _icon = AwesomeAssertUtils.extractValue(NOTIFICATION_ICON, mapData); + _label = AwesomeAssertUtils.extractValue( + NOTIFICATION_BUTTON_LABEL, mapData); _enabled = - AwesomeAssertUtils.extractValue(NOTIFICATION_ENABLED, mapData, bool); - _requireInputText = AwesomeAssertUtils.extractValue( - NOTIFICATION_REQUIRE_INPUT_TEXT, mapData, bool); - _autoDismissible = AwesomeAssertUtils.extractValue( - NOTIFICATION_AUTO_DISMISSIBLE, mapData, bool); - _showInCompactView = AwesomeAssertUtils.extractValue( - NOTIFICATION_SHOW_IN_COMPACT_VIEW, mapData, bool); - _isDangerousOption = AwesomeAssertUtils.extractValue( - NOTIFICATION_IS_DANGEROUS_OPTION, mapData, bool); + AwesomeAssertUtils.extractValue(NOTIFICATION_ENABLED, mapData); + _requireInputText = AwesomeAssertUtils.extractValue( + NOTIFICATION_REQUIRE_INPUT_TEXT, mapData); + _autoDismissible = AwesomeAssertUtils.extractValue( + NOTIFICATION_AUTO_DISMISSIBLE, mapData); + _showInCompactView = AwesomeAssertUtils.extractValue( + NOTIFICATION_SHOW_IN_COMPACT_VIEW, mapData); + _isDangerousOption = AwesomeAssertUtils.extractValue( + NOTIFICATION_IS_DANGEROUS_OPTION, mapData); _actionType = AwesomeAssertUtils.extractEnum( NOTIFICATION_ACTION_TYPE, mapData, ActionType.values); _color = - AwesomeAssertUtils.extractValue(NOTIFICATION_COLOR, mapData, Color); + AwesomeAssertUtils.extractValue(NOTIFICATION_COLOR, mapData); return this; } - void _processRetroCompatibility(Map dataMap) { + @visibleForTesting + void processRetroCompatibility(Map dataMap) { if (dataMap.containsKey("autoCancel")) { developer .log("autoCancel is deprecated. Please use autoDismissible instead."); - _autoDismissible = - AwesomeAssertUtils.extractValue("autoCancel", dataMap, bool); + dataMap[NOTIFICATION_AUTO_DISMISSIBLE] = + AwesomeAssertUtils.extractValue('autoCancel', dataMap); } if (dataMap.containsKey("buttonType")) { @@ -130,10 +132,11 @@ class NotificationActionButton extends Model { "buttonType", dataMap, ActionType.values); } - _adaptInputFieldToRequireText(); + adaptInputFieldToRequireText(); } - void _adaptInputFieldToRequireText() { + @visibleForTesting + void adaptInputFieldToRequireText() { // ignore: deprecated_member_use_from_same_package if (_actionType == ActionType.InputField) { developer.log( @@ -145,7 +148,7 @@ class NotificationActionButton extends Model { @override Map toMap() { - _adaptInputFieldToRequireText(); + adaptInputFieldToRequireText(); return { NOTIFICATION_KEY: _key, @@ -163,21 +166,13 @@ class NotificationActionButton extends Model { @override void validate() { - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_key, String)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_key)) { throw const AwesomeNotificationsException(message: 'key id is requried'); } - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_label, String)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_label)) { throw const AwesomeNotificationsException( message: 'label id is requried'); } - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_autoDismissible, bool)) { - throw const AwesomeNotificationsException( - message: 'autoDismissible id is requried'); - } - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_showInCompactView, bool)) { - throw const AwesomeNotificationsException( - message: 'showInCompactView id is requried'); - } // For action buttons, it's only allowed resource media types if (!AwesomeStringUtils.isNullOrEmpty(_icon) && diff --git a/lib/src/models/notification_calendar.dart b/lib/src/models/notification_calendar.dart index 22c7154c..c29fa687 100644 --- a/lib/src/models/notification_calendar.dart +++ b/lib/src/models/notification_calendar.dart @@ -66,7 +66,7 @@ class NotificationCalendar extends NotificationSchedule { this.weekOfYear, String? timeZone, bool allowWhileIdle = false, - bool preciseAlarm = false, + bool preciseAlarm = true, bool repeats = false, }) : super( timeZone: timeZone ?? AwesomeNotifications.localTimeZoneIdentifier, @@ -101,26 +101,26 @@ class NotificationCalendar extends NotificationSchedule { @override NotificationCalendar? fromMap(Map mapData) { - era = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_ERA, mapData, int); - year = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_YEAR, mapData, int); - month = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_MONTH, mapData, int); - day = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_DAY, mapData, int); - hour = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_HOUR, mapData, int); - minute = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_MINUTE, mapData, int); - second = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_SECOND, mapData, int); - weekday = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_WEEKDAY, mapData, int); - weekOfMonth = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_WEEKOFMONTH, mapData, int); - weekOfYear = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_WEEKOFYEAR, mapData, int); + era = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_ERA, mapData); + year = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_YEAR, mapData); + month = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_MONTH, mapData); + day = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_DAY, mapData); + hour = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_HOUR, mapData); + minute = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_MINUTE, mapData); + second = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_SECOND, mapData); + weekday = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_WEEKDAY, mapData); + weekOfMonth = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_WEEKOFMONTH, mapData); + weekOfYear = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_WEEKOFYEAR, mapData); super.fromMap(mapData); diff --git a/lib/src/models/notification_channel.dart b/lib/src/models/notification_channel.dart index bc1c6e2f..28a5a645 100644 --- a/lib/src/models/notification_channel.dart +++ b/lib/src/models/notification_channel.dart @@ -80,60 +80,57 @@ class NotificationChannel extends Model { this.defaultPrivacy, this.criticalAlerts}) : super() { - channelKey = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_CHANNEL_KEY, channelKey, String); - channelName = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_CHANNEL_NAME, channelName, String); - channelDescription = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_CHANNEL_DESCRIPTION, channelDescription, String); - channelShowBadge = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_CHANNEL_SHOW_BADGE, channelShowBadge, bool); - - channelGroupKey = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_CHANNEL_GROUP_KEY, channelGroupKey, String); - - importance = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_IMPORTANCE, importance, NotificationImportance); - playSound = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_PLAY_SOUND, playSound, bool); - criticalAlerts = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_CHANNEL_CRITICAL_ALERTS, criticalAlerts, bool); - soundSource = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_SOUND_SOURCE, soundSource, String); - enableVibration = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_ENABLE_VIBRATION, enableVibration, bool); - vibrationPattern = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_VIBRATION_PATTERN, vibrationPattern, Int64List); - enableLights = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_ENABLE_LIGHTS, enableLights, bool); - ledColor = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_LED_COLOR, ledColor, Color); - ledOnMs = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_LED_ON_MS, ledOnMs, int); - ledOffMs = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_LED_OFF_MS, ledOffMs, int); - groupKey = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_GROUP_KEY, groupKey, String); - groupSort = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_GROUP_SORT, groupSort, GroupSort); - groupAlertBehavior = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_GROUP_ALERT_BEHAVIOR, - groupAlertBehavior, - GroupAlertBehavior); - icon = - AwesomeAssertUtils.getValueOrDefault(NOTIFICATION_ICON, icon, String); - defaultColor = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_DEFAULT_COLOR, defaultColor, Color); + channelKey = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CHANNEL_KEY, channelKey); + channelName = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CHANNEL_NAME, channelName); + channelDescription = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CHANNEL_DESCRIPTION, channelDescription); + channelShowBadge = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CHANNEL_SHOW_BADGE, channelShowBadge); + + channelGroupKey = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CHANNEL_GROUP_KEY, channelGroupKey); + + importance = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_IMPORTANCE, importance); + playSound = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_PLAY_SOUND, playSound); + criticalAlerts = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_CHANNEL_CRITICAL_ALERTS, criticalAlerts); + soundSource = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_SOUND_SOURCE, soundSource); + enableVibration = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_ENABLE_VIBRATION, enableVibration); + vibrationPattern = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_VIBRATION_PATTERN, vibrationPattern); + enableLights = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_ENABLE_LIGHTS, enableLights); + ledColor = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_LED_COLOR, ledColor); + ledOnMs = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_LED_ON_MS, ledOnMs); + ledOffMs = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_LED_OFF_MS, ledOffMs); + groupKey = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_GROUP_KEY, groupKey); + groupSort = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_GROUP_SORT, groupSort); + groupAlertBehavior = + AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_GROUP_ALERT_BEHAVIOR, groupAlertBehavior); + icon = AwesomeAssertUtils.getValueOrDefault(NOTIFICATION_ICON, icon); + defaultColor = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_DEFAULT_COLOR, defaultColor); locked = - AwesomeAssertUtils.getValueOrDefault(NOTIFICATION_LOCKED, locked, bool); - onlyAlertOnce = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_ONLY_ALERT_ONCE, onlyAlertOnce, bool); - defaultPrivacy = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_DEFAULT_PRIVACY, defaultPrivacy, NotificationPrivacy); - defaultRingtoneType = AwesomeAssertUtils.getValueOrDefault( - NOTIFICATION_DEFAULT_RINGTONE_TYPE, - defaultRingtoneType, - DefaultRingtoneType); + AwesomeAssertUtils.getValueOrDefault(NOTIFICATION_LOCKED, locked); + onlyAlertOnce = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_ONLY_ALERT_ONCE, onlyAlertOnce); + defaultPrivacy = AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_DEFAULT_PRIVACY, defaultPrivacy); + defaultRingtoneType = + AwesomeAssertUtils.getValueOrDefault( + NOTIFICATION_DEFAULT_RINGTONE_TYPE, defaultRingtoneType); // For small icons, it's only allowed resource media types assert(AwesomeStringUtils.isNullOrEmpty(icon) || @@ -172,29 +169,29 @@ class NotificationChannel extends Model { @override NotificationChannel fromMap(Map mapData) { - channelKey = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_KEY, mapData, String); - channelName = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_NAME, mapData, String); - channelDescription = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_DESCRIPTION, mapData, String); - channelShowBadge = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_SHOW_BADGE, mapData, bool); - - channelGroupKey = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_GROUP_KEY, mapData, String); + channelKey = AwesomeAssertUtils.extractValue( + NOTIFICATION_CHANNEL_KEY, mapData); + channelName = AwesomeAssertUtils.extractValue( + NOTIFICATION_CHANNEL_NAME, mapData); + channelDescription = AwesomeAssertUtils.extractValue( + NOTIFICATION_CHANNEL_DESCRIPTION, mapData); + channelShowBadge = AwesomeAssertUtils.extractValue( + NOTIFICATION_CHANNEL_SHOW_BADGE, mapData); + + channelGroupKey = AwesomeAssertUtils.extractValue( + NOTIFICATION_CHANNEL_GROUP_KEY, mapData); playSound = - AwesomeAssertUtils.extractValue(NOTIFICATION_PLAY_SOUND, mapData, bool); - soundSource = AwesomeAssertUtils.extractValue( - NOTIFICATION_SOUND_SOURCE, mapData, String); + AwesomeAssertUtils.extractValue(NOTIFICATION_PLAY_SOUND, mapData); + soundSource = AwesomeAssertUtils.extractValue( + NOTIFICATION_SOUND_SOURCE, mapData); - enableVibration = AwesomeAssertUtils.extractValue( - NOTIFICATION_ENABLE_VIBRATION, mapData, bool); - vibrationPattern = AwesomeAssertUtils.extractValue( - NOTIFICATION_VIBRATION_PATTERN, mapData, Int64List); - enableLights = AwesomeAssertUtils.extractValue( - NOTIFICATION_ENABLE_LIGHTS, mapData, bool); + enableVibration = AwesomeAssertUtils.extractValue( + NOTIFICATION_ENABLE_VIBRATION, mapData); + vibrationPattern = AwesomeAssertUtils.extractValue( + NOTIFICATION_VIBRATION_PATTERN, mapData); + enableLights = AwesomeAssertUtils.extractValue( + NOTIFICATION_ENABLE_LIGHTS, mapData); importance = AwesomeAssertUtils.extractEnum( NOTIFICATION_IMPORTANCE, mapData, NotificationImportance.values); @@ -205,48 +202,48 @@ class NotificationChannel extends Model { mapData, DefaultRingtoneType.values); - groupKey = AwesomeAssertUtils.extractValue( - NOTIFICATION_GROUP_KEY, mapData, String); + groupKey = AwesomeAssertUtils.extractValue( + NOTIFICATION_GROUP_KEY, mapData); groupSort = AwesomeAssertUtils.extractEnum( NOTIFICATION_GROUP_SORT, mapData, GroupSort.values); groupAlertBehavior = AwesomeAssertUtils.extractEnum( NOTIFICATION_GROUP_ALERT_BEHAVIOR, mapData, GroupAlertBehavior.values); - icon = AwesomeAssertUtils.extractValue(NOTIFICATION_ICON, mapData, String); + icon = AwesomeAssertUtils.extractValue(NOTIFICATION_ICON, mapData); locked = - AwesomeAssertUtils.extractValue(NOTIFICATION_LOCKED, mapData, bool); - onlyAlertOnce = AwesomeAssertUtils.extractValue( - NOTIFICATION_ONLY_ALERT_ONCE, mapData, bool); + AwesomeAssertUtils.extractValue(NOTIFICATION_LOCKED, mapData); + onlyAlertOnce = AwesomeAssertUtils.extractValue( + NOTIFICATION_ONLY_ALERT_ONCE, mapData); - defaultColor = AwesomeAssertUtils.extractValue( - NOTIFICATION_DEFAULT_COLOR, mapData, Color); + defaultColor = AwesomeAssertUtils.extractValue( + NOTIFICATION_DEFAULT_COLOR, mapData); ledColor = - AwesomeAssertUtils.extractValue(NOTIFICATION_LED_COLOR, mapData, Color); + AwesomeAssertUtils.extractValue(NOTIFICATION_LED_COLOR, mapData); ledOnMs = - AwesomeAssertUtils.extractValue(NOTIFICATION_LED_ON_MS, mapData, int); + AwesomeAssertUtils.extractValue(NOTIFICATION_LED_ON_MS, mapData); ledOffMs = - AwesomeAssertUtils.extractValue(NOTIFICATION_LED_OFF_MS, mapData, int); + AwesomeAssertUtils.extractValue(NOTIFICATION_LED_OFF_MS, mapData); - criticalAlerts = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_CRITICAL_ALERTS, mapData, bool); + criticalAlerts = AwesomeAssertUtils.extractValue( + NOTIFICATION_CHANNEL_CRITICAL_ALERTS, mapData); return this; } @override void validate() { - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(channelKey, String)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(channelKey)) { throw const AwesomeNotificationsException( - message: 'Property channelKey is requried'); + message: 'Property channelKey is required'); } - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(channelName, String)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(channelName)) { throw const AwesomeNotificationsException( - message: 'Property channelName is requried'); + message: 'Property channelName is required'); } - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(channelDescription, String)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(channelDescription)) { throw const AwesomeNotificationsException( - message: 'Property channelDescription is requried'); + message: 'Property channelDescription is required'); } } } diff --git a/lib/src/models/notification_channel_group.dart b/lib/src/models/notification_channel_group.dart index b360f96c..30333bf9 100644 --- a/lib/src/models/notification_channel_group.dart +++ b/lib/src/models/notification_channel_group.dart @@ -24,9 +24,9 @@ class NotificationChannelGroup extends Model { @override NotificationChannelGroup? fromMap(Map mapData) { _channelGroupKey = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_GROUP_KEY, mapData, String); + NOTIFICATION_CHANNEL_GROUP_KEY, mapData); _channelGroupName = AwesomeAssertUtils.extractValue( - NOTIFICATION_CHANNEL_GROUP_NAME, mapData, String); + NOTIFICATION_CHANNEL_GROUP_NAME, mapData); return this; } @@ -41,11 +41,11 @@ class NotificationChannelGroup extends Model { @override void validate() { - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_channelGroupKey, String)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_channelGroupKey)) { throw const AwesomeNotificationsException( message: 'channelGroupKey is required'); } - if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_channelGroupName, String)) { + if (AwesomeAssertUtils.isNullOrEmptyOrInvalid(_channelGroupName)) { throw const AwesomeNotificationsException( message: 'channelGroupName is required'); } diff --git a/lib/src/models/notification_content.dart b/lib/src/models/notification_content.dart index d22af2ed..ca6f7c34 100644 --- a/lib/src/models/notification_content.dart +++ b/lib/src/models/notification_content.dart @@ -72,6 +72,8 @@ class NotificationContent extends BaseNotificationContent { bool roundedBigPicture = false, bool autoDismissible = true, Color? color, + Duration? timeoutAfter, + Duration? chronometer, Color? backgroundColor, ActionType actionType = ActionType.Default, NotificationLayout notificationLayout = NotificationLayout.Default, @@ -106,6 +108,8 @@ class NotificationContent extends BaseNotificationContent { showWhen: showWhen, payload: payload, icon: icon, + timeoutAfter: timeoutAfter, + chronometer: chronometer, largeIcon: largeIcon, bigPicture: bigPicture, customSound: customSound, @@ -119,24 +123,25 @@ class NotificationContent extends BaseNotificationContent { @override NotificationContent? fromMap(Map mapData) { super.fromMap(mapData); - _hideLargeIconOnExpand = AwesomeAssertUtils.extractValue( - NOTIFICATION_HIDE_LARGE_ICON_ON_EXPAND, mapData, bool); + _hideLargeIconOnExpand = AwesomeAssertUtils.extractValue( + NOTIFICATION_HIDE_LARGE_ICON_ON_EXPAND, mapData); _progress = - AwesomeAssertUtils.extractValue(NOTIFICATION_PROGRESS, mapData, int); - _badge = AwesomeAssertUtils.extractValue(NOTIFICATION_BADGE, mapData, int); + AwesomeAssertUtils.extractValue(NOTIFICATION_PROGRESS, mapData); + _badge = AwesomeAssertUtils.extractValue(NOTIFICATION_BADGE, mapData); _ticker = - AwesomeAssertUtils.extractValue(NOTIFICATION_TICKER, mapData, String); + AwesomeAssertUtils.extractValue(NOTIFICATION_TICKER, mapData); _locked = - AwesomeAssertUtils.extractValue(NOTIFICATION_LOCKED, mapData, bool); + AwesomeAssertUtils.extractValue(NOTIFICATION_LOCKED, mapData); _notificationLayout = AwesomeAssertUtils.extractEnum( NOTIFICATION_LAYOUT, mapData, NotificationLayout.values); - _displayOnForeground = AwesomeAssertUtils.extractValue( - NOTIFICATION_DISPLAY_ON_FOREGROUND, mapData, bool); - _displayOnBackground = AwesomeAssertUtils.extractValue( - NOTIFICATION_DISPLAY_ON_BACKGROUND, mapData, bool); + _displayOnForeground = AwesomeAssertUtils.extractValue( + NOTIFICATION_DISPLAY_ON_FOREGROUND, mapData); + + _displayOnBackground = AwesomeAssertUtils.extractValue( + NOTIFICATION_DISPLAY_ON_BACKGROUND, mapData); try { validate(); diff --git a/lib/src/models/notification_interval.dart b/lib/src/models/notification_interval.dart index 7b556246..879967c9 100644 --- a/lib/src/models/notification_interval.dart +++ b/lib/src/models/notification_interval.dart @@ -15,7 +15,7 @@ class NotificationInterval extends NotificationSchedule { String? timeZone, bool allowWhileIdle = false, bool repeats = false, - bool preciseAlarm = false}) + bool preciseAlarm = true}) : super( timeZone: timeZone ?? AwesomeNotifications.localTimeZoneIdentifier, allowWhileIdle: allowWhileIdle, @@ -26,8 +26,8 @@ class NotificationInterval extends NotificationSchedule { NotificationInterval? fromMap(Map mapData) { super.fromMap(mapData); - interval = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_INTERVAL, mapData, int); + interval = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_INTERVAL, mapData); try { validate(); diff --git a/lib/src/models/notification_localization.dart b/lib/src/models/notification_localization.dart new file mode 100644 index 00000000..8f056fdd --- /dev/null +++ b/lib/src/models/notification_localization.dart @@ -0,0 +1,103 @@ +import '../../awesome_notifications.dart'; +import 'model.dart'; + +/// A model class that represents a set of localized strings for a notification, +/// including the title, body, summary, large icon, big picture, and button labels. +/// +/// This class is used to provide localized strings for a notification. The [title] +/// and [body] fields are used to specify the main text of the notification, while +/// the [summary] field is used to provide a brief summary or additional context. +/// The [largeIcon] and [bigPicture] fields can be used to specify images or icons +/// that are displayed alongside the notification text, and the [buttonLabels] field +/// can be used to provide localized labels for the action buttons that are associated +/// with the notification. +class NotificationLocalization extends Model { + String? title; + String? body; + String? summary; + String? largeIcon; + String? bigPicture; + Map? buttonLabels; + + /// Creates a new instance of the `NotificationLocalization` class. If any of + /// the respective parameters is set to null, the original content is preserved. + /// + /// The optional [title] parameter is a string that represents the title of the + /// notification. + /// + /// The optional [body] parameter is a string that represents the body text of + /// the notification. + /// + /// The optional [summary] parameter is a string that represents a brief summary, + /// subtitle or additional context for the notification. + /// + /// The optional [largeIcon] parameter is a string that represents the name or + /// path of an image file that should be used as the large icon for the notification, + /// for cases where the image contains some text that also needs translation. + /// + /// The optional [bigPicture] parameter is a string that represents the name or + /// path of an image file that should be used as the big picture for the notification, + /// for cases where the image contains some text that also needs translation. + /// + /// The optional [buttonLabels] parameter is a map that contains the localized + /// labels for the action buttons that are associated with the notification. + NotificationLocalization({ + this.title, + this.body, + this.summary, + this.largeIcon, + this.bigPicture, + this.buttonLabels, + }); + + /// Parses the input [mapData] into a new [NotificationLocalization] instance. + /// + /// The [mapData] parameter is a [Map] that represents the data to be parsed. + /// + /// This method returns a [NotificationLocalization] instance that contains + /// the parsed data. If the [mapData] parameter is null, empty or invalid, + /// this method will return `null`. + @override + NotificationLocalization? fromMap(Map mapData) { + if (mapData.isEmpty) return null; + + title = + AwesomeAssertUtils.extractValue(NOTIFICATION_TITLE, mapData); + body = AwesomeAssertUtils.extractValue(NOTIFICATION_BODY, mapData); + summary = + AwesomeAssertUtils.extractValue(NOTIFICATION_SUMMARY, mapData); + largeIcon = AwesomeAssertUtils.extractValue( + NOTIFICATION_LARGE_ICON, mapData); + bigPicture = AwesomeAssertUtils.extractValue( + NOTIFICATION_BIG_PICTURE, mapData); + + buttonLabels = mapData[NOTIFICATION_BUTTON_LABELS] is Map + ? { + for (MapEntry entry + in (mapData[NOTIFICATION_BUTTON_LABELS] as Map).entries) + entry.key.toString(): entry.value.toString() + } + : null; + return this; + } + + /// Converts the current instance of [NotificationLocalization] to a [Map] instance. + /// + /// This method returns a [Map] instance that contains the converted data. If + /// a field has a null value, it will not be added to the map. Finally, it + /// returns the created map. + @override + Map toMap() => { + if (title?.isNotEmpty ?? false) NOTIFICATION_TITLE: title, + if (body?.isNotEmpty ?? false) NOTIFICATION_BODY: body, + if (summary?.isNotEmpty ?? false) NOTIFICATION_SUMMARY: summary, + if (bigPicture?.isNotEmpty ?? false) + NOTIFICATION_BIG_PICTURE: bigPicture, + if (largeIcon?.isNotEmpty ?? false) NOTIFICATION_LARGE_ICON: largeIcon, + if (buttonLabels?.isNotEmpty ?? false) + NOTIFICATION_BUTTON_LABELS: buttonLabels, + }; + + @override + void validate() {} +} diff --git a/lib/src/models/notification_model.dart b/lib/src/models/notification_model.dart index 8c0067ca..e3fe4eb2 100644 --- a/lib/src/models/notification_model.dart +++ b/lib/src/models/notification_model.dart @@ -6,111 +6,150 @@ import 'package:awesome_notifications/src/models/notification_button.dart'; import 'package:awesome_notifications/src/models/notification_calendar.dart'; import 'package:awesome_notifications/src/models/notification_content.dart'; import 'package:awesome_notifications/src/models/notification_interval.dart'; +import 'package:awesome_notifications/src/models/notification_localization.dart'; import 'package:awesome_notifications/src/models/notification_schedule.dart'; -/// Reference Model to create a new notification -/// [schedule] and [actionButtons] are optional +/// A model class representing a single notification instance. +/// +/// The [NotificationModel] class encapsulates all the information needed to +/// display a single notification, including the notification's content, schedule, +/// action buttons, and localizations. Each instance of the class represents a +/// single notification that can be displayed to the user. class NotificationModel extends Model { NotificationContent? _content; NotificationSchedule? _schedule; List? _actionButtons; + Map? _localizations; - NotificationContent? get content { - return _content; - } + /// The content of the notification. + NotificationContent? get content => _content; - NotificationSchedule? get schedule { - return _schedule; - } + /// The schedule to display the notification. + NotificationSchedule? get schedule => _schedule; - List? get actionButtons { - return _actionButtons; - } + /// The action buttons for the notification. + List? get actionButtons => _actionButtons; + + /// The localizations for the notification. + Map? get localizations => _localizations; - NotificationModel( - {NotificationContent? content, - NotificationSchedule? schedule, - List? actionButtons}) - : _content = content, + /// Creates a new instance of the [NotificationModel] class with the given + /// content, schedule, action buttons, and localizations. + NotificationModel({ + NotificationContent? content, + NotificationSchedule? schedule, + List? actionButtons, + Map? localizations, + }) : _content = content, _schedule = schedule, - _actionButtons = actionButtons; + _actionButtons = actionButtons, + _localizations = localizations; /// Imports data from a serializable object @override NotificationModel? fromMap(Map mapData) { try { - assert(mapData.containsKey(NOTIFICATION_CONTENT) && - mapData[NOTIFICATION_CONTENT] is Map); - - Map contentData = - Map.from(mapData[NOTIFICATION_CONTENT]); - - _content = - NotificationContent(id: 0, channelKey: '').fromMap(contentData); - if (_content == null) return null; - - _content!.validate(); - - if (mapData.containsKey(NOTIFICATION_SCHEDULE)) { - Map scheduleData = - Map.from(mapData[NOTIFICATION_SCHEDULE]); - - if (scheduleData.containsKey(NOTIFICATION_SCHEDULE_INTERVAL)) { - _schedule = NotificationInterval(interval: 0).fromMap(scheduleData); - } else if (scheduleData.containsKey(NOTIFICATION_CRONTAB_EXPRESSION)) { - _schedule = NotificationAndroidCrontab().fromMap(scheduleData); - } else { - _schedule = NotificationCalendar().fromMap(scheduleData); - } - _schedule?.validate(); - } - - if (mapData.containsKey(NOTIFICATION_BUTTONS) && - mapData[NOTIFICATION_BUTTONS] != null) { - _actionButtons = []; - List actionButtonsData = - List.from(mapData[NOTIFICATION_BUTTONS]); - - for (dynamic buttonData in actionButtonsData) { - Map actionButtonData = - Map.from(buttonData); - - NotificationActionButton button = - NotificationActionButton(label: '', key: '') - .fromMap(actionButtonData) as NotificationActionButton; - button.validate(); - - _actionButtons!.add(button); - } - assert(_actionButtons!.isNotEmpty); - } + _content = _extractContentFromMap(mapData); + _schedule = _extractScheduleFromMap(mapData); + _actionButtons = _extractButtonsFromMap(mapData); + _localizations = _extractLocalizationsFromMap(mapData); } catch (e) { return null; } - return this; } - /// Exports all content into a serializable object - @override - Map toMap() { - List> actionButtonsData = []; - if (_actionButtons != null) { - for (NotificationActionButton button in _actionButtons!) { - Map data = button.toMap(); - if (data.isNotEmpty) actionButtonsData.add(data); - } + NotificationContent _extractContentFromMap(Map mapData) { + assert(mapData[NOTIFICATION_CONTENT] is Map); + + NotificationContent? content = NotificationContent(id: 0, channelKey: '') + .fromMap(Map.from(mapData[NOTIFICATION_CONTENT])); + assert(content != null); + + return content!..validate(); + } + + NotificationSchedule? _extractScheduleFromMap(Map mapData) { + if (mapData[NOTIFICATION_SCHEDULE] is! Map) return null; + if (mapData[NOTIFICATION_SCHEDULE].isEmpty) return null; + + Map scheduleData = Map.from( + Map.from(mapData[NOTIFICATION_SCHEDULE])); + + if (scheduleData.containsKey(NOTIFICATION_SCHEDULE_INTERVAL)) { + return NotificationInterval(interval: 0).fromMap(scheduleData) + ?..validate(); + } + + if (scheduleData.containsKey(NOTIFICATION_CRONTAB_EXPRESSION) || + scheduleData.containsKey(NOTIFICATION_PRECISE_SCHEDULES)) { + return NotificationAndroidCrontab().fromMap(scheduleData)?..validate(); + } + + return NotificationCalendar().fromMap(scheduleData)?..validate(); + } + + List? _extractButtonsFromMap( + Map mapData) { + if (mapData[NOTIFICATION_BUTTONS] is! List) return null; + if (mapData[NOTIFICATION_BUTTONS].isEmpty) return null; + + List finalList = []; + for (dynamic buttonData in mapData[NOTIFICATION_BUTTONS]) { + NotificationActionButton? actionButton = + NotificationActionButton(label: '', key: '') + .fromMap(Map.from(buttonData)) + ?..validate(); + if (actionButton == null) continue; + finalList.add(actionButton); + } + + return finalList; + } + + Map? _extractLocalizationsFromMap( + Map mapData) { + if (mapData[NOTIFICATION_LOCALIZATIONS] is! Map) { + return null; } - return { - 'content': _content?.toMap() ?? {}, - 'schedule': _schedule?.toMap() ?? {}, - 'actionButtons': actionButtonsData.isEmpty ? null : actionButtonsData - }; + if (mapData[NOTIFICATION_LOCALIZATIONS].isEmpty) return null; + + Map finalLocalizations = {}; + + for (MapEntry entry + in mapData[NOTIFICATION_LOCALIZATIONS].entries) { + if (entry.value is! Map) continue; + var localization = NotificationLocalization().fromMap(entry.value); + if (localization == null) continue; + localization.validate(); + + finalLocalizations[entry.key] = localization; + } + + return finalLocalizations; } + /// Exports all content into a serializable object + @override + Map toMap() => { + NOTIFICATION_CONTENT: _content?.toMap() ?? {}, + if (_schedule != null) NOTIFICATION_SCHEDULE: _schedule!.toMap(), + if (_actionButtons?.isNotEmpty ?? false) + NOTIFICATION_BUTTONS: [ + for (NotificationActionButton button in _actionButtons!) + button.toMap() + ], + if (_localizations?.isNotEmpty ?? false) + NOTIFICATION_LOCALIZATIONS: { + for (MapEntry localization + in _localizations!.entries) + localization.key: localization.value.toMap() + }, + }; + @override - /// Validates if the models has all the requirements to be considerated valid + /// Validates if the models has all the requirements to be considered valid void validate() { if (_content == null) { throw const AwesomeNotificationsException( diff --git a/lib/src/models/notification_schedule.dart b/lib/src/models/notification_schedule.dart index 3bafd067..104aec77 100644 --- a/lib/src/models/notification_schedule.dart +++ b/lib/src/models/notification_schedule.dart @@ -13,12 +13,8 @@ abstract class NotificationSchedule extends Model { {required this.timeZone, this.allowWhileIdle = false, this.repeats = false, - this.preciseAlarm = false}); - - String? _createdDate; - - /// Reference - String? get createdDate => _createdDate; + this.preciseAlarm = false, + this.delayTolerance = 600000}); /// Full time zone identifier to schedule a notification, in english (ex: America/Sao_Paulo, America/New_York, Europe/Helsinki or GMT-07:00) String timeZone; @@ -26,9 +22,12 @@ abstract class NotificationSchedule extends Model { /// Displays the notification, even when the device is low battery bool allowWhileIdle; - /// Displays the notification at precise date, even when the device is low battery. Requires explicity permission in Android 12 and beyond. + /// Require schedules to be precise, even when the device is low battery. Requires explicit permission in Android 12 and beyond. bool preciseAlarm; + /// Delay tolerance in milliseconds to schedule notifications being displayed. The minimum tolerance for inexact schedules is 600.000 (10 minutes). + int delayTolerance; + /// Specify false to deliver the notification one time. Specify true to reschedule the notification request each time the notification is delivered. bool repeats; @@ -36,19 +35,23 @@ abstract class NotificationSchedule extends Model { @override NotificationSchedule? fromMap(Map mapData) { timeZone = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_TIMEZONE, mapData, String) ?? - false; + NOTIFICATION_SCHEDULE_TIMEZONE, mapData) ?? + timeZone; allowWhileIdle = AwesomeAssertUtils.extractValue( - NOTIFICATION_ALLOW_WHILE_IDLE, mapData, bool) ?? + NOTIFICATION_ALLOW_WHILE_IDLE, mapData) ?? false; repeats = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_REPEATS, mapData, bool) ?? + NOTIFICATION_SCHEDULE_REPEATS, mapData) ?? false; + delayTolerance = AwesomeAssertUtils.extractValue( + NOTIFICATION_SCHEDULE_DELAY_TOLERANCE, mapData) ?? + 0; + preciseAlarm = AwesomeAssertUtils.extractValue( - NOTIFICATION_SCHEDULE_PRECISE_ALARM, mapData, bool) ?? + NOTIFICATION_SCHEDULE_PRECISE_ALARM, mapData) ?? false; return this; @@ -60,14 +63,10 @@ abstract class NotificationSchedule extends Model { NOTIFICATION_SCHEDULE_TIMEZONE: timeZone, NOTIFICATION_ALLOW_WHILE_IDLE: allowWhileIdle, NOTIFICATION_SCHEDULE_REPEATS: repeats, - NOTIFICATION_SCHEDULE_PRECISE_ALARM: preciseAlarm + NOTIFICATION_SCHEDULE_PRECISE_ALARM: preciseAlarm, + NOTIFICATION_SCHEDULE_DELAY_TOLERANCE: delayTolerance }; return dataMap; } - - @override - String toString() { - return toMap().toString().replaceAll(',', ',\n'); - } } diff --git a/lib/src/models/received_models/received_action.dart b/lib/src/models/received_models/received_action.dart index 23cb106f..476b5b2b 100644 --- a/lib/src/models/received_models/received_action.dart +++ b/lib/src/models/received_models/received_action.dart @@ -30,16 +30,16 @@ class ReceivedAction extends ReceivedNotification { dataMap, NotificationLifeCycle.values); - actionDate = AwesomeAssertUtils.extractValue( - NOTIFICATION_ACTION_DATE, dataMap, DateTime); - dismissedDate = AwesomeAssertUtils.extractValue( - NOTIFICATION_DISMISSED_DATE, dataMap, DateTime); + actionDate = AwesomeAssertUtils.extractValue( + NOTIFICATION_ACTION_DATE, dataMap); + dismissedDate = AwesomeAssertUtils.extractValue( + NOTIFICATION_DISMISSED_DATE, dataMap); - buttonKeyPressed = AwesomeAssertUtils.extractValue( - NOTIFICATION_BUTTON_KEY_PRESSED, dataMap, String); + buttonKeyPressed = AwesomeAssertUtils.extractValue( + NOTIFICATION_BUTTON_KEY_PRESSED, dataMap); - buttonKeyInput = AwesomeAssertUtils.extractValue( - NOTIFICATION_BUTTON_KEY_INPUT, dataMap, String); + buttonKeyInput = AwesomeAssertUtils.extractValue( + NOTIFICATION_BUTTON_KEY_INPUT, dataMap); return this; } diff --git a/lib/src/models/received_models/received_notification.dart b/lib/src/models/received_models/received_notification.dart index 473a7804..73f04c52 100644 --- a/lib/src/models/received_models/received_notification.dart +++ b/lib/src/models/received_models/received_notification.dart @@ -13,11 +13,11 @@ class ReceivedNotification extends BaseNotificationContent { ReceivedNotification fromMap(Map mapData) { super.fromMap(mapData); - createdDate = AwesomeAssertUtils.extractValue( - NOTIFICATION_CREATED_DATE, mapData, DateTime); + createdDate = AwesomeAssertUtils.extractValue( + NOTIFICATION_CREATED_DATE, mapData); - displayedDate = AwesomeAssertUtils.extractValue( - NOTIFICATION_DISPLAYED_DATE, mapData, DateTime); + displayedDate = AwesomeAssertUtils.extractValue( + NOTIFICATION_DISPLAYED_DATE, mapData); createdSource = AwesomeAssertUtils.extractEnum( NOTIFICATION_CREATED_SOURCE, mapData, NotificationSource.values); diff --git a/lib/src/utils/assert_utils.dart b/lib/src/utils/assert_utils.dart index 10f9f19a..d34bb49d 100644 --- a/lib/src/utils/assert_utils.dart +++ b/lib/src/utils/assert_utils.dart @@ -1,29 +1,22 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:awesome_notifications/src/models/model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; - -import 'package:awesome_notifications/src/models/model.dart'; import 'package:intl/intl.dart'; -import '../definitions.dart'; -import 'string_utils.dart'; - -const String _dateFormat = "yyyy-MM-dd HH:mm:ss Z"; +const String _dateFormatTimezone = "yyyy-MM-dd HH:mm:ss Z"; class AwesomeAssertUtils { static String? toSimpleEnumString(T? e) { return e?.name; } - static bool isNullOrEmptyOrInvalid(dynamic value, Type T) { - if (value?.runtimeType != T) return true; - - switch (value.runtimeType) { - case String: - case Map: - case List: - return value.isEmpty; - } - + static bool isNullOrEmptyOrInvalid(dynamic value) { + if (value == null) return true; + if (value is! T) return true; + if (value is String) return value.isEmpty; + if (value is Map) return value.isEmpty; + if (value is List) return value.isEmpty; return false; } @@ -38,7 +31,7 @@ class AwesomeAssertUtils { return returnList; } - static getValueOrDefault(String reference, dynamic value, Type T) { + static T? getValueOrDefault(String reference, dynamic value) { switch (value.runtimeType) { case MaterialColor: value = (value as MaterialColor).shade500; @@ -53,27 +46,34 @@ class AwesomeAssertUtils { break; } - if (value?.runtimeType == T) return value; - - return _getDefaultValue(reference, T); + T? defaultValue = _getDefaultValue(reference); + if (value == null) return defaultValue; + if (value is T) return value; + return defaultValue; } - static extractValue(String reference, Map dataMap, Type T) { - dynamic defaultValue = _getDefaultValue(reference, T); + static extractValue(String reference, Map dataMap) { + dynamic defaultValue = _getDefaultValue(reference); dynamic value = dataMap[reference]; if (value is String) { String valueCasted = value; - if (AwesomeStringUtils.isNullOrEmpty(valueCasted, - considerWhiteSpaceAsEmpty: true)) return defaultValue; - switch (T) { case DateTime: try { - return DateFormat(_dateFormat).parse(valueCasted, true); + if (RegExp(r'\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} \w+') + .hasMatch(valueCasted)) { + return DateFormat(_dateFormatTimezone).parse(valueCasted, true); + } + if (RegExp(r'\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}(\.\d{1,3})?') + .hasMatch(valueCasted)) { + return AwesomeDateUtils.parseStringToDate(valueCasted); + } + return null; } catch (err) { return defaultValue; } + case String: return valueCasted; @@ -82,7 +82,7 @@ class AwesomeAssertUtils { // Color hexadecimal representation final RegExpMatch? match = - RegExp(r'^(0x|#)(\w{2})?(\w{6,6})$').firstMatch(valueCasted); + RegExp(r'^(0x|#)(\w{2})?(\w{6})$').firstMatch(valueCasted); if (match != null) { String hex = (match.group(2) ?? 'FF') + match.group(3)!; @@ -91,7 +91,8 @@ class AwesomeAssertUtils { } else if (T == int) { int? parsedValue = int.tryParse(valueCasted.replaceFirstMapped( RegExp(r'^(\d+)\.\d+$'), (match) => '${match.group(1)}')); - return parsedValue ?? defaultValue; + var finalValue = parsedValue ?? defaultValue; + return finalValue; } break; @@ -100,7 +101,17 @@ class AwesomeAssertUtils { return parsedValue ?? defaultValue; case bool: - return valueCasted.toLowerCase() == 'true'; + switch (valueCasted.toLowerCase()) { + case 'true': + return true; + case 'false': + return false; + case '1': + return true; + case '0': + return false; + } + return null; } } @@ -117,6 +128,8 @@ class AwesomeAssertUtils { case Color: switch (value.runtimeType) { + case int: + return Color(value); case MaterialColor: value = (value as MaterialColor).shade500; break; @@ -131,24 +144,34 @@ class AwesomeAssertUtils { } break; + case Duration: + if (value == null) return defaultValue; + Duration? duration; + if (value is String) duration = Duration(seconds: int.parse(value)); + if (value is int) duration = Duration(seconds: value); + return (duration?.inSeconds ?? -1) < 0 ? defaultValue : duration; + case bool: - return value ?? defaultValue ?? false; + if (value == null) return defaultValue; + if (value is int) return value == 1; + if (value is bool) return value; + return defaultValue; } - if (value.runtimeType.hashCode != T.hashCode) return defaultValue; + if (value is T) return value; return defaultValue; } - static extractMap(Map dataMap, String reference) { - Map? defaultValue = _getDefaultValue(reference, Map); + static extractMap(String reference, Map dataMap) { + Map? defaultValue = _getDefaultValue(reference); dynamic value = dataMap[reference]; if (value == null || value is! Map) return defaultValue; try { - Map castedValue = Map.from(value); - return castedValue.isEmpty ? defaultValue : castedValue; + if (value.isEmpty) return defaultValue; + return Map.from(value); } catch (e) { return defaultValue; } @@ -156,16 +179,21 @@ class AwesomeAssertUtils { static T? extractEnum( String reference, Map dataMap, List values) { - T? defaultValue = _getDefaultValue(reference, T); + T? defaultValue = _getDefaultValue(reference); dynamic value = dataMap[reference]; if (value is T) return value; if (value == null || value is! String) return defaultValue; + if (AwesomeStringUtils.isNullOrEmpty(value, + considerWhiteSpaceAsEmpty: true)) { + return defaultValue; + } + String castedValue = value; castedValue = castedValue.trim(); - return enumToString(castedValue, values, defaultValue ?? values.first); + return enumToString(castedValue, values, defaultValue); } static T? enumToString( @@ -177,17 +205,17 @@ class AwesomeAssertUtils { return defaultValue; } - static dynamic _getDefaultValue(String reference, Type T) { + static dynamic _getDefaultValue(String reference) { dynamic defaultValue = Definitions.initialValues[reference]; - if (defaultValue?.runtimeType != T) return null; + if (defaultValue is! T) return null; return defaultValue; } static List? fromListMap( Object? mapData, Function newModel) { - if (mapData == null || mapData is List>) return null; + if (mapData == null || mapData is! List>) return null; - List> listMapData = List.from(mapData as List); + List> listMapData = List.from(mapData); if (listMapData.isEmpty) return null; List actionButtons = []; diff --git a/lib/src/utils/date_utils.dart b/lib/src/utils/date_utils.dart index 74b9cef6..2f7eb028 100644 --- a/lib/src/utils/date_utils.dart +++ b/lib/src/utils/date_utils.dart @@ -1,12 +1,32 @@ import 'package:intl/intl.dart'; class AwesomeDateUtils { - static DateTime? parseStringToDate(String? date, + static DateTime? parseStringToDateUtc(String? date, {String format = 'yyyy-MM-dd HH:mm:ss'}) { if (date == null || date.isEmpty) return null; + + RegExp dateRegex = + RegExp(r'(\d{4}-\d{2}-\d{2})[T\s]+(\d{2}:\d{2}:\d{2})(.\d{1,3})?'); + Match? match = dateRegex.firstMatch(date); + if (match == null) return null; + + date = '${match.group(1)} ${match.group(2)}'; return DateFormat(format).parseUTC(date); } + static DateTime? parseStringToDate(String? date, + {String format = 'yyyy-MM-dd HH:mm:ss'}) { + if (date == null || date.isEmpty) return null; + + RegExp dateRegex = + RegExp(r'(\d{4}-\d{2}-\d{2})[T\s]+(\d{2}:\d{2}:\d{2})(.\d{1,3})?'); + Match? match = dateRegex.firstMatch(date); + if (match == null) return null; + + date = '${match.group(1)} ${match.group(2)}'; + return DateFormat(format).parse(date); + } + static String? parseDateToString(DateTime? date, {String format = 'yyyy-MM-dd HH:mm:ss'}) { if (date == null) return null; diff --git a/lib/src/utils/resource_image_provider.dart b/lib/src/utils/resource_image_provider.dart index 482f504b..5afd6ba7 100644 --- a/lib/src/utils/resource_image_provider.dart +++ b/lib/src/utils/resource_image_provider.dart @@ -12,7 +12,7 @@ import '../../awesome_notifications.dart'; /// The provided [bytes] buffer should not be changed after it is provided /// to a [ResourceImage]. To provide an [ImageStream] that represents an image /// that changes over time, consider creating a new subclass of [ImageProvider] -/// whose [loadBuffer] method returns a subclass of [ImageStreamCompleter] that can +/// whose [loadImage] method returns a subclass of [ImageStreamCompleter] that can /// handle providing multiple images. /// /// See also: @@ -22,9 +22,12 @@ class ResourceImage extends ImageProvider { /// Creates an object that decodes a [Uint8List] buffer as an image. /// /// The arguments must not be null. - const ResourceImage(this.drawablePath, {this.scale = 1.0}); + const ResourceImage(this.drawablePath, + {this.scale = 1.0, AwesomeNotifications? awesomeNotifications}) + : _awesomeNotifications = awesomeNotifications; final String drawablePath; + final AwesomeNotifications? _awesomeNotifications; /// The scale to place in the [ImageInfo] object of the image. final double scale; @@ -35,27 +38,30 @@ class ResourceImage extends ImageProvider { } @override - @protected - ImageStreamCompleter loadBuffer( - ResourceImage key, DecoderBufferCallback decode) { + ImageStreamCompleter loadImage( + ResourceImage key, ImageDecoderCallback decode) { return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, decode), + codec: loadAsync(key, decode), scale: key.scale, ); } - Future _loadAsync( - ResourceImage key, DecoderBufferCallback decode) async { + @visibleForTesting + Future loadAsync( + ResourceImage key, ImageDecoderCallback decode) async { assert(key == this); - Uint8List? bytes = - await AwesomeNotifications().getDrawableData(drawablePath); + Uint8List? bytes = await (_awesomeNotifications ?? AwesomeNotifications()) + .getDrawableData(drawablePath); - if (bytes?.lengthInBytes == 0) { - throw Exception('image is invalid'); + if ((bytes?.lengthInBytes ?? 0) == 0) { + throw const AwesomeNotificationsException(message: 'image is invalid'); } final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(bytes!); - return decode(buffer); + final ImageDescriptor descriptor = await ImageDescriptor.encoded(buffer); + + buffer.dispose(); + return descriptor.instantiateCodec(); } @override diff --git a/pubspec.lock b/pubspec.lock index ec6595ce..e1d01483 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,48 +1,158 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "8880b4cfe7b5b17d57c052a5a3a8cc1d4f546261c7cc8fbd717bd53f48db0568" + url: "https://pub.dev" + source: hosted + version: "59.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: a89627f49b0e70e068130a36571409726b04dab12da7e5625941d2c8ec278b96 + url: "https://pub.dev" + source: hosted + version: "5.11.1" + args: + dependency: transitive + description: + name: args + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + url: "https://pub.dev" + source: hosted + version: "2.4.0" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0" + url: "https://pub.dev" + source: hosted + version: "8.4.4" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "315a598c7fbe77f22de1c9da7cfd6fd21816312f16ffa124453b4fc679e540f1" + url: "https://pub.dev" + source: hosted + version: "4.6.0" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" + source: hosted + version: "1.6.3" + crypto: + dependency: transitive + description: + name: crypto + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "6d691edde054969f0e0f26abb1b30834b5138b963793e56f69d3a9a4435e6352" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "2.3.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -52,9 +162,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter @@ -65,116 +176,363 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" intl: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" source: hosted - version: "0.17.0" + version: "1.0.4" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.7" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.1.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.5.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "7d5b53bcd556c1bc7ffbe4e4d5a19c3e112b7e925e9e172dd7c6ad0630812616" + url: "https://pub.dev" + source: hosted + version: "5.4.2" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "9503969a7c2c78c7292022c70c0289ed6241df7a9ba720010c0b215af29a5a58" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" plugin_platform_interface: dependency: "direct main" description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + url: "https://pub.dev" + source: hosted + version: "2.1.6" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + url: "https://pub.dev" source: hosted version: "2.1.3" + shelf: + dependency: transitive + description: + name: shelf + sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + url: "https://pub.dev" + source: hosted + version: "1.4.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306 + url: "https://pub.dev" + source: hosted + version: "3.0.1" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c + url: "https://pub.dev" + source: hosted + version: "1.1.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + url: "https://pub.dev" + source: hosted + version: "1.0.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298 + url: "https://pub.dev" + source: hosted + version: "1.2.7" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" + url: "https://pub.dev" + source: hosted + version: "1.24.3" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.6.0" + test_core: + dependency: transitive + description: + name: test_core + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" + url: "https://pub.dev" + source: hosted + version: "0.5.3" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" + source: hosted + version: "1.3.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + url: "https://pub.dev" + source: hosted + version: "9.4.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "3.1.1" sdks: - dart: ">=2.17.0-206.0.dev <3.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index e75c2480..e98a5ffc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: awesome_notifications -description: A complete solution to create Local and Push Notifications (Firebase Messaging), customizing buttons, images, sounds, emoticons and applying many different layouts for Flutter apps. -version: 0.7.4 +description: A complete solution to create Local and Push Notifications, customizing buttons, images, sounds, emoticons and applying many different layouts for Flutter apps. +version: 0.7.5 repository: https://github.com/rafaelsetragni/awesome_notifications environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=2.19.0 <4.0.0' flutter: ">=2.5.0" dependencies: @@ -13,13 +13,16 @@ dependencies: flutter_web_plugins: sdk: flutter - plugin_platform_interface: ^2.0.2 - intl: ^0.17.0 + plugin_platform_interface: ^2.1.6 + intl: ^0.18.1 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 + flutter_lints: ^2.0.3 + + mockito: ^5.4.2 + mocktail: ^1.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -53,11 +56,9 @@ flutter: pluginClass: AwesomeNotificationsWeb fileName: awesome_notifications_web.dart - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # + assets: + - test/assets/images/test_image.png + # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # diff --git a/test/assets/images/test_image.png b/test/assets/images/test_image.png new file mode 100644 index 00000000..5900cdb9 Binary files /dev/null and b/test/assets/images/test_image.png differ diff --git a/test/awesome_method_channel_test.dart b/test/awesome_method_channel_test.dart new file mode 100644 index 00000000..f9d4c36f --- /dev/null +++ b/test/awesome_method_channel_test.dart @@ -0,0 +1,811 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:awesome_notifications/awesome_notifications_method_channel.dart'; +import 'package:awesome_notifications/src/isolates/isolate_main.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'src/isolates/isolate_main_test.dart'; + +class MockMethodChannel extends MethodChannel { + MockMethodChannel(String name) : super(name); + + final Map _responses = {}; + + // Use this method to set up the response for the method call + void setMockMethodCallHandler(String methodName, dynamic response) { + _responses[methodName] = response; + } + + @override + Future invokeMethod(String method, [dynamic arguments]) async { + T? response; + if (_responses[method] is Function) { + dynamic handler = _responses[method]; + if (handler is T Function(T)) { + response = handler(arguments); + } else if (handler is Future Function(T)) { + response = await handler(arguments); + } else if (handler is T Function(Map)) { + response = handler(arguments); + } else if (handler is Future Function(Map)) { + response = await handler(arguments); + } else if (handler is Future Function()) { + response = await handler(); + } else if (handler is FutureOr Function()) { + response = await handler(); + } else if (handler is FutureOr Function(dynamic)) { + response = await handler(arguments); + } else { + throw Exception("Unsupported function signature for method $method"); + } + } else { + response = _responses[method]; + } + return Future.value(response); + } +} + +void main() { + group('MethodChannelAwesomeNotifications', () { + late MockMethodChannel mockMethodChannel; + late MethodChannelAwesomeNotifications awesomeNotifications; + + setUpAll(() { + mockMethodChannel = MockMethodChannel('awesome_notifications'); + awesomeNotifications = MethodChannelAwesomeNotifications() + ..methodChannel = mockMethodChannel; + }); + + test('validateId', () async { + expect(() => awesomeNotifications.validateId(0), returnsNormally); + expect(() => awesomeNotifications.validateId(1), returnsNormally); + expect(() => awesomeNotifications.validateId(-1), returnsNormally); + expect( + () => awesomeNotifications.validateId(0x7FFFFFFF), returnsNormally); + expect( + () => awesomeNotifications.validateId(-0x80000000), returnsNormally); + + expect(() => awesomeNotifications.validateId(-0x80000000 - 1), + throwsA(isA())); + + expect(() => awesomeNotifications.validateId(0x7FFFFFFF + 1), + throwsA(isA())); + }); + + test('cancel', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CANCEL_NOTIFICATION, null); + await awesomeNotifications.cancel(1); + }); + + test('cancelAll', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CANCEL_ALL_NOTIFICATIONS, null); + await awesomeNotifications.cancelAll(); + }); + + test('cancelAllSchedules', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CANCEL_ALL_SCHEDULES, null); + await awesomeNotifications.cancelAllSchedules(); + }); + + // Another example: + test('createNotification', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CREATE_NOTIFICATION, true); + + final bool result = await awesomeNotifications.createNotification( + content: NotificationContent( + id: 1, + channelKey: 'test_channel', + title: 'Test title', + body: 'Test body'), + ); + + expect(result, true); + }); + + test('cancelNotificationsByChannelKey', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CANCEL_NOTIFICATIONS_BY_CHANNEL_KEY, null); + await awesomeNotifications + .cancelNotificationsByChannelKey('test_channel'); + }); + + test('cancelNotificationsByGroupKey', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CANCEL_NOTIFICATIONS_BY_GROUP_KEY, null); + await awesomeNotifications.cancelNotificationsByGroupKey('test_group'); + }); + + test('cancelSchedule', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CANCEL_SCHEDULE, null); + await awesomeNotifications.cancelSchedule(1); + }); + + test('cancelSchedulesByChannelKey', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CANCEL_SCHEDULES_BY_CHANNEL_KEY, null); + await awesomeNotifications.cancelSchedulesByChannelKey('test_channel'); + }); + + test('cancelSchedulesByGroupKey', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CANCEL_SCHEDULES_BY_GROUP_KEY, null); + await awesomeNotifications.cancelSchedulesByGroupKey('test_group'); + }); + + test('checkPermissionList', () async { + List permissions = [ + NotificationPermission.Badge, + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Vibration, + NotificationPermission.Light + ]; + List permissionList = permissions.map((e) => e.name).toList(); + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CHECK_PERMISSIONS, permissionList); + + List result = + await awesomeNotifications.checkPermissionList( + channelKey: 'test_channel', + permissions: permissions, + ); + + expect(result, permissions); + }); + + test('createNotificationFromJsonData with valid data', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CREATE_NOTIFICATION, true); + + Map validMapData = { + NOTIFICATION_CONTENT: json.encode({ + 'id': 1, + 'channelKey': 'test_channel', + 'title': 'Test title', + 'body': 'Test body', + }), + }; + + bool result = await awesomeNotifications + .createNotificationFromJsonData(validMapData); + + expect(result, true); + }); + + test('createNotificationFromJsonData with invalid data', () async { + Map invalidMapData = { + NOTIFICATION_CONTENT: json.encode({ + 'id': 1, + // Missing 'channelKey' field + 'title': 'Test title', + 'body': 'Test body', + }), + }; + + bool result = await awesomeNotifications + .createNotificationFromJsonData(invalidMapData); + + expect(result, false); + }); + + test('createNotificationFromJsonData with different data types', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_CREATE_NOTIFICATION, true); + + Map mapDataWithDifferentTypes = { + NOTIFICATION_CONTENT: json.encode({ + 'id': 1, + 'channelKey': 'test_channel', + 'title': 'Test title', + 'body': 'Test body', + }), + NOTIFICATION_SCHEDULE: json.encode({ + 'interval': 10, + 'repeat': true, + 'timeZone': 'UTC', + 'scheduledDate': '2023-01-01T00:00:00.000Z', + }), + NOTIFICATION_BUTTONS: json.encode([ + { + 'key': 'action_key', + 'label': 'Action label', + }, + ]), + NOTIFICATION_LOCALIZATIONS: json.encode({ + 'en_US': { + 'title': 'Localized title', + 'body': 'Localized body', + }, + }), + }; + + bool result = await awesomeNotifications + .createNotificationFromJsonData(mapDataWithDifferentTypes); + + expect(result, true); + }); + + // Add these test cases inside the group('MethodChannelAwesomeNotifications', () { ... }) block + + test('decrementGlobalBadgeCounter', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_DECREMENT_BADGE_COUNT, 5); + + int badgeCount = await awesomeNotifications.decrementGlobalBadgeCounter(); + + expect(badgeCount, 5); + }); + + test('dismiss', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_DISMISS_NOTIFICATION, null); + + await awesomeNotifications.dismiss(1); + }); + + test('dismissAllNotifications', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_DISMISS_ALL_NOTIFICATIONS, null); + + await awesomeNotifications.dismissAllNotifications(); + }); + + test('dismissNotificationsByChannelKey', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_DISMISS_NOTIFICATIONS_BY_CHANNEL_KEY, null); + + await awesomeNotifications + .dismissNotificationsByChannelKey('test_channel'); + }); + + test('dismissNotificationsByGroupKey', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_DISMISS_NOTIFICATIONS_BY_GROUP_KEY, null); + + await awesomeNotifications.dismissNotificationsByGroupKey('test_group'); + }); + + // Add these test cases inside the group('MethodChannelAwesomeNotifications', () { ... }) block + + test('getAppLifeCycle', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_APP_LIFE_CYCLE, + NotificationLifeCycle.Terminated.name); + + NotificationLifeCycle lifeCycle = + await awesomeNotifications.getAppLifeCycle(); + + expect(lifeCycle, NotificationLifeCycle.Terminated); + }); + + test('getDrawableData', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_DRAWABLE_DATA, + Uint8List.fromList([1, 2, 3, 4, 5])); + + Uint8List? drawableData = + await awesomeNotifications.getDrawableData('test_drawable_path'); + + expect(drawableData, isNotNull); + expect(drawableData, isA()); + expect(drawableData, equals(Uint8List.fromList([1, 2, 3, 4, 5]))); + }); + + test('getInitialNotificationAction', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_INITIAL_ACTION, + ReceivedAction().fromMap( + {'id': 1, 'buttonKeyPressed': 'test_button_key'}).toMap()); + + ReceivedAction? initialAction = + await awesomeNotifications.getInitialNotificationAction(); + + expect(initialAction, isNotNull); + expect(initialAction, isA()); + expect(initialAction?.id, 1); + expect(initialAction?.buttonKeyPressed, 'test_button_key'); + }); + + test('getGlobalBadgeCounter', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_BADGE_COUNT, 7); + + int badgeCount = await awesomeNotifications.getGlobalBadgeCounter(); + + expect(badgeCount, 7); + }); + + // Add these test cases inside the group('MethodChannelAwesomeNotifications', () { ... }) block + + test('getLocalTimeZoneIdentifier', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_LOCAL_TIMEZONE_IDENTIFIER, 'America/New_York'); + + String localTimeZoneIdentifier = + await awesomeNotifications.getLocalTimeZoneIdentifier(); + + expect(localTimeZoneIdentifier, 'America/New_York'); + }); + + test('getNextDate', () async { + DateTime fixedDate = DateTime(2023, 5, 3); + DateTime expectedNextDate = fixedDate.add(const Duration(days: 1)); + + mockMethodChannel.setMockMethodCallHandler(CHANNEL_METHOD_GET_NEXT_DATE, + AwesomeDateUtils.parseDateToString(expectedNextDate)); + + DateTime? nextDate = await awesomeNotifications + .getNextDate(NotificationInterval(interval: 1), fixedDate: fixedDate); + + expect(nextDate, isNotNull); + expect(nextDate, expectedNextDate); + }); + + test('getUtcTimeZoneIdentifier', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_UTC_TIMEZONE_IDENTIFIER, 'UTC'); + + String utcTimeZoneIdentifier = + await awesomeNotifications.getUtcTimeZoneIdentifier(); + + expect(utcTimeZoneIdentifier, 'UTC'); + }); + + test('incrementGlobalBadgeCounter', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_INCREMENT_BADGE_COUNT, 9); + + int badgeCount = await awesomeNotifications.incrementGlobalBadgeCounter(); + + expect(badgeCount, 9); + }); + + test('initialize', () async { + // Set up the mocked method channel handlers + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_INITIALIZE, true); + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_LOCAL_TIMEZONE_IDENTIFIER, 'Local'); + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_UTC_TIMEZONE_IDENTIFIER, 'UTC'); + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_SET_LOCALIZATION, true); + + try { + await awesomeNotifications.initialize( + 'test_icon', + [ + NotificationChannel( + channelKey: 'test_channel', + channelName: 'Test Channel', + channelDescription: 'Test Channel Description', + defaultColor: const Color(0xFF9D50DD), + ledColor: Colors.yellow) + ], + channelGroups: [ + NotificationChannelGroup( + channelGroupKey: 'test_group', + channelGroupName: 'Test Group Name') + ], + languageCode: 'pt-br', + debug: true, + ); + fail("exception not thrown"); + } catch (e) { + expect(e, isA()); + } + + try { + bool result = await awesomeNotifications.initialize( + 'resource://test_icon', + [ + NotificationChannel( + channelKey: 'test_channel', + channelName: 'Test Channel', + channelDescription: 'Test Channel Description', + defaultColor: const Color(0xFF9D50DD), + ledColor: Colors.yellow) + ], + channelGroups: [ + NotificationChannelGroup( + channelGroupKey: 'test_group', + channelGroupName: 'Test Group Name') + ], + languageCode: 'pt-br', + debug: true, + ); + expect(result, true); + } catch (e) { + fail("exception thrown: $e"); + } + + // Assert that the local and UTC time zone identifiers have been set + expect(AwesomeNotifications.localTimeZoneIdentifier, 'Local'); + expect(AwesomeNotifications.utcTimeZoneIdentifier, 'UTC'); + }); + + test('isNotificationAllowed', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_IS_NOTIFICATION_ALLOWED, () async { + return true; + }); + + bool isAllowed = await awesomeNotifications.isNotificationAllowed(); + expect(isAllowed, true); + }); + + test('listScheduledNotifications', () async { + mockMethodChannel + .setMockMethodCallHandler(CHANNEL_METHOD_LIST_ALL_SCHEDULES, [ + { + NOTIFICATION_CONTENT: { + NOTIFICATION_CHANNEL_KEY: 'test_channel', + NOTIFICATION_ID: 1, + NOTIFICATION_TITLE: 'Test Title', + NOTIFICATION_BODY: 'Test Body', + }, + NOTIFICATION_SCHEDULE: { + NOTIFICATION_SCHEDULE_INTERVAL: 1, + } + } + ]); + + List scheduledNotifications = + await awesomeNotifications.listScheduledNotifications(); + expect(scheduledNotifications.length, 1); + expect(scheduledNotifications[0].content!.title, 'Test Title'); + }); + + test('removeChannel', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_REMOVE_NOTIFICATION_CHANNEL, true); + + bool wasRemoved = + await awesomeNotifications.removeChannel('test_channel'); + expect(wasRemoved, true); + }); + + test('requestPermissionToSendNotifications', () async { + mockMethodChannel + .setMockMethodCallHandler(CHANNEL_METHOD_REQUEST_NOTIFICATIONS, []); + + bool isAllowed = await awesomeNotifications + .requestPermissionToSendNotifications(permissions: [ + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Badge, + NotificationPermission.Vibration, + NotificationPermission.Light + ]); + expect(isAllowed, true); + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_REQUEST_NOTIFICATIONS, [NotificationPermission.Alert]); + + isAllowed = await awesomeNotifications + .requestPermissionToSendNotifications(permissions: [ + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Badge, + NotificationPermission.Vibration, + NotificationPermission.Light + ]); + expect(isAllowed, false); + + mockMethodChannel + .setMockMethodCallHandler(CHANNEL_METHOD_REQUEST_NOTIFICATIONS, [ + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Badge, + NotificationPermission.Vibration, + NotificationPermission.Light + ]); + + isAllowed = await awesomeNotifications + .requestPermissionToSendNotifications(permissions: [ + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Badge, + NotificationPermission.Vibration, + NotificationPermission.Light + ]); + expect(isAllowed, false); + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_REQUEST_NOTIFICATIONS, null); + + isAllowed = await awesomeNotifications + .requestPermissionToSendNotifications(permissions: [ + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Badge, + NotificationPermission.Vibration, + NotificationPermission.Light + ]); + expect(isAllowed, false); + }); + + test('resetGlobalBadge', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_RESET_BADGE, true); + await awesomeNotifications.resetGlobalBadge(); + }); + + test('setChannel', () async { + mockMethodChannel + .setMockMethodCallHandler(CHANNEL_METHOD_SET_NOTIFICATION_CHANNEL, + (Map parameters) async { + expect(parameters[NOTIFICATION_CHANNEL_KEY], 'test_channel'); + expect(parameters[NOTIFICATION_CHANNEL_NAME], 'Test Channel'); + expect(parameters[CHANNEL_FORCE_UPDATE], false); + }); + + await awesomeNotifications.setChannel(NotificationChannel( + channelKey: 'test_channel', + channelName: 'Test Channel', + channelDescription: 'Test Channel Description')); + }); + + test('setGlobalBadgeCounter', () async { + await awesomeNotifications.setGlobalBadgeCounter(10); + }); + + test('setListeners', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_SET_EVENT_HANDLES, true); + // (Map arguments) async { + // expect(arguments[CREATED_HANDLE], isNotNull); + // expect(arguments[DISPLAYED_HANDLE], isNotNull); + // expect(arguments[ACTION_HANDLE], isNotNull); + // expect(arguments[DISMISSED_HANDLE], isNotNull); + // setEventHandlesCalled = true; + // return true; + // }); + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_SET_EVENT_HANDLES, false); + + bool result = await awesomeNotifications.setListeners( + onActionReceivedMethod: onGlobalActionReceivedMethod, + onNotificationCreatedMethod: onGlobalNotificationCreatedMethod, + onNotificationDisplayedMethod: onGlobalNotificationDisplayedMethod, + onDismissActionReceivedMethod: onGlobalDismissActionReceivedMethod, + ); + + expect(result, false); + + Future onActionReceivedMethod( + ReceivedAction receivedAction) async {} + Future onNotificationCreatedMethod( + ReceivedNotification receivedNotification) async {} + Future onNotificationDisplayedMethod( + ReceivedNotification receivedNotification) async {} + Future onDismissActionReceivedMethod( + ReceivedAction receivedAction) async {} + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_SET_EVENT_HANDLES, false); + + result = await awesomeNotifications.setListeners( + onActionReceivedMethod: onActionReceivedMethod, + onNotificationCreatedMethod: onNotificationCreatedMethod, + onNotificationDisplayedMethod: onNotificationDisplayedMethod, + onDismissActionReceivedMethod: onDismissActionReceivedMethod, + ); + + expect(result, false); + }); + + test('shouldShowRationaleToRequest', () async { + mockMethodChannel + .setMockMethodCallHandler(CHANNEL_METHOD_SHOULD_SHOW_RATIONALE, + (Map arguments) { + expect(arguments[NOTIFICATION_CHANNEL_KEY], null); + expect(arguments[NOTIFICATION_PERMISSIONS], isNotNull); + return []; + }); + + List permissions = + await awesomeNotifications.shouldShowRationaleToRequest(); + expect(permissions, isEmpty); + }); + + test('showAlarmPage', () async { + bool showAlarmPageCalled = false; + mockMethodChannel.setMockMethodCallHandler(CHANNEL_METHOD_SHOW_ALARM_PAGE, + () async { + showAlarmPageCalled = true; + }); + + await awesomeNotifications.showAlarmPage(); + expect(showAlarmPageCalled, true); + }); + + test('showGlobalDndOverridePage', () async { + bool showGlobalDndOverridePageCalled = false; + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_SHOW_GLOBAL_DND_PAGE, () async { + showGlobalDndOverridePageCalled = true; + }); + + await awesomeNotifications.showGlobalDndOverridePage(); + expect(showGlobalDndOverridePageCalled, true); + }); + + test('showNotificationConfigPage', () async { + bool showNotificationConfigPageCalled = false; + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_SHOW_NOTIFICATION_PAGE, (dynamic arguments) async { + expect(arguments, null); + showNotificationConfigPageCalled = true; + }); + + await awesomeNotifications.showNotificationConfigPage(); + expect(showNotificationConfigPageCalled, true); + }); + + test('getLocalization', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_LOCALIZATION, 'en_US'); + + String localization = await awesomeNotifications.getLocalization(); + expect(localization, 'en_US'); + }); + + test('setLocalization', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_SET_LOCALIZATION, (dynamic arguments) async { + expect(arguments, 'en_US'); + return true; + }); + + bool success = + await awesomeNotifications.setLocalization(languageCode: 'en_US'); + expect(success, true); + }); + + test('isNotificationActiveOnStatusBar', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_IS_NOTIFICATION_ACTIVE, (dynamic arguments) async { + expect(arguments, 1); + return true; + }); + + bool isActive = + await awesomeNotifications.isNotificationActiveOnStatusBar(id: 1); + expect(isActive, true); + }); + + test('getAllActiveNotificationIdsOnStatusBar', () async { + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_ALL_ACTIVE_NOTIFICATION_IDS, () async { + return [1, 2, 3]; + }); + + List? ids = + await awesomeNotifications.getAllActiveNotificationIdsOnStatusBar(); + expect(ids, [1, 2, 3]); + + mockMethodChannel.setMockMethodCallHandler( + CHANNEL_METHOD_GET_ALL_ACTIVE_NOTIFICATION_IDS, () async { + return null; + }); + + ids = await awesomeNotifications.getAllActiveNotificationIdsOnStatusBar(); + expect(ids, []); + }); + + test('_handleMethod: EVENT_NOTIFICATION_CREATED', () async { + Map notificationData = {}; // Fill with actual data + + bool createdHandlerCalled = false; + awesomeNotifications.createdHandler = + (ReceivedNotification receivedNotification) async { + createdHandlerCalled = true; + }; + + await awesomeNotifications.handleMethod( + MethodCall(EVENT_NOTIFICATION_CREATED, notificationData)); + + expect(createdHandlerCalled, true); + }); + + test('_handleMethod: EVENT_NOTIFICATION_DISPLAYED', () async { + Map notificationData = {}; // Fill with actual data + + bool displayedHandlerCalled = false; + awesomeNotifications.displayedHandler = + (ReceivedNotification receivedNotification) async { + displayedHandlerCalled = true; + }; + + await awesomeNotifications.handleMethod( + MethodCall(EVENT_NOTIFICATION_DISPLAYED, notificationData)); + + expect(displayedHandlerCalled, true); + }); + + test('_handleMethod: EVENT_NOTIFICATION_DISMISSED', () async { + Map actionData = {}; // Fill with actual data + + bool dismissedHandlerCalled = false; + awesomeNotifications.dismissedHandler = + (ReceivedAction receivedAction) async { + dismissedHandlerCalled = true; + }; + + await awesomeNotifications + .handleMethod(MethodCall(EVENT_NOTIFICATION_DISMISSED, actionData)); + + expect(dismissedHandlerCalled, true); + }); + + test('_handleMethod: EVENT_DEFAULT_ACTION', () async { + Map actionData = {}; // Fill with actual data + + bool actionHandlerCalled = false; + awesomeNotifications.actionHandler = + (ReceivedAction receivedAction) async { + actionHandlerCalled = true; + }; + + await awesomeNotifications + .handleMethod(MethodCall(EVENT_DEFAULT_ACTION, actionData)); + + expect(actionHandlerCalled, true); + }); + + test('_handleMethod: EVENT_SILENT_ACTION', () async { + Map actionData = ReceivedAction().toMap(); + + MockIsolateController isolateController = MockIsolateController(); + IsolateController.singleton = isolateController; + + when(() => isolateController.receiveSilentAction(any())) + .thenAnswer((invocation) => Future.value(true)); + + await awesomeNotifications + .handleMethod(MethodCall(EVENT_SILENT_ACTION, actionData)); + + verify(() => isolateController.receiveSilentAction(any())).called(1); + + actionData['actionType'] = ActionType.SilentBackgroundAction.name; + await awesomeNotifications + .handleMethod(MethodCall(EVENT_SILENT_ACTION, actionData)); + }); + + test('_handleMethod: UnsupportedError', () { + expect( + () async => await awesomeNotifications + .handleMethod(const MethodCall('unsupported_method', {})), + throwsA(isA())); + }); + + test('dispose', () async { + await awesomeNotifications.dispose(); + }); + }); +} + +Future onGlobalActionReceivedMethod( + ReceivedAction receivedAction) async {} +Future onGlobalNotificationCreatedMethod( + ReceivedNotification receivedNotification) async {} +Future onGlobalNotificationDisplayedMethod( + ReceivedNotification receivedNotification) async {} +Future onGlobalDismissActionReceivedMethod( + ReceivedAction receivedAction) async {} diff --git a/test/awesome_notifications_empty_test.dart b/test/awesome_notifications_empty_test.dart new file mode 100644 index 00000000..b4507948 --- /dev/null +++ b/test/awesome_notifications_empty_test.dart @@ -0,0 +1,237 @@ +import 'dart:typed_data'; + +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:awesome_notifications/awesome_notifications_empty.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('AwesomeNotificationsEmpty', () { + late AwesomeNotificationsEmpty notifications; + + setUp(() { + notifications = AwesomeNotificationsEmpty(); + }); + + test('cancel method', () async { + await notifications.cancel(1); + }); + + test('cancelAll method', () async { + await notifications.cancelAll(); + }); + + test('cancelAllSchedules method', () async { + await notifications.cancelAllSchedules(); + }); + + test('cancelNotificationsByChannelKey method', () async { + await notifications.cancelNotificationsByChannelKey('channelKey'); + }); + + test('cancelNotificationsByGroupKey method', () async { + await notifications.cancelNotificationsByGroupKey('groupKey'); + }); + + test('cancelSchedule method', () async { + await notifications.cancelSchedule(1); + }); + + test('cancelSchedulesByChannelKey method', () async { + await notifications.cancelSchedulesByChannelKey('channelKey'); + }); + + test('cancelSchedulesByGroupKey method', () async { + await notifications.cancelSchedulesByGroupKey('groupKey'); + }); + + test('checkPermissionList method', () async { + List result = + await notifications.checkPermissionList(); + expect(result, [ + NotificationPermission.Badge, + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Vibration, + NotificationPermission.Light + ]); + }); + + test('createNotification method', () async { + bool result = await notifications.createNotification( + content: NotificationContent(id: 1, channelKey: 'test'), + ); + expect(result, false); + }); + + test('createNotificationFromJsonData method', () async { + bool result = await notifications.createNotificationFromJsonData({ + 'id': 1, + 'channelKey': 'test', + }); + expect(result, false); + }); + + test('decrementGlobalBadgeCounter method', () async { + int result = await notifications.decrementGlobalBadgeCounter(); + expect(result, 0); + }); + + test('dismiss method', () async { + await notifications.dismiss(1); + }); + + test('dismissAllNotifications method', () async { + await notifications.dismissAllNotifications(); + }); + + test('dismissNotificationsByChannelKey method', () async { + await notifications.dismissNotificationsByChannelKey('channelKey'); + }); + + test('dismissNotificationsByGroupKey method', () async { + await notifications.dismissNotificationsByGroupKey('groupKey'); + }); + + test('getAppLifeCycle method', () async { + NotificationLifeCycle result = await notifications.getAppLifeCycle(); + expect(result, NotificationLifeCycle.Terminated); + }); + + test('getDrawableData method', () async { + Uint8List? result = await notifications.getDrawableData('drawablePath'); + expect(result, null); + }); + + test('getInitialNotificationAction method', () async { + ReceivedAction? result = + await notifications.getInitialNotificationAction(); + expect(result, null); + }); + + test('getGlobalBadgeCounter method', () async { + int result = await notifications.getGlobalBadgeCounter(); + expect(result, 0); + }); + + test('getLocalTimeZoneIdentifier method', () async { + String result = await notifications.getLocalTimeZoneIdentifier(); + expect(result, AwesomeNotifications.localTimeZoneIdentifier); + }); + + test('getNextDate method', () async { + DateTime? result = await notifications.getNextDate( + NotificationInterval(interval: 10), + ); + expect(result, null); + }); + + test('getUtcTimeZoneIdentifier method', () async { + String result = await notifications.getUtcTimeZoneIdentifier(); + expect(result, AwesomeNotifications.utcTimeZoneIdentifier); + }); + + test('incrementGlobalBadgeCounter method', () async { + int result = await notifications.incrementGlobalBadgeCounter(); + expect(result, 0); + }); + + test('initialize method', () async { + bool result = await notifications.initialize( + 'defaultIcon', + [], + ); + expect(result, true); + }); + + test('isNotificationAllowed method', () async { + bool result = await notifications.isNotificationAllowed(); + expect(result, false); + }); + + test('listScheduledNotifications method', () async { + List result = + await notifications.listScheduledNotifications(); + expect(result, []); + }); + + test('removeChannel method', () async { + bool result = await notifications.removeChannel('channelKey'); + expect(result, false); + }); + + test('requestPermissionToSendNotifications method', () async { + bool result = await notifications.requestPermissionToSendNotifications(); + expect(result, false); + }); + + test('resetGlobalBadge method', () async { + await notifications.resetGlobalBadge(); + }); + + test('setChannel method', () async { + await notifications.setChannel( + NotificationChannel( + channelKey: 'test', + channelName: 'Test Channel', + channelDescription: 'Test Channel Description', + ), + ); + }); + + test('setGlobalBadgeCounter method', () async { + await notifications.setGlobalBadgeCounter(0); + }); + + test('setListeners method', () async { + bool result = await notifications.setListeners( + onActionReceivedMethod: (ReceivedAction action) async {}, + ); + expect(result, true); + }); + + test('shouldShowRationaleToRequest method', () async { + List result = + await notifications.shouldShowRationaleToRequest(); + expect(result, []); + }); + + test('showAlarmPage method', () async { + await notifications.showAlarmPage(); + }); + + test('showGlobalDndOverridePage method', () async { + await notifications.showGlobalDndOverridePage(); + }); + + test('showNotificationConfigPage method', () async { + await notifications.showNotificationConfigPage(); + }); + + test('getLocalization method', () async { + String result = await notifications.getLocalization(); + expect(result, ''); + }); + + test('setLocalization method', () async { + bool result = await notifications.setLocalization( + languageCode: 'en', + ); + expect(result, false); + }); + + test('isNotificationActiveOnStatusBar method', () async { + bool result = await notifications.isNotificationActiveOnStatusBar(id: 1); + expect(result, false); + }); + + test('getAllActiveNotificationIdsOnStatusBar method', () async { + List result = + await notifications.getAllActiveNotificationIdsOnStatusBar(); + expect(result, []); + }); + + test('dispose method', () { + notifications.dispose(); + }); + }); +} diff --git a/test/awesome_notifications_method_channel_test.dart b/test/awesome_notifications_method_channel_test.dart deleted file mode 100644 index 3622ff1e..00000000 --- a/test/awesome_notifications_method_channel_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:awesome_notifications/awesome_notifications_method_channel.dart'; - -void main() { - MethodChannelAwesomeNotifications platform = MethodChannelAwesomeNotifications(); - const MethodChannel channel = MethodChannel('awesome_notifications'); - - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; - }); - }); - - tearDown(() { - channel.setMockMethodCallHandler(null); - }); - - test('getPlatformVersion', () async { - //expect(await platform.getPlatformVersion(), '42'); - }); -} diff --git a/test/awesome_notifications_test.dart b/test/awesome_notifications_test.dart index 307bfbf7..43b47b1c 100644 --- a/test/awesome_notifications_test.dart +++ b/test/awesome_notifications_test.dart @@ -1,263 +1,381 @@ import 'dart:typed_data'; -import 'package:flutter_test/flutter_test.dart'; import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:awesome_notifications/awesome_notifications_platform_interface.dart'; -import 'package:awesome_notifications/awesome_notifications_method_channel.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -class MockAwesomeNotificationsPlatform - with MockPlatformInterfaceMixin - implements AwesomeNotificationsPlatform { - @override - Future cancel(int id) { - // TODO: implement cancel - throw UnimplementedError(); - } - - @override - Future cancelAll() { - // TODO: implement cancelAll - throw UnimplementedError(); - } - - @override - Future cancelAllSchedules() { - // TODO: implement cancelAllSchedules - throw UnimplementedError(); - } - - @override - Future cancelNotificationsByChannelKey(String channelKey) { - // TODO: implement cancelNotificationsByChannelKey - throw UnimplementedError(); - } - - @override - Future cancelNotificationsByGroupKey(String groupKey) { - // TODO: implement cancelNotificationsByGroupKey - throw UnimplementedError(); - } - - @override - Future cancelSchedule(int id) { - // TODO: implement cancelSchedule - throw UnimplementedError(); - } - - @override - Future cancelSchedulesByChannelKey(String channelKey) { - // TODO: implement cancelSchedulesByChannelKey - throw UnimplementedError(); - } - - @override - Future cancelSchedulesByGroupKey(String groupKey) { - // TODO: implement cancelSchedulesByGroupKey - throw UnimplementedError(); - } - - @override - Future> checkPermissionList({String? channelKey, List permissions = const [NotificationPermission.Badge, NotificationPermission.Alert, NotificationPermission.Sound, NotificationPermission.Vibration, NotificationPermission.Light]}) { - // TODO: implement checkPermissionList - throw UnimplementedError(); - } - - @override - Future createNotification({required NotificationContent content, NotificationSchedule? schedule, List? actionButtons}) { - // TODO: implement createNotification - throw UnimplementedError(); - } - - @override - Future createNotificationFromJsonData(Map mapData) { - // TODO: implement createNotificationFromJsonData - throw UnimplementedError(); - } - - @override - Future decrementGlobalBadgeCounter() { - // TODO: implement decrementGlobalBadgeCounter - throw UnimplementedError(); - } - - @override - Future dismiss(int id) { - // TODO: implement dismiss - throw UnimplementedError(); - } - - @override - Future dismissAllNotifications() { - // TODO: implement dismissAllNotifications - throw UnimplementedError(); - } - - @override - Future dismissNotificationsByChannelKey(String channelKey) { - // TODO: implement dismissNotificationsByChannelKey - throw UnimplementedError(); - } - - @override - Future dismissNotificationsByGroupKey(String groupKey) { - // TODO: implement dismissNotificationsByGroupKey - throw UnimplementedError(); - } - - @override - dispose() { - // TODO: implement dispose - throw UnimplementedError(); - } - - @override - Future getAppLifeCycle() { - // TODO: implement getAppLifeCycle - throw UnimplementedError(); - } - - @override - Future getDrawableData(String drawablePath) { - // TODO: implement getDrawableData - throw UnimplementedError(); - } - - @override - Future getGlobalBadgeCounter() { - // TODO: implement getGlobalBadgeCounter - throw UnimplementedError(); - } - - @override - Future getLocalTimeZoneIdentifier() { - // TODO: implement getLocalTimeZoneIdentifier - throw UnimplementedError(); - } - - @override - Future getNextDate(NotificationSchedule schedule, {DateTime? fixedDate}) { - // TODO: implement getNextDate - throw UnimplementedError(); - } - - @override - Future getPlatformVersion() { - // TODO: implement getPlatformVersion - throw UnimplementedError(); - } - - @override - Future getUtcTimeZoneIdentifier() { - // TODO: implement getUtcTimeZoneIdentifier - throw UnimplementedError(); - } - - @override - Future incrementGlobalBadgeCounter() { - // TODO: implement incrementGlobalBadgeCounter - throw UnimplementedError(); - } - - @override - Future initialize(String? defaultIcon, List channels, {List? channelGroups, bool debug = false}) { - // TODO: implement initialize - throw UnimplementedError(); - } - - @override - Future isNotificationAllowed() { - // TODO: implement isNotificationAllowed - throw UnimplementedError(); - } - - @override - Future> listScheduledNotifications() { - // TODO: implement listScheduledNotifications - throw UnimplementedError(); - } - - @override - Future removeChannel(String channelKey) { - // TODO: implement removeChannel - throw UnimplementedError(); - } - - @override - Future requestPermissionToSendNotifications({String? channelKey, List permissions = const [NotificationPermission.Alert, NotificationPermission.Sound, NotificationPermission.Badge, NotificationPermission.Vibration, NotificationPermission.Light]}) { - // TODO: implement requestPermissionToSendNotifications - throw UnimplementedError(); - } - - @override - Future resetGlobalBadge() { - // TODO: implement resetGlobalBadge - throw UnimplementedError(); - } - - @override - Future setChannel(NotificationChannel notificationChannel, {bool forceUpdate = false}) { - // TODO: implement setChannel - throw UnimplementedError(); - } - - @override - Future setGlobalBadgeCounter(int? amount) { - // TODO: implement setGlobalBadgeCounter - throw UnimplementedError(); - } - - @override - Future setListeners({required ActionHandler onActionReceivedMethod, NotificationHandler? onNotificationCreatedMethod, NotificationHandler? onNotificationDisplayedMethod, ActionHandler? onDismissActionReceivedMethod}) { - // TODO: implement setListeners - throw UnimplementedError(); - } - - @override - Future> shouldShowRationaleToRequest({String? channelKey, List permissions = const [NotificationPermission.Badge, NotificationPermission.Alert, NotificationPermission.Sound, NotificationPermission.Vibration, NotificationPermission.Light]}) { - // TODO: implement shouldShowRationaleToRequest - throw UnimplementedError(); - } - - @override - Future showAlarmPage() { - // TODO: implement showAlarmPage - throw UnimplementedError(); - } - - @override - Future showGlobalDndOverridePage() { - // TODO: implement showGlobalDndOverridePage - throw UnimplementedError(); - } - - @override - Future showNotificationConfigPage({String? channelKey}) { - // TODO: implement showNotificationConfigPage - throw UnimplementedError(); - } - - @override - Future getInitialNotificationAction({ - bool removeFromActionEvents = false - }) { - // TODO: implement getInitialNotificationAction - throw UnimplementedError(); - } -} +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; -void main() { - final AwesomeNotificationsPlatform initialPlatform = AwesomeNotificationsPlatform.instance; +// Mock the AwesomeNotifications class +class MockAwesomeNotificationsPlatform extends AwesomeNotificationsPlatform + with Mock {} - test('$MethodChannelAwesomeNotifications is the default instance', () { - expect(initialPlatform, isInstanceOf()); +void main() { + group('AwesomeNotifications', () { + late MockAwesomeNotificationsPlatform mockNotifications; + + setUp(() { + mockNotifications = MockAwesomeNotificationsPlatform(); + AwesomeNotificationsPlatform.instance = mockNotifications; + }); + + tearDown(() { + reset(mockNotifications); + }); + + test('cancel method is called once', () async { + when(() => mockNotifications.cancel(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().cancel(1); + verify(() => mockNotifications.cancel(1)).called(1); + }); + + test('cancelAll method is called once', () async { + when(() => mockNotifications.cancelAll()) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().cancelAll(); + verify(() => mockNotifications.cancelAll()).called(1); + }); + + test('cancelAllSchedules method is called once', () async { + when(() => mockNotifications.cancelAllSchedules()) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().cancelAllSchedules(); + verify(() => mockNotifications.cancelAllSchedules()).called(1); + }); + + test('cancelNotificationsByChannelKey method is called once', () async { + when(() => mockNotifications.cancelNotificationsByChannelKey(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications() + .cancelNotificationsByChannelKey('channel_key'); + verify(() => + mockNotifications.cancelNotificationsByChannelKey('channel_key')) + .called(1); + }); + + test('cancelNotificationsByGroupKey method is called once', () async { + when(() => mockNotifications.cancelNotificationsByGroupKey(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().cancelNotificationsByGroupKey('group_key'); + verify(() => mockNotifications.cancelNotificationsByGroupKey('group_key')) + .called(1); + }); + + test('cancelSchedule method is called once', () async { + when(() => mockNotifications.cancelSchedule(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().cancelSchedule(1); + verify(() => mockNotifications.cancelSchedule(1)).called(1); + }); + + test('cancelSchedulesByChannelKey method is called once', () async { + when(() => mockNotifications.cancelSchedulesByChannelKey(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().cancelSchedulesByChannelKey('channel_key'); + verify(() => mockNotifications.cancelSchedulesByChannelKey('channel_key')) + .called(1); + }); + + test('cancelSchedulesByGroupKey method is called once', () async { + when(() => mockNotifications.cancelSchedulesByGroupKey(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().cancelSchedulesByGroupKey('group_key'); + verify(() => mockNotifications.cancelSchedulesByGroupKey('group_key')) + .called(1); + }); + + test('checkPermissionList method is called once', () async { + when(() => mockNotifications.checkPermissionList()) + .thenAnswer((_) async => []); + await AwesomeNotifications().checkPermissionList(); + verify(() => mockNotifications.checkPermissionList()).called(1); + }); + + test('createNotification method is called once', () async { + NotificationContent content = NotificationContent( + id: 1, channelKey: 'channel_key', title: 'title', body: 'body'); + when(() => mockNotifications.createNotification(content: content)) + .thenAnswer((_) async => true); + + await AwesomeNotifications().createNotification(content: content); + verify(() => mockNotifications.createNotification(content: content)) + .called(1); + }); + + test('createNotificationFromJsonData method is called once', () async { + Map jsonData = { + 'id': 1, + 'channelKey': 'channel_key', + 'title': 'title', + 'body': 'body' + }; + when(() => mockNotifications.createNotificationFromJsonData(jsonData)) + .thenAnswer((_) async => true); + + await AwesomeNotifications().createNotificationFromJsonData(jsonData); + verify(() => mockNotifications.createNotificationFromJsonData(jsonData)) + .called(1); + }); + + test('decrementGlobalBadgeCounter method is called once', () async { + when(() => mockNotifications.decrementGlobalBadgeCounter()) + .thenAnswer((_) async => 1); + await AwesomeNotifications().decrementGlobalBadgeCounter(); + verify(() => mockNotifications.decrementGlobalBadgeCounter()).called(1); + }); + + test('dismiss method is called once', () async { + when(() => mockNotifications.dismiss(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().dismiss(1); + verify(() => mockNotifications.dismiss(1)).called(1); + }); + + test('dismissAllNotifications method is called once', () async { + when(() => mockNotifications.dismissAllNotifications()) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().dismissAllNotifications(); + verify(() => mockNotifications.dismissAllNotifications()).called(1); + }); + + test('dismissNotificationsByChannelKey method is called once', () async { + when(() => mockNotifications.dismissNotificationsByChannelKey(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications() + .dismissNotificationsByChannelKey('channel_key'); + verify(() => + mockNotifications.dismissNotificationsByChannelKey('channel_key')) + .called(1); + }); + + test('dismissNotificationsByGroupKey method is called once', () async { + when(() => mockNotifications.dismissNotificationsByGroupKey(any())) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().dismissNotificationsByGroupKey('group_key'); + verify(() => + mockNotifications.dismissNotificationsByGroupKey('group_key')) + .called(1); + }); + + test('dispose method is called once', () async { + when(() => mockNotifications.dispose()).thenReturn(true); + await AwesomeNotifications().dispose(); + verify(() => mockNotifications.dispose()).called(1); + }); + + test('getAppLifeCycle method is called once', () async { + when(() => mockNotifications.getAppLifeCycle()) + .thenAnswer((_) async => NotificationLifeCycle.Foreground); + await AwesomeNotifications().getAppLifeCycle(); + verify(() => mockNotifications.getAppLifeCycle()).called(1); + }); + + test('getDrawableData method is called once', () async { + when(() => mockNotifications.getDrawableData('drawable_path')) + .thenAnswer((_) async => null); + await AwesomeNotifications().getDrawableData('drawable_path'); + verify(() => mockNotifications.getDrawableData('drawable_path')) + .called(1); + }); + + test('getInitialNotificationAction method is called once', () async { + when(() => mockNotifications.getInitialNotificationAction()) + .thenAnswer((_) async => null); + await AwesomeNotifications().getInitialNotificationAction(); + verify(() => mockNotifications.getInitialNotificationAction()).called(1); + }); + + test('getGlobalBadgeCounter method is called once', () async { + when(() => mockNotifications.getGlobalBadgeCounter()) + .thenAnswer((_) async => 0); + await AwesomeNotifications().getGlobalBadgeCounter(); + verify(() => mockNotifications.getGlobalBadgeCounter()).called(1); + }); + + test('getLocalTimeZoneIdentifier method is called once', () async { + when(() => mockNotifications.getLocalTimeZoneIdentifier()) + .thenAnswer((_) async => 'UTC'); + await AwesomeNotifications().getLocalTimeZoneIdentifier(); + verify(() => mockNotifications.getLocalTimeZoneIdentifier()).called(1); + }); + + test('getNextDate method is called once', () async { + NotificationSchedule schedule = NotificationInterval(interval: 60); + when(() => mockNotifications.getNextDate(schedule)) + .thenAnswer((_) async => DateTime.now()); + await AwesomeNotifications().getNextDate(schedule); + verify(() => mockNotifications.getNextDate(schedule)).called(1); + }); + + test('getUtcTimeZoneIdentifier method is called once', () async { + when(() => mockNotifications.getUtcTimeZoneIdentifier()) + .thenAnswer((_) async => 'UTC'); + await AwesomeNotifications().getUtcTimeZoneIdentifier(); + verify(() => mockNotifications.getUtcTimeZoneIdentifier()).called(1); + }); + + test('incrementGlobalBadgeCounter method is called once', () async { + when(() => mockNotifications.incrementGlobalBadgeCounter()) + .thenAnswer((_) async => 1); + await AwesomeNotifications().incrementGlobalBadgeCounter(); + verify(() => mockNotifications.incrementGlobalBadgeCounter()).called(1); + }); + + test('initialize method is called once', () async { + List channels = [ + NotificationChannel( + channelKey: 'channel_key', + channelName: 'channel_name', + channelDescription: 'channel_description', + ), + ]; + when(() => mockNotifications.initialize(null, channels)) + .thenAnswer((_) async => true); + await AwesomeNotifications().initialize(null, channels); + verify(() => mockNotifications.initialize(null, channels)).called(1); + }); + + test('isNotificationAllowed method is called once', () async { + when(() => mockNotifications.isNotificationAllowed()) + .thenAnswer((_) async => true); + await AwesomeNotifications().isNotificationAllowed(); + verify(() => mockNotifications.isNotificationAllowed()).called(1); + }); + + test('listScheduledNotifications method is called once', () async { + when(() => mockNotifications.listScheduledNotifications()) + .thenAnswer((_) async => []); + await AwesomeNotifications().listScheduledNotifications(); + verify(() => mockNotifications.listScheduledNotifications()).called(1); + }); + + test('removeChannel method is called once', () async { + when(() => mockNotifications.removeChannel(any())) + .thenAnswer((_) async => true); + await AwesomeNotifications().removeChannel('channel_key'); + verify(() => mockNotifications.removeChannel('channel_key')).called(1); + }); + + test('requestPermissionToSendNotifications method is called once', + () async { + when(() => mockNotifications.requestPermissionToSendNotifications()) + .thenAnswer((_) async => true); + await AwesomeNotifications().requestPermissionToSendNotifications(); + verify(() => mockNotifications.requestPermissionToSendNotifications()) + .called(1); + }); + + test('resetGlobalBadge method is called once', () async { + when(() => mockNotifications.resetGlobalBadge()) + .thenAnswer((_) async => true); + await AwesomeNotifications().resetGlobalBadge(); + verify(() => mockNotifications.resetGlobalBadge()).called(1); + }); + + test('setChannel method is called once', () async { + NotificationChannel notificationChannel = NotificationChannel( + channelKey: 'channel_key', + channelName: 'channel_name', + channelDescription: 'channel_description', + ); + when(() => mockNotifications.setChannel(notificationChannel)) + .thenAnswer((_) async => true); + await AwesomeNotifications().setChannel(notificationChannel); + verify(() => mockNotifications.setChannel(notificationChannel)).called(1); + }); + + test('setGlobalBadgeCounter method is called once', () async { + when(() => mockNotifications.setGlobalBadgeCounter(any())) + .thenAnswer((_) async => true); + await AwesomeNotifications().setGlobalBadgeCounter(1); + verify(() => mockNotifications.setGlobalBadgeCounter(1)).called(1); + }); + + test('setListeners method is called once', () async { + actionMethod(_) async {} + + when(() => mockNotifications.setListeners( + onActionReceivedMethod: actionMethod)).thenAnswer((_) async => true); + + await AwesomeNotifications() + .setListeners(onActionReceivedMethod: actionMethod); + + verify(() => mockNotifications.setListeners( + onActionReceivedMethod: actionMethod)).called(1); + }); + + test('shouldShowRationaleToRequest method is called once', () async { + when(() => mockNotifications.shouldShowRationaleToRequest()) + .thenAnswer((_) async => []); + await AwesomeNotifications().shouldShowRationaleToRequest(); + verify(() => mockNotifications.shouldShowRationaleToRequest()).called(1); + }); + + test('showAlarmPage method is called once', () async { + when(() => mockNotifications.showAlarmPage()) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().showAlarmPage(); + verify(() => mockNotifications.showAlarmPage()).called(1); + }); + + test('showGlobalDndOverridePage method is called once', () async { + when(() => mockNotifications.showGlobalDndOverridePage()) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().showGlobalDndOverridePage(); + verify(() => mockNotifications.showGlobalDndOverridePage()).called(1); + }); + + test('showNotificationConfigPage method is called once', () async { + when(() => mockNotifications.showNotificationConfigPage()) + .thenAnswer((_) => Future.value()); + await AwesomeNotifications().showNotificationConfigPage(); + verify(() => mockNotifications.showNotificationConfigPage()).called(1); + }); + + test('getLocalization method is called once', () async { + when(() => mockNotifications.getLocalization()) + .thenAnswer((_) async => 'en_US'); + await AwesomeNotifications().getLocalization(); + verify(() => mockNotifications.getLocalization()).called(1); + }); + + test('setLocalization method is called once', () async { + when(() => mockNotifications.setLocalization(languageCode: 'en_US')) + .thenAnswer((_) async => true); + await AwesomeNotifications().setLocalization(languageCode: 'en_US'); + verify(() => mockNotifications.setLocalization(languageCode: 'en_US')) + .called(1); + }); + + test('isNotificationActiveOnStatusBar method is called once', () async { + when(() => mockNotifications.isNotificationActiveOnStatusBar(id: 1)) + .thenAnswer((_) async => true); + await AwesomeNotifications().isNotificationActiveOnStatusBar(id: 1); + verify(() => mockNotifications.isNotificationActiveOnStatusBar(id: 1)) + .called(1); + }); + + test('getAllActiveNotificationIdsOnStatusBar method is called once', + () async { + when(() => mockNotifications.getAllActiveNotificationIdsOnStatusBar()) + .thenAnswer((_) async => []); + await AwesomeNotifications().getAllActiveNotificationIdsOnStatusBar(); + verify(() => mockNotifications.getAllActiveNotificationIdsOnStatusBar()) + .called(1); + }); }); - test('getPlatformVersion', () async { - AwesomeNotifications awesomeNotificationsPlugin = AwesomeNotifications(); - MockAwesomeNotificationsPlatform fakePlatform = MockAwesomeNotificationsPlatform(); - AwesomeNotificationsPlatform.instance = fakePlatform; - - // expect(await awesomeNotificationsPlugin.getPlatformVersion(), '42'); + group('default values', () { + test('limits', () async { + expect(AwesomeNotifications.maxID, 2147483647); + }); + + test('vibration standards', () async { + expect(lowVibrationPattern, Int64List.fromList([0, 200, 200, 200])); + expect(mediumVibrationPattern, + Int64List.fromList([0, 500, 200, 200, 200, 200])); + expect(highVibrationPattern, + Int64List.fromList([0, 1000, 200, 200, 200, 200, 200, 200])); + }); }); } diff --git a/test/awesome_platform_interface_test.dart b/test/awesome_platform_interface_test.dart new file mode 100644 index 00000000..a01606a8 --- /dev/null +++ b/test/awesome_platform_interface_test.dart @@ -0,0 +1,73 @@ +import 'package:awesome_notifications/awesome_notifications_empty.dart'; +import 'package:awesome_notifications/awesome_notifications_method_channel.dart'; +import 'package:awesome_notifications/awesome_notifications_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAwesomeNotificationsIOS extends AwesomeNotificationsPlatform + with Mock {} + +class MockAwesomeNotificationsAndroid extends AwesomeNotificationsPlatform + with Mock {} + +class MockAwesomeNotificationsEmpty extends AwesomeNotificationsPlatform + with Mock {} + +void main() { + group('AwesomeNotificationsPlatform', () { + test('sets instance for iOS', () { + AwesomeNotificationsPlatform.operatingSystem = 'ios'; + AwesomeNotificationsPlatform.resetInstance(); + + // Test if the instance is set correctly + expect(AwesomeNotificationsPlatform.instance, + isA()); + }); + + test('sets instance for Android', () { + AwesomeNotificationsPlatform.operatingSystem = 'android'; + AwesomeNotificationsPlatform.resetInstance(); + + // Test if the instance is set correctly + expect(AwesomeNotificationsPlatform.instance, + isA()); + }); + + test('sets instance for other platforms', () { + AwesomeNotificationsPlatform.operatingSystem = 'web'; + AwesomeNotificationsPlatform.resetInstance(); + + // Test if the instance is set correctly + expect(AwesomeNotificationsPlatform.instance, + isA()); + + AwesomeNotificationsPlatform.operatingSystem = "linux"; + AwesomeNotificationsPlatform.resetInstance(); + + // Test if the instance is set correctly + expect(AwesomeNotificationsPlatform.instance, + isA()); + + AwesomeNotificationsPlatform.operatingSystem = "macos"; + AwesomeNotificationsPlatform.resetInstance(); + + // Test if the instance is set correctly + expect(AwesomeNotificationsPlatform.instance, + isA()); + + AwesomeNotificationsPlatform.operatingSystem = "windows"; + AwesomeNotificationsPlatform.resetInstance(); + + // Test if the instance is set correctly + expect(AwesomeNotificationsPlatform.instance, + isA()); + + AwesomeNotificationsPlatform.operatingSystem = "fuchsia"; + AwesomeNotificationsPlatform.resetInstance(); + + // Test if the instance is set correctly + expect(AwesomeNotificationsPlatform.instance, + isA()); + }); + }); +} diff --git a/test/definitions.dart b/test/definitions.dart new file mode 100644 index 00000000..d38c959e --- /dev/null +++ b/test/definitions.dart @@ -0,0 +1,66 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Definitions', () { + test('initialValues default values', () { + expect(Definitions.initialValues[NOTIFICATION_ID], 0); + expect( + Definitions.initialValues[NOTIFICATION_GROUP_SORT], GroupSort.Desc); + expect(Definitions.initialValues[NOTIFICATION_GROUP_ALERT_BEHAVIOR], + GroupAlertBehavior.All); + expect(Definitions.initialValues[NOTIFICATION_IMPORTANCE], + NotificationImportance.Default); + expect(Definitions.initialValues[NOTIFICATION_LAYOUT], + NotificationLayout.Default); + expect(Definitions.initialValues[NOTIFICATION_DEFAULT_PRIVACY], + NotificationPrivacy.Private); + expect(Definitions.initialValues[NOTIFICATION_ACTION_TYPE], + ActionType.Default); + expect(Definitions.initialValues[NOTIFICATION_PRIVACY], + NotificationPrivacy.Private); + expect(Definitions.initialValues[NOTIFICATION_DEFAULT_RINGTONE_TYPE], + DefaultRingtoneType.Notification); + expect( + Definitions.initialValues[NOTIFICATION_DISPLAY_ON_FOREGROUND], true); + expect( + Definitions.initialValues[NOTIFICATION_DISPLAY_ON_BACKGROUND], true); + expect(Definitions.initialValues[NOTIFICATION_CHANNEL_DESCRIPTION], + 'Notifications'); + expect(Definitions.initialValues[NOTIFICATION_CHANNEL_NAME], + 'Notifications'); + expect(Definitions.initialValues[NOTIFICATION_SHOW_WHEN], true); + expect(Definitions.initialValues[NOTIFICATION_CHANNEL_SHOW_BADGE], false); + expect(Definitions.initialValues[NOTIFICATION_ENABLED], true); + expect(Definitions.initialValues[NOTIFICATION_PAYLOAD], null); + expect(Definitions.initialValues[NOTIFICATION_ENABLE_VIBRATION], true); + expect(Definitions.initialValues[NOTIFICATION_COLOR], Colors.black); + expect(Definitions.initialValues[NOTIFICATION_LED_COLOR], Colors.white); + expect(Definitions.initialValues[NOTIFICATION_ENABLE_LIGHTS], true); + expect(Definitions.initialValues[NOTIFICATION_LED_OFF_MS], 700); + expect(Definitions.initialValues[NOTIFICATION_LED_ON_MS], 300); + expect(Definitions.initialValues[NOTIFICATION_PLAY_SOUND], true); + expect(Definitions.initialValues[NOTIFICATION_AUTO_DISMISSIBLE], true); + expect(Definitions.initialValues[NOTIFICATION_LOCKED], false); + expect(Definitions.initialValues[NOTIFICATION_TICKER], 'ticker'); + expect(Definitions.initialValues[NOTIFICATION_ALLOW_WHILE_IDLE], false); + expect(Definitions.initialValues[NOTIFICATION_ONLY_ALERT_ONCE], false); + expect( + Definitions.initialValues[NOTIFICATION_SHOW_IN_COMPACT_VIEW], true); + expect(Definitions.initialValues[NOTIFICATION_SCHEDULE_REPEATS], false); + expect(Definitions.initialValues[NOTIFICATION_BUTTON_KEY_PRESSED], ''); + expect(Definitions.initialValues[NOTIFICATION_BUTTON_KEY_INPUT], ''); + expect( + Definitions.initialValues[NOTIFICATION_IS_DANGEROUS_OPTION], false); + expect(Definitions.initialValues[NOTIFICATION_WAKE_UP_SCREEN], false); + expect(Definitions.initialValues[NOTIFICATION_FULL_SCREEN_INTENT], false); + expect(Definitions.initialValues[NOTIFICATION_CRITICAL_ALERT], false); + expect(Definitions.initialValues[NOTIFICATION_CHANNEL_CRITICAL_ALERTS], + false); + expect(Definitions.initialValues[NOTIFICATION_ROUNDED_LARGE_ICON], false); + expect( + Definitions.initialValues[NOTIFICATION_ROUNDED_BIG_PICTURE], false); + }); + }); +} diff --git a/test/extraction_values_test.dart b/test/extraction_values_test.dart deleted file mode 100644 index 0409fa39..00000000 --- a/test/extraction_values_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:awesome_notifications/awesome_notifications.dart'; -import 'package:flutter/material.dart' hide DateUtils; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() {}); - - tearDown(() {}); - - test('extractValueTest', () async { - expect("title", AwesomeAssertUtils.extractValue("test", {"test": "title"}, String)); - expect(" title", AwesomeAssertUtils.extractValue("test", {"test": " title"}, String)); - expect("", AwesomeAssertUtils.extractValue("test", {"test": ""}, String)); - expect(" ", AwesomeAssertUtils.extractValue("test", {"test": " "}, String)); - - expect(10, AwesomeAssertUtils.extractValue("test", {"test": "10"}, int)); - expect(10, AwesomeAssertUtils.extractValue("test", {"test": 10}, int)); - expect(10, AwesomeAssertUtils.extractValue("test", {"test": "10.0"}, int)); - expect(10.0, AwesomeAssertUtils.extractValue("test", {"test": "10.0"}, double)); - expect(0, AwesomeAssertUtils.extractValue("test", {"test": "0"}, int)); - expect(0.0, AwesomeAssertUtils.extractValue("test", {"test": "0"}, double)); - expect(0, AwesomeAssertUtils.extractValue("test", {"test": "0.0"}, int)); - expect(0, AwesomeAssertUtils.extractValue("test", {"test": 0}, int)); - - expect(0xFFFF0000, AwesomeAssertUtils.extractValue("test", {"test": "#FF0000"}, int)); - expect(0xFFFF0000, AwesomeAssertUtils.extractValue("test", {"test": "#ff0000"}, int)); - expect(0xFFFF0000, AwesomeAssertUtils.extractValue("test", {"test": "#FFFF0000"}, int)); - expect(0x00FF0000, AwesomeAssertUtils.extractValue("test", {"test": "#00FF0000"}, int)); - expect(0xFFFF0000, AwesomeAssertUtils.extractValue("test", {"test": "0xFF0000"}, int)); - expect(0xFFFF0000, AwesomeAssertUtils.extractValue("test", {"test": "0xFFff0000"}, int)); - - expect(Colors.black, AwesomeAssertUtils.extractValue("test", {"test": "#000000"}, Color)); - expect(Colors.black, AwesomeAssertUtils.extractValue("test", {"test": "#FF000000"}, Color)); - expect(Colors.transparent, AwesomeAssertUtils.extractValue("test", {"test": "#00000000"}, Color)); - - expect(null, AwesomeAssertUtils.extractValue("test", {"test": null}, Color)); - expect(null, AwesomeAssertUtils.extractValue("test", {"test": "#0004"}, Color)); - expect(null, AwesomeAssertUtils.extractValue("test", {"test": "#04"}, Color)); - expect(null, AwesomeAssertUtils.extractValue("test", {"test": " "}, Color)); - - expect(null, AwesomeAssertUtils.extractValue("test", {"test": null}, int)); - expect(null, AwesomeAssertUtils.extractValue("test", {"test": ""}, int)); - expect(null, AwesomeAssertUtils.extractValue("test", {"test": " "}, int)); - - expect(null, AwesomeAssertUtils.extractValue("test", {"test": 0}, String)); - expect(null, AwesomeAssertUtils.extractValue("test", {"test": null}, String)); - - expect(true, AwesomeAssertUtils.extractValue("test", {"test": true}, bool)); - expect(true, AwesomeAssertUtils.extractValue("test", {"test": "true"}, bool)); - expect(false, AwesomeAssertUtils.extractValue("test", {"test": "false"}, bool)); - }); - - test('extractEnumTest', () async { - expect(NotificationPrivacy.Private, - AwesomeAssertUtils.extractEnum( - "test", {"test": "Private"}, NotificationPrivacy.values)); - - expect(NotificationPrivacy.Public, - AwesomeAssertUtils.extractEnum( - "test", {"test": "Public"}, NotificationPrivacy.values)); - - expect(null, - AwesomeAssertUtils.extractEnum( - "test", {"test": ""}, NotificationPrivacy.values)); - - expect(null, - AwesomeAssertUtils.extractEnum( - "test", {"test": " "}, NotificationPrivacy.values)); - - expect(null, - AwesomeAssertUtils.extractEnum( - "test", {"test": null}, NotificationPrivacy.values)); - }); -} diff --git a/test/src/enumerators/media_source_test.dart b/test/src/enumerators/media_source_test.dart new file mode 100644 index 00000000..053fecc6 --- /dev/null +++ b/test/src/enumerators/media_source_test.dart @@ -0,0 +1,21 @@ +import 'package:awesome_notifications/src/enumerators/media_source.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('MediaSourcePrefix tests', () { + test('MediaSourcePrefix values are set correctly', () { + expect(MediaSourcePrefix.Resource, 'resource://'); + expect(MediaSourcePrefix.Asset, 'asset://'); + expect(MediaSourcePrefix.File, 'file://'); + expect(MediaSourcePrefix.Network, 'https://'); + expect(MediaSourcePrefix.Unknown, ''); + + expect(MediaSourcePrefix.values, + containsAll(['resource://', 'asset://', 'file://', 'https://', ''])); + }); + + test('MediaSourcePrefix index is set correctly', () { + expect(const MediaSourcePrefix(1).value, 1); + }); + }); +} diff --git a/test/src/enumerators/notification_life_cycle_test.dart b/test/src/enumerators/notification_life_cycle_test.dart new file mode 100644 index 00000000..5ed66495 --- /dev/null +++ b/test/src/enumerators/notification_life_cycle_test.dart @@ -0,0 +1,12 @@ +import 'package:awesome_notifications/src/enumerators/notification_life_cycle.dart'; +import 'package:flutter_test/flutter_test.dart'; + +// ignore_for_file: deprecated_member_use_from_same_package + +void main() { + group('NotificationLifeCycle tests', () { + test('AppKilled returns same value as Terminated', () { + expect(NotificationLifeCycle.AppKilled, NotificationLifeCycle.Terminated); + }); + }); +} diff --git a/test/src/exceptions/awesome_exception_test.dart b/test/src/exceptions/awesome_exception_test.dart new file mode 100644 index 00000000..32d95683 --- /dev/null +++ b/test/src/exceptions/awesome_exception_test.dart @@ -0,0 +1,14 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('AwesomeNotificationsException tests', () { + test('Exception message is stored and returned correctly', () { + const errorMessage = 'Test error message'; + const exception = AwesomeNotificationsException(message: errorMessage); + expect(exception.message, errorMessage); + expect(exception.toString(), + 'AwesomeNotificationsException{msg: $errorMessage}'); + }); + }); +} diff --git a/test/src/exceptions/isolate_exception_test.dart b/test/src/exceptions/isolate_exception_test.dart new file mode 100644 index 00000000..4da84ca1 --- /dev/null +++ b/test/src/exceptions/isolate_exception_test.dart @@ -0,0 +1,13 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('IsolateCallbackException tests', () { + test('Exception message is stored and returned correctly', () { + const errorMessage = 'Test error message'; + final exception = IsolateCallbackException(errorMessage); + expect(exception.msg, errorMessage); + expect(exception.toString(), 'IsolateCallbackException: $errorMessage'); + }); + }); +} diff --git a/test/src/extensions/navigator_extension_test.dart b/test/src/extensions/navigator_extension_test.dart new file mode 100644 index 00000000..a10b2c83 --- /dev/null +++ b/test/src/extensions/navigator_extension_test.dart @@ -0,0 +1,67 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('NavigatorStateExtension tests', () { + testWidgets('pushNamedIfNotCurrent', (WidgetTester tester) async { + final GlobalKey navigatorKey = + GlobalKey(); + + await tester.pumpWidget(MaterialApp( + navigatorKey: navigatorKey, + initialRoute: '/', + routes: { + '/': (context) => const Text('Home'), + '/second': (context) => const Text('Second'), + }, + )); + + // Should push the route if it's not the current route + navigatorKey.currentState!.pushNamedIfNotCurrent('/second'); + await tester.pumpAndSettle(); + expect(find.text('Second'), findsOneWidget); + + // Should not push the route if it's the current route + navigatorKey.currentState!.pushNamedIfNotCurrent('/second'); + await tester.pump(); + expect(find.text('Second'), findsOneWidget); + }); + + testWidgets('getCurrentPage', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + initialRoute: '/', + routes: { + '/': (context) => const Text('Home'), + '/second': (context) => const Text('Second'), + }, + )); + + await tester.tap(find.text('Home')); + await tester.pumpAndSettle(); + + expect( + Navigator.of(tester.element(find.text('Home'))) + .getCurrentPage('/') + .settings + .name, + '/'); + }); + + testWidgets('isCurrent', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + initialRoute: '/', + routes: { + '/': (context) => const Text('Home'), + '/second': (context) => const Text('Second'), + }, + )); + + expect( + Navigator.of(tester.element(find.text('Home'))).isCurrent('/'), true); + expect( + Navigator.of(tester.element(find.text('Home'))).isCurrent('/second'), + false); + }); + }); +} diff --git a/test/src/helpers/bitmap_helper_test.dart b/test/src/helpers/bitmap_helper_test.dart new file mode 100644 index 00000000..03095b30 --- /dev/null +++ b/test/src/helpers/bitmap_helper_test.dart @@ -0,0 +1,97 @@ +import 'dart:typed_data'; + +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/painting.dart'; + +void main() { + group('BitmapHelper tests', () { + const int width = 10; + const int height = 10; + + test('Creating BitmapHelper using different constructors', () { + final Uint8List content = Uint8List.fromList( + List.filled(width * height * bitmapPixelLength, 0)); + + // Test BitmapHelper.fromHeadless + final BitmapHelper bitmapHeadless = + BitmapHelper.fromHeadless(width, height, content); + expect(bitmapHeadless.width, width); + expect(bitmapHeadless.height, height); + expect(bitmapHeadless.content, content); + + // Test BitmapHelper.fromHeadful + final Uint8List headedContent = + Uint8List.fromList([...List.filled(RGBA32HeaderSize, 0), ...content]); + final BitmapHelper bitmapHeadful = + BitmapHelper.fromHeadful(width, height, headedContent); + expect(bitmapHeadful.width, width); + expect(bitmapHeadful.height, height); + expect(bitmapHeadful.content, content); + + // Test BitmapHelper.blank + final BitmapHelper bitmapBlank = BitmapHelper.blank(width, height); + expect(bitmapBlank.width, width); + expect(bitmapBlank.height, height); + expect(bitmapBlank.content, content); + }); + + test('Cloning BitmapHelper', () { + final Uint8List content = Uint8List.fromList( + List.filled(width * height * bitmapPixelLength, 0)); + final BitmapHelper original = + BitmapHelper.fromHeadless(width, height, content); + final BitmapHelper clone = original.cloneHeadless(); + + expect(clone.width, original.width); + expect(clone.height, original.height); + expect(clone.content, original.content); + }); + + test('Build image from BitmapHelper', () async { + final Uint8List content = Uint8List.fromList( + List.filled(width * height * bitmapPixelLength, 0)); + final BitmapHelper bitmap = + BitmapHelper.fromHeadless(width, height, content); + + final image = await bitmap.buildImage(); + expect(image.width, width); + expect(image.height, height); + }); + + test('Build headed content from BitmapHelper', () { + final Uint8List content = Uint8List.fromList( + List.filled(width * height * bitmapPixelLength, 0)); + final BitmapHelper bitmap = + BitmapHelper.fromHeadless(width, height, content); + + final headedContent = bitmap.buildHeaded(); + expect(headedContent.length, bitmap.size + RGBA32HeaderSize); + expect(headedContent.sublist(RGBA32HeaderSize), content); + }); + }); + + group('BitmapHelper tests', () { + const int width = 2480; + const int height = 2480; + + setUp(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + // Other test cases (previously provided) go here + + test('Create BitmapHelper from ImageProvider', () async { + const ImageProvider provider = AssetImage( + 'test/assets/images/test_image.png'); // Replace with an actual image file in your assets folder + final BitmapHelper bitmap = await BitmapHelper.fromImageProvider(provider); + + expect(bitmap.width, width); // Replace with actual image width + expect(bitmap.height, height); // Replace with actual image height + + final image = await bitmap.buildImage(); + expect(image.width, width); // Replace with actual image width + expect(image.height, height); // Replace with actual image height + }); + }); +} diff --git a/test/src/helpers/cron_helper_test.dart b/test/src/helpers/cron_helper_test.dart new file mode 100644 index 00000000..96423dd4 --- /dev/null +++ b/test/src/helpers/cron_helper_test.dart @@ -0,0 +1,64 @@ +import 'package:awesome_notifications/src/helpers/cron_helper.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('CronHelper tests', () { + late CronHelper cronHelper; + + setUp(() { + cronHelper = CronHelper.private(); + }); + + test('atDate', () { + DateTime referenceDateTime = DateTime(2023, 05, 01, 14, 45, 00); + expect(cronHelper.atDate(referenceDateTime: referenceDateTime), + '0 45 14 1 5 ? 2023'); + }); + + test('yearly', () { + DateTime referenceDateTime = DateTime(2023, 12, 25, 07, 00, 00); + expect(cronHelper.yearly(referenceDateTime: referenceDateTime), + '0 0 7 25 12 ? *'); + }); + + test('monthly', () { + DateTime referenceDateTime = DateTime(2023, 05, 15, 10, 30, 00); + expect(cronHelper.monthly(referenceDateTime: referenceDateTime), + '0 30 10 15 * ? *'); + }); + + test('weekly', () { + DateTime referenceDateTime = DateTime(2023, 05, 02, 18, 30, 00); + expect(cronHelper.weekly(referenceDateTime: referenceDateTime), + '0 30 18 ? 5 TUE *'); + }); + + test('daily', () { + DateTime referenceDateTime = DateTime(2023, 05, 01, 22, 15, 00); + expect(cronHelper.daily(referenceDateTime: referenceDateTime), + '0 15 22 * * ? *'); + }); + + test('hourly', () { + DateTime referenceDateTime = DateTime(2023, 05, 01, 12, 45, 00); + expect(cronHelper.hourly(referenceDateTime: referenceDateTime), + '0 45 * * * ? *'); + }); + + test('minutely', () { + expect(cronHelper.minutely(initialSecond: 10), '10 * * * * ? *'); + }); + + test('workweekDay', () { + DateTime referenceDateTime = DateTime(2023, 05, 01, 9, 0, 0); + expect(cronHelper.workweekDay(referenceDateTime: referenceDateTime), + '0 0 9 ? * MON-FRI *'); + }); + + test('weekendDay', () { + DateTime referenceDateTime = DateTime(2023, 05, 01, 10, 30, 0); + expect(cronHelper.weekendDay(referenceDateTime: referenceDateTime), + '0 30 10 ? * SAT,SUN *'); + }); + }); +} diff --git a/test/src/isolates/isolate_main_test.dart b/test/src/isolates/isolate_main_test.dart new file mode 100644 index 00000000..a710b2db --- /dev/null +++ b/test/src/isolates/isolate_main_test.dart @@ -0,0 +1,62 @@ +import 'dart:ui'; + +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:awesome_notifications/src/isolates/isolate_main.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +typedef ActionHandler = Future Function(ReceivedAction receivedAction); + +class MockPluginUtilities extends Mock implements PluginUtilities {} + +class MockWidgetsFlutterBinding extends Mock implements WidgetsFlutterBinding {} + +class MockMethodChannel extends Mock implements MethodChannel {} + +class MockIsolateController extends Mock implements IsolateController {} + +class MockMethodCall extends Fake implements MethodCall {} + +void main() { + channel = MockMethodChannel(); + + setUpAll(() { + registerFallbackValue(MockMethodCall()); + }); + + test('dartIsolateMain', () async { + // WidgetsFlutterBinding binding = MockWidgetsFlutterBinding(); + // Ensure binding is called once + // verify(() => binding.ensureInitialized()).called(1); + + when(() => channel.invokeMethod(any())) + .thenAnswer((invocation) => Future.value()); + + dartIsolateMain(); + + // Ensure the CHANNEL_METHOD_PUSH_NEXT_DATA method is invoked + verify(() => channel.invokeMethod(CHANNEL_METHOD_PUSH_NEXT_DATA)) + .called(1); + + // MockIsolateController mockIsolateController = MockIsolateController(); + // IsolateController.singleton = mockIsolateController; + + // when(() => mockIsolateController.channelMethodSilentCallbackHandle(any())) + // .thenAnswer((invocation) => Future.value()); + + // when(() => mockIsolateController.channelMethodIsolateShutdown(any())) + // .thenAnswer((invocation) => Future.value()); + + // channel.invokeMethod(CHANNEL_METHOD_SILENT_CALLBACK); + + // verify(() => mockIsolateController.channelMethodSilentCallbackHandle(any())) + // .called(1); + + // channel.invokeMethod(CHANNEL_METHOD_ISOLATE_SHUTDOWN); + + // verify(() => mockIsolateController.channelMethodIsolateShutdown(any())) + // .called(1); + }); +} diff --git a/test/src/logs/logger_test.dart b/test/src/logs/logger_test.dart new file mode 100644 index 00000000..64e9fcce --- /dev/null +++ b/test/src/logs/logger_test.dart @@ -0,0 +1,35 @@ +import 'dart:async'; + +import 'package:awesome_notifications/src/logs/logger.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('Logger prints expected log messages', () { + // Capture logs during the test + List logMessages = []; + String className = 'TestClass'; + + runZoned(() { + // Call the Logger methods + Logger.d(className, 'Debug message'); + Logger.e(className, 'Error message'); + Logger.i(className, 'Info message'); + Logger.w(className, 'Warning message'); + }, zoneSpecification: ZoneSpecification( + print: (Zone self, ZoneDelegate parent, Zone zone, String line) { + logMessages.add(line); + })); + + // Verify that the log messages were printed + expect(logMessages[0], + startsWith('[Awesome Notifications - DEBUG]: Debug message')); + expect(logMessages[1], + startsWith('\x1B[31m[Awesome Notifications - ERROR]: Error message')); + expect(logMessages[2], + startsWith('\x1B[34m[Awesome Notifications - INFO]: Info message')); + expect( + logMessages[3], + startsWith( + '\x1B[33m[Awesome Notifications - WARNING]: Warning message')); + }); +} diff --git a/test/src/models/base_notification_content_test.dart b/test/src/models/base_notification_content_test.dart new file mode 100644 index 00000000..19917ed0 --- /dev/null +++ b/test/src/models/base_notification_content_test.dart @@ -0,0 +1,1552 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:awesome_notifications/src/models/base_notification_content.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +// ignore_for_file: deprecated_member_use_from_same_package + +void main() { + group('BaseNotificationContent tests', () { + group('toMap method', () { + test('id field', () { + String failureReason = + 'The id field was not correctly exported as a map'; + int? defaultValue = Definitions.initialValues[NOTIFICATION_ID] as int?; + + expect(BaseNotificationContent(id: 1).toMap()[NOTIFICATION_ID], 1, + reason: failureReason); + expect(BaseNotificationContent(id: 0).toMap()[NOTIFICATION_ID], 0, + reason: failureReason); + expect(BaseNotificationContent(id: -1).toMap()[NOTIFICATION_ID], -1, + reason: failureReason); + expect(BaseNotificationContent().toMap()[NOTIFICATION_ID], defaultValue, + reason: failureReason); + }); + + test('channelKey field', () { + String failureReason = + 'The channelKey field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_CHANNEL_KEY] as String?; + + expect( + BaseNotificationContent(channelKey: 'channel_1') + .toMap()[NOTIFICATION_CHANNEL_KEY], + 'channel_1', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent(channelKey: '') + .toMap()[NOTIFICATION_CHANNEL_KEY], + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent(channelKey: ' ') + .toMap()[NOTIFICATION_CHANNEL_KEY], + ' ', + reason: '$failureReason: string with white spaces value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_CHANNEL_KEY], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('groupKey field', () { + String failureReason = + 'The groupKey field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_GROUP_KEY] as String?; + + expect( + BaseNotificationContent(groupKey: 'group_1') + .toMap()[NOTIFICATION_GROUP_KEY], + 'group_1', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent(groupKey: '') + .toMap()[NOTIFICATION_GROUP_KEY], + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent(groupKey: ' ') + .toMap()[NOTIFICATION_GROUP_KEY], + ' ', + reason: '$failureReason: string with white spaces value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_GROUP_KEY], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('actionType field', () { + String failureReason = + 'The action field was not correctly exported as a map'; + String? defaultValue = + (Definitions.initialValues[NOTIFICATION_ACTION_TYPE] as ActionType?) + ?.name; + + expect( + BaseNotificationContent(actionType: ActionType.Default) + .toMap()[NOTIFICATION_ACTION_TYPE], + ActionType.Default.name, + reason: '$failureReason: Default value'); + expect( + BaseNotificationContent(actionType: ActionType.InputField) + .toMap()[NOTIFICATION_ACTION_TYPE], + ActionType.InputField.name, + reason: '$failureReason: InputField value (deprecated)'); + expect( + BaseNotificationContent(actionType: ActionType.DisabledAction) + .toMap()[NOTIFICATION_ACTION_TYPE], + ActionType.DisabledAction.name, + reason: '$failureReason: DisabledAction value'); + expect( + BaseNotificationContent(actionType: ActionType.KeepOnTop) + .toMap()[NOTIFICATION_ACTION_TYPE], + ActionType.KeepOnTop.name, + reason: '$failureReason: KeepOnTop value'); + expect( + BaseNotificationContent(actionType: ActionType.SilentAction) + .toMap()[NOTIFICATION_ACTION_TYPE], + ActionType.SilentAction.name, + reason: '$failureReason: SilentAction value'); + expect( + BaseNotificationContent( + actionType: ActionType.SilentBackgroundAction) + .toMap()[NOTIFICATION_ACTION_TYPE], + ActionType.SilentBackgroundAction.name, + reason: '$failureReason: SilentBackgroundAction value'); + expect( + BaseNotificationContent(actionType: ActionType.DismissAction) + .toMap()[NOTIFICATION_ACTION_TYPE], + ActionType.DismissAction.name, + reason: '$failureReason: DismissAction value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_ACTION_TYPE], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('title field', () { + String failureReason = + 'The title field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_TITLE] as String?; + + expect( + BaseNotificationContent(title: 'notification title') + .toMap()[NOTIFICATION_TITLE], + 'notification title', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent(title: '').toMap()[NOTIFICATION_TITLE], '', + reason: '$failureReason: empty string value'); + expect(BaseNotificationContent(title: ' ').toMap()[NOTIFICATION_TITLE], + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent().toMap()[NOTIFICATION_TITLE], defaultValue, + reason: '$failureReason: default value'); + }); + + test('body field', () { + String failureReason = + 'The body field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_BODY] as String?; + + expect( + BaseNotificationContent(body: 'notification body') + .toMap()[NOTIFICATION_BODY], + 'notification body', + reason: '$failureReason: normal string value'); + expect(BaseNotificationContent(body: '').toMap()[NOTIFICATION_BODY], '', + reason: '$failureReason: empty string value'); + expect(BaseNotificationContent(body: ' ').toMap()[NOTIFICATION_BODY], + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent().toMap()[NOTIFICATION_BODY], defaultValue, + reason: '$failureReason: default value'); + }); + + test('summary field', () { + String failureReason = + 'The summary field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_SUMMARY] as String?; + + expect( + BaseNotificationContent(summary: 'notification summary') + .toMap()[NOTIFICATION_SUMMARY], + 'notification summary', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent(summary: '').toMap()[NOTIFICATION_SUMMARY], + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent(summary: ' ') + .toMap()[NOTIFICATION_SUMMARY], + ' ', + reason: '$failureReason: string with white spaces value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_SUMMARY], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('showWhen field', () { + String failureReason = + 'The showWhen field was not correctly exported as a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_SHOW_WHEN] as bool?; + + expect( + BaseNotificationContent(showWhen: true) + .toMap()[NOTIFICATION_SHOW_WHEN], + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent(showWhen: false) + .toMap()[NOTIFICATION_SHOW_WHEN], + false, + reason: '$failureReason: false value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_SHOW_WHEN], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('icon field', () { + String failureReason = + 'The icon field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_ICON] as String?; + + expect( + BaseNotificationContent(icon: 'icon_name') + .toMap()[NOTIFICATION_ICON], + 'icon_name', + reason: '$failureReason: normal string value'); + expect(BaseNotificationContent(icon: '').toMap()[NOTIFICATION_ICON], '', + reason: '$failureReason: empty string value'); + expect(BaseNotificationContent(icon: ' ').toMap()[NOTIFICATION_ICON], + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent().toMap()[NOTIFICATION_ICON], defaultValue, + reason: '$failureReason: default value'); + }); + + test('largeIcon field', () { + String failureReason = + 'The largeIcon field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_LARGE_ICON] as String?; + + expect( + BaseNotificationContent(largeIcon: 'large_icon_name') + .toMap()[NOTIFICATION_LARGE_ICON], + 'large_icon_name', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent(largeIcon: '') + .toMap()[NOTIFICATION_LARGE_ICON], + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent(largeIcon: ' ') + .toMap()[NOTIFICATION_LARGE_ICON], + ' ', + reason: '$failureReason: string with white spaces value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_LARGE_ICON], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('bigPicture field', () { + String failureReason = + 'The bigPicture field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_BIG_PICTURE] as String?; + + expect( + BaseNotificationContent(bigPicture: 'big_picture_name') + .toMap()[NOTIFICATION_BIG_PICTURE], + 'big_picture_name', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent(bigPicture: '') + .toMap()[NOTIFICATION_BIG_PICTURE], + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent(bigPicture: ' ') + .toMap()[NOTIFICATION_BIG_PICTURE], + ' ', + reason: '$failureReason: string with white spaces value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_BIG_PICTURE], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('wakeUpScreen field', () { + String failureReason = + 'The wakeUpScreen field was not correctly exported as a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_WAKE_UP_SCREEN] as bool?; + + expect( + BaseNotificationContent(wakeUpScreen: true) + .toMap()[NOTIFICATION_WAKE_UP_SCREEN], + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent(wakeUpScreen: false) + .toMap()[NOTIFICATION_WAKE_UP_SCREEN], + false, + reason: '$failureReason: false value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_WAKE_UP_SCREEN], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('fullScreenIntent field', () { + String failureReason = + 'The fullScreenIntent field was not correctly exported as a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_FULL_SCREEN_INTENT] as bool?; + + expect( + BaseNotificationContent(fullScreenIntent: true) + .toMap()[NOTIFICATION_FULL_SCREEN_INTENT], + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent(fullScreenIntent: false) + .toMap()[NOTIFICATION_FULL_SCREEN_INTENT], + false, + reason: '$failureReason: false value'); + expect( + BaseNotificationContent().toMap()[NOTIFICATION_FULL_SCREEN_INTENT], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('criticalAlert field', () { + String failureReason = + 'The criticalAlert field was not correctly exported as a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_CRITICAL_ALERT] as bool?; + + expect( + BaseNotificationContent(criticalAlert: true) + .toMap()[NOTIFICATION_CRITICAL_ALERT], + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent(criticalAlert: false) + .toMap()[NOTIFICATION_CRITICAL_ALERT], + false, + reason: '$failureReason: false value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_CRITICAL_ALERT], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('category field', () { + String failureReason = + 'The category field was not correctly exported as a map'; + NotificationCategory? defaultValue = Definitions + .initialValues[NOTIFICATION_CATEGORY] as NotificationCategory?; + + for (var category in NotificationCategory.values) { + expect( + BaseNotificationContent(category: category) + .toMap()[NOTIFICATION_CATEGORY], + category.toString().split('.').last, + reason: + '$failureReason: ${category.toString().split('.').last} value'); + } + expect(BaseNotificationContent().toMap()[NOTIFICATION_CATEGORY], + defaultValue?.name, + reason: '$failureReason: default value'); + }); + + test('color field', () { + String failureReason = + 'The showWhen field was not correctly exported as a map'; + int? defaultValue = + (Definitions.initialValues[NOTIFICATION_COLOR] as Color?)?.value; + + expect( + BaseNotificationContent(color: Colors.red) + .toMap()[NOTIFICATION_COLOR], + Colors.red.value, + reason: '$failureReason: without transparency value'); + expect( + BaseNotificationContent(color: Colors.transparent) + .toMap()[NOTIFICATION_COLOR], + Colors.transparent.value, + reason: '$failureReason: with transparency value'); + expect( + BaseNotificationContent().toMap()[NOTIFICATION_COLOR], defaultValue, + reason: '$failureReason: default value'); + }); + + test('backgroundColor field', () { + String failureReason = + 'The showWhen field was not correctly exported as a map'; + int? defaultValue = + (Definitions.initialValues[NOTIFICATION_BACKGROUND_COLOR] as Color?) + ?.value; + + expect( + BaseNotificationContent(backgroundColor: Colors.red) + .toMap()[NOTIFICATION_BACKGROUND_COLOR], + Colors.red.value, + reason: '$failureReason: without transparency value'); + expect( + BaseNotificationContent(backgroundColor: Colors.transparent) + .toMap()[NOTIFICATION_BACKGROUND_COLOR], + Colors.transparent.value, + reason: '$failureReason: with transparency value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_BACKGROUND_COLOR], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('timeoutAfter field', () { + String failureReason = + 'The timeoutAfter field was not correctly exported as a map'; + int? defaultValue = + Definitions.initialValues[NOTIFICATION_TIMEOUT_AFTER] as int?; + + expect( + BaseNotificationContent(timeoutAfter: const Duration(seconds: 1000)) + .toMap()[NOTIFICATION_TIMEOUT_AFTER], + 1000, + reason: '$failureReason: positive value'); + expect( + BaseNotificationContent(timeoutAfter: const Duration(seconds: 1)) + .toMap()[NOTIFICATION_TIMEOUT_AFTER], + 1, + reason: '$failureReason: minimal positive value'); + expect( + BaseNotificationContent(timeoutAfter: const Duration(seconds: 0)) + .toMap()[NOTIFICATION_TIMEOUT_AFTER], + 0, + reason: '$failureReason: zero value returning valid duration'); + + expect( + BaseNotificationContent(timeoutAfter: const Duration(seconds: -1)) + .toMap()[NOTIFICATION_TIMEOUT_AFTER], + null, + reason: '$failureReason: zero or negative value'); + expect( + BaseNotificationContent(timeoutAfter: const Duration(seconds: -1000)) + .toMap()[NOTIFICATION_TIMEOUT_AFTER], + null, + reason: '$failureReason: zero or negative value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_TIMEOUT_AFTER], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('customSound field', () { + String failureReason = + 'The customSound field was not correctly exported as a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_CUSTOM_SOUND] as String?; + + expect( + BaseNotificationContent(customSound: 'sound_1') + .toMap()[NOTIFICATION_CUSTOM_SOUND], + 'sound_1', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent(customSound: '') + .toMap()[NOTIFICATION_CUSTOM_SOUND], + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent(customSound: ' ') + .toMap()[NOTIFICATION_CUSTOM_SOUND], + ' ', + reason: '$failureReason: string with white spaces value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_CUSTOM_SOUND], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('roundedLargeIcon field', () { + String failureReason = + 'The roundedLargeIcon field was not correctly exported as a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_ROUNDED_LARGE_ICON] as bool?; + + expect( + BaseNotificationContent(roundedLargeIcon: true) + .toMap()[NOTIFICATION_ROUNDED_LARGE_ICON], + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent(roundedLargeIcon: false) + .toMap()[NOTIFICATION_ROUNDED_LARGE_ICON], + false, + reason: '$failureReason: false value'); + expect( + BaseNotificationContent().toMap()[NOTIFICATION_ROUNDED_LARGE_ICON], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('roundedBigPicture field', () { + String failureReason = + 'The roundedBigPicture field was not correctly exported as a map'; + bool? defaultValue = Definitions + .initialValues[NOTIFICATION_ROUNDED_BIG_PICTURE] as bool?; + + expect( + BaseNotificationContent(roundedBigPicture: true) + .toMap()[NOTIFICATION_ROUNDED_BIG_PICTURE], + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent(roundedBigPicture: false) + .toMap()[NOTIFICATION_ROUNDED_BIG_PICTURE], + false, + reason: '$failureReason: false value'); + expect( + BaseNotificationContent().toMap()[NOTIFICATION_ROUNDED_BIG_PICTURE], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('autoDismissible field', () { + String failureReason = + 'The autoDismissible field was not correctly exported as a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_AUTO_DISMISSIBLE] as bool?; + + expect( + BaseNotificationContent(autoDismissible: true) + .toMap()[NOTIFICATION_AUTO_DISMISSIBLE], + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent(autoDismissible: false) + .toMap()[NOTIFICATION_AUTO_DISMISSIBLE], + false, + reason: '$failureReason: false value'); + expect( + BaseNotificationContent(autoCancel: true) + .toMap()[NOTIFICATION_AUTO_DISMISSIBLE], + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent(autoCancel: false) + .toMap()[NOTIFICATION_AUTO_DISMISSIBLE], + false, + reason: '$failureReason: false value'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_AUTO_DISMISSIBLE], + defaultValue, + reason: '$failureReason: default value'); + }); + + test('payload field', () { + String failureReason = + 'The payload field was not correctly exported as a map'; + Map? defaultValue = Definitions + .initialValues[NOTIFICATION_PAYLOAD] as Map?; + + Map emptyPayload = {}; + expect( + BaseNotificationContent(payload: emptyPayload) + .toMap()[NOTIFICATION_PAYLOAD], + emptyPayload, + reason: '$failureReason: empty payload'); + expect(BaseNotificationContent().toMap()[NOTIFICATION_PAYLOAD], + defaultValue, + reason: '$failureReason: default value'); + + Map customPayload = { + 'key1': 'value1', + 'key2': 'value2' + }; + final customBaseWithPayload = + BaseNotificationContent(payload: customPayload).toMap(); + expect(customBaseWithPayload[NOTIFICATION_PAYLOAD], customPayload, + reason: '$failureReason: custom payload'); + expect(customBaseWithPayload[NOTIFICATION_PAYLOAD]['key1'], 'value1', + reason: '$failureReason: custom payload'); + expect(customBaseWithPayload[NOTIFICATION_PAYLOAD]['key2'], 'value2', + reason: '$failureReason: custom payload'); + expect(customBaseWithPayload[NOTIFICATION_PAYLOAD].length, 2, + reason: '$failureReason: custom payload'); + }); + }); + + group('fromMap method', () { + test('id field fromMap', () { + String failureReason = + 'The id field was not correctly imported from a map'; + int? defaultValue = Definitions.initialValues[NOTIFICATION_ID] as int?; + + expect(BaseNotificationContent().fromMap({NOTIFICATION_ID: 1})?.id, 1, + reason: '$failureReason: positive value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_ID: 0})?.id, 0, + reason: '$failureReason: positive value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_ID: -1})?.id, -1, + reason: '$failureReason: positive value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_ID: "1"})?.id, 1, + reason: '$failureReason: positive value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_ID: "0"})?.id, 0, + reason: '$failureReason: positive value'); + expect( + BaseNotificationContent().fromMap({NOTIFICATION_ID: "-1"})?.id, -1, + reason: '$failureReason: positive value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_ID: null})?.id, + defaultValue, + reason: '$failureReason: positive value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_ID: ""})?.id, + defaultValue, + reason: '$failureReason: positive value'); + }); + + test('channelKey field fromMap', () { + String failureReason = + 'The channelKey field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_CHANNEL_KEY] as String?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CHANNEL_KEY: 'channel_1'})?.channelKey, + 'channel_1', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CHANNEL_KEY: ''})?.channelKey, + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CHANNEL_KEY: ' '})?.channelKey, + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CHANNEL_KEY: null})?.channelKey, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CHANNEL_KEY: 1})?.channelKey, + defaultValue, + reason: '$failureReason: non-string value'); + }); + + test('actionType field fromMap', () { + String failureReason = + 'The actionType field was not correctly imported from a map'; + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ACTION_TYPE: 'SilentAction'})?.actionType, + ActionType.SilentAction, + reason: '$failureReason: normal string value'); + + expect( + BaseNotificationContent().fromMap({ + NOTIFICATION_ACTION_TYPE: 'SilentBackgroundAction' + })?.actionType, + ActionType.SilentBackgroundAction, + reason: '$failureReason: normal string value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ACTION_TYPE: 'DisabledAction'})?.actionType, + ActionType.DisabledAction, + reason: '$failureReason: normal string value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ACTION_TYPE: 'DismissAction'})?.actionType, + ActionType.DismissAction, + reason: '$failureReason: normal string value'); + }); + + test('groupKey field fromMap', () { + String failureReason = + 'The groupKey field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_GROUP_KEY] as String?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_GROUP_KEY: 'group_1'})?.groupKey, + 'group_1', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_GROUP_KEY: ''})?.groupKey, + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_GROUP_KEY: ' '})?.groupKey, + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_GROUP_KEY: null})?.groupKey, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_GROUP_KEY: 1})?.groupKey, + defaultValue, + reason: '$failureReason: non-string value'); + }); + + test('title field fromMap', () { + String failureReason = + 'The title field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_TITLE] as String?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_TITLE: 'notification title'})?.title, + 'notification title', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent().fromMap({NOTIFICATION_TITLE: ''})?.title, + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_TITLE: ' '})?.title, + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_TITLE: null})?.title, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent().fromMap({NOTIFICATION_TITLE: 1})?.title, + defaultValue, + reason: '$failureReason: non-string value'); + }); + + test('body field fromMap', () { + String failureReason = + 'The body field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_BODY] as String?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_BODY: 'notification body'})?.body, + 'notification body', + reason: '$failureReason: normal string value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_BODY: ''})?.body, + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent().fromMap({NOTIFICATION_BODY: ' '})?.body, + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent().fromMap({NOTIFICATION_BODY: null})?.body, + defaultValue, + reason: '$failureReason: null value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_BODY: 1})?.body, + defaultValue, + reason: '$failureReason: non-string value'); + }); + + test('summary field fromMap', () { + String failureReason = + 'The summary field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_SUMMARY] as String?; + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_SUMMARY: 'notification summary'})?.summary, + 'notification summary', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SUMMARY: ''})?.summary, + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SUMMARY: ' '})?.summary, + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SUMMARY: null})?.summary, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SUMMARY: 1})?.summary, + defaultValue, + reason: '$failureReason: non-string value'); + }); + + test('showWhen field fromMap', () { + String failureReason = + 'The showWhen field was not correctly imported from a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_SHOW_WHEN] as bool?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SHOW_WHEN: true})?.showWhen, + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SHOW_WHEN: false})?.showWhen, + false, + reason: '$failureReason: false value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SHOW_WHEN: null})?.showWhen, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SHOW_WHEN: 'true'})?.showWhen, + defaultValue, + reason: '$failureReason: non-boolean value (string)'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_SHOW_WHEN: 1})?.showWhen, + defaultValue, + reason: '$failureReason: non-boolean value (integer)'); + }); + + test('icon field fromMap', () { + String failureReason = + 'The icon field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_ICON] as String?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_ICON: 'icon_1'})?.icon, + 'icon_1', + reason: '$failureReason: normal string value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_ICON: ''})?.icon, + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent().fromMap({NOTIFICATION_ICON: ' '})?.icon, + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent().fromMap({NOTIFICATION_ICON: null})?.icon, + defaultValue, + reason: '$failureReason: null value'); + expect(BaseNotificationContent().fromMap({NOTIFICATION_ICON: 1})?.icon, + defaultValue, + reason: '$failureReason: non-string value (integer)'); + }); + + test('largeIcon field fromMap', () { + String failureReason = + 'The largeIcon field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_LARGE_ICON] as String?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_LARGE_ICON: 'large_icon_1'})?.largeIcon, + 'large_icon_1', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_LARGE_ICON: ''})?.largeIcon, + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_LARGE_ICON: ' '})?.largeIcon, + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_LARGE_ICON: null})?.largeIcon, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_LARGE_ICON: 1})?.largeIcon, + defaultValue, + reason: '$failureReason: non-string value (integer)'); + }); + + test('bigPicture field fromMap', () { + String failureReason = + 'The bigPicture field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_BIG_PICTURE] as String?; + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_BIG_PICTURE: 'big_picture_1'})?.bigPicture, + 'big_picture_1', + reason: '$failureReason: normal string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_BIG_PICTURE: ''})?.bigPicture, + '', + reason: '$failureReason: empty string value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_BIG_PICTURE: ' '})?.bigPicture, + ' ', + reason: '$failureReason: string with white spaces value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_BIG_PICTURE: null})?.bigPicture, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_BIG_PICTURE: 1})?.bigPicture, + defaultValue, + reason: '$failureReason: non-string value (integer)'); + }); + + test('wakeUpScreen field fromMap', () { + String failureReason = + 'The wakeUpScreen field was not correctly imported from a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_WAKE_UP_SCREEN] as bool?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_WAKE_UP_SCREEN: true})?.wakeUpScreen, + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_WAKE_UP_SCREEN: false})?.wakeUpScreen, + false, + reason: '$failureReason: false value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_WAKE_UP_SCREEN: null})?.wakeUpScreen, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_WAKE_UP_SCREEN: 'true'})?.wakeUpScreen, + true, + reason: '$failureReason: non-boolean value (string)'); + }); + + test('fullScreenIntent field fromMap', () { + String failureReason = + 'The fullScreenIntent field was not correctly imported from a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_FULL_SCREEN_INTENT] as bool?; + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_FULL_SCREEN_INTENT: true})?.fullScreenIntent, + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_FULL_SCREEN_INTENT: false})?.fullScreenIntent, + false, + reason: '$failureReason: false value'); + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_FULL_SCREEN_INTENT: 'true'})?.fullScreenIntent, + true, + reason: '$failureReason: non-boolean value (string)'); + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_FULL_SCREEN_INTENT: 'false'})?.fullScreenIntent, + false, + reason: '$failureReason: non-boolean value (string)'); + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_FULL_SCREEN_INTENT: 1})?.fullScreenIntent, + true, + reason: '$failureReason: non-boolean value (int)'); + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_FULL_SCREEN_INTENT: 0})?.fullScreenIntent, + false, + reason: '$failureReason: non-boolean value (int)'); + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_FULL_SCREEN_INTENT: null})?.fullScreenIntent, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('criticalAlert field fromMap', () { + String failureReason = + 'The criticalAlert field was not correctly imported from a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_CRITICAL_ALERT] as bool?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: true})?.criticalAlert, + true, + reason: '$failureReason: true value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: false})?.criticalAlert, + false, + reason: '$failureReason: false value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: 'true'})?.criticalAlert, + true, + reason: '$failureReason: non-boolean value (string)'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: 'false'})?.criticalAlert, + false, + reason: '$failureReason: non-boolean value (string)'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: '1'})?.criticalAlert, + true, + reason: '$failureReason: non-boolean value (string)'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: '0'})?.criticalAlert, + false, + reason: '$failureReason: non-boolean value (string)'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: 1})?.criticalAlert, + true, + reason: '$failureReason: non-boolean value (int)'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: 0})?.criticalAlert, + false, + reason: '$failureReason: non-boolean value (int)'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CRITICAL_ALERT: null})?.criticalAlert, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('category field fromMap', () { + String failureReason = + 'The category field was not correctly imported from a map'; + NotificationCategory? defaultValue = Definitions + .initialValues[NOTIFICATION_CATEGORY] as NotificationCategory?; + + for (var category in NotificationCategory.values) { + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CATEGORY: category.name})?.category, + category, + reason: '$failureReason: valid enum value'); + } + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CATEGORY: 'invalid_value'})?.category, + defaultValue, + reason: '$failureReason: invalid enum value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CATEGORY: null})?.category, + defaultValue, + reason: '$failureReason: null value'); + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CATEGORY: 1})?.category, + defaultValue, + reason: '$failureReason: non-string value (integer)'); + }); + + test('color field fromMap', () { + String failureReason = + 'The color field was not correctly imported from a map'; + Color? defaultValue = + Definitions.initialValues[NOTIFICATION_COLOR] as Color?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_COLOR: '#FF0000'})?.color, + const Color(0xFFFF0000), + reason: '$failureReason: valid hex color value'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_COLOR: '#FFFF0000'})?.color, + const Color(0xFFFF0000), + reason: '$failureReason: valid hex color value with hash symbol'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_COLOR: 'invalid_color'})?.color, + defaultValue, + reason: '$failureReason: invalid color value'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_COLOR: null})?.color, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('backgroundColor field fromMap', () { + String failureReason = + 'The backgroundColor field was not correctly imported from a map'; + Color? defaultValue = + Definitions.initialValues[NOTIFICATION_BACKGROUND_COLOR] as Color?; + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_BACKGROUND_COLOR: '#00FF00'})?.backgroundColor, + const Color(0xFF00FF00), + reason: '$failureReason: valid hex color value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_BACKGROUND_COLOR: '#FF00FF00'})?.backgroundColor, + const Color(0xFF00FF00), + reason: '$failureReason: valid hex color value with hash symbol'); + + expect( + BaseNotificationContent().fromMap({ + NOTIFICATION_BACKGROUND_COLOR: 'invalid_color' + })?.backgroundColor, + defaultValue, + reason: '$failureReason: invalid color value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_BACKGROUND_COLOR: null})?.backgroundColor, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('timeoutAfter field fromMap', () { + String failureReason = + 'The timeoutAfter field was not correctly imported from a map'; + int? defaultValue = + Definitions.initialValues[NOTIFICATION_TIMEOUT_AFTER] as int?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_TIMEOUT_AFTER: 1000})?.timeoutAfter, + const Duration(seconds: 1000), + reason: '$failureReason: positive value'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_TIMEOUT_AFTER: '1000'})?.timeoutAfter, + const Duration(seconds: 1000), + reason: '$failureReason: valid value in string form'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_TIMEOUT_AFTER: 0})?.timeoutAfter, + Duration.zero, + reason: '$failureReason: zero value returning valid duration'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_TIMEOUT_AFTER: -1})?.timeoutAfter, + null, + reason: '$failureReason: valid value in string form'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_TIMEOUT_AFTER: null})?.timeoutAfter, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('payload field fromMap', () { + String failureReason = + 'The payload field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_PAYLOAD] as String?; + + Map customPayload = { + 'key1': 'value1', + 'key2': 'value2' + }; + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_PAYLOAD: customPayload})?.payload, + {'key1': 'value1', 'key2': 'value2'}, + reason: '$failureReason: valid value'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_PAYLOAD: null})?.payload, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('customSound field fromMap', () { + String failureReason = + 'The customSound field was not correctly imported from a map'; + String? defaultValue = + Definitions.initialValues[NOTIFICATION_CUSTOM_SOUND] as String?; + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CUSTOM_SOUND: 'sound.wav'})?.customSound, + 'sound.wav', + reason: '$failureReason: valid value'); + + expect( + BaseNotificationContent() + .fromMap({NOTIFICATION_CUSTOM_SOUND: null})?.customSound, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('roundedLargeIcon field fromMap', () { + String failureReason = + 'The roundedLargeIcon field was not correctly imported from a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_ROUNDED_LARGE_ICON] as bool?; + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ROUNDED_LARGE_ICON: true})?.roundedLargeIcon, + true, + reason: '$failureReason: true value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ROUNDED_LARGE_ICON: false})?.roundedLargeIcon, + false, + reason: '$failureReason: false value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ROUNDED_LARGE_ICON: null})?.roundedLargeIcon, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('roundedBigPicture field fromMap', () { + String failureReason = + 'The roundedBigPicture field was not correctly imported from a map'; + bool? defaultValue = Definitions + .initialValues[NOTIFICATION_ROUNDED_BIG_PICTURE] as bool?; + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ROUNDED_BIG_PICTURE: true})?.roundedBigPicture, + true, + reason: '$failureReason: true value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ROUNDED_BIG_PICTURE: false})?.roundedBigPicture, + false, + reason: '$failureReason: false value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_ROUNDED_BIG_PICTURE: null})?.roundedBigPicture, + defaultValue, + reason: '$failureReason: null value'); + }); + + test('autoDismissible field fromMap', () { + String failureReason = + 'The autoDismissible field was not correctly imported from a map'; + bool? defaultValue = + Definitions.initialValues[NOTIFICATION_AUTO_DISMISSIBLE] as bool?; + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_AUTO_DISMISSIBLE: true})?.autoDismissible, + true, + reason: '$failureReason: true value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_AUTO_DISMISSIBLE: false})?.autoDismissible, + false, + reason: '$failureReason: false value'); + + expect( + BaseNotificationContent().fromMap( + {NOTIFICATION_AUTO_DISMISSIBLE: null})?.autoDismissible, + defaultValue, + reason: '$failureReason: null value'); + + expect( + BaseNotificationContent() + .fromMap({'autoCancel': true})?.autoDismissible, + true, + reason: '$failureReason: true value'); + + expect( + BaseNotificationContent() + .fromMap({'autoCancel': false})?.autoDismissible, + false, + reason: '$failureReason: false value'); + + expect( + BaseNotificationContent() + .fromMap({'autoCancel': null})?.autoDismissible, + defaultValue, + reason: '$failureReason: null value'); + }); + }); + + group('processRetroCompatibility method', () { + test('autoCancel field fromMap', () { + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': true})['autoDismissible'], + true, + reason: + 'The deprecated autoCancel field should have been removed.'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': false})['autoDismissible'], + false, + reason: + 'The deprecated autoCancel field should have been removed.'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': 'true'})['autoDismissible'], + true, + reason: + 'The deprecated autoCancel field should have been removed.'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': 'false'})['autoDismissible'], + false, + reason: + 'The deprecated autoCancel field should have been removed.'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': 1})['autoDismissible'], + true, + reason: + 'The deprecated autoCancel field should have been removed.'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': 0})['autoDismissible'], + false, + reason: + 'The deprecated autoCancel field should have been removed.'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': '1'})['autoDismissible'], + true, + reason: + 'The deprecated autoCancel field should have been removed.'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': '0'})['autoDismissible'], + false, + reason: + 'The deprecated autoCancel field should have been removed.'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'autoCancel': null})['autoDismissible'], + null, + reason: + 'The deprecated autoCancel field should have been removed.'); + }); + + test('AppKilled enum deprecated', () { + expect( + BaseNotificationContent().processRetroCompatibility( + {'someField': 'AppKilled'})['someField'], + NotificationLifeCycle.Terminated.name, + reason: 'The deprecated AppKilled value should have been ' + 'replaced with Terminated.'); + + expect( + BaseNotificationContent().processRetroCompatibility({ + 'someField': NotificationLifeCycle.Terminated.name + })['someField'], + NotificationLifeCycle.Terminated.name, + reason: 'The Terminated value must be preserved'); + + expect( + BaseNotificationContent().processRetroCompatibility( + {'anotherField': 'NotAppKilled'})['anotherField'], + 'NotAppKilled', + reason: 'Values that are not AppKilled should remain unchanged.'); + }); + }); + + group('validate method', () { + test('All valid fields', () { + expect( + () => BaseNotificationContent(id: 1, channelKey: 'channel_key') + .validate(), + returnsNormally, + reason: 'Valid content should not throw an exception.'); + + expect( + () => BaseNotificationContent(id: null, channelKey: 'channel_key') + .validate(), + returnsNormally, + reason: 'id have a value of -1'); + }); + }); + + test('bigPictureImage method', () { + BaseNotificationContent contentWithBigPicture = + BaseNotificationContent(bigPicture: 'asset://path/to/bigPicture'); + + expect(contentWithBigPicture.bigPictureImage, isNotNull, + reason: 'bigPictureImage should not be null when bigPicture is set.'); + + BaseNotificationContent contentWithoutBigPicture = + BaseNotificationContent(); + + expect(contentWithoutBigPicture.bigPictureImage, isNull, + reason: 'bigPictureImage should be null when bigPicture is not set.'); + }); + + test('largeIconImage method', () { + BaseNotificationContent contentWithLargeIcon = + BaseNotificationContent(largeIcon: 'asset://path/to/largeIcon'); + + expect(contentWithLargeIcon.largeIconImage, isNotNull, + reason: 'largeIconImage should not be null when largeIcon is set.'); + + BaseNotificationContent contentWithoutLargeIcon = + BaseNotificationContent(); + + expect(contentWithoutLargeIcon.largeIconImage, isNull, + reason: 'largeIconImage should be null when largeIcon is not set.'); + }); + + test('bigPicturePath method', () { + BaseNotificationContent contentWithBigPicture = + BaseNotificationContent(bigPicture: 'asset://path/to/bigPicture'); + + expect(contentWithBigPicture.bigPicturePath, 'path/to/bigPicture', + reason: + 'bigPicturePath should return the correct path when bigPicture is set.'); + + BaseNotificationContent contentWithoutBigPicture = + BaseNotificationContent(); + + expect(contentWithoutBigPicture.bigPicturePath, isNull, + reason: 'bigPicturePath should be null when bigPicture is not set.'); + }); + + test('largeIconPath method', () { + BaseNotificationContent contentWithLargeIcon = + BaseNotificationContent(largeIcon: 'asset://path/to/largeIcon'); + + expect(contentWithLargeIcon.largeIconPath, 'path/to/largeIcon', + reason: + 'largeIconPath should return the correct path when largeIcon is set.'); + + BaseNotificationContent contentWithoutLargeIcon = + BaseNotificationContent(); + + expect(contentWithoutLargeIcon.largeIconPath, isNull, + reason: 'largeIconPath should be null when largeIcon is not set.'); + }); + + test('titleWithoutHtml method', () { + BaseNotificationContent contentWithTitle = + BaseNotificationContent(title: 'Title'); + + expect(contentWithTitle.titleWithoutHtml, 'Title', + reason: + 'titleWithoutHtml should return the title without HTML tags.'); + + BaseNotificationContent contentWithoutTitle = BaseNotificationContent(); + + expect(contentWithoutTitle.titleWithoutHtml, isNull, + reason: 'titleWithoutHtml should be null when title is not set.'); + }); + + test('bodyWithoutHtml method', () { + BaseNotificationContent contentWithBody = + BaseNotificationContent(body: 'Body'); + + expect(contentWithBody.bodyWithoutHtml, 'Body', + reason: 'bodyWithoutHtml should return the body without HTML tags.'); + + BaseNotificationContent contentWithoutBody = BaseNotificationContent(); + + expect(contentWithoutBody.bodyWithoutHtml, isNull, + reason: 'bodyWithoutHtml should be null when body is not set.'); + }); + }); + + test('Getters and Setters', () { + BaseNotificationContent content = BaseNotificationContent(); + + content.displayedDate = DateTime(2023, 5, 1); + expect(content.displayedDate, DateTime(2023, 5, 1), + reason: 'displayedDate setter and getter should work correctly.'); + + content.createdDate = DateTime(2023, 5, 2); + expect(content.createdDate, DateTime(2023, 5, 2), + reason: 'createdDate setter and getter should work correctly.'); + + content.createdSource = NotificationSource.Firebase; + expect(content.createdSource, NotificationSource.Firebase, + reason: 'createdSource setter and getter should work correctly.'); + + content.createdLifeCycle = NotificationLifeCycle.Terminated; + expect(content.createdLifeCycle, NotificationLifeCycle.Terminated, + reason: 'createdLifeCycle setter and getter should work correctly.'); + + content.displayedLifeCycle = NotificationLifeCycle.Background; + expect(content.displayedLifeCycle, NotificationLifeCycle.Background, + reason: 'displayedLifeCycle setter and getter should work correctly.'); + + content.actionType = ActionType.SilentAction; + expect(content.actionType, ActionType.SilentAction, + reason: 'displayedLifeCycle setter and getter should work correctly.'); + + content.privacy = NotificationPrivacy.Private; + expect(content.privacy, NotificationPrivacy.Private, + reason: 'displayedLifeCycle setter and getter should work correctly.'); + }); + + test('Deprecated Getters and Setters', () { + // Deprecated getters + BaseNotificationContent content = + BaseNotificationContent(autoCancel: false); + expect(content.autoCancel, false, + reason: 'autoCancel getter should return the same as autoDismissible.'); + expect(content.autoCancel, false, + reason: 'autoCancel getter should return the same as autoDismissible.'); + + content = BaseNotificationContent(autoDismissible: false); + expect(content.autoCancel, false, + reason: 'autoCancel getter should return the same as autoDismissible.'); + expect(content.autoDismissable, false, + reason: 'autoCancel getter should return the same as autoDismissible.'); + }); +} diff --git a/test/src/models/model_test.dart b/test/src/models/model_test.dart new file mode 100644 index 00000000..5c199565 --- /dev/null +++ b/test/src/models/model_test.dart @@ -0,0 +1,71 @@ +import 'package:awesome_notifications/src/models/model.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class TestModel extends Model { + int id; + String name; + + TestModel({required this.id, required this.name}); + + @override + Map toMap() { + return { + 'id': id, + 'name': name, + }; + } + + @override + TestModel? fromMap(Map mapData) { + if (mapData.isEmpty) return null; + + return TestModel( + id: mapData['id'], + name: mapData['name'], + ); + } + + @override + void validate() { + if (id <= 0) throw ArgumentError('Invalid id'); + if (name.isEmpty) throw ArgumentError('Invalid name'); + } +} + +void main() { + group('Model tests', () { + test('toMap method', () { + TestModel model = TestModel(id: 1, name: 'Test'); + Map expectedMap = {'id': 1, 'name': 'Test'}; + + expect(model.toMap(), expectedMap); + }); + + test('fromMap method', () { + TestModel model = TestModel(id: 1, name: 'Test'); + Map mapData = {'id': 1, 'name': 'Test'}; + + TestModel? fromMapModel = model.fromMap(mapData); + + expect(fromMapModel?.id, model.id); + expect(fromMapModel?.name, model.name); + }); + + test('toString method', () { + TestModel model = TestModel(id: 1, name: 'Test'); + String expectedString = "{\n \"id\": 1,\n \"name\": \"Test\"\n}"; + + expect(model.toString(), expectedString); + }); + + test('validate method', () { + TestModel validModel = TestModel(id: 1, name: 'Test'); + TestModel invalidModel1 = TestModel(id: 0, name: 'Test'); + TestModel invalidModel2 = TestModel(id: 1, name: ''); + + expect(validModel.validate, returnsNormally); + expect(() => invalidModel1.validate(), throwsA(isA())); + expect(() => invalidModel2.validate(), throwsA(isA())); + }); + }); +} diff --git a/test/src/models/notification_android_crontab_test.dart b/test/src/models/notification_android_crontab_test.dart new file mode 100644 index 00000000..dff75f9b --- /dev/null +++ b/test/src/models/notification_android_crontab_test.dart @@ -0,0 +1,351 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; + +void main() { + group('NotificationAndroidCrontab', () { + test('Constructors and toMap', () { + DateTime initialDate = DateTime(2023, 5, 1); + DateTime expirationDate = DateTime(2023, 5, 31); + DateTime referenceDate = DateTime(2023, 5, 15, 14, 30); + String crontabExpression = '0 30 14 * * *'; + + // Test main constructor + NotificationAndroidCrontab crontab = NotificationAndroidCrontab( + initialDateTime: initialDate, + expirationDateTime: expirationDate, + crontabExpression: crontabExpression); + expect(crontab.initialDateTime, initialDate); + expect(crontab.expirationDateTime, expirationDate); + expect(crontab.crontabExpression, crontabExpression); + + // Test fromDate constructor + NotificationAndroidCrontab crontabFromDate = + NotificationAndroidCrontab.fromDate(date: referenceDate); + expect(crontabFromDate.initialDateTime, referenceDate); + + // Test daily constructor + NotificationAndroidCrontab crontabDaily = + NotificationAndroidCrontab.daily(referenceDateTime: referenceDate); + expect(crontabDaily.crontabExpression, '0 30 14 * * ? *'); + // Check if crontabDaily has a crontabExpression + + // Test other constructors similarly... + + // Test toMap + Map crontabMap = crontab.toMap(); + expect(crontabMap[NOTIFICATION_INITIAL_DATE_TIME], + AwesomeDateUtils.parseDateToString(initialDate)); + expect(crontabMap[NOTIFICATION_EXPIRATION_DATE_TIME], + AwesomeDateUtils.parseDateToString(expirationDate)); + expect(crontabMap[NOTIFICATION_CRONTAB_EXPRESSION], crontabExpression); + }); + + test('NotificationAndroidCrontab toMap with preciseSchedules', () { + DateTime initialDateTime = DateTime(2023, 5, 2, 15, 30); + DateTime expirationDateTime = DateTime(2023, 5, 10, 15, 30); + List preciseSchedules = [ + DateTime(2023, 5, 2, 15, 30), + DateTime(2023, 5, 3, 16, 30), + ]; + String crontabExpression = '0 30 15 2 5 ? *'; + + NotificationAndroidCrontab crontab = NotificationAndroidCrontab( + initialDateTime: initialDateTime, + expirationDateTime: expirationDateTime, + preciseSchedules: preciseSchedules, + crontabExpression: crontabExpression, + ); + + expect(crontab.initialDateTime, initialDateTime); + expect(crontab.expirationDateTime, expirationDateTime); + expect(crontab.preciseSchedules, preciseSchedules); + expect(crontab.crontabExpression, crontabExpression); + + var toMapData = crontab.toMap(); + + expect(toMapData['initialDateTime'], "2023-05-02 15:30:00"); + expect(toMapData['expirationDateTime'], "2023-05-10 15:30:00"); + expect(toMapData['preciseSchedules'], isNotNull); + expect(toMapData['preciseSchedules'][0], "2023-05-02 15:30:00"); + expect(toMapData['preciseSchedules'][1], "2023-05-03 16:30:00"); + expect(toMapData['crontabExpression'], '0 30 15 2 5 ? *'); + }); + + test('fromMap', () { + DateTime initialDate = DateTime(2023, 5, 1); + DateTime expirationDate = DateTime(2023, 5, 31); + String crontabExpression = '0 30 14 * * *'; + + Map crontabMap = { + NOTIFICATION_INITIAL_DATE_TIME: + AwesomeDateUtils.parseDateToString(initialDate), + NOTIFICATION_EXPIRATION_DATE_TIME: + AwesomeDateUtils.parseDateToString(expirationDate), + NOTIFICATION_CRONTAB_EXPRESSION: crontabExpression + }; + + NotificationAndroidCrontab crontab = NotificationAndroidCrontab() + .fromMap(crontabMap) as NotificationAndroidCrontab; + + expect(crontab.initialDateTime, initialDate); + expect(crontab.expirationDateTime, expirationDate); + expect(crontab.crontabExpression, crontabExpression); + }); + }); + + group('NotificationAndroidCrontab factories using local date', () { + test( + 'fromDate constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.fromDate(date: date); + + expect(crontab.initialDateTime, date); + }); + + test( + 'yearly constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.yearly(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 2 5 ? *'); + }); + + test( + 'monthly constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.monthly(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 2 * ? *'); + }); + + test( + 'weekly constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.weekly(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 ? 5 TUE *'); + }); + + test( + 'daily constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.daily(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 * * ? *'); + }); + + test( + 'hourly constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.hourly(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 * * * ? *'); + }); + + test( + 'minutely constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30, 45); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.minutely(referenceDateTime: date); + + expect(crontab.crontabExpression, '45 * * * * ? *'); + }); + + test( + 'workweekDay constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.workweekDay(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 ? * MON-FRI *'); + }); + test( + 'weekendDay constructor creates a NotificationAndroidCrontab with the given date', + () { + DateTime date = DateTime(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.weekendDay(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 ? * SAT,SUN *'); + }); + + test('NotificationAndroidCrontab constructor with preciseSchedules', () { + DateTime initialDateTime = DateTime(2023, 5, 2, 15, 30); + DateTime expirationDateTime = DateTime(2023, 5, 10, 15, 30); + List preciseSchedules = [ + DateTime(2023, 5, 2, 15, 30), + DateTime(2023, 5, 3, 16, 30), + ]; + String crontabExpression = '0 30 15 2 5 ? *'; + + NotificationAndroidCrontab crontab = NotificationAndroidCrontab( + initialDateTime: initialDateTime, + expirationDateTime: expirationDateTime, + preciseSchedules: preciseSchedules, + crontabExpression: crontabExpression, + ); + + expect(crontab.initialDateTime, initialDateTime); + expect(crontab.expirationDateTime, expirationDateTime); + expect(crontab.preciseSchedules, preciseSchedules); + expect(crontab.crontabExpression, crontabExpression); + }); + }); + + group('NotificationAndroidCrontab factories using utc date', () { + test( + 'fromDate constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.fromDate(date: date); + + expect(crontab.initialDateTime, date); + }); + + test( + 'yearly constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.yearly(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 2 5 ? *'); + }); + + test( + 'monthly constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.monthly(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 2 * ? *'); + }); + + test( + 'weekly constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.weekly(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 ? 5 TUE *'); + }); + + test( + 'daily constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.daily(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 * * ? *'); + }); + + test( + 'hourly constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.hourly(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 * * * ? *'); + }); + + test( + 'minutely constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 2, 15, 30, 45); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.minutely(referenceDateTime: date); + + expect(crontab.crontabExpression, '45 * * * * ? *'); + }); + + test( + 'workweekDay constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 2, 15, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.workweekDay(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 30 15 ? * MON-FRI *'); + }); + + test( + 'weekendDay constructor creates a NotificationAndroidCrontab with the given UTC date', + () { + DateTime date = DateTime.utc(2023, 5, 215, 30); + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab.weekendDay(referenceDateTime: date); + + expect(crontab.crontabExpression, '0 0 6 ? * SAT,SUN *'); + }); + + test( + 'NotificationAndroidCrontab constructor creates a NotificationAndroidCrontab with the given UTC parameters', + () { + DateTime initialDateTime = DateTime.utc(2023, 5, 2, 15, 30); + DateTime expirationDateTime = DateTime.utc(2023, 5, 10, 15, 30); + List preciseSchedules = [ + DateTime.utc(2023, 5, 2, 15, 30), + DateTime.utc(2023, 5, 3, 16, 30), + ]; + String crontabExpression = '0 30 15 2 5 ? *'; + + NotificationAndroidCrontab crontab = NotificationAndroidCrontab( + initialDateTime: initialDateTime, + expirationDateTime: expirationDateTime, + preciseSchedules: preciseSchedules, + crontabExpression: crontabExpression, + ); + + expect(crontab.initialDateTime, initialDateTime); + expect(crontab.expirationDateTime, expirationDateTime); + expect(crontab.preciseSchedules, preciseSchedules); + expect(crontab.crontabExpression, crontabExpression); + }); + + test( + 'fromMap constructor populates preciseSchedules correctly when given a valid list of strings', + () { + Map mapData = { + NOTIFICATION_CRONTAB_EXPRESSION: '0 30 15 2 5 ? *', + NOTIFICATION_INITIAL_DATE_TIME: + DateTime(2023, 5, 2, 15, 30).toIso8601String(), + NOTIFICATION_EXPIRATION_DATE_TIME: + DateTime(2023, 5, 10, 15, 30).toIso8601String(), + NOTIFICATION_PRECISE_SCHEDULES: [ + DateTime(2023, 5, 2, 15, 30).toIso8601String(), + DateTime(2023, 5, 3, 16, 30).toIso8601String(), + ], + }; + + NotificationAndroidCrontab crontab = + NotificationAndroidCrontab().fromMap(mapData)!; + + expect(crontab.crontabExpression, '0 30 15 2 5 ? *'); + expect(crontab.initialDateTime, DateTime(2023, 5, 2, 15, 30)); + expect(crontab.expirationDateTime, DateTime(2023, 5, 10, 15, 30)); + expect(crontab.preciseSchedules, [ + DateTime(2023, 5, 2, 15, 30), + DateTime(2023, 5, 3, 16, 30), + ]); + }); + }); +} diff --git a/test/src/models/notification_calendar_test.dart b/test/src/models/notification_calendar_test.dart new file mode 100644 index 00000000..b4ea2b5f --- /dev/null +++ b/test/src/models/notification_calendar_test.dart @@ -0,0 +1,106 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; + +void main() { + group('NotificationCalendar', () { + test('should create a NotificationCalendar object fromMap', () { + Map dataMap = { + NOTIFICATION_SCHEDULE_ERA: 1, + NOTIFICATION_SCHEDULE_YEAR: 2023, + NOTIFICATION_SCHEDULE_MONTH: 5, + NOTIFICATION_SCHEDULE_DAY: 1, + NOTIFICATION_SCHEDULE_HOUR: 12, + NOTIFICATION_SCHEDULE_MINUTE: 30, + NOTIFICATION_SCHEDULE_SECOND: 45, + NOTIFICATION_SCHEDULE_WEEKDAY: 1, + NOTIFICATION_SCHEDULE_WEEKOFYEAR: 23, + }; + + NotificationCalendar calendar = + NotificationCalendar().fromMap(dataMap) as NotificationCalendar; + + expect(calendar.era, 1); + expect(calendar.year, 2023); + expect(calendar.month, 5); + expect(calendar.day, 1); + expect(calendar.hour, 12); + expect(calendar.minute, 30); + expect(calendar.second, 45); + expect(calendar.weekday, 1); + expect(calendar.weekOfYear, 23); + }); + + test( + 'should throw an exception a NotificationCalendar ' + 'object fromMap warning about weekOfMonth is not implemented yet', () { + Map dataMap = { + NOTIFICATION_SCHEDULE_ERA: 1, + NOTIFICATION_SCHEDULE_YEAR: 2023, + NOTIFICATION_SCHEDULE_MONTH: 5, + NOTIFICATION_SCHEDULE_DAY: 1, + NOTIFICATION_SCHEDULE_HOUR: 12, + NOTIFICATION_SCHEDULE_MINUTE: 30, + NOTIFICATION_SCHEDULE_SECOND: 45, + NOTIFICATION_SCHEDULE_WEEKDAY: 1, + NOTIFICATION_SCHEDULE_WEEKOFYEAR: 23, + NOTIFICATION_SCHEDULE_WEEKOFMONTH: 1 + }; + + expect( + () => NotificationCalendar() + ..fromMap(dataMap) + ..validate(), + throwsA(isA())); + }); + + test('should create a NotificationCalendar object fromDate using local', + () { + DateTime date = DateTime(2023, 5, 1, 12, 30, 45); + NotificationCalendar calendar = NotificationCalendar.fromDate(date: date); + + expect(calendar.year, 2023); + expect(calendar.month, 5); + expect(calendar.day, 1); + expect(calendar.hour, 12); + expect(calendar.minute, 30); + expect(calendar.second, 45); + }); + + test('should create a NotificationCalendar object fromDate using UTC', () { + DateTime date = DateTime.utc(2023, 5, 1, 12, 30, 45); + NotificationCalendar calendar = NotificationCalendar.fromDate(date: date); + + expect(calendar.year, 2023); + expect(calendar.month, 5); + expect(calendar.day, 1); + expect(calendar.hour, 12); + expect(calendar.minute, 30); + expect(calendar.second, 45); + }); + + test('should throw an exception if no time condition is provided', () { + expect( + () => NotificationCalendar().validate(), + throwsA(isA().having( + (error) => error.message, + 'message', + 'At least one shedule time condition is required.'))); + }); + + test('should throw an exception if weekOfMonth is used', () { + expect( + () => NotificationCalendar(weekOfMonth: 2), + throwsA(isA().having((error) => error.message, + 'message', 'weekOfMonth is not fully implemented yet'))); + }); + + test('should throw an exception if a time condition is negative', () { + expect( + () => NotificationCalendar(day: -1).validate(), + throwsA(isA().having( + (error) => error.message, + 'message', + 'A shedule time condition must be greater or equal to zero.'))); + }); + }); +} diff --git a/test/src/models/notification_channel_group_test.dart b/test/src/models/notification_channel_group_test.dart new file mode 100644 index 00000000..fc4d8da5 --- /dev/null +++ b/test/src/models/notification_channel_group_test.dart @@ -0,0 +1,82 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; + +void main() { + group('NotificationChannelGroup', () { + test('should create a NotificationChannelGroup object from a map', () { + Map data = { + NOTIFICATION_CHANNEL_GROUP_KEY: 'groupKey', + NOTIFICATION_CHANNEL_GROUP_NAME: 'groupName', + }; + + NotificationChannelGroup? channelGroup = + NotificationChannelGroup(channelGroupKey: '', channelGroupName: '') + .fromMap(data); + + expect(channelGroup, isNotNull); + expect(channelGroup?.channelGroupKey, 'groupKey'); + expect(channelGroup?.channelGroupName, 'groupName'); + }); + + test( + 'should throw an exception when creating a NotificationChannelGroup object with an empty or null channelGroupKey', + () { + Map data = { + NOTIFICATION_CHANNEL_GROUP_NAME: 'groupName', + }; + + expect( + () => NotificationChannelGroup( + channelGroupKey: '', channelGroupName: '') + .fromMap(data) + ?.validate(), + throwsA(isA())); + + data[NOTIFICATION_CHANNEL_GROUP_KEY] = ''; + + expect( + () => NotificationChannelGroup( + channelGroupKey: '', channelGroupName: '') + .fromMap(data) + ?.validate(), + throwsA(isA())); + }); + + test( + 'should throw an exception when creating a NotificationChannelGroup object with an empty or null channelGroupName', + () { + Map data = { + NOTIFICATION_CHANNEL_GROUP_KEY: 'groupKey', + NOTIFICATION_CHANNEL_GROUP_NAME: null, + }; + + expect( + () => NotificationChannelGroup( + channelGroupKey: '', channelGroupName: '') + .fromMap(data) + ?.validate(), + throwsA(isA())); + + data[NOTIFICATION_CHANNEL_GROUP_NAME] = ''; + + expect( + () => NotificationChannelGroup( + channelGroupKey: '', channelGroupName: '') + .fromMap(data) + ?.validate(), + throwsA(isA())); + }); + + test('should convert a NotificationChannelGroup object to a map', () { + NotificationChannelGroup channelGroup = NotificationChannelGroup( + channelGroupKey: 'groupKey', + channelGroupName: 'groupName', + ); + + Map data = channelGroup.toMap(); + + expect(data[NOTIFICATION_CHANNEL_GROUP_KEY], 'groupKey'); + expect(data[NOTIFICATION_CHANNEL_GROUP_NAME], 'groupName'); + }); + }); +} diff --git a/test/src/models/notification_channel_test.dart b/test/src/models/notification_channel_test.dart new file mode 100644 index 00000000..d7ae7d05 --- /dev/null +++ b/test/src/models/notification_channel_test.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; + +void main() { + group('NotificationChannel', () { + test('should create a NotificationChannel object from a map', () { + Map data = { + NOTIFICATION_CHANNEL_KEY: 'channelKey', + NOTIFICATION_CHANNEL_NAME: 'channelName', + NOTIFICATION_CHANNEL_DESCRIPTION: 'channelDescription', + NOTIFICATION_CHANNEL_SHOW_BADGE: true, + NOTIFICATION_PLAY_SOUND: true, + NOTIFICATION_SOUND_SOURCE: 'soundSource', + NOTIFICATION_IMPORTANCE: NotificationImportance.Max.name, + NOTIFICATION_DEFAULT_PRIVACY: NotificationPrivacy.Private.name, + NOTIFICATION_DEFAULT_RINGTONE_TYPE: DefaultRingtoneType.Ringtone.name, + NOTIFICATION_ENABLE_VIBRATION: true, + NOTIFICATION_ENABLE_LIGHTS: true, + NOTIFICATION_LED_COLOR: Colors.red.value, + NOTIFICATION_LED_ON_MS: 1000, + NOTIFICATION_LED_OFF_MS: 1000, + NOTIFICATION_ICON: 'icon', + NOTIFICATION_DEFAULT_COLOR: Colors.blue.value, + }; + + NotificationChannel channel = NotificationChannel( + channelKey: '', channelName: '', channelDescription: '') + .fromMap(data); + + expect(channel.channelKey, 'channelKey'); + expect(channel.channelName, 'channelName'); + expect(channel.channelDescription, 'channelDescription'); + expect(channel.channelShowBadge, true); + expect(channel.playSound, true); + expect(channel.soundSource, 'soundSource'); + expect(channel.importance, NotificationImportance.Max); + expect(channel.defaultPrivacy, NotificationPrivacy.Private); + expect(channel.defaultRingtoneType, DefaultRingtoneType.Ringtone); + expect(channel.enableVibration, true); + expect(channel.enableLights, true); + expect(channel.ledColor, Colors.red.shade500); + expect(channel.ledOnMs, 1000); + expect(channel.ledOffMs, 1000); + expect(channel.icon, 'icon'); + expect(channel.defaultColor, Colors.blue.shade500); + }); + + test( + 'should throw an exception when creating a NotificationChannel object with an empty or null channelKey, channelName, or channelDescription', + () { + Map data = { + NOTIFICATION_CHANNEL_KEY: null, + NOTIFICATION_CHANNEL_NAME: 'channelName', + NOTIFICATION_CHANNEL_DESCRIPTION: 'channelDescription', + }; + + expect( + () => NotificationChannel( + channelKey: '', channelName: '', channelDescription: '') + .fromMap(data) + .validate(), + throwsA(isA())); + + data[NOTIFICATION_CHANNEL_KEY] = ''; + expect( + () => NotificationChannel( + channelKey: '', channelName: '', channelDescription: '') + .fromMap(data) + .validate(), + throwsA(isA())); + + data[NOTIFICATION_CHANNEL_KEY] = 'channelKey'; + data[NOTIFICATION_CHANNEL_NAME] = null; + expect( + () => NotificationChannel( + channelKey: '', channelName: '', channelDescription: '') + .fromMap(data) + .validate(), + throwsA(isA())); + + data[NOTIFICATION_CHANNEL_NAME] = ''; + expect( + () => NotificationChannel( + channelKey: '', channelName: '', channelDescription: '') + .fromMap(data) + .validate(), + throwsA(isA())); + + data[NOTIFICATION_CHANNEL_NAME] = 'channelName'; + data[NOTIFICATION_CHANNEL_DESCRIPTION] = null; + expect( + () => NotificationChannel( + channelKey: '', channelName: '', channelDescription: '') + .fromMap(data) + .validate(), + throwsA(isA())); + + data[NOTIFICATION_CHANNEL_DESCRIPTION] = ''; + expect( + () => NotificationChannel( + channelKey: '', channelName: '', channelDescription: '') + .fromMap(data) + .validate(), + throwsA(isA())); + }); + + test('should convert a NotificationChannel object to a map', () { + NotificationChannel channel = NotificationChannel( + channelKey: 'channelKey', + channelName: 'channelName', + channelDescription: 'channelDescription', + channelShowBadge: true); + + Map data = channel.toMap(); + + expect(data[NOTIFICATION_CHANNEL_KEY], 'channelKey'); + expect(data[NOTIFICATION_CHANNEL_NAME], 'channelName'); + expect(data[NOTIFICATION_CHANNEL_DESCRIPTION], 'channelDescription'); + expect(data[NOTIFICATION_CHANNEL_SHOW_BADGE], true); + }); + }); + group('NotificationChannel constructor assert', () { + test('throws AssertionError if icon is not a resource media type', () { + expect( + () => NotificationChannel( + channelKey: 'channelKey', + channelName: 'channelName', + channelDescription: 'channelDescription', + icon: 'http://example.com/icon.png', + ), + throwsA(isA()), + ); + }); + + test('does not throw AssertionError if icon is a resource media type', () { + expect( + () => NotificationChannel( + channelKey: 'channelKey', + channelName: 'channelName', + channelDescription: 'channelDescription', + icon: 'resource://drawable/res_icon', + ), + returnsNormally, + ); + }); + + test('does not throw AssertionError if icon is null', () { + expect( + () => NotificationChannel( + channelKey: 'channelKey', + channelName: 'channelName', + channelDescription: 'channelDescription', + icon: null, + ), + returnsNormally, + ); + }); + }); +} diff --git a/test/src/models/notification_content_test.dart b/test/src/models/notification_content_test.dart new file mode 100644 index 00000000..f6ec9218 --- /dev/null +++ b/test/src/models/notification_content_test.dart @@ -0,0 +1,144 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('NotificationContent Tests', () { + group('Constructor Tests', () { + test('Default values are properly set', () { + final notificationContent = NotificationContent( + id: 1, + channelKey: 'test_channel', + ); + + expect(notificationContent.id, 1); + expect(notificationContent.channelKey, 'test_channel'); + // Check default values for optional parameters + expect(notificationContent.showWhen, true); + expect(notificationContent.wakeUpScreen, false); + expect(notificationContent.fullScreenIntent, false); + expect(notificationContent.criticalAlert, false); + expect(notificationContent.roundedLargeIcon, false); + expect(notificationContent.roundedBigPicture, false); + expect(notificationContent.autoDismissible, true); + expect(notificationContent.actionType, ActionType.Default); + expect( + notificationContent.notificationLayout, NotificationLayout.Default); + expect(notificationContent.displayOnForeground, true); + expect(notificationContent.displayOnBackground, true); + }); + }); + + group('fromMap Tests', () { + test('Valid data is properly deserialized', () { + final Map mapData = { + 'id': 1, + 'channelKey': 'test_channel', + 'hideLargeIconOnExpand': true, + 'progress': 50, + 'badge': 2, + 'ticker': 'ticker_text', + 'locked': true, + 'notificationLayout': 'BigText', + 'displayOnForeground': false, + 'displayOnBackground': false, + }; + + final notificationContent = NotificationContent( + id: 1, + channelKey: 'test_channel', + ).fromMap(mapData); + + expect(notificationContent, isNotNull); + expect(notificationContent!.id, 1); + expect(notificationContent.channelKey, 'test_channel'); + expect(notificationContent.hideLargeIconOnExpand, true); + expect(notificationContent.progress, 50); + expect(notificationContent.badge, 2); + expect(notificationContent.ticker, 'ticker_text'); + expect(notificationContent.locked, true); + expect( + notificationContent.notificationLayout, NotificationLayout.BigText); + expect(notificationContent.displayOnForeground, false); + expect(notificationContent.displayOnBackground, false); + }); + + test('Invalid data returns null', () { + final Map invalidMapData = { + // Missing required fields + }; + + final notificationContent = NotificationContent( + id: 1, + channelKey: 'test_channel', + ).fromMap(invalidMapData); + + expect(notificationContent, isNull); + }); + }); + + group('toMap Tests', () { + test('Object is properly serialized', () { + final notificationContent = NotificationContent( + id: 1, + channelKey: 'test_channel', + hideLargeIconOnExpand: true, + progress: 50, + badge: 2, + ticker: 'ticker_text', + locked: true, + notificationLayout: NotificationLayout.BigText, + displayOnForeground: false, + displayOnBackground: false, + ); + + final mapData = notificationContent.toMap(); + + expect(mapData, isNotNull); + expect(mapData['id'], 1); + expect(mapData['channelKey'], 'test_channel'); + expect(mapData['hideLargeIconOnExpand'], true); + expect(mapData['progress'], 50); + expect(mapData['badge'], 2); + expect(mapData['ticker'], 'ticker_text'); + expect(mapData['locked'], true); + expect(mapData['notificationLayout'], 'BigText'); + expect(mapData['displayOnForeground'], false); + expect(mapData['displayOnBackground'], false); + }); + }); + group('toString Tests', () { + test('Object is properly converted to String', () { + final notificationContent = NotificationContent( + id: 1, + channelKey: 'test_channel', + hideLargeIconOnExpand: true, + progress: 50, + badge: 2, + ticker: 'ticker_text', + locked: true, + notificationLayout: NotificationLayout.BigText, + displayOnForeground: false, + displayOnBackground: false, + ); + + final stringRepresentation = notificationContent.toString(); + + expect(stringRepresentation, isNotNull); + expect(stringRepresentation.contains('id: 1'), true); + expect(stringRepresentation.contains('channelKey: test_channel'), true); + expect( + stringRepresentation.contains('hideLargeIconOnExpand: true'), true); + expect(stringRepresentation.contains('progress: 50'), true); + expect(stringRepresentation.contains('badge: 2'), true); + expect(stringRepresentation.contains('ticker: ticker_text'), true); + expect(stringRepresentation.contains('locked: true'), true); + expect( + stringRepresentation.contains('notificationLayout: BigText'), true); + expect( + stringRepresentation.contains('displayOnForeground: false'), true); + expect( + stringRepresentation.contains('displayOnBackground: false'), true); + }); + }); + }); +} diff --git a/test/src/models/notification_interval_test.dart b/test/src/models/notification_interval_test.dart new file mode 100644 index 00000000..40f74b16 --- /dev/null +++ b/test/src/models/notification_interval_test.dart @@ -0,0 +1,95 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; + +void main() { + group('NotificationInterval', () { + test('should create a NotificationInterval object fromMap', () { + Map dataMap = { + NOTIFICATION_SCHEDULE_INTERVAL: 120, + }; + + NotificationInterval interval = NotificationInterval(interval: 120) + .fromMap(dataMap) as NotificationInterval; + + expect(interval.interval, 120); + }); + + test('should throw an exception if interval is negative', () { + expect( + () => NotificationInterval(interval: -1).validate(), + throwsA(isA().having( + (error) => error.message, + 'message', + 'interval must be greater or equal to zero.'))); + }); + + test('should throw an exception if interval is less than 60 and repeating', + () { + expect( + () => NotificationInterval(interval: 59, repeats: true).validate(), + throwsA(isA().having( + (error) => error.message, + 'message', + 'time interval must be greater or equal to 60 if repeating'))); + }); + }); + group('NotificationInterval - toMap and fromMap', () { + test('should create a NotificationInterval object and convert it to a map', + () { + NotificationInterval interval = NotificationInterval(interval: 120); + Map intervalMap = interval.toMap(); + expect(intervalMap[NOTIFICATION_SCHEDULE_INTERVAL], 120); + }); + + test('should create a NotificationInterval object from a map', () { + Map dataMap = { + NOTIFICATION_SCHEDULE_INTERVAL: 120, + }; + NotificationInterval interval = NotificationInterval(interval: 120) + .fromMap(dataMap) as NotificationInterval; + expect(interval.interval, 120); + }); + + test( + 'should create a NotificationInterval object and convert it back from a map', + () { + NotificationInterval interval = NotificationInterval( + interval: 120, + timeZone: 'America/New_York', + allowWhileIdle: true, + repeats: true, + preciseAlarm: true, + ); + Map intervalMap = interval.toMap(); + NotificationInterval intervalFromMap = NotificationInterval(interval: 0) + .fromMap(intervalMap) as NotificationInterval; + + expect(intervalFromMap.interval, 120); + expect(intervalFromMap.timeZone, 'America/New_York'); + expect(intervalFromMap.allowWhileIdle, true); + expect(intervalFromMap.repeats, true); + expect(intervalFromMap.preciseAlarm, true); + }); + }); + + group('NotificationInterval - toString', () { + test( + 'should return a string representation of a NotificationInterval object', + () { + NotificationInterval interval = NotificationInterval( + interval: 120, + timeZone: 'America/New_York', + allowWhileIdle: true, + repeats: true, + preciseAlarm: true, + ); + + String intervalString = interval.toString(); + expect(intervalString.contains('interval: 120'), true); + expect(intervalString.contains('timeZone: America/New_York'), true); + expect(intervalString.contains('allowWhileIdle: true'), true); + expect(intervalString.contains('repeats: true'), true); + expect(intervalString.contains('preciseAlarm: true'), true); + }); + }); +} diff --git a/test/src/models/notification_localization_test.dart b/test/src/models/notification_localization_test.dart new file mode 100644 index 00000000..4c7aab16 --- /dev/null +++ b/test/src/models/notification_localization_test.dart @@ -0,0 +1,95 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; + +void main() { + group('NotificationLocalization', () { + test('should create a NotificationLocalization object from a map', () { + Map data = { + NOTIFICATION_TITLE: 'Title', + NOTIFICATION_BODY: 'Body', + NOTIFICATION_SUMMARY: 'Summary', + NOTIFICATION_LARGE_ICON: 'LargeIcon', + NOTIFICATION_BIG_PICTURE: 'BigPicture', + NOTIFICATION_BUTTON_LABELS: {'key1': 'Label1', 'key2': 'Label2'}, + }; + + NotificationLocalization? localization = + NotificationLocalization().fromMap(data); + + expect(localization, isNotNull); + expect(localization?.title, 'Title'); + expect(localization?.body, 'Body'); + expect(localization?.summary, 'Summary'); + expect(localization?.largeIcon, 'LargeIcon'); + expect(localization?.bigPicture, 'BigPicture'); + expect(localization?.buttonLabels, {'key1': 'Label1', 'key2': 'Label2'}); + }); + + test( + 'should not create an empty NotificationLocalization object when given an empty map', + () { + Map data = {}; + + NotificationLocalization? localization = + NotificationLocalization().fromMap(data); + + expect(localization, isNull); + }); + + test('should convert a NotificationLocalization object to a map', () { + NotificationLocalization localization = NotificationLocalization( + title: 'Title', + body: 'Body', + summary: 'Summary', + largeIcon: 'LargeIcon', + bigPicture: 'BigPicture', + buttonLabels: {'key1': 'Label1', 'key2': 'Label2'}, + ); + + Map data = localization.toMap(); + + expect(data[NOTIFICATION_TITLE], 'Title'); + expect(data[NOTIFICATION_BODY], 'Body'); + expect(data[NOTIFICATION_SUMMARY], 'Summary'); + expect(data[NOTIFICATION_LARGE_ICON], 'LargeIcon'); + expect(data[NOTIFICATION_BIG_PICTURE], 'BigPicture'); + expect(data[NOTIFICATION_BUTTON_LABELS], + {'key1': 'Label1', 'key2': 'Label2'}); + }); + + test( + 'should not include null or empty values in the map when converting a NotificationLocalization object to a map', + () { + NotificationLocalization localization = NotificationLocalization( + title: null, + body: '', + summary: null, + largeIcon: null, + bigPicture: null, + buttonLabels: null, + ); + + Map data = localization.toMap(); + + expect(data.containsKey(NOTIFICATION_TITLE), false); + expect(data.containsKey(NOTIFICATION_BODY), false); + expect(data.containsKey(NOTIFICATION_SUMMARY), false); + expect(data.containsKey(NOTIFICATION_LARGE_ICON), false); + expect(data.containsKey(NOTIFICATION_BIG_PICTURE), false); + expect(data.containsKey(NOTIFICATION_BUTTON_LABELS), false); + }); + }); + + test('validate tests', () { + NotificationLocalization localization = NotificationLocalization( + title: null, + body: '', + summary: null, + largeIcon: null, + bigPicture: null, + buttonLabels: null, + ); + + expect(() => localization.validate(), returnsNormally); + }); +} diff --git a/test/src/models/notification_model_test.dart b/test/src/models/notification_model_test.dart new file mode 100644 index 00000000..f3bdcb81 --- /dev/null +++ b/test/src/models/notification_model_test.dart @@ -0,0 +1,256 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('fromMap and toMap', () { + test('using notification interval', () { + NotificationContent content = NotificationContent( + id: 1, + channelKey: 'test_channel', + title: 'Test Title', + body: 'Test Body', + ); + + NotificationSchedule schedule = NotificationInterval(interval: 60); + + List actionButtons = [ + NotificationActionButton( + label: 'Accept', + key: 'accept', + ), + NotificationActionButton( + label: 'Decline', + key: 'decline', + ), + ]; + + Map localizations = { + 'en_US': NotificationLocalization( + title: 'English Title', + body: 'English Body', + ), + 'es_ES': NotificationLocalization( + title: 'Título en Español', + body: 'Cuerpo en Español', + ), + }; + + Map dataMap = { + 'content': content.toMap(), + 'schedule': schedule.toMap(), + 'actionButtons': actionButtons.map((button) => button.toMap()).toList(), + 'localizations': + localizations.map((key, value) => MapEntry(key, value.toMap())), + }; + + NotificationModel? notificationModel = + NotificationModel().fromMap(dataMap); + + expect(notificationModel, isNotNull); + expect(notificationModel?.content, isNotNull); + expect(notificationModel?.content?.title, 'Test Title'); + expect(notificationModel?.content?.body, 'Test Body'); + + expect(notificationModel?.schedule, isNotNull); + + expect(notificationModel?.actionButtons, isNotNull); + expect(notificationModel?.actionButtons?.length, 2); + expect(notificationModel?.actionButtons?[0].label, 'Accept'); + expect(notificationModel?.actionButtons?[1].label, 'Decline'); + + expect(notificationModel?.localizations, isNotNull); + expect( + notificationModel?.localizations?['en_US']?.title, 'English Title'); + expect(notificationModel?.localizations?['es_ES']?.title, + 'Título en Español'); + + Map newDataMap = notificationModel?.toMap() ?? {}; + expect(newDataMap['content']['title'], 'Test Title'); + expect(newDataMap['content']['body'], 'Test Body'); + + // ... add assertions for schedule properties in newDataMap + expect(newDataMap['actionButtons'][0]['label'], 'Accept'); + expect(newDataMap['actionButtons'][1]['label'], 'Decline'); + + expect(newDataMap['localizations']['en_US']['title'], 'English Title'); + expect( + newDataMap['localizations']['es_ES']['title'], 'Título en Español'); + }); + + test('using notification callendar', () { + NotificationContent content = NotificationContent( + id: 1, + channelKey: 'test_channel', + title: 'Test Title', + body: 'Test Body', + ); + + NotificationSchedule schedule = NotificationCalendar(second: 0); + + List actionButtons = [ + NotificationActionButton( + label: 'Accept', + key: 'accept', + ), + NotificationActionButton( + label: 'Decline', + key: 'decline', + ), + ]; + + Map localizations = { + 'en_US': NotificationLocalization( + title: 'English Title', + body: 'English Body', + ), + 'es_ES': NotificationLocalization( + title: 'Título en Español', + body: 'Cuerpo en Español', + ), + }; + + Map dataMap = { + 'content': content.toMap(), + 'schedule': schedule.toMap(), + 'actionButtons': actionButtons.map((button) => button.toMap()).toList(), + 'localizations': + localizations.map((key, value) => MapEntry(key, value.toMap())), + }; + + NotificationModel? notificationModel = + NotificationModel().fromMap(dataMap); + + expect(notificationModel, isNotNull); + expect(notificationModel?.content, isNotNull); + expect(notificationModel?.content?.title, 'Test Title'); + expect(notificationModel?.content?.body, 'Test Body'); + + expect(notificationModel?.schedule, isNotNull); + + expect(notificationModel?.actionButtons, isNotNull); + expect(notificationModel?.actionButtons?.length, 2); + expect(notificationModel?.actionButtons?[0].label, 'Accept'); + expect(notificationModel?.actionButtons?[1].label, 'Decline'); + + expect(notificationModel?.localizations, isNotNull); + expect( + notificationModel?.localizations?['en_US']?.title, 'English Title'); + expect(notificationModel?.localizations?['es_ES']?.title, + 'Título en Español'); + + Map newDataMap = notificationModel?.toMap() ?? {}; + expect(newDataMap['content']['title'], 'Test Title'); + expect(newDataMap['content']['body'], 'Test Body'); + + // ... add assertions for schedule properties in newDataMap + expect(newDataMap['actionButtons'][0]['label'], 'Accept'); + expect(newDataMap['actionButtons'][1]['label'], 'Decline'); + + expect(newDataMap['localizations']['en_US']['title'], 'English Title'); + expect( + newDataMap['localizations']['es_ES']['title'], 'Título en Español'); + }); + + test('using notification crontab', () { + NotificationContent content = NotificationContent( + id: 1, + channelKey: 'test_channel', + title: 'Test Title', + body: 'Test Body', + ); + + NotificationSchedule schedule = + NotificationAndroidCrontab(crontabExpression: '0 30 14 * * *'); + + List actionButtons = [ + NotificationActionButton( + label: 'Accept', + key: 'accept', + ), + NotificationActionButton( + label: 'Decline', + key: 'decline', + ), + ]; + + Map localizations = { + 'en_US': NotificationLocalization( + title: 'English Title', + body: 'English Body', + ), + 'es_ES': NotificationLocalization( + title: 'Título en Español', + body: 'Cuerpo en Español', + ), + }; + + Map dataMap = { + 'content': content.toMap(), + 'schedule': schedule.toMap(), + 'actionButtons': actionButtons.map((button) => button.toMap()).toList(), + 'localizations': + localizations.map((key, value) => MapEntry(key, value.toMap())), + }; + + NotificationModel? notificationModel = + NotificationModel().fromMap(dataMap); + + expect(notificationModel, isNotNull); + expect(notificationModel?.content, isNotNull); + expect(notificationModel?.content?.title, 'Test Title'); + expect(notificationModel?.content?.body, 'Test Body'); + + expect(notificationModel?.schedule, isNotNull); + + expect(notificationModel?.actionButtons, isNotNull); + expect(notificationModel?.actionButtons?.length, 2); + expect(notificationModel?.actionButtons?[0].label, 'Accept'); + expect(notificationModel?.actionButtons?[1].label, 'Decline'); + + expect(notificationModel?.localizations, isNotNull); + expect( + notificationModel?.localizations?['en_US']?.title, 'English Title'); + expect(notificationModel?.localizations?['es_ES']?.title, + 'Título en Español'); + + Map newDataMap = notificationModel?.toMap() ?? {}; + expect(newDataMap['content']['title'], 'Test Title'); + expect(newDataMap['content']['body'], 'Test Body'); + + // ... add assertions for schedule properties in newDataMap + expect(newDataMap['actionButtons'][0]['label'], 'Accept'); + expect(newDataMap['actionButtons'][1]['label'], 'Decline'); + + expect(newDataMap['localizations']['en_US']['title'], 'English Title'); + expect( + newDataMap['localizations']['es_ES']['title'], 'Título en Español'); + }); + }); + + group('validate', () { + test('validate throws an exception when content is null', () { + NotificationModel notificationModel = NotificationModel(); + + expect( + () => notificationModel.validate(), + throwsA(isA()), + ); + }); + + test('validate does not throw an exception when content is not null', () { + NotificationContent content = NotificationContent( + id: 1, + channelKey: 'test_channel', + title: 'Test Title', + body: 'Test Body', + ); + + NotificationModel notificationModel = NotificationModel(content: content); + + expect( + () => notificationModel.validate(), + returnsNormally, + ); + }); + }); +} diff --git a/test/src/models/notification_schedule_test.dart b/test/src/models/notification_schedule_test.dart new file mode 100644 index 00000000..ac2deff4 --- /dev/null +++ b/test/src/models/notification_schedule_test.dart @@ -0,0 +1,52 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('toString', () { + test('toString prints the respective inherited notification model', () { + NotificationSchedule schedule1 = NotificationCalendar(second: 0); + NotificationSchedule schedule2 = NotificationInterval(interval: 60); + NotificationSchedule schedule3 = + NotificationAndroidCrontab(crontabExpression: '0 30 14 * * *'); + + expect( + schedule1.toString(), + '{timeZone: UTC,\n' + ' allowWhileIdle: false,\n' + ' repeats: false,\n' + ' preciseAlarm: true,\n' + ' delayTolerance: 600000,\n' + ' era: null,\n' + ' year: null,\n' + ' month: null,\n' + ' day: null,\n' + ' hour: null,\n' + ' minute: null,\n' + ' second: 0,\n' + ' weekday: null,\n' + ' weekOfMonth: null,\n' + ' weekOfYear: null}'); + + expect( + schedule2.toString(), + '{timeZone: UTC,\n' + ' allowWhileIdle: false,\n' + ' repeats: false,\n' + ' preciseAlarm: true,\n' + ' delayTolerance: 600000,\n' + ' interval: 60}'); + + expect( + schedule3.toString(), + '{timeZone: UTC,\n' + ' allowWhileIdle: false,\n' + ' repeats: false,\n' + ' preciseAlarm: true,\n' + ' delayTolerance: 600000,\n' + ' crontabExpression: 0 30 14 * * *,\n' + ' initialDateTime: null,\n' + ' expirationDateTime: null,\n' + ' preciseSchedules: null}'); + }); + }); +} diff --git a/test/src/models/notificaton_button_test.dart b/test/src/models/notificaton_button_test.dart new file mode 100644 index 00000000..3ae654cd --- /dev/null +++ b/test/src/models/notificaton_button_test.dart @@ -0,0 +1,154 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter/material.dart'; + +// ignore_for_file: deprecated_member_use_from_same_package + +void main() { + group('NotificationActionButton', () { + test('toMap and fromMap should work properly', () { + NotificationActionButton actionButton = NotificationActionButton( + key: 'test_key', + label: 'test_label', + icon: 'test_icon', + enabled: true, + requireInputText: false, + autoDismissible: true, + showInCompactView: false, + isDangerousOption: true, + color: Colors.red, + actionType: ActionType.Default, + ); + + Map actionButtonMap = actionButton.toMap(); + + expect(actionButton.key, actionButtonMap['key']); + expect(actionButton.label, actionButtonMap['label']); + expect(actionButton.icon, actionButtonMap['icon']); + expect(actionButton.enabled, actionButtonMap['enabled']); + expect( + actionButton.requireInputText, actionButtonMap['requireInputText']); + expect(actionButton.autoDismissible, actionButtonMap['autoDismissible']); + expect( + actionButton.showInCompactView, actionButtonMap['showInCompactView']); + expect( + actionButton.isDangerousOption, actionButtonMap['isDangerousOption']); + expect(actionButton.color!.value, actionButtonMap['color']); + expect(actionButton.actionType!.name, actionButtonMap['actionType']); + }); + + test('fromMap should work properly', () { + Map actionButtonMap = { + 'key': 'test_key', + 'label': 'test_label', + 'icon': 'test_icon', + 'enabled': true, + 'requireInputText': false, + 'autoDismissible': true, + 'showInCompactView': false, + 'isDangerousOption': true, + 'color': Colors.red.value, + 'actionType': ActionType.Default.name, + }; + + NotificationActionButton? actionButton = + NotificationActionButton(key: '', label: '').fromMap(actionButtonMap); + + expect(actionButton, isNotNull); + expect(actionButton?.key, 'test_key'); + expect(actionButton?.label, 'test_label'); + expect(actionButton?.icon, 'test_icon'); + expect(actionButton?.enabled, true); + expect(actionButton?.requireInputText, false); + expect(actionButton?.autoDismissible, true); + expect(actionButton?.showInCompactView, false); + expect(actionButton?.isDangerousOption, true); + expect(actionButton?.color, Colors.red.shade500); + expect(actionButton?.actionType, ActionType.Default); + }); + }); + + group('Validate tests', () { + test('should throw exception if key is null or empty', () { + NotificationActionButton actionButton = NotificationActionButton( + key: '', label: 'test_label', actionType: ActionType.Default); + expect(() => actionButton.validate(), + throwsA(isA())); + }); + + test('should throw exception if label is null or empty', () { + NotificationActionButton actionButton = NotificationActionButton( + key: 'test_key', label: '', actionType: ActionType.Default); + expect(() => actionButton.validate(), + throwsA(isA())); + }); + + test('should throw exception if icon is not a native resource media type', + () { + NotificationActionButton actionButton = NotificationActionButton( + key: 'test_key', + label: 'test_label', + icon: 'http://example.com/icon.png', + actionType: ActionType.Default); + expect(() => actionButton.validate(), + throwsA(isA())); + }); + + test('should not throw exception for valid action button', () { + NotificationActionButton actionButton = NotificationActionButton( + key: 'test_key', + label: 'test_label', + icon: 'resource://test_icon', + actionType: ActionType.Default); + expect(() => actionButton.validate(), returnsNormally); + }); + }); + + group('NotificationActionButton processRetroCompatibility', () { + test('should process autoCancel', () { + NotificationActionButton actionButton = NotificationActionButton( + key: 'test_key', label: 'test_label', actionType: ActionType.Default); + Map dataMap = actionButton.toMap(); + dataMap['autoCancel'] = true; + + actionButton.processRetroCompatibility(dataMap); + + expect(dataMap[NOTIFICATION_AUTO_DISMISSIBLE], isTrue); + }); + + test('should process buttonType', () { + NotificationActionButton actionButton = NotificationActionButton( + key: 'test_key', label: 'test_label', actionType: ActionType.Default); + Map dataMap = actionButton.toMap(); + dataMap['buttonType'] = ActionType.InputField.name; + + actionButton.processRetroCompatibility(dataMap); + + expect(actionButton.requireInputText, equals(true)); + }); + }); + + group('NotificationActionButton adaptInputFieldToRequireText', () { + test('should adapt InputField to requireInputText', () { + NotificationActionButton actionButton = NotificationActionButton( + key: 'test_key', + label: 'test_label', + actionType: ActionType.InputField); + + actionButton.adaptInputFieldToRequireText(); + + expect(actionButton.requireInputText, isTrue); + expect(actionButton.actionType, equals(ActionType.SilentAction)); + }); + + test('should not change anything for other actionTypes', () { + NotificationActionButton actionButton = NotificationActionButton( + key: 'test_key', label: 'test_label', actionType: ActionType.Default); + + actionButton.adaptInputFieldToRequireText(); + + expect(actionButton.requireInputText, isFalse); + expect(actionButton.actionType, equals(ActionType.Default)); + }); + }); +} diff --git a/test/src/models/received_models/push_notificaton_test.dart b/test/src/models/received_models/push_notificaton_test.dart new file mode 100644 index 00000000..4abb93d5 --- /dev/null +++ b/test/src/models/received_models/push_notificaton_test.dart @@ -0,0 +1,41 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('PushNotification', () { + test( + 'constructor creates a PushNotification with given content, schedule, and actionButtons', + () { + NotificationContent content = NotificationContent( + id: 1, + channelKey: 'test_channel', + title: 'Test Title', + body: 'Test Body', + ); + + NotificationSchedule schedule = NotificationInterval(interval: 60); + + List actionButtons = [ + NotificationActionButton( + label: 'Accept', + key: 'accept', + ), + NotificationActionButton( + label: 'Decline', + key: 'decline', + ), + ]; + + // ignore: deprecated_member_use_from_same_package + PushNotification pushNotification = PushNotification( + content: content, + schedule: schedule, + actionButtons: actionButtons, + ); + + expect(pushNotification.content, content); + expect(pushNotification.schedule, schedule); + expect(pushNotification.actionButtons, actionButtons); + }); + }); +} diff --git a/test/src/models/received_models/received_action_test.dart b/test/src/models/received_models/received_action_test.dart new file mode 100644 index 00000000..2e7e5a30 --- /dev/null +++ b/test/src/models/received_models/received_action_test.dart @@ -0,0 +1,38 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('ReceivedAction', () { + test('fromMap and toMap', () { + Map dataMap = { + // Add data for the ReceivedNotification superclass + // ... + 'actionLifeCycle': NotificationLifeCycle.Foreground.name, + 'dismissedLifeCycle': NotificationLifeCycle.Terminated.name, + 'actionDate': '2023-05-01 12:34:56', + 'dismissedDate': '2023-05-01 12:35:56', + 'buttonKeyPressed': 'testButton', + 'buttonKeyInput': 'testInput' + }; + + ReceivedAction receivedAction = ReceivedAction().fromMap(dataMap); + expect(receivedAction.actionLifeCycle, NotificationLifeCycle.Foreground); + expect( + receivedAction.dismissedLifeCycle, NotificationLifeCycle.Terminated); + expect(receivedAction.actionDate, DateTime(2023, 5, 1, 12, 34, 56)); + expect(receivedAction.dismissedDate, DateTime(2023, 5, 1, 12, 35, 56)); + expect(receivedAction.buttonKeyPressed, 'testButton'); + expect(receivedAction.buttonKeyInput, 'testInput'); + + Map newDataMap = receivedAction.toMap(); + expect( + newDataMap['actionLifeCycle'], NotificationLifeCycle.Foreground.name); + expect(newDataMap['dismissedLifeCycle'], + NotificationLifeCycle.Terminated.name); + expect(newDataMap['actionDate'], '2023-05-01 12:34:56'); + expect(newDataMap['dismissedDate'], '2023-05-01 12:35:56'); + expect(newDataMap['buttonKeyPressed'], 'testButton'); + expect(newDataMap['buttonKeyInput'], 'testInput'); + }); + }); +} diff --git a/test/src/models/received_models/received_notification_test.dart b/test/src/models/received_models/received_notification_test.dart new file mode 100644 index 00000000..aa7593d8 --- /dev/null +++ b/test/src/models/received_models/received_notification_test.dart @@ -0,0 +1,39 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('ReceivedNotification', () { + test('fromMap and toMap', () { + Map dataMap = { + // Add data for the BaseNotificationContent superclass + // ... + 'createdSource': NotificationSource.Firebase.name, + 'createdLifeCycle': NotificationLifeCycle.Foreground.name, + 'displayedLifeCycle': NotificationLifeCycle.Background.name, + 'createdDate': '2023-05-01 12:34:56', + 'displayedDate': '2023-05-01 12:35:56', + }; + + ReceivedNotification receivedNotification = + ReceivedNotification().fromMap(dataMap); + expect(receivedNotification.createdSource, NotificationSource.Firebase); + expect(receivedNotification.createdLifeCycle, + NotificationLifeCycle.Foreground); + expect(receivedNotification.displayedLifeCycle, + NotificationLifeCycle.Background); + expect( + receivedNotification.createdDate, DateTime(2023, 5, 1, 12, 34, 56)); + expect( + receivedNotification.displayedDate, DateTime(2023, 5, 1, 12, 35, 56)); + + Map newDataMap = receivedNotification.toMap(); + expect(newDataMap['createdSource'], NotificationSource.Firebase.name); + expect(newDataMap['createdLifeCycle'], + NotificationLifeCycle.Foreground.name); + expect(newDataMap['displayedLifeCycle'], + NotificationLifeCycle.Background.name); + expect(newDataMap['createdDate'], '2023-05-01 12:34:56'); + expect(newDataMap['displayedDate'], '2023-05-01 12:35:56'); + }); + }); +} diff --git a/test/src/utils/assert_utils_test.dart b/test/src/utils/assert_utils_test.dart new file mode 100644 index 00000000..0b951362 --- /dev/null +++ b/test/src/utils/assert_utils_test.dart @@ -0,0 +1,448 @@ +import 'dart:typed_data'; + +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:awesome_notifications/src/models/model.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart' hide DateUtils; +import 'package:flutter_test/flutter_test.dart'; + +enum TestEnum { value1, value2 } + +class TestModel extends Model { + TestModel({this.id, this.name}); + + final int? id; + final String? name; + + @override + TestModel fromMap(Map map) { + return TestModel( + id: map['id'] as int?, + name: map['name'] as String?, + ); + } + + @override + Map toMap() { + return { + 'id': id, + 'name': name, + }; + } + + @override + void validate() {} +} + +class MockModel extends Model { + final int id; + + MockModel({required this.id}); + + @override + MockModel fromMap(Map map) { + return MockModel(id: map['id']); + } + + @override + Map toMap() { + return {'id': id}; + } + + @override + void validate() {} +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() {}); + + tearDown(() {}); + + test('isNullOrEmptyOrInvalid() tests', () { + expect(AwesomeAssertUtils.isNullOrEmptyOrInvalid(""), true); + expect(AwesomeAssertUtils.isNullOrEmptyOrInvalid("Hello"), false); + expect(AwesomeAssertUtils.isNullOrEmptyOrInvalid(123), true); + }); + + test('toSimpleEnumString() tests', () { + expect(AwesomeAssertUtils.toSimpleEnumString(TestEnum.value1), 'value1'); + expect(AwesomeAssertUtils.toSimpleEnumString(TestEnum.value2), 'value2'); + }); + + test('getValueOrDefault() tests', () { + // Test for MaterialColor + expect(AwesomeAssertUtils.getValueOrDefault('key', Colors.red), + Colors.red.shade500); + + // Test for MaterialAccentColor + expect( + AwesomeAssertUtils.getValueOrDefault('key', Colors.pinkAccent), + Color(Colors.pinkAccent.value)); + + // Test for CupertinoDynamicColor + expect( + AwesomeAssertUtils.getValueOrDefault( + 'key', CupertinoColors.systemBlue), + Color(CupertinoColors.systemBlue.value)); + + // Test for invalid value + expect(AwesomeAssertUtils.getValueOrDefault('key', 123), null); + }); + + test('extractValueTest', () async { + expect("title", + AwesomeAssertUtils.extractValue("test", {"test": "title"})); + expect(" title", + AwesomeAssertUtils.extractValue("test", {"test": " title"})); + expect("", AwesomeAssertUtils.extractValue("test", {"test": ""})); + expect(" ", AwesomeAssertUtils.extractValue("test", {"test": " "})); + + expect( + AwesomeAssertUtils.extractValue( + "test", {"test": Uint8List.fromList([])}), + Uint8List.fromList([])); + + expect(10, AwesomeAssertUtils.extractValue("test", {"test": "10"})); + expect(10, AwesomeAssertUtils.extractValue("test", {"test": 10})); + expect(10, AwesomeAssertUtils.extractValue("test", {"test": "10.0"})); + expect(10.0, + AwesomeAssertUtils.extractValue("test", {"test": "10.0"})); + expect(0, AwesomeAssertUtils.extractValue("test", {"test": "0"})); + expect(0.0, AwesomeAssertUtils.extractValue("test", {"test": "0"})); + expect(0, AwesomeAssertUtils.extractValue("test", {"test": "0.0"})); + expect(0, AwesomeAssertUtils.extractValue("test", {"test": 0})); + + expect(0xFFFF0000, + AwesomeAssertUtils.extractValue("test", {"test": "#FF0000"})); + expect(0xFFFF0000, + AwesomeAssertUtils.extractValue("test", {"test": "#ff0000"})); + expect(0xFFFF0000, + AwesomeAssertUtils.extractValue("test", {"test": "#FFFF0000"})); + expect(0x00FF0000, + AwesomeAssertUtils.extractValue("test", {"test": "#00FF0000"})); + expect(0xFFFF0000, + AwesomeAssertUtils.extractValue("test", {"test": "0xFF0000"})); + expect(0xFFFF0000, + AwesomeAssertUtils.extractValue("test", {"test": "0xFFff0000"})); + + expect(Colors.black, + AwesomeAssertUtils.extractValue("test", {"test": "#000000"})); + expect(Colors.black, + AwesomeAssertUtils.extractValue("test", {"test": "#FF000000"})); + expect(Colors.transparent, + AwesomeAssertUtils.extractValue("test", {"test": "#00000000"})); + + expect( + null, AwesomeAssertUtils.extractValue("test", {"test": null})); + expect(null, + AwesomeAssertUtils.extractValue("test", {"test": "#0004"})); + expect( + null, AwesomeAssertUtils.extractValue("test", {"test": "#04"})); + expect(null, AwesomeAssertUtils.extractValue("test", {"test": " "})); + + expect(null, AwesomeAssertUtils.extractValue("test", {"test": null})); + expect(null, AwesomeAssertUtils.extractValue("test", {"test": ""})); + expect(null, AwesomeAssertUtils.extractValue("test", {"test": " "})); + + expect(null, AwesomeAssertUtils.extractValue("test", {"test": 0})); + expect( + null, AwesomeAssertUtils.extractValue("test", {"test": null})); + + expect(true, AwesomeAssertUtils.extractValue("test", {"test": true})); + expect( + true, AwesomeAssertUtils.extractValue("test", {"test": "true"})); + expect(false, + AwesomeAssertUtils.extractValue("test", {"test": "false"})); + }); + + test('extractEnumTest', () async { + expect( + NotificationPrivacy.Private, + AwesomeAssertUtils.extractEnum( + "test", {"test": "Private"}, NotificationPrivacy.values)); + + expect( + NotificationPrivacy.Public, + AwesomeAssertUtils.extractEnum( + "test", {"test": "Public"}, NotificationPrivacy.values)); + + expect( + null, + AwesomeAssertUtils.extractEnum( + "test", {"test": ""}, NotificationPrivacy.values)); + + expect( + null, + AwesomeAssertUtils.extractEnum( + "test", {"test": " "}, NotificationPrivacy.values)); + + expect( + null, + AwesomeAssertUtils.extractEnum( + "test", {"test": null}, NotificationPrivacy.values)); + }); + + group('AwesomeAssertUtils tests', () { + test('toSimpleEnumString returns the correct string representation', () { + const testEnumValue = NotificationPrivacy.Secret; + + final result = AwesomeAssertUtils.toSimpleEnumString(testEnumValue); + expect(result, 'Secret'); + }); + + test('isNullOrEmptyOrInvalid returns the correct boolean value', () { + expect(AwesomeAssertUtils.isNullOrEmptyOrInvalid(null), true); + expect(AwesomeAssertUtils.isNullOrEmptyOrInvalid(''), true); + expect(AwesomeAssertUtils.isNullOrEmptyOrInvalid(123), true); + expect(AwesomeAssertUtils.isNullOrEmptyOrInvalid('test'), false); + }); + + test('toListMap returns the correct list of maps', () { + final input = [ + TestModel(id: 1, name: 'test1'), + TestModel(id: 2, name: 'test2') + ]; + + final result = AwesomeAssertUtils.toListMap(input); + + expect( + result, + [ + {'id': 1, 'name': 'test1'}, + {'id': 2, 'name': 'test2'} + ], + ); + }); + + // Add more tests for the remaining methods + }); + group('fromListMap', () { + // Other test cases... + + test('returns the correct list of models when using fromListMap', () { + final listData = [ + {'id': 1}, + {'id': 2}, + {'id': 3}, + ]; + + final result = AwesomeAssertUtils.fromListMap( + listData, + () => MockModel(id: 0), + ); + + expect(result, isNotNull); + expect(result!.length, 3); + expect(result[0].id, 1); + expect(result[1].id, 2); + expect(result[2].id, 3); + }); + + test('returns null when mapData is null in fromListMap', () { + final result = AwesomeAssertUtils.fromListMap( + null, + () => MockModel(id: 0), + ); + expect(result, isNull); + }); + + test('returns null when mapData is not a list in fromListMap', () { + final mapData = {'id': 1}; + final result = AwesomeAssertUtils.fromListMap( + mapData, + () => MockModel(id: 0), + ); + expect(result, isNull); + }); + + test('returns null when list is empty in fromListMap', () { + final listData = []; + final result = AwesomeAssertUtils.fromListMap( + listData, + () => MockModel(id: 0), + ); + expect(result, isNull); + }); + }); + + group('extractValue', () { + test('returns the correct value when T is DateTime', () { + final dataMap = {'reference': '2021-09-01T10:00:00'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, DateTime.parse('2021-09-01T10:00:00')); + }); + + test( + 'returns the correct value when T is DateTime and value is string with timezone', + () { + final dataMap = {'reference': '2021-09-01 10:00:00 UTC'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, DateTime.parse('2021-09-01T10:00:00Z')); + }); + + test('returns the default value when T is DateTime and input is invalid', + () { + final dataMap = {'reference': 'invalid date'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, null); + }); + + test('returns the correct value when T is a string with double', () { + final dataMap = {'reference': '10.5'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, 10.5); + }); + + test('returns the correct value when T is double', () { + final dataMap = {'reference': 10.5}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, 10.5); + }); + + test('returns the default value when T is double and input is invalid', () { + final dataMap = {'reference': 'invalid double'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, null); + }); + + test('returns the correct value when T is bool', () { + final dataMap = {'reference': 'true'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, true); + }); + + test('returns the correct value when T is bool and input is 1', () { + final dataMap = {'reference': '1'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, true); + }); + + test('returns the default value when T is bool and input is invalid', () { + final dataMap = {'reference': 'invalid bool'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, null); + }); + + test('returns the correct value when T is int and value is double', () { + final dataMap = {'reference': 10.5}; + final result = AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, 11); + }); + + test('returns the default value when T is int and value is invalid', () { + final dataMap = {'reference': 'invalid int'}; + final result = AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, null); + }); + + test('returns the correct value when T is double and value is int', () { + final dataMap = {'reference': 10}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, 10.0); + }); + + test('returns the correct value when T is Color and value is MaterialColor', + () { + final dataMap = {'reference': Colors.blue}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, Colors.blue.shade500); + }); + + test( + 'returns the correct value when T is Color and value is MaterialAccentColor', + () { + final dataMap = {'reference': Colors.pinkAccent}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, Color(Colors.pinkAccent.value)); + }); + + test( + 'returns the correct value when T is Color and value is CupertinoDynamicColor', + () { + final dataMap = {'reference': CupertinoColors.systemBlue}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, Color(CupertinoColors.systemBlue.value)); + }); + + test('returns the default value when T is Color and value is invalid', () { + final dataMap = {'reference': 'invalid color'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, null); + }); + + test('returns the correct value when T is int and value is hex string', () { + final dataMap = {'reference': '0xFF123456'}; + final result = AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, 0xFF123456); + }); + + test('returns the correct value when T is Color and value is hex string', + () { + final dataMap = {'reference': '#FF123456'}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, const Color(0xFF123456)); + }); + + test('returns the correct value when T is Color and value is an integer', + () { + final dataMap = {'reference': 0xFF123456}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, const Color(0xFF123456)); + }); + + test( + 'returns the default value when T is int and value is invalid hex string', + () { + final dataMap = {'reference': 'invalid hex'}; + final result = AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, null); + }); + + test( + 'returns the correct value when T is int and value is a decimal string', + () { + final dataMap = {'reference': '10.5'}; + final result = AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, 10); + }); + + test('returns the correct value when T is bool and value is null', () { + final dataMap = {'reference': null}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, null); + }); + + test('returns the correct value when T is bool and value is int', () { + final dataMap = {'reference': 1}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, true); + }); + + test('returns the correct value when T is bool and value is false', () { + final dataMap = {'reference': false}; + final result = + AwesomeAssertUtils.extractValue('reference', dataMap); + expect(result, false); + }); + }); +} diff --git a/test/src/utils/audio_utils_test.dart b/test/src/utils/audio_utils_test.dart new file mode 100644 index 00000000..fced1aa3 --- /dev/null +++ b/test/src/utils/audio_utils_test.dart @@ -0,0 +1,28 @@ +import 'package:awesome_notifications/src/utils/audio_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + final audioUtils = AwesomeAudioUtils(); + + group('getFromMediaPath()', () { + test('Unknown media path', () { + expect(audioUtils.getFromMediaPath('unknown:///path/to/audio.mp3'), null); + }); + + test('Network media path', () { + expect(audioUtils.getFromMediaPath('https://example.com/audio.mp3'), null); + }); + + test('File media path', () { + expect(audioUtils.getFromMediaPath('file://path/to/audio.mp3'), null); + }); + + test('Asset media path', () { + expect(audioUtils.getFromMediaPath('asset://path/to/audio.mp3'), null); + }); + + test('Resource media path', () { + expect(audioUtils.getFromMediaPath('resource://path/to/audio.mp3'), null); + }); + }); +} \ No newline at end of file diff --git a/test/src/utils/bitmap_utils_test.dart b/test/src/utils/bitmap_utils_test.dart new file mode 100644 index 00000000..bef1a9a3 --- /dev/null +++ b/test/src/utils/bitmap_utils_test.dart @@ -0,0 +1,40 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + late AwesomeBitmapUtils bitmapUtils; + + setUp(() { + bitmapUtils = AwesomeBitmapUtils(); + }); + + group('getFromMediaPath()', () { + test('Unknown media path', () { + expect(bitmapUtils.getFromMediaPath('unknown:///path/to/image.png'), null); + }); + + test('Network media path', () { + ImageProvider? imageProvider1 = bitmapUtils.getFromMediaPath('http://example.com/image.png'); + expect(imageProvider1, isA()); + + ImageProvider? imageProvider2 = bitmapUtils.getFromMediaPath('https://example.com/image.png'); + expect(imageProvider2, isA()); + }); + + test('File media path', () { + ImageProvider? imageProvider = bitmapUtils.getFromMediaPath('file://path/to/image.png'); + expect(imageProvider, isA()); + }); + + test('Asset media path', () { + ImageProvider? imageProvider = bitmapUtils.getFromMediaPath('asset://path/to/image.png'); + expect(imageProvider, isA()); + }); + + test('Resource media path', () { + ImageProvider? imageProvider = bitmapUtils.getFromMediaPath('resource://path/to/image.png'); + expect(imageProvider, isA()); + }); + }); +} diff --git a/test/src/utils/date_utils_test.dart b/test/src/utils/date_utils_test.dart new file mode 100644 index 00000000..028e906f --- /dev/null +++ b/test/src/utils/date_utils_test.dart @@ -0,0 +1,49 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('parseStringToDate()', () { + test('Null string', () { + expect(AwesomeDateUtils.parseStringToDateUtc(null), null); + }); + + test('Empty string', () { + expect(AwesomeDateUtils.parseStringToDateUtc(''), null); + }); + + test('Valid date string', () { + expect( + AwesomeDateUtils.parseStringToDateUtc('2023-01-01 00:00:00') + ?.toIso8601String(), + '2023-01-01T00:00:00.000Z'); + }); + }); + + group('parseDateToString()', () { + test('Null DateTime', () { + expect(AwesomeDateUtils.parseDateToString(null), null); + }); + + test('Valid DateTime', () { + DateTime dateTime = DateTime.parse('2023-01-01T00:00:00.000Z'); + expect( + AwesomeDateUtils.parseDateToString(dateTime), '2023-01-01 00:00:00'); + }); + }); + + group('utcToLocal()', () { + test('UTC to Local conversion', () { + DateTime utcDate = DateTime.parse('2023-01-01T00:00:00.000Z'); + DateTime localDate = AwesomeDateUtils.utcToLocal(utcDate); + expect(localDate.isAtSameMomentAs(utcDate.toLocal()), true); + }); + }); + + group('localToUtc()', () { + test('Local to UTC conversion', () { + DateTime localDate = DateTime.parse('2023-01-01T00:00:00.000Z').toLocal(); + DateTime utcDate = AwesomeDateUtils.localToUtc(localDate); + expect(utcDate.isAtSameMomentAs(localDate.toUtc()), true); + }); + }); +} diff --git a/test/src/utils/html_utils_test.dart b/test/src/utils/html_utils_test.dart new file mode 100644 index 00000000..1e17ff7a --- /dev/null +++ b/test/src/utils/html_utils_test.dart @@ -0,0 +1,38 @@ +import 'package:awesome_notifications/src/utils/html_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('removeAllHtmlTags()', () { + test('Null string', () { + expect(AwesomeHtmlUtils.removeAllHtmlTags(null), null); + }); + + test('Empty string', () { + expect(AwesomeHtmlUtils.removeAllHtmlTags(""), ""); + }); + + test('Plain text without HTML tags', () { + expect(AwesomeHtmlUtils.removeAllHtmlTags("Hello, World!"), "Hello, World!"); + }); + + test('Text with single HTML tag', () { + expect( + AwesomeHtmlUtils.removeAllHtmlTags("

Hello, World!

"), + "Hello, World!"); + }); + + test('Text with nested HTML tags', () { + expect( + AwesomeHtmlUtils.removeAllHtmlTags( + "

Hello, World!

"), + "Hello, World!"); + }); + + test('Text with multiple HTML tags', () { + expect( + AwesomeHtmlUtils.removeAllHtmlTags( + "

Title

Paragraph with emphasis.

"), + "TitleParagraph with emphasis."); + }); + }); +} \ No newline at end of file diff --git a/test/src/utils/list_utils_test.dart b/test/src/utils/list_utils_test.dart new file mode 100644 index 00000000..ce3250e5 --- /dev/null +++ b/test/src/utils/list_utils_test.dart @@ -0,0 +1,18 @@ +import 'package:awesome_notifications/src/utils/list_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('isNullOrEmpty test', () { + test('detect if list is null or empty', () { + expect(AwesomeListUtils.isNullOrEmpty(null), true); + expect(AwesomeListUtils.isNullOrEmpty([]), true); + }); + + test('detect if a non empty list is not empty', () { + expect(AwesomeListUtils.isNullOrEmpty([null]), false); + expect(AwesomeListUtils.isNullOrEmpty([1]), false); + expect(AwesomeListUtils.isNullOrEmpty([1, 2]), false); + expect(AwesomeListUtils.isNullOrEmpty([1, 2, 3]), false); + }); + }); +} diff --git a/test/src/utils/map_utils_test.dart b/test/src/utils/map_utils_test.dart new file mode 100644 index 00000000..1a505b1a --- /dev/null +++ b/test/src/utils/map_utils_test.dart @@ -0,0 +1,52 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('Test with non-empty map', () { + Map mapData = { + "c": 3, + "a": 1, + "b": 2, + "nested": {"y": 5, "x": 4}, + }; + + String expectedOutput = +'''{ + "a": 1, + "b": 2, + "c": 3, + "nested": { + "y": 5, + "x": 4 + } +}'''; + + expect(AwesomeMapUtils.printPrettyMap(mapData), expectedOutput); + }); + + test('Test with empty map', () { + Map emptyMapData = {}; + + String expectedEmptyOutput = '{}'; + expect(AwesomeMapUtils.printPrettyMap(emptyMapData), expectedEmptyOutput); + }); + + test('Test with null values', () { + Map mapWithNullValues = { + "a": null, + "b": 2, + "c": null, + }; + + String expectedOutputWithNullValues = +'''{ + "a": null, + "b": 2, + "c": null +}'''; + + expect( + AwesomeMapUtils.printPrettyMap(mapWithNullValues), + expectedOutputWithNullValues); + }); +} \ No newline at end of file diff --git a/test/src/utils/media_abstract_utils_test.dart b/test/src/utils/media_abstract_utils_test.dart new file mode 100644 index 00000000..8c8fdb8f --- /dev/null +++ b/test/src/utils/media_abstract_utils_test.dart @@ -0,0 +1,114 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:awesome_notifications/src/utils/media_abstract_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class TestAwesomeMediaUtils extends AwesomeMediaUtils { + @override + getFromMediaAsset(String mediaPath) { + return null; + } + + @override + getFromMediaNetwork(String mediaPath) { + return null; + } + + @override + getFromMediaResource(String mediaPath) { + return null; + } +} + +void main() { + final mediaUtils = TestAwesomeMediaUtils(); + + group('getMediaSource()', () { + test('Null media path', () { + expect(mediaUtils.getMediaSource(null), MediaSource.Unknown); + }); + + test('Network media path', () { + expect( + mediaUtils.getMediaSource('http://example.com/image.jpg'), + MediaSource.Network); + expect( + mediaUtils.getMediaSource('https://example.com/image.jpg'), + MediaSource.Network); + }); + + test('File media path', () { + expect( + mediaUtils.getMediaSource('file://path/to/image.jpg'), + MediaSource.File); + }); + + test('Asset media path', () { + expect( + mediaUtils.getMediaSource('asset://path/to/image.jpg'), + MediaSource.Asset); + }); + + test('Resource media path', () { + expect( + mediaUtils.getMediaSource('resource://path/to/image.jpg'), + MediaSource.Resource); + }); + }); + + group('cleanMediaPath()', () { + test('File media path', () { + expect(mediaUtils.cleanMediaPath('file://path/to/image.jpg'), + '/path/to/image.jpg'); + }); + + test('Asset media path', () { + expect(mediaUtils.cleanMediaPath('asset://path/to/image.jpg'), + 'path/to/image.jpg'); + }); + + test('Resource media path', () { + expect(mediaUtils.cleanMediaPath('resource://path/to/image.jpg'), + 'path/to/image.jpg'); + }); + + test('Non-prefixed media path', () { + expect(mediaUtils.cleanMediaPath('/path/to/image.jpg'), + '/path/to/image.jpg'); + }); + }); + + // Since getFromMediaAsset, getFromMediaNetwork, and getFromMediaResource + // are marked as @protected, you should test getFromMediaPath() method + // which internally calls these methods. + group('getFromMediaPath()', () { + test('Unknown media path', () { + expect(mediaUtils.getFromMediaPath('unknown://path/to/image.jpg'), + null); + expect(mediaUtils.getFromMediaPath('resources://path/to/image.jpg'), + null); + expect(mediaUtils.getFromMediaPath('assets://path/to/image.jpg'), + null); + expect(mediaUtils.getFromMediaPath('files://path/to/image.jpg'), + null); + }); + + test('Network media path', () { + expect(mediaUtils.getFromMediaPath('https://example.com/image.jpg'), + null); + }); + + test('File media path', () { + // Due to the usage of FileImage, this test case will not work in this example. + // You should test this on a real device or emulator. + }); + + test('Asset media path', () { + expect(mediaUtils.getFromMediaPath('asset://path/to/image.jpg'), null); + }); + + test('Resource media path', () { + expect(mediaUtils.getFromMediaPath('resource://path/to/image.jpg'), + null); + }); + }); +} diff --git a/test/src/utils/media_abstract_utils_web_test.dart b/test/src/utils/media_abstract_utils_web_test.dart new file mode 100644 index 00000000..0f928630 --- /dev/null +++ b/test/src/utils/media_abstract_utils_web_test.dart @@ -0,0 +1,67 @@ +import 'package:awesome_notifications/src/utils/media_abstract_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:awesome_notifications/src/enumerators/media_source.dart'; + +class TestAwesomeMediaUtils extends AwesomeMediaUtils { + @override + getFromMediaAsset(String mediaPath) { + return null; + } + + @override + getFromMediaNetwork(String mediaPath) { + return null; + } + + @override + getFromMediaResource(String mediaPath) { + return null; + } +} + +void main() { + late TestAwesomeMediaUtils mediaUtils; + + setUp(() { + mediaUtils = TestAwesomeMediaUtils(); + }); + + group('AwesomeMediaUtils', () { + + test('Get media source', () { + expect(mediaUtils.getMediaSource('https://example.com/image.png'), + MediaSource.Network); + expect(mediaUtils.getMediaSource('file://path/to/image.png'), + MediaSource.File); + expect(mediaUtils.getMediaSource('asset://path/to/image.png'), + MediaSource.Asset); + expect(mediaUtils.getMediaSource('resource://path/to/image.png'), + MediaSource.Resource); + expect(mediaUtils.getMediaSource(null), MediaSource.Unknown); + }); + + test('Clean media path', () { + expect( + mediaUtils.cleanMediaPath('file://path/to/image.png'), + '/path/to/image.png'); + expect( + mediaUtils.cleanMediaPath('asset://path/to/image.png'), + 'path/to/image.png'); + expect( + mediaUtils.cleanMediaPath('resource://path/to/image.png'), + 'path/to/image.png'); + expect(mediaUtils.cleanMediaPath('https://example.com/image.png'), + 'https://example.com/image.png'); + }); + + test('Get from media path', () { + expect(mediaUtils.getFromMediaPath('http://example.com/image.png'), + null); + expect(mediaUtils.getFromMediaPath('https://example.com/image.png'), + null); + expect(mediaUtils.getFromMediaPath('asset://path/to/image.png'), null); + expect(mediaUtils.getFromMediaPath('resource://path/to/image.png'), + null); + }); + }); +} diff --git a/test/src/utils/resource_image_provider_test.dart b/test/src/utils/resource_image_provider_test.dart new file mode 100644 index 00000000..a3f165e2 --- /dev/null +++ b/test/src/utils/resource_image_provider_test.dart @@ -0,0 +1,110 @@ +import 'dart:ui'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/material.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:mocktail/mocktail.dart'; +import 'dart:ui' as ui show Codec; + +// Mock the AwesomeNotifications class +class MockAwesomeNotifications extends Mock implements AwesomeNotifications {} + +class MockCodec extends Mock implements ui.Codec {} + +void main() { + group('ResourceImage tests', () { + test('ResourceImage constructor creates an instance with provided data', + () { + ResourceImage resourceImage = + const ResourceImage('drawable_path', scale: 2.0); + + expect(resourceImage.drawablePath, 'drawable_path'); + expect(resourceImage.scale, 2.0); + }); + + test('ResourceImage equality and hashCode', () { + ResourceImage resourceImage1 = + const ResourceImage('drawable_path', scale: 2.0); + ResourceImage resourceImage2 = + const ResourceImage('drawable_path', scale: 2.0); + ResourceImage resourceImage3 = + const ResourceImage('different_drawable_path', scale: 2.0); + ResourceImage resourceImage4 = + const ResourceImage('drawable_path', scale: 1.0); + + expect(resourceImage1.hashCode, resourceImage2.hashCode); + expect(resourceImage1, resourceImage2); + expect(resourceImage1.hashCode != resourceImage3.hashCode, true); + expect(resourceImage1 != resourceImage3, true); + expect(resourceImage1.hashCode != resourceImage4.hashCode, true); + expect(resourceImage1 != resourceImage4, true); + }); + + testWidgets('ResourceImage loads and displays the image', + (WidgetTester tester) async { + // Replace the real AwesomeNotifications class with the mock + AwesomeNotifications awesomeNotifications = MockAwesomeNotifications(); + // Mock the getDrawableData method to return Uint8List of a small 1x1 pixel image + when(() => awesomeNotifications.getDrawableData(any())) + .thenAnswer((_) async { + final byteData = + await rootBundle.load('test/assets/images/test_image.png'); + return byteData.buffer.asUint8List(); + }); + + ResourceImage resourceImage = ResourceImage('drawable_path', + scale: 2.0, awesomeNotifications: awesomeNotifications); + + // Create a widget to test the ResourceImage + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Builder( + builder: (BuildContext context) { + return Image( + image: resourceImage, + ); + }, + ), + ), + ), + ); + + // Trigger a frame + await tester.pump(); + + // Verify that the image is displayed + final Finder imageFinder = find.byType(Image); + expect(imageFinder, findsOneWidget); + + // Clean up after the test + await tester.pumpWidget(Container()); + }); + + testWidgets('ResourceImage fails to load and display the invalid image', + (WidgetTester tester) async { + // Replace the real AwesomeNotifications class with the mock + AwesomeNotifications awesomeNotifications = MockAwesomeNotifications(); + // Mock the getDrawableData method to return Uint8List of a small 1x1 pixel image + when(() => awesomeNotifications.getDrawableData(any())) + .thenAnswer((_) async { + return Uint8List(0); + }); + + expect( + () { + ResourceImage resourceImage = ResourceImage('drawable_path', + scale: 2.0, awesomeNotifications: awesomeNotifications); + return resourceImage.loadAsync( + resourceImage, + (buffer, { + TargetImageSizeCallback? getTargetSize, + }) => + Future.value(MockCodec())); + }, + throwsA(isA()), + ); + }); + }); +} diff --git a/test/src/utils/string_utils_test.dart b/test/src/utils/string_utils_test.dart new file mode 100644 index 00000000..a7a84f67 --- /dev/null +++ b/test/src/utils/string_utils_test.dart @@ -0,0 +1,30 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('isNullOrEmpty()', () { + test('Empty string', () { + expect(AwesomeStringUtils.isNullOrEmpty(""), true); + }); + + test('Null string', () { + expect(AwesomeStringUtils.isNullOrEmpty(null), true); + }); + + test('Non-empty string', () { + expect(AwesomeStringUtils.isNullOrEmpty("Hello"), false); + }); + + test('White space string with considerWhiteSpaceAsEmpty = false', () { + expect(AwesomeStringUtils.isNullOrEmpty(" ", considerWhiteSpaceAsEmpty: false), false); + }); + + test('White space string with considerWhiteSpaceAsEmpty = true', () { + expect(AwesomeStringUtils.isNullOrEmpty(" ", considerWhiteSpaceAsEmpty: true), true); + }); + + test('White space and newline string with considerWhiteSpaceAsEmpty = true', () { + expect(AwesomeStringUtils.isNullOrEmpty(" \n", considerWhiteSpaceAsEmpty: true), true); + }); + }); +} \ No newline at end of file