Skip to content

Commit

Permalink
Merge pull request #8 from Karn/develop
Browse files Browse the repository at this point in the history
Release 0.0.4
  • Loading branch information
Karn authored May 4, 2018
2 parents db4b3ed + 44345bb commit b8da9fb
Show file tree
Hide file tree
Showing 17 changed files with 353 additions and 109 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Notify
title = "New dessert menu"
text = "The Cheesecake Factory has a new dessert for you to try!"
}
.send()
.show()
```

![Basic usecase](./docs/assets/default.svg)
Expand Down
12 changes: 11 additions & 1 deletion library/src/main/java/io/karn/notify/Creator.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package io.karn.notify

import android.support.v4.app.NotificationCompat
import io.karn.notify.entities.Action
import io.karn.notify.entities.NotifyConfig
import io.karn.notify.entities.Payload
import io.karn.notify.entities.RawNotification
import io.karn.notify.utils.Action
import io.karn.notify.utils.Errors
import io.karn.notify.utils.NotifyScopeMarker

/**
* Fluent API for creating a Notification object.
*/
@NotifyScopeMarker
class Creator internal constructor(private val notify: Notify, config: NotifyConfig = NotifyConfig()) {

private var meta = Payload.Meta()
Expand Down Expand Up @@ -100,6 +103,13 @@ class Creator internal constructor(private val notify: Notify, config: NotifyCon
fun stackable(init: Payload.Stackable.() -> Unit): Creator {
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)
}
}

return this
}

Expand Down
132 changes: 67 additions & 65 deletions library/src/main/java/io/karn/notify/NotificationInterop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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
Expand All @@ -25,7 +26,7 @@ internal object NotificationInterop {
}

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

if (key != null) {
Expand All @@ -42,7 +43,8 @@ internal object NotificationInterop {
NotificationManagerCompat.from(context).cancel(notificationId)
}

private fun getActiveNotifications(context: Context): List<NotifyExtender> {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun getActiveNotifications(context: Context): List<NotifyExtender> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return ArrayList()
}
Expand Down Expand Up @@ -99,6 +101,8 @@ internal object NotificationInterop {
.setContentText(Utils.getAsSecondaryFormattedText(
payload.stackable.summaryDescription?.invoke(lines.size)
?: ""))
// Attach the stack click handler.
.setContentIntent(payload.stackable.clickIntent)
.extend(
NotifyExtender().setStacked(true)
)
Expand Down Expand Up @@ -131,6 +135,10 @@ internal object NotificationInterop {
.setCategory(payload.meta.category)
// Manual specification of the priority.
.setPriority(payload.meta.priority)
// Set whether or not this notification is only relevant to the current device.
.setLocalOnly(payload.meta.localOnly)
// Set whether this notification is sticky.
.setOngoing(payload.meta.sticky)

// Standard notifications have the collapsed title and text.
if (payload.content is Payload.Content.Standard) {
Expand All @@ -145,79 +153,73 @@ internal object NotificationInterop {
builder.addAction(it)
}

payload.run {
var style: NotificationCompat.Style?
var style: NotificationCompat.Style? = null

if (stackable != null) {
if (stackable.key.isBlank()) {
throw IllegalArgumentException("Specified a stackable notification but did" +
" not provide a valid stack key.")
}

builder.setContentIntent(stackable.clickIntent)
.extend(NotifyExtender()
.setKey(stackable.key)
.setStackable(true)
.setSummaryText(stackable.summaryContent))

val activeNotifications = getActiveNotifications(notify.context)
if (activeNotifications.isNotEmpty()) {
style = buildStackedNotification(activeNotifications, builder, payload)
payload.stackable?.let {
builder.extend(NotifyExtender()
.setKey(it.key)
.setStackable(true)
.setSummaryText(it.summaryContent))

if (style != null) {
return@run
}
}
val activeNotifications = getActiveNotifications(notify.context)
if (activeNotifications.isNotEmpty()) {
style = buildStackedNotification(activeNotifications, builder, payload)
}
}

style = when (this.content) {
is Payload.Content.Default -> {
// Nothing to do here. There is no expanded text.
null
}
is Payload.Content.TextList -> {
NotificationCompat.InboxStyle().also { style ->
content.lines.forEach { style.addLine(it) }
}
}
is Payload.Content.BigText -> {
// Override the behavior of the second line.
builder.setContentText(Utils.getAsSecondaryFormattedText((content.text
?: "").toString()))

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

NotificationCompat.BigTextStyle()
.bigText(bigText)
}
is Payload.Content.BigPicture -> {
// Document these by linking to resource with labels. (1), (2), etc.
if (style == null) {
style = setStyle(builder, payload.content)
}

// This large icon is show in both expanded and collapsed views. Might consider creating a custom view for this.
// builder.setLargeIcon(content.image)
builder.setStyle(style)

NotificationCompat.BigPictureStyle()
// This is the second line in the 'expanded' notification.
.setSummaryText(content.collapsedText ?: content.text)
// This is the picture below.
.bigPicture(content.image)
return builder
}

private fun setStyle(builder: NotificationCompat.Builder, content: Payload.Content): NotificationCompat.Style? {
return when (content) {
is Payload.Content.Default -> {
// Nothing to do here. There is no expanded text.
null
}
is Payload.Content.TextList -> {
NotificationCompat.InboxStyle().also { style ->
content.lines.forEach { style.addLine(it) }
}
is Payload.Content.Message -> {
NotificationCompat.MessagingStyle(content.userDisplayName)
.setConversationTitle(content.conversationTitle)
.also { s ->
content.messages.forEach { s.addMessage(it.text, it.timestamp, it.sender) }
}
}
}
is Payload.Content.BigText -> {
// Override the behavior of the second line.
builder.setContentText(Utils.getAsSecondaryFormattedText((content.text
?: "").toString()))

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

NotificationCompat.BigTextStyle()
.bigText(bigText)
}
is Payload.Content.BigPicture -> {
// Document these by linking to resource with labels. (1), (2), etc.

builder.setStyle(style)
}
// This large icon is show in both expanded and collapsed views. Might consider creating a custom view for this.
// builder.setLargeIcon(content.image)

return builder
NotificationCompat.BigPictureStyle()
// This is the second line in the 'expanded' notification.
.setSummaryText(content.collapsedText ?: content.text)
// This is the picture below.
.bigPicture(content.image)

}
is Payload.Content.Message -> {
NotificationCompat.MessagingStyle(content.userDisplayName)
.setConversationTitle(content.conversationTitle)
.also { s ->
content.messages.forEach { s.addMessage(it.text, it.timestamp, it.sender) }
}
}
}
}
}
7 changes: 7 additions & 0 deletions library/src/main/java/io/karn/notify/Notify.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ 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 {
Expand Down
27 changes: 16 additions & 11 deletions library/src/main/java/io/karn/notify/NotifyExtender.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.karn.notify

import android.os.Bundle
import android.service.notification.StatusBarNotification
import android.support.annotation.VisibleForTesting
import android.support.v4.app.NotificationCompat

/**
Expand All @@ -25,18 +26,22 @@ internal class NotifyExtender : NotificationCompat.Extender {
private const val VALID = "notify_valid"

// Keys within EXTRA_NOTIFY_EXTENSIONS for synthetic notification options.
private const val STACKABLE = "stackable"
private const val STACKED = "stacked"
private const val STACK_KEY = "stack_key"

private const val SUMMARY_CONTENT = "summary_content"

private fun getExtensions(builder: NotificationCompat.Builder): Bundle {
return builder.extras.getBundle(EXTRA_NOTIFY_EXTENSIONS) ?: Bundle()
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val STACKABLE = "stackable"
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val STACKED = "stacked"
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val STACK_KEY = "stack_key"
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val SUMMARY_CONTENT = "summary_content"

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun getExtensions(extras: Bundle): Bundle {
return extras.getBundle(EXTRA_NOTIFY_EXTENSIONS) ?: Bundle()
}

internal fun getKey(builder: NotificationCompat.Builder): CharSequence? {
return getExtensions(builder).getCharSequence(STACK_KEY, null)
internal fun getKey(extras: Bundle): CharSequence? {
return getExtensions(extras).getCharSequence(STACK_KEY, null)
}
}

Expand Down Expand Up @@ -137,7 +142,7 @@ internal class NotifyExtender : NotificationCompat.Extender {
return this
}

internal fun setKey(key: CharSequence): NotifyExtender {
internal fun setKey(key: CharSequence?): NotifyExtender {
this.stackKey = key
return this
}
Expand Down
34 changes: 25 additions & 9 deletions library/src/main/java/io/karn/notify/entities/Payload.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package io.karn.notify.entities
import android.annotation.TargetApi
import android.app.PendingIntent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.Build
import android.support.annotation.ColorRes
import android.support.annotation.DrawableRes
import android.support.v4.app.NotificationCompat
import io.karn.notify.R
import io.karn.notify.utils.Action
import java.util.*

/**
* Wrapper class to provide configurable options for a NotifcationCompact object.
Expand Down Expand Up @@ -41,7 +42,16 @@ sealed class Payload {
/**
* Manual specification of the priority of the notification.
*/
var priority: Int = NotificationCompat.PRIORITY_DEFAULT
var priority: Int = NotificationCompat.PRIORITY_DEFAULT,
/**
* Set whether or not this notification is only relevant to the current device.
*/
var localOnly: Boolean = false,
/**
* Indicates whether the notification is sticky. If enabled, the notification is not
* affected by the clear all and is not dismissible.
*/
var sticky: Boolean = false
)

/**
Expand Down Expand Up @@ -135,10 +145,6 @@ sealed class Payload {
override var title: CharSequence? = null,
override var text: CharSequence? = null,
override var collapsedText: CharSequence? = null,
/**
* The small icon of the image that appears to the right of the notification.
*/
var icon: Bitmap? = null,
/**
* The large image that appears when the notification is expanded.s
*/
Expand Down Expand Up @@ -173,7 +179,7 @@ sealed class Payload {
/**
* The key which defines the stack as well as the corresponding notification ID.
*/
var key: String = "",
var key: String? = null,
/**
* The click intent of the stacked notification.
*/
Expand Down Expand Up @@ -202,6 +208,16 @@ sealed class Payload {
* The actions associated with the stackable notification when it is stacked. These
* actions override the actions for the individual notification.
*/
var stackableActions: ArrayList<Action>? = null
)
internal var stackableActions: ArrayList<Action>? = null
) {

/**
* Scoped function for modifying the behaviour of the actions associated with the 'Stacked'
* notification.
*/
fun actions(init: ArrayList<Action>.() -> Unit) {
this.stackableActions = ArrayList()
this.stackableActions?.init()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.karn.notify.entities

import io.karn.notify.utils.Action

internal data class RawNotification(
internal val meta: Payload.Meta,
internal val header: Payload.Header,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.karn.notify.entities
package io.karn.notify.utils

import android.support.v4.app.NotificationCompat

Expand Down
4 changes: 4 additions & 0 deletions library/src/main/java/io/karn/notify/utils/Annotations.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.karn.notify.utils

@DslMarker
annotation class NotifyScopeMarker
6 changes: 6 additions & 0 deletions library/src/main/java/io/karn/notify/utils/Errors.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.karn.notify.utils

internal object Errors {

const val INVALID_STACK_KEY_ERROR = "Invalid stack key provided."
}
Loading

0 comments on commit b8da9fb

Please sign in to comment.