From b232174fc36ed2eeb14aa3b182ef2d2e4a3174f9 Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Thu, 16 Jan 2025 15:50:03 +0500 Subject: [PATCH] mobile: reminders widget for android --- apps/mobile/app/app.tsx | 13 +- apps/mobile/app/hooks/use-app-events.tsx | 16 +-- .../mobile/app/navigation/navigation-stack.js | 17 ++- apps/mobile/app/services/intent.ts | 69 +++++++++ apps/mobile/app/services/notifications.ts | 41 +++++- apps/mobile/app/utils/notesnook-module.ts | 6 +- .../android/app/src/main/AndroidManifest.xml | 14 ++ .../notesnook/RCTNNativeModule.java | 19 ++- .../notesnook/ReminderViewsService.java | 100 +++++++++++++ .../notesnook/ReminderWidgetProvider.java | 50 +++++++ .../notesnook/datatypes/Reminder.java | 135 ++++++++++++++++++ .../main/res/layout/widget_reminder_empty.xml | 8 ++ .../res/layout/widget_reminder_layout.xml | 29 ++++ .../layout/widget_reminder_layout_small.xml | 22 +++ .../src/main/res/layout/widget_reminders.xml | 56 ++++++++ .../app/src/main/res/values-night/colors.xml | 1 + .../app/src/main/res/values/colors.xml | 1 + .../app/src/main/res/values/strings.xml | 2 + .../main/res/xml/widget_reminders_info.xml | 13 ++ 19 files changed, 582 insertions(+), 30 deletions(-) create mode 100644 apps/mobile/app/services/intent.ts create mode 100644 apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/ReminderViewsService.java create mode 100644 apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/ReminderWidgetProvider.java create mode 100644 apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/datatypes/Reminder.java create mode 100644 apps/mobile/native/android/app/src/main/res/layout/widget_reminder_empty.xml create mode 100644 apps/mobile/native/android/app/src/main/res/layout/widget_reminder_layout.xml create mode 100644 apps/mobile/native/android/app/src/main/res/layout/widget_reminder_layout_small.xml create mode 100644 apps/mobile/native/android/app/src/main/res/layout/widget_reminders.xml create mode 100644 apps/mobile/native/android/app/src/main/res/xml/widget_reminders_info.xml diff --git a/apps/mobile/app/app.tsx b/apps/mobile/app/app.tsx index c9ff19ead..def61e37c 100644 --- a/apps/mobile/app/app.tsx +++ b/apps/mobile/app/app.tsx @@ -39,8 +39,7 @@ import SettingsService from "./services/settings"; import { TipManager } from "./services/tip-manager"; import { useThemeStore } from "./stores/use-theme-store"; import { useUserStore } from "./stores/use-user-store"; -import { NotesnookModule } from "./utils/notesnook-module"; -import { setAppState } from "./screens/editor/tiptap/utils"; +import { IntentService } from "./services/intent"; I18nManager.allowRTL(false); I18nManager.forceRTL(false); @@ -49,16 +48,8 @@ const { appLockEnabled, appLockMode } = SettingsService.get(); if (appLockEnabled || appLockMode !== "none") { useUserStore.getState().lockApp(true); } -const launchIntent = NotesnookModule.getIntent(); -if (launchIntent["com.streetwriters.notesnook.OpenNoteId"]) { - setAppState({ - movedAway: false, - editing: true, - timestamp: Date.now(), - noteId: launchIntent["com.streetwriters.notesnook.OpenNoteId"] - }); -} +IntentService.onLaunch(); const App = (props: { configureMode: "note-preview" }) => { useAppEvents(); //@ts-ignore diff --git a/apps/mobile/app/hooks/use-app-events.tsx b/apps/mobile/app/hooks/use-app-events.tsx index 43dc75f7e..20b80595a 100644 --- a/apps/mobile/app/hooks/use-app-events.tsx +++ b/apps/mobile/app/hooks/use-app-events.tsx @@ -66,6 +66,7 @@ import { eSubscribeEvent, presentSheet } from "../services/event-manager"; +import { IntentService } from "../services/intent"; import { clearMessage, setEmailVerifyMessage, @@ -698,19 +699,8 @@ export const useAppEvents = () => { } setTimeout(async () => { - const intent = NotesnookModule.getIntent(); - if (intent["com.streetwriters.notesnook.OpenNoteId"]) { - const note = await db.notes.note( - intent["com.streetwriters.notesnook.OpenNoteId"] - ); - if (note) { - eSendEvent(eOnLoadNote, { - item: note - }); - tabBarRef.current?.goToPage(1, false); - } - } - }, 50); + IntentService.onAppStateChanged(); + }, 100); } else { await saveEditorState(); if ( diff --git a/apps/mobile/app/navigation/navigation-stack.js b/apps/mobile/app/navigation/navigation-stack.js index 53c62f692..33415f036 100644 --- a/apps/mobile/app/navigation/navigation-stack.js +++ b/apps/mobile/app/navigation/navigation-stack.js @@ -45,6 +45,9 @@ import { useSelectionStore } from "../stores/use-selection-store"; import { useSettingStore } from "../stores/use-setting-store"; import { rootNavigatorRef } from "../utils/global-refs"; import { strings } from "@notesnook/intl"; +import { IntentService } from "../services/intent"; +import ReminderSheet from "../components/sheets/reminder"; +import { db } from "../common/database"; const NativeStack = createNativeStackNavigator(); const IntroStack = createNativeStackNavigator(); @@ -76,12 +79,24 @@ const _Tabs = () => { const introCompleted = useSettingStore( (state) => state.settings.introCompleted ); + const height = useSettingStore((state) => state.dimensions.height); const insets = useGlobalSafeAreaInsets(); const screenHeight = height - (50 + insets.top + insets.bottom); React.useEffect(() => { - setTimeout(() => { + setTimeout(async () => { useNavigationStore.getState().update(homepage); + const intent = IntentService.getLaunchIntent(); + if (intent && intent["com.streetwriters.notesnook.OpenReminderId"]) { + const reminder = await db.reminders.reminder( + intent["com.streetwriters.notesnook.OpenReminderId"] + ); + if (reminder) { + ReminderSheet.present(reminder); + } + } else if (intent["com.streetwriters.notesnook.NewReminder"]) { + ReminderSheet.present(); + } }, 1000); }, [homepage]); diff --git a/apps/mobile/app/services/intent.ts b/apps/mobile/app/services/intent.ts new file mode 100644 index 000000000..2b30b6988 --- /dev/null +++ b/apps/mobile/app/services/intent.ts @@ -0,0 +1,69 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import { db } from "../common/database"; +import ReminderSheet from "../components/sheets/reminder"; +import { setAppState } from "../screens/editor/tiptap/utils"; +import { eOnLoadNote } from "../utils/events"; +import { tabBarRef } from "../utils/global-refs"; +import { NotesnookModule } from "../utils/notesnook-module"; +import { eSendEvent } from "./event-manager"; + +const launchIntent = NotesnookModule.getIntent(); +let used = false; +let launched = false; +export const IntentService = { + getLaunchIntent() { + if (used) return null; + used = true; + return launchIntent; + }, + onLaunch() { + if (launched) return; + launched = true; + if (launchIntent["com.streetwriters.notesnook.OpenNoteId"]) { + setAppState({ + movedAway: false, + editing: true, + timestamp: Date.now(), + noteId: launchIntent["com.streetwriters.notesnook.OpenNoteId"] + }); + } + }, + async onAppStateChanged() { + const intent = NotesnookModule.getIntent(); + if (intent["com.streetwriters.notesnook.OpenNoteId"]) { + const note = await db.notes.note( + intent["com.streetwriters.notesnook.OpenNoteId"] + ); + if (note) { + eSendEvent(eOnLoadNote, { + item: note + }); + tabBarRef.current?.goToPage(1, false); + } + } else if (intent["com.streetwriters.notesnook.OpenReminderId"]) { + const reminder = await db.reminders.reminder( + intent["com.streetwriters.notesnook.OpenReminderId"] + ); + if (reminder) ReminderSheet.present(reminder); + } else if (intent["com.streetwriters.notesnook.NewReminder"]) { + ReminderSheet.present(); + } + } +}; diff --git a/apps/mobile/app/services/notifications.ts b/apps/mobile/app/services/notifications.ts index ac98c6335..b9535e428 100644 --- a/apps/mobile/app/services/notifications.ts +++ b/apps/mobile/app/services/notifications.ts @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { Reminder } from "@notesnook/core"; +import { isReminderActive, Reminder } from "@notesnook/core"; import { strings } from "@notesnook/intl"; import notifee, { AndroidStyle, @@ -51,6 +51,7 @@ import { DDS } from "./device-detection"; import { eSendEvent } from "./event-manager"; import Navigation from "./navigation"; import SettingsService from "./settings"; +import { getFormattedReminderTime } from "@notesnook/common"; let pinned: DisplayedNotification[] = []; @@ -104,6 +105,7 @@ async function initDatabase() { } const onEvent = async ({ type, detail }: Event) => { + console.log("EVENT RECIEVED...."); await initDatabase(); const { notification, pressAction, input } = detail; if (type === EventType.DELIVERED && Platform.OS === "android") { @@ -120,7 +122,7 @@ const onEvent = async ({ type, detail }: Event) => { await scheduleNotification(reminder); } } - + updateRemindersForWidget(); return; } if (type === EventType.PRESS) { @@ -167,6 +169,7 @@ const onEvent = async ({ type, detail }: Event) => { ); useRelationStore.getState().update(); useReminderStore.getState().refresh(); + updateRemindersForWidget(); break; } case "REMINDER_DISABLE": { @@ -184,6 +187,7 @@ const onEvent = async ({ type, detail }: Event) => { ); useRelationStore.getState().update(); useReminderStore.getState().refresh(); + updateRemindersForWidget(); break; } case strings.unpin(): { @@ -249,6 +253,34 @@ const onEvent = async ({ type, detail }: Event) => { } }; +type ReminderWithFormattedTime = Reminder & { + formattedTime?: string; +}; + +async function updateRemindersForWidget() { + const reminders: ReminderWithFormattedTime[] = await db.reminders?.all.items( + undefined, + { + sortBy: "dueDate", + sortDirection: "desc" + } + ); + const activeReminders = []; + if (!reminders) return; + for (const reminder of reminders) { + if (isReminderActive(reminder)) { + reminder.formattedTime = getFormattedReminderTime(reminder); + activeReminders.push(reminder); + } + } + NotesnookModule.setString( + "appPreview", + "remindersList", + JSON.stringify(activeReminders) + ); + NotesnookModule.updateReminderWidget(); +} + async function setupIOSCategories() { try { if (Platform.OS === "ios") { @@ -398,6 +430,7 @@ async function scheduleNotification( trigger ); } + updateRemindersForWidget(); } catch (e) { console.log("Schedule notification", e); } @@ -943,6 +976,7 @@ async function setupReminders(checkNeedsScheduling = false) { trigger.notification.id && notifee.cancelTriggerNotification(trigger.notification.id as string) ); + updateRemindersForWidget(); } async function pinNote(id: string) { @@ -988,7 +1022,8 @@ const Notifications = { getChannelId, isNotePinned, pinNote, - Events + Events, + updateRemindersForWidget }; export default Notifications; diff --git a/apps/mobile/app/utils/notesnook-module.ts b/apps/mobile/app/utils/notesnook-module.ts index 2450cca54..07de78208 100644 --- a/apps/mobile/app/utils/notesnook-module.ts +++ b/apps/mobile/app/utils/notesnook-module.ts @@ -33,10 +33,13 @@ interface NotesnookModuleInterface { getWidgetId: () => void; getIntent: () => { "com.streetwriters.notesnook.OpenNoteId"?: string; + "com.streetwriters.notesnook.OpenReminderId"?: string; + "com.streetwriters.notesnook.NewReminder"?: string; }; getWidgetNotes: () => Promise; hasWidgetNote: (noteId: string) => Promise; updateWidgetNote: (noteId: string, data: string) => void; + updateReminderWidget: () => void; } export const NotesnookModule: NotesnookModuleInterface = Platform.select({ @@ -55,7 +58,8 @@ export const NotesnookModule: NotesnookModuleInterface = Platform.select({ getIntent: () => {}, getWidgetNotes: () => {}, hasWidgetNote: () => {}, - updateWidgetNote: () => {} + updateWidgetNote: () => {}, + updateReminderWidget: () => {} }, android: NativeModules.NNativeModule }); diff --git a/apps/mobile/native/android/app/src/main/AndroidManifest.xml b/apps/mobile/native/android/app/src/main/AndroidManifest.xml index fa0e846bd..bce821538 100644 --- a/apps/mobile/native/android/app/src/main/AndroidManifest.xml +++ b/apps/mobile/native/android/app/src/main/AndroidManifest.xml @@ -61,6 +61,15 @@ android:largeHeap="true" android:supportsRtl="false" tools:replace="android:supportsRtl"> + + + + + + + @@ -186,6 +195,11 @@ + + map = pref.getAll(); WritableArray arr = Arguments.createArray(); for(Map.Entry entry : map.entrySet()){ + if (entry.getKey().equals("remindersList")) continue; String value = (String) entry.getValue(); Gson gson = new Gson(); Note note = gson.fromJson(value, Note.class); @@ -176,7 +182,6 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { } @ReactMethod public void updateWidgetNote(final String noteId, final String data) { - SharedPreferences pref = getReactApplicationContext().getSharedPreferences("appPreview", Context.MODE_PRIVATE); Map map = pref.getAll(); SharedPreferences.Editor edit = pref.edit(); @@ -193,4 +198,16 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule { NotePreviewWidget.updateAppWidget(mContext, AppWidgetManager.getInstance(mContext), Integer.parseInt(id)); } } + + @ReactMethod + public void updateReminderWidget() { + AppWidgetManager wm = AppWidgetManager.getInstance(mContext); + int[] ids = wm.getAppWidgetIds(ComponentName.createRelative(mContext.getPackageName(), ReminderWidgetProvider.class.getName())); + for (int id: ids) { + Log.d("Reminders", "Updating" + id); + RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.widget_reminders); + ReminderWidgetProvider.updateAppWidget(mContext, wm, id, views); + wm.notifyAppWidgetViewDataChanged(id, R.id.widget_list_view); + } + } } diff --git a/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/ReminderViewsService.java b/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/ReminderViewsService.java new file mode 100644 index 000000000..78f0033da --- /dev/null +++ b/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/ReminderViewsService.java @@ -0,0 +1,100 @@ +package com.streetwriters.notesnook; + +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.widget.RemoteViewsService; +import android.content.Context; +import android.widget.RemoteViews; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.streetwriters.notesnook.datatypes.Reminder; + +import java.util.ArrayList; +import java.util.List; + +public class ReminderViewsService extends RemoteViewsService { + static String OpenReminderId = "com.streetwriters.notesnook.OpenReminderId"; + @Override + public RemoteViewsFactory onGetViewFactory(Intent intent) { + return new ReminderRemoteViewsFactory(this.getApplicationContext(), intent); + } +} + +class ReminderRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { + private Context context; + private List reminders; + + public ReminderRemoteViewsFactory(Context context, Intent intent) { + this.context = context; + } + + @Override + public void onCreate() { + // Initialize reminders list + reminders = new ArrayList(); + } + + @Override + public void onDataSetChanged() { + reminders.clear(); + SharedPreferences preferences = context.getSharedPreferences("appPreview", Context.MODE_PRIVATE); + Gson gson = new Gson(); + reminders = gson.fromJson(preferences.getString("remindersList","[]"), new TypeToken>(){}.getType()); + } + + @Override + public void onDestroy() { + reminders.clear(); + } + + @Override + public int getCount() { + return reminders.size(); + } + + @Override + public RemoteViews getViewAt(int position) { + Reminder reminder = reminders.get(position); + + boolean useMiniLayout = reminder.getDescription() == null || reminder.getDescription().isEmpty(); + + RemoteViews views = new RemoteViews(context.getPackageName(), useMiniLayout ? R.layout.widget_reminder_layout_small : R.layout.widget_reminder_layout); + views.setTextViewText(R.id.reminder_title, reminder.getTitle()); + if (!useMiniLayout) { + views.setTextViewText(R.id.reminder_description, reminder.getDescription()); + } + views.setTextViewText(R.id.reminder_time, reminder.getFormattedTime()); + final Intent fillInIntent = new Intent(); + final Bundle extras = new Bundle(); + extras.putString(ReminderViewsService.OpenReminderId, reminder.getId()); + fillInIntent.putExtras(extras); + views.setOnClickFillInIntent(R.id.reminder_item_btn, fillInIntent); + return views; + } + + + @Override + public RemoteViews getLoadingView() { + return null; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean hasStableIds() { + return true; + } +} \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/ReminderWidgetProvider.java b/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/ReminderWidgetProvider.java new file mode 100644 index 000000000..6cd1813ed --- /dev/null +++ b/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/ReminderWidgetProvider.java @@ -0,0 +1,50 @@ +package com.streetwriters.notesnook; + +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.widget.RemoteViews; + +public class ReminderWidgetProvider extends AppWidgetProvider { + static String NewReminder = "com.streetwriters.notesnook.NewReminder"; + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + for (int appWidgetId : appWidgetIds) { + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_reminders); + updateAppWidget(context, appWidgetManager, appWidgetId, views); + } + } + + + private static Bundle getActivityOptionsBundle() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + ActivityOptions activityOptions = ActivityOptions.makeBasic(); + activityOptions.setPendingIntentBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + return activityOptions.toBundle(); + } else + return null; + } + + static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId, RemoteViews views) { + Intent intent = new Intent(context, MainActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE, getActivityOptionsBundle()); + views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent); + + Intent intent2 = new Intent(context, MainActivity.class); + intent2.putExtra(NewReminder, NewReminder); + PendingIntent pendingIntent2 = PendingIntent.getActivity(context, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, getActivityOptionsBundle()); + views.setOnClickPendingIntent(R.id.add_button, pendingIntent2); + + Intent intent3 = new Intent(context, ReminderViewsService.class); + intent3.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + views.setRemoteAdapter(R.id.widget_list_view, intent3); + views.setEmptyView(R.id.widget_list_view, R.id.empty_view); + appWidgetManager.updateAppWidget(appWidgetId, views); + } +} \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/datatypes/Reminder.java b/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/datatypes/Reminder.java new file mode 100644 index 000000000..682abced4 --- /dev/null +++ b/apps/mobile/native/android/app/src/main/java/com/streetwriters/notesnook/datatypes/Reminder.java @@ -0,0 +1,135 @@ +package com.streetwriters.notesnook.datatypes; + +import java.util.concurrent.TimeUnit; + +public class Reminder extends BaseItem { + private String title; + private String description; + private String formattedTime; + private String priority; // "silent", "vibrate", "urgent" + private long date; + private String mode; // "repeat", "once", "permanent" + private String recurringMode; // "week", "month", "day", "year" + private int[] selectedDays; + private boolean localOnly; + private boolean disabled; + private long snoozeUntil; + + // Getters and Setters + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + this.priority = priority; + } + + public long getDate() { + return date; + } + + public void setDate(long date) { + this.date = date; + } + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public String getRecurringMode() { + return recurringMode; + } + + public void setRecurringMode(String recurringMode) { + this.recurringMode = recurringMode; + } + + public int[] getSelectedDays() { + return selectedDays; + } + + public void setSelectedDays(int[] selectedDays) { + this.selectedDays = selectedDays; + } + + public boolean isLocalOnly() { + return localOnly; + } + + public void setLocalOnly(boolean localOnly) { + this.localOnly = localOnly; + } + + public boolean isDisabled() { + return disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + + public long getSnoozeUntil() { + return snoozeUntil; + } + + public void setSnoozeUntil(long snoozeUntil) { + this.snoozeUntil = snoozeUntil; + } + + public String getFormattedTime() { + return formattedTime; + } + + public void setFormattedTime(String formattedTime) { + this.formattedTime = formattedTime; + } + + public String formatTime(long timeInMillis) { + long currentTime = System.currentTimeMillis(); + long diff = timeInMillis - currentTime; + + if (diff < TimeUnit.MINUTES.toMillis(1)) { + return "in " + (diff / 1000) + " seconds"; + } else if (diff < TimeUnit.HOURS.toMillis(1)) { + long minutes = TimeUnit.MILLISECONDS.toMinutes(diff); + return "in " + minutes + " minute" + (minutes > 1 ? "s" : ""); + } else if (diff < TimeUnit.DAYS.toMillis(1)) { + long hours = TimeUnit.MILLISECONDS.toHours(diff); + return "in " + hours + " hour" + (hours > 1 ? "s" : ""); + } else if (diff < TimeUnit.DAYS.toMillis(2)) { + return "tomorrow"; + } else if (diff < TimeUnit.DAYS.toMillis(7)) { + long days = TimeUnit.MILLISECONDS.toDays(diff); + return "in " + days + " day" + (days > 1 ? "s" : ""); + } else if (diff < TimeUnit.DAYS.toMillis(30)) { + long weeks = TimeUnit.MILLISECONDS.toDays(diff) / 7; + return "in " + weeks + " week" + (weeks > 1 ? "s" : ""); + } else if (diff < TimeUnit.DAYS.toMillis(365)) { + long months = TimeUnit.MILLISECONDS.toDays(diff) / 30; + return "in " + months + " month" + (months > 1 ? "s" : ""); + } else { + long years = TimeUnit.MILLISECONDS.toDays(diff) / 365; + return "in " + years + " year" + (years > 1 ? "s" : ""); + } + } +} \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_empty.xml b/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_empty.xml new file mode 100644 index 000000000..9be87bb7a --- /dev/null +++ b/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_empty.xml @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_layout.xml b/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_layout.xml new file mode 100644 index 000000000..f8bc21483 --- /dev/null +++ b/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_layout.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_layout_small.xml b/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_layout_small.xml new file mode 100644 index 000000000..98497b831 --- /dev/null +++ b/apps/mobile/native/android/app/src/main/res/layout/widget_reminder_layout_small.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/res/layout/widget_reminders.xml b/apps/mobile/native/android/app/src/main/res/layout/widget_reminders.xml new file mode 100644 index 000000000..643a30e15 --- /dev/null +++ b/apps/mobile/native/android/app/src/main/res/layout/widget_reminders.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/res/values-night/colors.xml b/apps/mobile/native/android/app/src/main/res/values-night/colors.xml index 34bb3eeaa..f1c79618b 100644 --- a/apps/mobile/native/android/app/src/main/res/values-night/colors.xml +++ b/apps/mobile/native/android/app/src/main/res/values-night/colors.xml @@ -6,5 +6,6 @@ #FF01579B #1f1f1f #1D1D1D + #2E2E2E #B4B4B4 \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/res/values/colors.xml b/apps/mobile/native/android/app/src/main/res/values/colors.xml index 1469babb1..ec65752c9 100644 --- a/apps/mobile/native/android/app/src/main/res/values/colors.xml +++ b/apps/mobile/native/android/app/src/main/res/values/colors.xml @@ -6,5 +6,6 @@ #FF01579B #FFFFFF #D8000000 + #CCCCCC #1D1D1D \ No newline at end of file diff --git a/apps/mobile/native/android/app/src/main/res/values/strings.xml b/apps/mobile/native/android/app/src/main/res/values/strings.xml index 6e998a307..1a233ee09 100644 --- a/apps/mobile/native/android/app/src/main/res/values/strings.xml +++ b/apps/mobile/native/android/app/src/main/res/values/strings.xml @@ -4,6 +4,8 @@ EXAMPLE Add widget Take a quick note. + Quick overview of upcoming reminders + Reminders Note Add a note to home screen Quick note diff --git a/apps/mobile/native/android/app/src/main/res/xml/widget_reminders_info.xml b/apps/mobile/native/android/app/src/main/res/xml/widget_reminders_info.xml new file mode 100644 index 000000000..c9ef39ccf --- /dev/null +++ b/apps/mobile/native/android/app/src/main/res/xml/widget_reminders_info.xml @@ -0,0 +1,13 @@ + \ No newline at end of file