From ca8e505cd03c1c56aa1fa281024147cf32737b1e Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Wed, 24 Dec 2025 14:17:52 +0500 Subject: [PATCH] mobile: add support for color shortcuts on homescreen --- .../notesnook/RCTNNativeModule.java | 42 +++++++++++-------- apps/mobile/app/hooks/use-actions.tsx | 9 +++- apps/mobile/app/hooks/use-app-events.tsx | 16 +++++++ .../app/navigation/navigation-stack.tsx | 12 ++++++ apps/mobile/app/stores/index.ts | 25 ++++++++++- apps/mobile/app/utils/notesnook-module.ts | 11 +++-- 6 files changed, 91 insertions(+), 24 deletions(-) diff --git a/apps/mobile/android/app/src/main/java/com/streetwriters/notesnook/RCTNNativeModule.java b/apps/mobile/android/app/src/main/java/com/streetwriters/notesnook/RCTNNativeModule.java index a32e85852..178fe31ab 100644 --- a/apps/mobile/android/app/src/main/java/com/streetwriters/notesnook/RCTNNativeModule.java +++ b/apps/mobile/android/app/src/main/java/com/streetwriters/notesnook/RCTNNativeModule.java @@ -246,7 +246,7 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { } @ReactMethod - public void addShortcut(final String id, final String type, final String title, final String description, Promise promise) { + public void addShortcut(final String id, final String type, final String title, final String description, final String color, Promise promise) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher"); return; @@ -263,7 +263,7 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { Intent intent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(uri)); intent.setPackage(mContext.getPackageName()); - Icon icon = createLetterIcon(type, title); + Icon icon = createLetterIcon(type, title, color); ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, id) .setShortLabel(title) .setLongLabel(description != null && !description.isEmpty() ? description : title) @@ -301,7 +301,7 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { } @ReactMethod - public void updateShortcut(final String id, final String title, final String description, Promise promise) { + public void updateShortcut(final String id, final String type, final String title, final String description,final String color, Promise promise) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher"); return; @@ -327,9 +327,10 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { if (existingShortcut == null) { return; } - + Icon icon = createLetterIcon(type, title, color); ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(mContext, id) .setShortLabel(title) + .setIcon(icon) .setLongLabel(description != null && !description.isEmpty() ? description : title) .setIntent(existingShortcut.getIntent()) .build(); @@ -397,6 +398,8 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { data.putString("type", "notebook"); } else if (info.getCategories().contains("tag")) { data.putString("type", "tag"); + } else if (info.getCategories().contains("color")) { + data.putString("type", "color"); } } shortcuts.pushMap(data); @@ -404,12 +407,12 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { promise.resolve(shortcuts); } - private Icon createLetterIcon(String type, String title) { + private Icon createLetterIcon(String type, String title, String colorCode) { String letter = type.contains("tag") ? "#" : title != null && !title.isEmpty() ? title.substring(0, 1).toUpperCase() : "?"; - int color = getColorForLetter(letter); + int color = type.equals("color") ? Color.parseColor(colorCode) : getColorForLetter(letter); if (type.equals("notebook")) return Icon.createWithResource(mContext, R.drawable.ic_notebook); // Use a larger canvas and fill it completely to avoid white borders from launcher masking. @@ -417,19 +420,24 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { Bitmap bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); + if (type.equals("color")) { + Paint backgroundPaint = new Paint(); + backgroundPaint.setColor(color); + backgroundPaint.setAntiAlias(true); + canvas.drawCircle(iconSize / 2, iconSize / 2, iconSize/2, backgroundPaint); + } else { + Paint textPaint = new Paint(); + textPaint.setColor(color); + textPaint.setTextSize(130); + textPaint.setAntiAlias(true); + textPaint.setTextAlign(Paint.Align.CENTER); + textPaint.setTypeface(android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD)); + float x = iconSize / 2f; + float y = (iconSize / 2f) - ((textPaint.descent() + textPaint.ascent()) / 2f); - Paint textPaint = new Paint(); - textPaint.setColor(color); - textPaint.setTextSize(130); - textPaint.setAntiAlias(true); - textPaint.setTextAlign(Paint.Align.CENTER); - textPaint.setTypeface(android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD)); - - float x = iconSize / 2f; - float y = (iconSize / 2f) - ((textPaint.descent() + textPaint.ascent()) / 2f); - - canvas.drawText(letter, x, y, textPaint); + canvas.drawText(letter, x, y, textPaint); + } return Icon.createWithBitmap(bitmap); } diff --git a/apps/mobile/app/hooks/use-actions.tsx b/apps/mobile/app/hooks/use-actions.tsx index d5962b9c8..d74b89569 100644 --- a/apps/mobile/app/hooks/use-actions.tsx +++ b/apps/mobile/app/hooks/use-actions.tsx @@ -19,6 +19,7 @@ along with this program. If not, see . /* eslint-disable no-inner-declarations */ import { useAreFeaturesAvailable } from "@notesnook/common"; import { + Color, createInternalLink, Item, ItemReference, @@ -1191,7 +1192,10 @@ export const useActions = ({ if ( Platform.OS === "android" && - (item.type === "tag" || item.type === "note" || item.type === "notebook") + (item.type === "tag" || + item.type === "note" || + item.type === "notebook" || + item.type === "color") ) { actions.push({ id: "launcher-shortcut", @@ -1203,7 +1207,8 @@ export const useActions = ({ item.id, item.type, item.title, - (item as Note).headline || (item as Notebook).description || "" + (item as Note).headline || (item as Notebook).description || "", + (item as Color).colorCode ); } catch (e) {} } diff --git a/apps/mobile/app/hooks/use-app-events.tsx b/apps/mobile/app/hooks/use-app-events.tsx index 7c572240d..3a13f5414 100644 --- a/apps/mobile/app/hooks/use-app-events.tsx +++ b/apps/mobile/app/hooks/use-app-events.tsx @@ -216,6 +216,22 @@ const onAppOpenedFromURL = async (event: { }); } } + } else if ( + url.startsWith("https://app.notesnook.com/open_color?") && + !event.isInitialUrl + ) { + const id = new URL(url).searchParams.get("id"); + if (id) { + const color = await db.colors.color(id); + if (color) { + Navigation.navigate("ColoredNotes", { + type: "color", + id: color.id, + item: color, + canGoBack: true + }); + } + } } else if (url.startsWith("https://app.notesnook.com/open_reminder")) { const id = new URL(url).searchParams.get("id"); if (id) { diff --git a/apps/mobile/app/navigation/navigation-stack.tsx b/apps/mobile/app/navigation/navigation-stack.tsx index 9884182ee..48fa476c4 100644 --- a/apps/mobile/app/navigation/navigation-stack.tsx +++ b/apps/mobile/app/navigation/navigation-stack.tsx @@ -87,6 +87,18 @@ const AppNavigation = React.memo( }); return; } + } else if (url?.startsWith("https://app.notesnook.com/open_color?")) { + const id = new URL(url).searchParams.get("id"); + if (id) { + setHome({ + name: "ColoredNotes", + params: { + type: "color", + id: id + } + }); + return; + } } } diff --git a/apps/mobile/app/stores/index.ts b/apps/mobile/app/stores/index.ts index a1280e8c3..e2f0218f7 100644 --- a/apps/mobile/app/stores/index.ts +++ b/apps/mobile/app/stores/index.ts @@ -47,6 +47,7 @@ async function syncShortcuts(result: ShortcutInfo[]) { } else if (note.title !== shortcut.title) { NotesnookModule.updateShortcut( shortcut.id, + "note", note.title, note.headline ); @@ -61,6 +62,7 @@ async function syncShortcuts(result: ShortcutInfo[]) { } else if (notebook.title !== shortcut.title) { NotesnookModule.updateShortcut( shortcut.id, + "notebook", notebook.title, notebook.description ); @@ -73,7 +75,28 @@ async function syncShortcuts(result: ShortcutInfo[]) { if (!tag) { NotesnookModule.removeShortcut(shortcut.id); } else if (tag.title !== shortcut.title) { - NotesnookModule.updateShortcut(shortcut.id, tag.title, tag.title); + NotesnookModule.updateShortcut( + shortcut.id, + "tag", + tag.title, + tag.title + ); + } + } + break; + case "color": + { + const color = await db.colors.color(shortcut.id); + if (!color) { + NotesnookModule.removeShortcut(shortcut.id); + } else if (color.title !== shortcut.title) { + NotesnookModule.updateShortcut( + shortcut.id, + "color", + color.title, + color.title, + color.colorCode + ); } } break; diff --git a/apps/mobile/app/utils/notesnook-module.ts b/apps/mobile/app/utils/notesnook-module.ts index 9aac19f41..47f7783a8 100644 --- a/apps/mobile/app/utils/notesnook-module.ts +++ b/apps/mobile/app/utils/notesnook-module.ts @@ -23,7 +23,7 @@ export type ShortcutInfo = { id: string; title: string; description: string; - type: "note" | "notebook" | "tag"; + type: "note" | "notebook" | "tag" | "color"; }; interface NotesnookModuleInterface { @@ -50,15 +50,18 @@ interface NotesnookModuleInterface { isGestureNavigationEnabled: () => boolean; addShortcut: ( id: string, - type: "note" | "notebook" | "tag", + type: "note" | "notebook" | "tag" | "color", title: string, - description?: string + description?: string, + color?: string ) => Promise; removeShortcut: (id: string) => Promise; updateShortcut: ( id: string, + type: "note" | "notebook" | "tag" | "color", title: string, - description?: string + description?: string, + color?: string ) => Promise; removeAllShortcuts: () => Promise; getAllShortcuts: () => Promise;