Skip to content

Commit

Permalink
Merge pull request #10 from Karn/develop
Browse files Browse the repository at this point in the history
Release 0.0.5
  • Loading branch information
Karn authored May 6, 2018
2 parents b8da9fb + cb06caf commit f5d96c3
Show file tree
Hide file tree
Showing 24 changed files with 676 additions and 125 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Simplified notification delivery for Android.
](./../../releases)


###### GETTING STARTED
#### GETTING STARTED
You can install Notify using Jitpack while it is still in development.

As such there currently are pre-releases available until test coverage is improved.
Expand All @@ -33,10 +33,10 @@ dependencies {
```


###### USAGE
#### USAGE
The most basic case is as follows:

``` kotlin
```Kotlin
Notify
.with(context)
.content { // this: Payload.Content.Default
Expand All @@ -50,7 +50,7 @@ Notify

If you run into a case in which the library does not provide the requisite builder functions you can get the `NotificationCompat.Builder` object and continue to use it as you would normally by calling `Creator#asBuilder()`.

###### NOTIFICATION ANATOMY
#### NOTIFICATION ANATOMY

![Anatory](./docs/assets/anatomy.svg)

Expand All @@ -64,8 +64,8 @@ If you run into a case in which the library does not provide the requisite build
| 6 | Content | The "meat" of the notification set using of of the `Creator#as[Type]((Type) -> Unit)` scoped functions. |
| 7 | Actions | Set using the `Creator#actions((ArrayList<Action>) -> Unit)` scoped function. |

###### CONTRIBUTING
#### CONTRIBUTING
There are many ways to [contribute](./.github/CONTRIBUTING.md), you can
- submit bugs,
- help track issues,
- review code changes.
- review code changes.
266 changes: 266 additions & 0 deletions docs/assets/notification-breakdown.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ android {
compileSdkVersion 27

defaultConfig {
minSdkVersion 21
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
Expand Down
29 changes: 22 additions & 7 deletions library/src/main/java/io/karn/notify/Creator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import io.karn.notify.utils.NotifyScopeMarker
class Creator internal constructor(private val notify: Notify, config: NotifyConfig = NotifyConfig()) {

private var meta = Payload.Meta()
private var header = config.header
private var alerts = Payload.Alerts()
private var header = config.header.copy()
private var content: Payload.Content = Payload.Content.Default()
private var actions: ArrayList<Action>? = null
private var stackable: Payload.Stackable? = null
Expand All @@ -30,6 +31,16 @@ class Creator internal constructor(private val notify: Notify, config: NotifyCon
return this
}

/**
* Scoped function for modifying the Alerting of a notification. This includes visibility,
* sounds, lights, etc.
*/
fun alerting(init: Payload.Alerts.() -> Unit): Creator {
this.alerts.init()

return this
}

/**
* Scoped function for modifying the Header of a notification. Specifically, it allows the
* modification of the notificationIcon, color, the headerText (optional text next to the
Expand Down Expand Up @@ -104,11 +115,11 @@ class Creator internal constructor(private val notify: Notify, config: NotifyCon
this.stackable = Payload.Stackable()
(this.stackable as Payload.Stackable).init()

this.stackable.also {
it?.key.isNullOrBlank().let {
if (it) throw IllegalArgumentException(Errors.INVALID_STACK_KEY_ERROR)
}
}
this.stackable
?.takeIf { it.key.isNullOrEmpty() }
?.apply {
throw IllegalArgumentException(Errors.INVALID_STACK_KEY_ERROR)
}

return this
}
Expand All @@ -118,7 +129,7 @@ class Creator internal constructor(private val notify: Notify, config: NotifyCon
* transformations (if any) from the {@see Creator} builder object.
*/
fun asBuilder(): NotificationCompat.Builder {
return notify.asBuilder(RawNotification(meta, header, content, stackable, actions))
return notify.asBuilder(RawNotification(meta, alerts, header, content, stackable, actions))
}

/**
Expand All @@ -133,4 +144,8 @@ class Creator internal constructor(private val notify: Notify, config: NotifyCon
fun show(): Int {
return notify.show(asBuilder())
}

fun cancel(id: Int) {
return notify.cancel(id)
}
}
60 changes: 22 additions & 38 deletions library/src/main/java/io/karn/notify/NotificationInterop.kt
Original file line number Diff line number Diff line change
@@ -1,58 +1,43 @@
package io.karn.notify

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.support.annotation.RequiresApi
import android.support.annotation.VisibleForTesting
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
import android.text.Html
import io.karn.notify.entities.Payload
import io.karn.notify.entities.RawNotification
import io.karn.notify.utils.Utils

internal object NotificationInterop {

@RequiresApi(Build.VERSION_CODES.O)
fun registerChannel(context: Context, channelKey: String, channelName: String, channelDescription: String, importance: Int = NotificationManager.IMPORTANCE_DEFAULT) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
val channel = NotificationChannel(channelKey, channelName, importance)
channel.description = channelDescription
// Register the channel with the system
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}

fun showNotification(context: Context, notification: NotificationCompat.Builder): Int {
fun showNotification(notificationManager: NotificationManager, notification: NotificationCompat.Builder): Int {
val key = NotifyExtender.getKey(notification.extras)
var id = Utils.getRandomInt()

if (key != null) {
id = key.hashCode()
NotificationManagerCompat.from(context).notify(key.toString(), id, notification.build())
notificationManager.notify(key.toString(), id, notification.build())
} else {
NotificationManagerCompat.from(context).notify(id, notification.build())
notificationManager.notify(id, notification.build())
}

return id
}

fun cancelNotification(context: Context, notificationId: Int) {
NotificationManagerCompat.from(context).cancel(notificationId)
fun cancelNotification(notificationManager: NotificationManager, notificationId: Int) {
notificationManager.cancel(notificationId)
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun getActiveNotifications(context: Context): List<NotifyExtender> {
internal fun getActiveNotifications(notificationManager: NotificationManager): List<NotifyExtender> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return ArrayList()
}

val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
return notificationManager.activeNotifications
.map { NotifyExtender(it) }
.filter { it.isValid }
.filter { it.valid }
}

private fun buildStackedNotification(groupedNotifications: List<NotifyExtender>, builder: NotificationCompat.Builder, payload: RawNotification): NotificationCompat.InboxStyle? {
Expand All @@ -71,15 +56,10 @@ internal object NotificationInterop {
.forEach {
// Handle case where we already have a stacked notification.
if (it.stacked) {

it.stackItems?.forEach {
lines.add(it.toString())
}

return@forEach
it.stackItems?.forEach { lines.add(it.toString()) }
} else {
it.summaryContent?.let { lines.add(it) }
}

it.summaryContent?.let { lines.add(it) }
}

if (lines.size == 0) return null
Expand All @@ -98,9 +78,7 @@ internal object NotificationInterop {
// Sets the first line of the 'collapsed' RawNotification.
.setContentTitle(payload.stackable.summaryTitle?.invoke(lines.size))
// Sets the second line of the 'collapsed' RawNotification.
.setContentText(Utils.getAsSecondaryFormattedText(
payload.stackable.summaryDescription?.invoke(lines.size)
?: ""))
.setContentText(Utils.getAsSecondaryFormattedText(payload.stackable.summaryDescription?.invoke(lines.size)))
// Attach the stack click handler.
.setContentIntent(payload.stackable.clickIntent)
.extend(
Expand All @@ -118,12 +96,16 @@ internal object NotificationInterop {

fun buildNotification(notify: Notify, payload: RawNotification): NotificationCompat.Builder {
val builder = NotificationCompat.Builder(notify.context, payload.header.channel)
// Ensures that this notification is marked as a Notify notification.
.extend(NotifyExtender())
// The color of the RawNotification Icon, App_Name and the expanded chevron.
.setColor(notify.context.resources.getColor(payload.header.color))
// The RawNotification icon.
.setSmallIcon(payload.header.icon)
// The text that is visible to the right of the app name in the notification header.
.setSubText(payload.header.headerText)
// Show the relative timestamp next to the application name.
.setShowWhen(payload.header.showTimestamp)
// Dismiss the notification on click?
.setAutoCancel(payload.meta.cancelOnClick)
// Set the click handler for the notifications
Expand All @@ -139,6 +121,10 @@ internal object NotificationInterop {
.setLocalOnly(payload.meta.localOnly)
// Set whether this notification is sticky.
.setOngoing(payload.meta.sticky)
// The visibility of the notification on the lockscreen.
.setVisibility(payload.alerting.lockScreenVisibility)
// The duration of time after which the notification is automatically dismissed.
.setTimeoutAfter(payload.alerting.timeout)

// Standard notifications have the collapsed title and text.
if (payload.content is Payload.Content.Standard) {
Expand All @@ -161,7 +147,7 @@ internal object NotificationInterop {
.setStackable(true)
.setSummaryText(it.summaryContent))

val activeNotifications = getActiveNotifications(notify.context)
val activeNotifications = getActiveNotifications(Notify.defaultConfig.notificationManager!!)
if (activeNotifications.isNotEmpty()) {
style = buildStackedNotification(activeNotifications, builder, payload)
}
Expand Down Expand Up @@ -193,9 +179,7 @@ internal object NotificationInterop {
?: "").toString()))

val bigText: CharSequence = Html.fromHtml("<font color='#3D3D3D'>" + (content.collapsedText
?: content.title
?: "")
.toString() + "</font><br>" + content.bigText?.replace("\n".toRegex(), "<br>"))
?: content.title) + "</font><br>" + content.bigText?.replace("\n".toRegex(), "<br>"))

NotificationCompat.BigTextStyle()
.bigText(bigText)
Expand Down
35 changes: 19 additions & 16 deletions library/src/main/java/io/karn/notify/Notify.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.karn.notify

import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.support.v4.app.NotificationCompat
import io.karn.notify.entities.NotifyConfig
import io.karn.notify.entities.RawNotification
Expand All @@ -26,7 +26,7 @@ class Notify internal constructor(internal var context: Context) {
const val DEFAULT_CHANNEL_DESCRIPTION = "General application notifications."

// This is the initial configuration of the Notify Creator.
private var defaultConfig = NotifyConfig()
internal var defaultConfig = NotifyConfig()

/**
* Modify the default configuration.
Expand All @@ -46,25 +46,21 @@ class Notify internal constructor(internal var context: Context) {
fun with(context: Context): Creator {
return Creator(Notify(context), defaultConfig)
}

/**
* Cancel an existing notification.
*/
fun cancel(context: Context, id: Int) {
return NotificationInterop.cancelNotification(context, id)
}
}

init {
this.context = context.applicationContext

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationInterop.registerChannel(
this.context,
defaultConfig.defaultChannelKey,
defaultConfig.defaultChannelName,
defaultConfig.defaultChannelDescription)
// Initialize notification manager instance.
if (Companion.defaultConfig.notificationManager == null) {
Companion.defaultConfig.notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}

NotifyChannel.registerChannel(
Companion.defaultConfig.notificationManager!!,
defaultConfig.defaultChannelKey,
defaultConfig.defaultChannelName,
defaultConfig.defaultChannelDescription)
}

/**
Expand All @@ -85,6 +81,13 @@ class Notify internal constructor(internal var context: Context) {
* this returned integer to make updates or to cancel the notification.
*/
internal fun show(builder: NotificationCompat.Builder): Int {
return NotificationInterop.showNotification(context, builder)
return NotificationInterop.showNotification(Notify.defaultConfig.notificationManager!!, builder)
}

/**
* Cancel an existing notification with a particular id.
*/
internal fun cancel(id: Int) {
return NotificationInterop.cancelNotification(Notify.defaultConfig.notificationManager!!, id)
}
}
27 changes: 27 additions & 0 deletions library/src/main/java/io/karn/notify/NotifyChannel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.karn.notify

import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build

/**
* Provides compatibility functionality for the Notification channels introduced in Android O.
*/
internal object NotifyChannel {

fun registerChannel(notificationManager: NotificationManager, channelKey: String, channelName: String, channelDescription: String, importance: Int = NotificationManager.IMPORTANCE_DEFAULT): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return false
}

// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
val channel = NotificationChannel(channelKey, channelName, importance)

channel.description = channelDescription
// Register the channel with the system
notificationManager.createNotificationChannel(channel)

return true
}
}
Loading

0 comments on commit f5d96c3

Please sign in to comment.