├── sample
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ └── styles.xml
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── layout
│ │ │ ├── activity_detail.xml
│ │ │ └── activity_main.xml
│ │ ├── java
│ │ └── pl
│ │ │ └── tajchert
│ │ │ └── sample
│ │ │ ├── DetailActivity.kt
│ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── library
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── pl
│ │ └── tajchert
│ │ └── houston
│ │ ├── NotificationCategory.kt
│ │ ├── HoustonLandReceiver.kt
│ │ ├── NotificationWrapper.kt
│ │ └── Houston.kt
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── img
└── icon_library.jpg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── LICENSE
├── gradlew.bat
├── README.md
└── gradlew
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library', ':sample'
2 |
--------------------------------------------------------------------------------
/img/icon_library.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tajchert/Houston/HEAD/img/icon_library.jpg
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tajchert/Houston/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Houston
3 |
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Houston Sample
3 |
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tajchert/Houston/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tajchert/Houston/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tajchert/Houston/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tajchert/Houston/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tajchert/Houston/HEAD/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jun 13 23:44:11 CEST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/java/pl/tajchert/sample/DetailActivity.kt:
--------------------------------------------------------------------------------
1 | package pl.tajchert.sample
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 |
6 | class DetailActivity : AppCompatActivity() {
7 |
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 | setContentView(R.layout.activity_detail)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/library/src/main/java/pl/tajchert/houston/NotificationCategory.kt:
--------------------------------------------------------------------------------
1 | package pl.tajchert.houston
2 |
3 | class NotificationCategory(var title: String?) {
4 |
5 | override fun equals(o: Any?): Boolean {
6 | if (this === o) return true
7 | if (o == null || javaClass != o.javaClass) return false
8 |
9 | val notificationCategory = o as NotificationCategory?
10 |
11 | return if (title != null) title == notificationCategory!!.title else notificationCategory!!.title == null
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/workspace.xml
38 | .idea/
39 | .DS_Store
40 |
41 | # Keystore files
42 | *.jks
43 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/library/src/main/java/pl/tajchert/houston/HoustonLandReceiver.kt:
--------------------------------------------------------------------------------
1 | package pl.tajchert.houston
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 |
7 | class HoustonLandReceiver : BroadcastReceiver() {
8 |
9 | override fun onReceive(
10 | context: Context,
11 | intent: Intent
12 | ) {
13 | if (intent.hasExtra(NOTIF_ID)) {
14 | val id = intent.getIntExtra(NOTIF_ID, Integer.MAX_VALUE)
15 | if (id != Integer.MAX_VALUE) {
16 | val houston = Houston(context)
17 | houston.land(id)
18 | }
19 | }
20 | }
21 |
22 | companion object {
23 | val NOTIF_ID = "pl.tajchert.houston_NOTIF_ID"
24 | }
25 | }
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\mtajc\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\mtajc\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | group='com.github.tajchert'
5 |
6 | android {
7 | compileSdkVersion 28
8 | buildToolsVersion "28.0.3"
9 |
10 | defaultConfig {
11 | minSdkVersion 14
12 | targetSdkVersion 28
13 | versionCode 1
14 | versionName "0.1.1"
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | implementation 'com.google.code.gson:gson:2.8.5'
26 | implementation 'androidx.appcompat:appcompat:1.0.2'
27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
28 | }
29 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/library/src/main/java/pl/tajchert/houston/NotificationWrapper.kt:
--------------------------------------------------------------------------------
1 | package pl.tajchert.houston
2 |
3 | class NotificationWrapper {
4 | var id: Int = 0
5 | var tag: String? = null
6 | var category: NotificationCategory? = null
7 | var showTime: Long? = null
8 |
9 | fun equals(tag: String?): Boolean {
10 | return if (tag != null) tag == this.tag else this.tag == null
11 | }
12 |
13 | fun equals(notificationCategory: NotificationCategory?): Boolean {
14 | return if (this.category == null) false else notificationCategory != null && category == notificationCategory
15 | }
16 |
17 | override fun equals(o: Any?): Boolean {
18 | if (this === o) return true
19 | if (o == null || javaClass != o.javaClass) return false
20 | val yawn = o as NotificationWrapper?
21 | return id == yawn!!.id
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | android.enableJetifier=true
13 | android.useAndroidX=true
14 | org.gradle.jvmargs=-Xmx1536m
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | # org.gradle.parallel=true
20 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 28
7 | buildToolsVersion "28.0.3"
8 |
9 | defaultConfig {
10 | applicationId "pl.tajchert.houston.sample"
11 | minSdkVersion 14
12 | targetSdkVersion 28
13 | versionCode 1
14 | versionName "1.0"
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | implementation fileTree(dir: 'libs', include: ['*.jar'])
26 | implementation 'androidx.appcompat:appcompat:1.0.2'
27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
28 | implementation 'androidx.core:core-ktx:1.2.0-alpha01'
29 | implementation project(':library')
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Michal Tajchert
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
24 |
32 |
41 |
50 |
59 |
60 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Houston - Notification Helper for Android
2 | [](https://jitpack.io/#tajchert/Houston) [](https://jitci.com/gh/tajchert/Houston)
3 |
4 |
5 |
6 | "Houston, we've had a problem here" - managing notifications on Android is a pain sometime. You need to store Ids for all of them if you wish to dismiss some of them.
7 |
8 | Example: in news app you have categories and you notify user about new articles in each category. But you would like to easily dismiss notifications about particular category when user opens it, or particular one when it was clicked without storing all Ids and managing them.
9 |
10 | ### Houston to the rescue!
11 |
12 | Houston aim is to allow easy show, hide and track all notifications displayed without hassle of Ids.
13 |
14 |
15 |
16 | To display notification:
17 | ```
18 | houston.launch(id, notification);
19 | //or
20 | houston.launch(id, notification, "anyCategory")
21 | ```
22 |
23 | To dismiss all notifications:
24 |
25 | ```
26 | houston.land(id); //hides only notifications with particular Id
27 | //or
28 | houston.landAll("anyCategory"); //hides only notifications with particular category
29 | //or
30 | houston.landAll();
31 | ```
32 |
33 | Also you can get list of notifications:
34 | ```
35 | houston.getNotification(id);
36 | houston.getNotifications("anyCategory");
37 | houston.getNotifications();
38 | ```
39 |
40 | To track dismissed notifications:
41 | ```
42 | //create notification
43 | houston.addDismissListener(notificationBuilder, id, context);
44 | //here goes houston.launch(...);
45 | ```
46 |
47 | To track clicked notifications:
48 | ```
49 | houston.removeNotification(id);
50 | ```
51 |
52 |
53 | Refresh (not obligatory, works only on API >= 23):
54 | ```
55 | houston.refreshList();
56 | //In such case calling removeNotification() on opened activity is not needed
57 | ```
58 |
59 | ### How to add?
60 |
61 | Gradle depedency:
62 | Add Jitpack in your root build.gradle at the end of repositories:
63 | ```gradle
64 | allprojects {
65 | repositories {
66 | ...
67 | maven { url "https://jitpack.io" }
68 | }
69 | }
70 | ```
71 | Add the dependency itself:
72 | ```gradle
73 | compile 'com.github.tajchert:Houston:0.2.0'
74 | ```
75 |
76 |
77 |
78 | ### Proguard:
79 | ```
80 | -keep class pl.tajchert.houston.NotificationWrapper
81 | -keep class pl.tajchert.houston.NotificationCategory
82 |
83 | -keepclassmembers class pl.tajchert.houston.NotificationWrapper { *; }
84 | -keepclassmembers class pl.tajchert.houston.NotificationCategory { *; }
85 | ```
86 |
87 | >"Persistence is very important" Elon Musk - SpaceX CEO
88 |
89 | So we have:
90 | ```
91 | houston.persistNotifications();//to save all notification currently available in getNotifications()
92 | ```
93 | :)
94 |
95 | ### AndroidX, Kotlin...
96 | AndroidX since version 2.0.0, last build without is 0.1.5. Kotlin since 0.2.0.
97 |
98 | ### ToDo
99 |
100 | It is on launch sequence list but any Pull Request with those features are more than welcomed.
101 |
102 | * Special filter for ongoing notifications
103 | * Persist order of notifications (priority?)
104 | * Allow to group notifications (by category?) show new group notification and hide previous ones
105 |
106 |
107 | ### WIP
108 | This is very simple - categories and tags. But in most cases enought, any suggestions or Pull Request to make it more powerful are welcomed.
109 |
--------------------------------------------------------------------------------
/sample/src/main/java/pl/tajchert/sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package pl.tajchert.sample
2 |
3 | import android.app.Notification
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.app.PendingIntent
7 | import android.content.Intent
8 | import android.graphics.BitmapFactory
9 | import android.os.Build
10 | import android.os.Bundle
11 | import android.widget.Toast
12 | import androidx.appcompat.app.AppCompatActivity
13 | import androidx.core.app.NotificationCompat
14 | import androidx.core.app.TaskStackBuilder
15 | import kotlinx.android.synthetic.main.activity_main.buttonCount
16 | import kotlinx.android.synthetic.main.activity_main.buttonHideOne
17 | import kotlinx.android.synthetic.main.activity_main.buttonHideTwo
18 | import kotlinx.android.synthetic.main.activity_main.buttonSendOne
19 | import kotlinx.android.synthetic.main.activity_main.buttonSendTwo
20 | import pl.tajchert.houston.Houston
21 | import java.util.Random
22 |
23 | class MainActivity : AppCompatActivity() {
24 | private var houston: Houston? = null
25 | private val random = Random()
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | setContentView(R.layout.activity_main)
30 | houston = Houston(this)
31 | setClickListeners()
32 | }
33 |
34 | private fun setClickListeners() {
35 | buttonSendOne.setOnClickListener {
36 | showNotification("Notification #1", "categoryOne")
37 | }
38 | buttonSendTwo.setOnClickListener {
39 | showNotification("Notification #2", "categoryTwo")
40 | }
41 | buttonHideOne.setOnClickListener {
42 | houston!!.landAll("categoryOne")
43 | }
44 | buttonHideTwo.setOnClickListener {
45 | houston!!.landAll("categoryTwo")
46 | }
47 | buttonCount.setOnClickListener {
48 | houston!!.getNotifications("categoryOne")
49 | houston!!.getNotifications("categoryTwo")
50 | val notifications = houston!!.notifications
51 | Toast.makeText(this, notifications.size.toString() + " notifications", Toast.LENGTH_SHORT)
52 | .show()
53 | }
54 | }
55 |
56 | override fun onResume() {
57 | super.onResume()
58 | houston!!.refreshList()
59 | }
60 |
61 | override fun onDestroy() {
62 | houston!!.onDestroy()
63 | super.onDestroy()
64 | }
65 |
66 | fun showNotification(title: String) {
67 | val notificationId = random.nextInt()
68 |
69 | val notificationBuilder = buildTestNotification(title, notificationId)
70 | houston!!.addDismissListener(notificationBuilder, notificationId, this@MainActivity)
71 | houston!!.launch(notificationId, notificationBuilder.build())
72 | }
73 |
74 | fun showNotification(
75 | title: String,
76 | category: String
77 | ) {
78 | val notificationId = random.nextInt()
79 |
80 | val notificationBuilder = buildTestNotification(title, notificationId)
81 | houston!!.addDismissListener(notificationBuilder, notificationId, this@MainActivity)
82 | houston!!.launch(notificationId, notificationBuilder.build(), category)
83 | }
84 |
85 | private fun buildTestNotification(
86 | title: String,
87 | notificationId: Int
88 | ): NotificationCompat.Builder {
89 | val intent = Intent(this@MainActivity, DetailActivity::class.java)
90 | val stackBuilder = TaskStackBuilder.create(this@MainActivity)
91 | stackBuilder.addParentStack(MainActivity::class.java)
92 | stackBuilder.addNextIntent(intent)
93 | val pendingIntent = stackBuilder.getPendingIntent(1, PendingIntent.FLAG_CANCEL_CURRENT)
94 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
95 | val channel = NotificationChannel("channelId", "channel name", NotificationManager.IMPORTANCE_DEFAULT)
96 | channel.description = "channel description"
97 | val notificationManager = getSystemService(NotificationManager::class.java)
98 | notificationManager.createNotificationChannel(channel)
99 | }
100 |
101 | return NotificationCompat.Builder(this@MainActivity, "channelId")
102 | .setSmallIcon(R.mipmap.ic_launcher)
103 | .setLargeIcon(BitmapFactory.decodeResource(this@MainActivity.resources, R.mipmap.ic_launcher))
104 | .setAutoCancel(true)
105 | .setContentTitle(title)
106 | .setContentText("Id: $notificationId")
107 | .setContentIntent(pendingIntent)
108 | .setDefaults(Notification.DEFAULT_ALL)
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/library/src/main/java/pl/tajchert/houston/Houston.kt:
--------------------------------------------------------------------------------
1 | package pl.tajchert.houston
2 |
3 | import android.app.Notification
4 | import android.app.NotificationManager
5 | import android.app.PendingIntent
6 | import android.content.Context
7 | import android.content.Context.MODE_PRIVATE
8 | import android.content.Intent
9 | import android.content.SharedPreferences
10 | import android.os.Bundle
11 | import androidx.core.app.NotificationCompat
12 | import com.google.gson.GsonBuilder
13 | import com.google.gson.reflect.TypeToken
14 | import pl.tajchert.houston.HoustonLandReceiver.Companion.NOTIF_ID
15 | import java.util.ArrayList
16 |
17 | class Houston(
18 | private val sharedPreferences: SharedPreferences,
19 | private val notificationManager: NotificationManager
20 | ) : SharedPreferences.OnSharedPreferenceChangeListener {
21 | private val gson = GsonBuilder().create()
22 | var notifications = ArrayList()
23 | private set
24 |
25 | constructor(context: Context) : this(context.getSharedPreferences(context.applicationContext.packageName, MODE_PRIVATE), context)
26 |
27 | constructor(
28 | sharedPreferences: SharedPreferences,
29 | context: Context
30 | ) : this(sharedPreferences, context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
31 |
32 | init {
33 | notifications = readNotificationListFromStorage(sharedPreferences)
34 | this.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
35 | }
36 |
37 | private fun readNotificationListFromStorage(sharedPreferences: SharedPreferences): ArrayList {
38 | var notificationFromStorage: ArrayList? = null
39 | try {
40 | notificationFromStorage = gson.fromJson>(
41 | this.sharedPreferences.getString(KEY_STORED, ""), object : TypeToken>() {
42 |
43 | }.type
44 | )
45 | } catch (e: Exception) {
46 | //JsonSyntaxException or NumberFormatExcetion and similar, hard to reproduce but in such case it is much better to drop data instead of throwing exception
47 | e.printStackTrace()
48 | sharedPreferences.edit()
49 | .remove(KEY_STORED)
50 | .apply()
51 | }
52 |
53 | if (notificationFromStorage == null) {
54 | notificationFromStorage = ArrayList()
55 | }
56 | return notificationFromStorage
57 | }
58 |
59 | fun saveNotification(notificationWrapper: NotificationWrapper) {
60 | if (notifications.contains(notificationWrapper)) {
61 | notifications.remove(notificationWrapper)
62 | }
63 | notifications.add(notificationWrapper)
64 | persistNotifications()
65 | }
66 |
67 | fun removeNotification(id: Int) {
68 | val notificationWrapper = getNotification(id)
69 | if (notificationWrapper != null) {
70 | notifications.remove(notificationWrapper)
71 | }
72 | persistNotifications()
73 | }
74 |
75 | fun getNotification(id: Int): NotificationWrapper? {
76 | for (notificationWrapper in notifications) {
77 | if (notificationWrapper.id == id) {
78 | return notificationWrapper
79 | }
80 | }
81 | return null
82 | }
83 |
84 | fun getNotifications(categoryName: String): ArrayList {
85 | val notificationWrappers = ArrayList()
86 | for (notificationWrapper in notifications) {
87 | if (notificationWrapper.category != null && categoryName == notificationWrapper.category!!.title) {
88 | notificationWrappers.add(notificationWrapper)
89 | }
90 | }
91 | return notificationWrappers
92 | }
93 |
94 | fun getNotifications(notificationCategory: NotificationCategory): ArrayList {
95 | val notificationWrappers = ArrayList()
96 | for (notificationWrapper in notifications) {
97 | if (notificationWrapper.category != null && notificationWrapper.category == notificationCategory) {
98 | notificationWrappers.add(notificationWrapper)
99 | }
100 | }
101 | return notificationWrappers
102 | }
103 |
104 | fun persistNotifications() {
105 | sharedPreferences.edit()
106 | .putString(KEY_STORED, gson.toJson(notifications))
107 | .apply()
108 | }
109 |
110 | private fun createNotificationWrapper(id: Int): NotificationWrapper {
111 | val notificationWrapper = NotificationWrapper()
112 | notificationWrapper.id = id
113 | notificationWrapper.showTime = System.currentTimeMillis()
114 | return notificationWrapper
115 | }
116 |
117 | fun addDismissListener(
118 | builder: Notification.Builder,
119 | id: Int,
120 | context: Context
121 | ): Notification.Builder {
122 | val intent = Intent(context, HoustonLandReceiver::class.java)
123 | val intentExtras = Bundle()
124 | intentExtras.putInt(NOTIF_ID, id)
125 | intent.putExtras(intentExtras)
126 | val pendingIntent = PendingIntent.getBroadcast(context.applicationContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
127 | builder.setDeleteIntent(pendingIntent)
128 | return builder
129 | }
130 |
131 | fun addDismissListener(
132 | builder: NotificationCompat.Builder,
133 | id: Int,
134 | context: Context
135 | ): NotificationCompat.Builder {
136 | val intent = Intent(context, HoustonLandReceiver::class.java)
137 | val intentExtras = Bundle()
138 | intentExtras.putInt(NOTIF_ID, id)
139 | intent.putExtras(intentExtras)
140 | val pendingIntent = PendingIntent.getBroadcast(context.applicationContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
141 | builder.setDeleteIntent(pendingIntent)
142 | return builder
143 | }
144 |
145 | fun launch(
146 | id: Int,
147 | notification: Notification
148 | ) {
149 | notificationManager.notify(id, notification)
150 | saveNotification(createNotificationWrapper(id))
151 | }
152 |
153 | fun launch(
154 | id: Int,
155 | notification: Notification,
156 | category: String
157 | ) {
158 | launch(id, notification)
159 | val notificationWrapper = createNotificationWrapper(id)
160 | notificationWrapper.category = NotificationCategory(category)
161 | saveNotification(notificationWrapper)
162 | }
163 |
164 | fun refreshList() {
165 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
166 | val activeNotifications = notificationManager.activeNotifications
167 | if (activeNotifications != null) {
168 | val notificationWrappers = ArrayList(notifications)
169 | for (notif in activeNotifications) {
170 | val notificationWrapper = getNotification(notif.id)
171 | if (notificationWrapper != null) {
172 | notificationWrappers.remove(notificationWrapper)
173 | }
174 | }
175 | for (notificationWrapper in notificationWrappers) {
176 | land(notificationWrapper.id)
177 | }
178 | } else {
179 | landAll()
180 | }
181 | }
182 | }
183 |
184 | fun land(id: Int) {
185 | notificationManager.cancel(id)
186 | val notificationWrapper = getNotification(id)
187 | notifications.remove(notificationWrapper)
188 | persistNotifications()
189 | }
190 |
191 | fun landAll(category: String) {
192 | val notifications = getNotifications(category)
193 | for (notificationWrapper in notifications) {
194 | notificationManager.cancel(notificationWrapper.id)
195 | this.notifications.remove(notificationWrapper)
196 | }
197 | persistNotifications()
198 | }
199 |
200 | fun landAll() {
201 | notificationManager.cancelAll()
202 | notifications.clear()
203 | persistNotifications()
204 | }
205 |
206 | override fun onSharedPreferenceChanged(
207 | sharedPreferences: SharedPreferences,
208 | s: String
209 | ) {
210 | if (KEY_STORED == s) {
211 | notifications = readNotificationListFromStorage(sharedPreferences)
212 | }
213 | }
214 |
215 | fun onDestroy() {
216 | sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
217 | }
218 |
219 | companion object {
220 | private val TAG = Houston::class.java!!.getCanonicalName()
221 | private val KEY_STORED = "houston_all_notifications_storage"
222 | }
223 | }
--------------------------------------------------------------------------------