Skip to content

Commit a432be8

Browse files
committed
feat: dark mode widgets
1 parent f6080a2 commit a432be8

23 files changed

Lines changed: 493 additions & 42 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## Added
11+
12+
- Added support for dark mode widgets
13+
1014
## [0.18.0] - 2026-01-04
1115

1216
## Added

android/src/main/java/com/reactnativeandroidwidget/RNWidget.java

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ public void drawWidgets() throws Exception {
5959
}
6060

6161
public void drawWidget(int widgetId) throws Exception {
62-
ReadableMap configClone = Arguments.makeNativeMap(config.toHashMap());
62+
ReadableMap light = config.getMap("light");
63+
ReadableMap dark = config.getMap("dark");
64+
65+
ReadableMap configClone = Arguments.makeNativeMap(light.toHashMap());
6366
RemoteViews remoteWidgetView = new RemoteViews(appContext.getPackageName(), R.layout.rn_widget);
6467

6568
WidgetWithViews widgetWithViews = WidgetFactory.buildWidgetFromRoot(
@@ -70,8 +73,38 @@ public void drawWidget(int widgetId) throws Exception {
7073
);
7174

7275
Bitmap bitmap = drawViewToBitmap(widgetWithViews.getRootView());
73-
Uri bitmapUri = saveBitmapToDisk(widgetId, bitmap);
74-
remoteWidgetView.setImageViewUri(R.id.rn_widget_image, bitmapUri);
76+
Uri bitmapUri = saveBitmapToDisk(widgetId, "light", bitmap);
77+
remoteWidgetView.setImageViewUri(R.id.rn_widget_image_light, bitmapUri);
78+
79+
if (dark != null) {
80+
WidgetWithViews darkWidgetWithViews = WidgetFactory.buildWidgetFromRoot(
81+
appContext,
82+
Arguments.makeNativeMap(dark.toHashMap()),
83+
RNWidgetUtil.getWidgetWidth(appContext, widgetId),
84+
RNWidgetUtil.getWidgetHeight(appContext, widgetId)
85+
);
86+
87+
Bitmap darkBitmap = drawViewToBitmap(darkWidgetWithViews.getRootView());
88+
Uri darkBitmapUri = saveBitmapToDisk(widgetId, "dark", darkBitmap);
89+
remoteWidgetView.setImageViewUri(R.id.rn_widget_image_dark, darkBitmapUri);
90+
91+
List<CollectionView> lightCollectionViews = widgetWithViews.getCollectionViews();
92+
List<CollectionView> darkCollectionViews = darkWidgetWithViews.getCollectionViews();
93+
94+
for (int i = 0; i < Math.min(darkCollectionViews.size(), RNWidgetCollectionService.MAX_COLLECTION_WIDGETS); i++) {
95+
RNWidgetCollectionService.storeCollection(appContext, widgetId, i, lightCollectionViews.get(i).getRenderedViews(), "light");
96+
RNWidgetCollectionService.storeCollection(appContext, widgetId, i, darkCollectionViews.get(i).getRenderedViews(), "dark");
97+
}
98+
} else {
99+
remoteWidgetView.setImageViewUri(R.id.rn_widget_image_dark, bitmapUri);
100+
101+
List<CollectionView> collectionViews = widgetWithViews.getCollectionViews();
102+
for (int i = 0; i < Math.min(collectionViews.size(), RNWidgetCollectionService.MAX_COLLECTION_WIDGETS); i++) {
103+
CollectionView collectionView = collectionViews.get(i);
104+
RNWidgetCollectionService.storeCollection(appContext, widgetId, i, collectionView.getRenderedViews(), "light");
105+
RNWidgetCollectionService.storeCollection(appContext, widgetId, i, collectionView.getRenderedViews(), "dark");
106+
}
107+
}
75108

76109
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
77110
addClickableAreas(widgetId, remoteWidgetView, widgetWithViews);
@@ -107,8 +140,8 @@ private Bitmap drawViewToBitmap(View rootView) {
107140
return bitmap;
108141
}
109142

110-
private Uri saveBitmapToDisk(int widgetId, Bitmap bitmap) {
111-
String fileName = "widget_" + widgetId + ".png";
143+
private Uri saveBitmapToDisk(int widgetId, String mode, Bitmap bitmap) {
144+
String fileName = "widget_" + widgetId + "_mode_" + mode + ".png";
112145
RNWidgetImageProvider.writeImage(appContext, fileName, bitmap);
113146
return RNWidgetImageProvider.getImageUri(appContext, fileName);
114147
}
@@ -175,8 +208,6 @@ private void addCollectionViews(int widgetId, RemoteViews remoteWidgetView, Widg
175208
}
176209

177210
private void addCollectionView(RemoteViews remoteWidgetView, ViewGroup rootView, CollectionView collectionView, int widgetId, int collectionId) {
178-
RNWidgetCollectionService.storeCollection(appContext, widgetId, collectionId, collectionView.getRenderedViews());
179-
180211
View collectionWidget = collectionView.getView();
181212
Rect offsetViewBounds = new Rect();
182213
collectionWidget.getDrawingRect(offsetViewBounds);
@@ -225,7 +256,8 @@ private Intent createCollectionRemoteAdapterIntent(int widgetId, int collectionI
225256
bundle.putString("clickAction", collectionViewItem.getClickAction());
226257
bundle.putBundle("clickActionData", Arguments.toBundle(collectionViewItem.getClickActionData()));
227258
bundle.putInt("imageWidth", collectionViewItem.getBitmap().getWidth());
228-
bundle.putString("imageName", RNWidgetCollectionService.getImageName(widgetId, collectionId, i));
259+
bundle.putString("lightImageName", RNWidgetCollectionService.getImageName(widgetId, collectionId, i, "light"));
260+
bundle.putString("darkImageName", RNWidgetCollectionService.getImageName(widgetId, collectionId, i, "dark"));
229261
collectionItemsBundle.add(bundle);
230262

231263
ArrayList<Bundle> clickableAreas = new ArrayList<>();

android/src/main/java/com/reactnativeandroidwidget/RNWidgetCollectionService.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@
2222
public class RNWidgetCollectionService extends RemoteViewsService {
2323
public static final int MAX_COLLECTION_WIDGETS = 2;
2424

25-
public static void storeCollection(ReactApplicationContext context, int widgetId, int collectionId, List<CollectionViewItem> collectionViewItems) {
26-
RNWidgetImageProvider.deleteCollectionImages(context, widgetId, collectionId);
25+
public static void storeCollection(ReactApplicationContext context, int widgetId, int collectionId, List<CollectionViewItem> collectionViewItems, String mode) {
26+
RNWidgetImageProvider.deleteCollectionImages(context, widgetId, collectionId, mode);
2727
for (int i = 0; i < collectionViewItems.size(); i++) {
2828
CollectionViewItem item = collectionViewItems.get(i);
2929

30-
RNWidgetImageProvider.writeImage(context, getImageName(widgetId, collectionId, i), item.getBitmap());
30+
RNWidgetImageProvider.writeImage(context, getImageName(widgetId, collectionId, i, mode), item.getBitmap());
3131
}
3232
}
3333

34-
static String getImageName(int widgetId, int collectionId, int position) {
35-
return "widget_" + widgetId + "_collection_" + collectionId + "_" + position + ".png";
34+
static String getImageName(int widgetId, int collectionId, int position, String mode) {
35+
return "widget_" + widgetId + "_mode_" + mode + "_collection_" + collectionId + "_" + position + ".png";
3636
}
3737

3838
@Override
@@ -72,14 +72,16 @@ public RemoteViews getViewAt(int position) {
7272

7373
RemoteViews listItemView = new RemoteViews(mContext.getPackageName(), R.layout.rn_widget_list_item);
7474
listItemView.removeAllViews(R.id.rn_widget_list_item_clickable_container);
75-
Uri imageUri = RNWidgetImageProvider.getImageUri(mContext, bundle.getString("imageName"));
76-
listItemView.setImageViewUri(R.id.rn_widget_list_item, imageUri);
75+
Uri lightImageUri = RNWidgetImageProvider.getImageUri(mContext, bundle.getString("lightImageName"));
76+
listItemView.setImageViewUri(R.id.rn_widget_list_item_light, lightImageUri);
77+
Uri darkImageUri = RNWidgetImageProvider.getImageUri(mContext, bundle.getString("darkImageName"));
78+
listItemView.setImageViewUri(R.id.rn_widget_list_item_dark, darkImageUri);
7779

7880
// Set a fill-intent which will be used to fill-in the pending intent template
7981
// which is set on the collection view in RNWidget.
8082
if (bundle.getString("clickAction", null) != null) {
8183
Intent fillInIntent = createFillInIntent(bundle);
82-
listItemView.setOnClickFillInIntent(R.id.rn_widget_list_item, fillInIntent);
84+
listItemView.setOnClickFillInIntent(R.id.rn_widget_list_item_clickable_container, fillInIntent);
8385
}
8486

8587
ArrayList<Bundle> clickableAreas = bundle.getParcelableArrayList("clickableAreas");

android/src/main/java/com/reactnativeandroidwidget/RNWidgetImageProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ static void deleteWidgetImages(Context context, int widgetId) {
7070
deleteImages(context, "widget_" + widgetId);
7171
}
7272

73-
static void deleteCollectionImages(Context context, int widgetId, int collectionId) {
74-
deleteImages(context, "widget_" + widgetId + "_collection_" + collectionId);
73+
static void deleteCollectionImages(Context context, int widgetId, int collectionId, String mode) {
74+
deleteImages(context, "widget_" + widgetId + "_mode_" + mode + "_collection_" + collectionId);
7575
}
7676

7777
private static void deleteImages(Context context, String prefix) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:id="@android:id/background"
3+
android:layout_width="match_parent"
4+
android:layout_height="match_parent">
5+
6+
<ImageView
7+
android:id="@+id/rn_widget_image_light"
8+
android:layout_width="match_parent"
9+
android:layout_height="match_parent"
10+
android:background="@android:color/transparent"
11+
android:scaleType="matrix"
12+
android:visibility="gone" />
13+
14+
<ImageView
15+
android:id="@+id/rn_widget_image_dark"
16+
android:layout_width="match_parent"
17+
android:layout_height="match_parent"
18+
android:background="@android:color/transparent"
19+
android:scaleType="matrix"
20+
android:visibility="visible" />
21+
22+
<FrameLayout
23+
android:id="@+id/rn_widget_clickable_container"
24+
android:layout_width="match_parent"
25+
android:layout_height="match_parent" />
26+
27+
<FrameLayout
28+
android:id="@+id/rn_widget_collection_container"
29+
android:layout_width="match_parent"
30+
android:layout_height="match_parent" />
31+
</FrameLayout>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:layout_width="match_parent"
3+
android:layout_height="match_parent">
4+
5+
<ImageView
6+
android:id="@+id/rn_widget_list_item_light"
7+
android:layout_width="match_parent"
8+
android:layout_height="match_parent"
9+
android:scaleType="fitStart"
10+
android:visibility="gone" />
11+
12+
<ImageView
13+
android:id="@+id/rn_widget_list_item_dark"
14+
android:layout_width="match_parent"
15+
android:layout_height="match_parent"
16+
android:scaleType="fitStart"
17+
android:visibility="visible" />
18+
19+
<FrameLayout
20+
android:id="@+id/rn_widget_list_item_clickable_container"
21+
android:layout_width="match_parent"
22+
android:layout_height="match_parent" />
23+
</FrameLayout>

android/src/main/res/layout/rn_widget.xml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,20 @@
44
android:layout_height="match_parent">
55

66
<ImageView
7-
android:id="@+id/rn_widget_image"
7+
android:id="@+id/rn_widget_image_light"
88
android:layout_width="match_parent"
99
android:layout_height="match_parent"
1010
android:background="@android:color/transparent"
11-
android:scaleType="matrix" />
11+
android:scaleType="matrix"
12+
android:visibility="visible" />
13+
14+
<ImageView
15+
android:id="@+id/rn_widget_image_dark"
16+
android:layout_width="match_parent"
17+
android:layout_height="match_parent"
18+
android:background="@android:color/transparent"
19+
android:scaleType="matrix"
20+
android:visibility="gone" />
1221

1322
<FrameLayout
1423
android:id="@+id/rn_widget_clickable_container"

android/src/main/res/layout/rn_widget_list_item.xml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@
33
android:layout_height="match_parent">
44

55
<ImageView
6-
android:id="@+id/rn_widget_list_item"
6+
android:id="@+id/rn_widget_list_item_light"
77
android:layout_width="match_parent"
88
android:layout_height="match_parent"
9-
android:scaleType="fitStart" />
9+
android:scaleType="fitStart"
10+
android:visibility="visible" />
11+
12+
<ImageView
13+
android:id="@+id/rn_widget_list_item_dark"
14+
android:layout_width="match_parent"
15+
android:layout_height="match_parent"
16+
android:scaleType="fitStart"
17+
android:visibility="gone" />
1018

1119
<FrameLayout
1220
android:id="@+id/rn_widget_list_item_clickable_container"

example-expo/app.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ const widgetConfig: WithAndroidWidgetsParams = {
5858
minWidth: '300dp',
5959
previewImage: './assets/widget-preview/list.png',
6060
},
61+
{
62+
name: 'DarkMode',
63+
label: 'Dark Mode Widget Demo',
64+
description: 'Different colors for light and dark mode',
65+
minHeight: '120dp',
66+
minWidth: '300dp',
67+
previewImage: './assets/widget-preview/darkmode.png',
68+
},
6169
{
6270
name: 'DebugEvents',
6371
label: 'Debug Events',
@@ -99,7 +107,6 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
99107
supportsTablet: true,
100108
},
101109
scheme: 'androidwidgetexample',
102-
newArchEnabled: true,
103110
android: {
104111
adaptiveIcon: {
105112
foregroundImage: './assets/adaptive-icon.png',
48.4 KB
Loading

0 commit comments

Comments
 (0)