Skip to content

Commit

Permalink
feat(Android): Add progress bar to notification (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rapsssito committed Sep 6, 2020
1 parent f42a710 commit 2ed041d
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 92 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,37 @@ await BackgroundService.stop();
| `taskIcon` | [`<taskIconOptions>`](#taskIconOptions) | **Android Required**. Notification icon. |
| `color` | `<string>` | Notification color. **Default**: `"#ffffff"`. |
| `linkingURI` | `<string>` | Link that will be called when the notification is clicked. Example: `"yourSchemeHere://chat/jane"`. See [Deep Linking](#deep-linking) for more info. **Default**: `undefined`. |
| `progressBar` | [`<taskProgressBarOptions>`](#taskProgressBarOptions) | Notification progress bar. |
| `parameters` | `<any>` | Parameters to pass to the task. |

#### taskIconOptions
**Android only**
| Property | Type | Description |
| ----------- | ---------- | -------------------------------------------------------------- |
| `name` | `<string>` | **Required**. Icon name in res/ folder. Ex: `ic_launcher`. |
| `type` | `<string>` | **Required**. Icon type in res/ folder. Ex: `mipmap`. |
| `package` | `<string>` | Icon package where to search the icon. Ex: `com.example.package`. **It defaults to the app's package. It is higly recommended to leave like that.** |

Example:

![photo5837026843969041365](https://user-images.githubusercontent.com/44206249/72532521-de49e280-3873-11ea-8bf6-00618bcb82ab.jpg)

#### taskProgressBarOptions
**Android only**
| Property | Type | Description |
| ----------- | ---------- | -------------------------------------------------------------- |
| `max` | `<number>` | **Required**. Maximum value. |
| `value` | `<number>` | **Required**. Current value. |
| `indeterminate` | `<boolean>` | Display the progress status as indeterminate. |

Example:

![ProgressBar](https://developer.android.com/images/ui/notifications/notification-progressbar_2x.png)

### Deep Linking
**Android only**. To handle incoming links when the notification is clicked by the user, first you need to modify your **`android/app/src/main/AndroidManifest.xml`** and add an `<intent-filter>` (fill `yourSchemeHere` with the name you prefer):
**Android only**

To handle incoming links when the notification is clicked by the user, first you need to modify your **`android/app/src/main/AndroidManifest.xml`** and add an `<intent-filter>` (fill `yourSchemeHere` with the name you prefer):
```xml
<manifest ... >
...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
Expand Down Expand Up @@ -46,8 +43,8 @@ public void start(@NonNull final ReadableMap options, @NonNull final Promise pro
currentServiceIntent = new Intent(reactContext, RNBackgroundActionsTask.class);
// Get the task info from the options
try {
final Bundle extras = getExtrasFromOptions(options);
currentServiceIntent.putExtras(extras);
final BackgroundTaskOptions bgOptions = new BackgroundTaskOptions(reactContext, options);
currentServiceIntent.putExtras(bgOptions.getExtras());
} catch (Exception e) {
promise.reject(e);
return;
Expand All @@ -70,14 +67,8 @@ public void stop(@NonNull final Promise promise) {
public void updateNotification(@NonNull final ReadableMap options, @NonNull final Promise promise) {
// Get the task info from the options
try {
final Bundle extras = getExtrasFromOptions(options);
// Get info
final String taskTitle = extras.getString("taskTitle", "RNBackgroundActionsTaskTitle");
final String taskDesc = extras.getString("taskDesc", "RNBackgroundActionsTaskDesc");
final int iconInt = extras.getInt("iconInt");
final int color = extras.getInt("color");
final String linkingURI = extras.getString("linkingURI");
final Notification notification = RNBackgroundActionsTask.buildNotification(reactContext, taskTitle, taskDesc, iconInt, color, linkingURI);
final BackgroundTaskOptions bgOptions = new BackgroundTaskOptions(reactContext, options);
final Notification notification = RNBackgroundActionsTask.buildNotification(reactContext, bgOptions);
final NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(RNBackgroundActionsTask.SERVICE_NOTIFICATION_ID, notification);
} catch (Exception e) {
Expand All @@ -86,64 +77,4 @@ public void updateNotification(@NonNull final ReadableMap options, @NonNull fina
}
promise.resolve(null);
}

@NonNull
private Bundle getExtrasFromOptions(@NonNull final ReadableMap options) {
// Create extras
final Bundle extras = Arguments.toBundle(options);
if (extras == null)
throw new IllegalArgumentException("Could not convert arguments to bundle");
// Get taskTitle
try {
if (options.getString("taskTitle") == null)
throw new IllegalArgumentException();
} catch (Exception e) {
throw new IllegalArgumentException("Task title cannot be null");
}
// Get taskDesc
try {
if (options.getString("taskDesc") == null)
throw new IllegalArgumentException();
} catch (Exception e) {
throw new IllegalArgumentException("Task description cannot be null");
}
// Get iconInt
try {
final ReadableMap iconMap = options.getMap("taskIcon");
if (iconMap == null)
throw new IllegalArgumentException();
final String iconName = iconMap.getString("name");
final String iconType = iconMap.getString("type");
String iconPackage;
try {
iconPackage = iconMap.getString("package");
if (iconPackage == null)
throw new IllegalArgumentException();
} catch (Exception e) {
// Get the current package as default
iconPackage = reactContext.getPackageName();
}
final int iconInt = reactContext.getResources().getIdentifier(iconName, iconType, iconPackage);
extras.putInt("iconInt", iconInt);
if (iconInt == 0)
throw new IllegalArgumentException();
} catch (Exception e) {
throw new IllegalArgumentException("Task icon not found");
}
// Get color
try {
final String color = options.getString("color");
extras.putInt("color", Color.parseColor(color));
} catch (Exception e) {
extras.putInt("color", Color.parseColor("#ffffff"));
}
// Get linkingURI
try {
final String linkingURI = options.getString("linkingURI");
extras.putString("linkingURI", linkingURI);
} catch (Exception e) {
extras.putString("linkingURI", null);
}
return extras;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.asterinet.react.bgactions;

import android.graphics.Color;
import android.os.Bundle;

import androidx.annotation.ColorInt;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMap;

public final class BackgroundTaskOptions {
private final Bundle extras;

public BackgroundTaskOptions(@NonNull final Bundle extras) {
this.extras = extras;
}

public BackgroundTaskOptions(@NonNull final ReactContext reactContext, @NonNull final ReadableMap options) {
// Create extras
extras = Arguments.toBundle(options);
if (extras == null)
throw new IllegalArgumentException("Could not convert arguments to bundle");
// Get taskTitle
try {
if (options.getString("taskTitle") == null)
throw new IllegalArgumentException();
} catch (Exception e) {
throw new IllegalArgumentException("Task title cannot be null");
}
// Get taskDesc
try {
if (options.getString("taskDesc") == null)
throw new IllegalArgumentException();
} catch (Exception e) {
throw new IllegalArgumentException("Task description cannot be null");
}
// Get iconInt
try {
final ReadableMap iconMap = options.getMap("taskIcon");
if (iconMap == null)
throw new IllegalArgumentException();
final String iconName = iconMap.getString("name");
final String iconType = iconMap.getString("type");
String iconPackage;
try {
iconPackage = iconMap.getString("package");
if (iconPackage == null)
throw new IllegalArgumentException();
} catch (Exception e) {
// Get the current package as default
iconPackage = reactContext.getPackageName();
}
final int iconInt = reactContext.getResources().getIdentifier(iconName, iconType, iconPackage);
extras.putInt("iconInt", iconInt);
if (iconInt == 0)
throw new IllegalArgumentException();
} catch (Exception e) {
throw new IllegalArgumentException("Task icon not found");
}
// Get color
try {
final String color = options.getString("color");
extras.putInt("color", Color.parseColor(color));
} catch (Exception e) {
extras.putInt("color", Color.parseColor("#ffffff"));
}
}

public Bundle getExtras() {
return extras;
}

public String getTaskTitle() {
return extras.getString("taskTitle", "");
}

public String getTaskDesc() {
return extras.getString("taskDesc", "");
}

@IdRes
public int getIconInt() {
return extras.getInt("iconInt");
}

@ColorInt
public int getColor() {
return extras.getInt("color");
}

@Nullable
public String getLinkingURI() {
return extras.getString("linkingURI");
}

@Nullable
public Bundle getProgressBar() {
return extras.getBundle("progressBar");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import android.os.Build;
import android.os.Bundle;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
Expand All @@ -25,23 +24,38 @@ final public class RNBackgroundActionsTask extends HeadlessJsTaskService {
private static final String CHANNEL_ID = "RN_BACKGROUND_ACTIONS_CHANNEL";

@NonNull
public static Notification buildNotification(@NonNull final ReactContext context, @NonNull final String taskTitle, @NonNull final String taskDesc, final int iconInt, @ColorInt int color, @Nullable final String linkingURI) {
public static Notification buildNotification(@NonNull final ReactContext context, @NonNull final BackgroundTaskOptions bgOptions) {
// Get info
final String taskTitle = bgOptions.getTaskTitle();
final String taskDesc = bgOptions.getTaskDesc();
final int iconInt = bgOptions.getIconInt();
final int color = bgOptions.getColor();
final String linkingURI = bgOptions.getLinkingURI();
Intent notificationIntent;
if (linkingURI != null) {
notificationIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(linkingURI));
} else {
notificationIntent = new Intent(context, context.getCurrentActivity().getClass());
}
final PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
return new NotificationCompat.Builder(context, CHANNEL_ID)
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(taskTitle)
.setContentText(taskDesc)
.setSmallIcon(iconInt)
.setContentIntent(contentIntent)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setColor(color)
.build();
.setColor(color);

final Bundle progressBarBundle = bgOptions.getProgressBar();
if (progressBarBundle != null) {
final int progressMax = (int) Math.floor(progressBarBundle.getDouble("max"));
final int progressCurrent = (int) Math.floor(progressBarBundle.getDouble("value"));
final boolean progressIndeterminate = progressBarBundle.getBoolean("indeterminate");
builder.setProgress(progressMax, progressCurrent, progressIndeterminate);
}

return builder.build();
}

@Override
Expand All @@ -60,21 +74,14 @@ public int onStartCommand(Intent intent, int flags, int startId) {
if (extras == null) {
throw new IllegalArgumentException("Extras cannot be null");
}
// Get info
final String taskTitle = extras.getString("taskTitle", "RNBackgroundActionsTaskTitle");
final String taskDesc = extras.getString("taskDesc", "RNBackgroundActionsTaskDesc");
final int iconInt = extras.getInt("iconInt");
final int color = extras.getInt("color");
final String linkingURI = extras.getString("linkingURI");
// Turning into a foreground service
createNotificationChannel(taskTitle, taskDesc); // Necessary creating channel for API 26+
final BackgroundTaskOptions bgOptions = new BackgroundTaskOptions(extras);
createNotificationChannel(bgOptions.getTaskTitle(), bgOptions.getTaskDesc()); // Necessary creating channel for API 26+
// Create the notification
final Notification notification = buildNotification(getReactNativeHost().getReactInstanceManager().getCurrentReactContext(), taskTitle, taskDesc, iconInt, color, linkingURI);
final Notification notification = buildNotification(getReactNativeHost().getReactInstanceManager().getCurrentReactContext(), bgOptions);
startForeground(SERVICE_NOTIFICATION_ID, notification);
return super.onStartCommand(intent, flags, startId);
}


private void createNotificationChannel(@NonNull final String taskTitle, @NonNull final String taskDesc) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final int importance = NotificationManager.IMPORTANCE_LOW;
Expand Down
10 changes: 7 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import RNBackgroundActions from './RNBackgroundActionsModule';
* taskDesc: string,
* taskIcon: {name: string, type: string, package?: string},
* color?: string
* linkingURI?: string}} BackgroundTaskOptions
* linkingURI?: string,
* progressBar?: {max: number, value: number, indeterminate?: boolean}
* }} BackgroundTaskOptions
*/
class BackgroundServer {
constructor() {
/** @private */
this._runnedTasks = 0;
/** @private */
/** @private @type {(arg0?: any) => void} */
this._stopTask = () => {};
/** @private */
this._isRunning = false;
Expand All @@ -32,7 +34,8 @@ class BackgroundServer {
* taskDesc?: string,
* taskIcon?: {name: string, type: string, package?: string},
* color?: string,
* linkingURI?: string}} taskData
* linkingURI?: string,
* progressBar?: {max: number, value: number, indeterminate?: boolean}}} taskData
*/
async updateNotification(taskData) {
if (Platform.OS !== 'android') return;
Expand Down Expand Up @@ -99,6 +102,7 @@ class BackgroundServer {
taskIcon: { ...options.taskIcon },
color: options.color || '#ffffff',
linkingURI: options.linkingURI,
progressBar: options.progressBar,
};
}

Expand Down

0 comments on commit 2ed041d

Please sign in to comment.