| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package net.cozic.joplin.widgets.recents; | ||
|
|
||
| import android.content.Context; | ||
| import android.content.Intent; | ||
| import android.os.Bundle; | ||
| import android.widget.RemoteViews; | ||
| import android.widget.RemoteViewsService; | ||
|
|
||
| import net.cozic.joplin.R; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class RecentsWidgetService extends RemoteViewsService { | ||
| @Override | ||
| public RemoteViewsFactory onGetViewFactory(Intent intent) { | ||
| return new RecentsWidgetDataViewsFactory(getApplicationContext(), intent); | ||
| } | ||
|
|
||
| private static class RecentsWidgetDataViewsFactory implements RemoteViewsService.RemoteViewsFactory { | ||
| private List<RecentsWidgetData.NoteItem> notes; | ||
| private Context context; | ||
| private RecentsWidgetData recentsWidgetData; | ||
|
|
||
| public RecentsWidgetDataViewsFactory(Context context, Intent intent) { | ||
| this.context = context; | ||
| recentsWidgetData = new RecentsWidgetData(context); | ||
| } | ||
|
|
||
| @Override | ||
| public void onCreate() { | ||
| notes = recentsWidgetData.readRecents(); | ||
| } | ||
|
|
||
| @Override | ||
| public void onDataSetChanged() { | ||
| notes = recentsWidgetData.readRecents(); | ||
| } | ||
|
|
||
| @Override | ||
| public RemoteViews getViewAt(int position) { | ||
| RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.recents_widget_item); | ||
| rv.setTextViewText(R.id.recents_widget_item, notes.get(position).getTitle()); | ||
| rv.setOnClickFillInIntent(R.id.recents_widget_item, fillInIntent(notes.get(position).getId())); | ||
| return rv; | ||
| } | ||
|
|
||
| private Intent fillInIntent(String noteId) { | ||
| Bundle extras = new Bundle(); | ||
| extras.putString(RecentsWidgetProvider.NOTE_ID, noteId); | ||
| Intent intent = new Intent(); | ||
| intent.putExtras(extras); | ||
| return intent; | ||
| } | ||
|
|
||
| @Override | ||
| public void onDestroy() { | ||
| notes.clear(); | ||
| } | ||
|
|
||
| @Override | ||
| public int getCount() { | ||
| return notes.size(); | ||
| } | ||
|
|
||
| @Override | ||
| public RemoteViews getLoadingView() { | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public int getViewTypeCount() { | ||
| return 1; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasStableIds() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public long getItemId(int position) { | ||
| return notes.get(position).hashCode(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:background="?android:attr/colorBackground"> | ||
|
|
||
| <ListView | ||
| android:id="@+id/list_view" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:loopViews="true" /> | ||
|
|
||
| <TextView | ||
| android:id="@+id/empty_view" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:gravity="center" | ||
| android:text="@string/no_recent_notes_found" | ||
| android:textColor="?android:attr/textColorPrimary" | ||
| android:textSize="@dimen/recents_title_text_size" | ||
| android:textStyle="bold" /> | ||
| </FrameLayout> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <TextView xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:id="@+id/recents_widget_item" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:textColor="?android:attr/textColorPrimary" | ||
| android:padding="@dimen/recents_title_padding" | ||
| android:textSize="@dimen/recents_title_text_size" | ||
| android:textStyle="bold" /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| <resources> | ||
| <dimen name="recents_title_text_size">16sp</dimen> | ||
| <dimen name="recents_title_padding">8dp</dimen> | ||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| <resources> | ||
| <string name="app_name">Joplin</string> | ||
| <string name="default_notification_channel_id">net.cozic.joplin.notification</string> | ||
| <string name="no_recent_notes_found">No recent notes found</string> | ||
laurent22 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| </resources> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:minWidth="40dp" | ||
| android:minHeight="40dp" | ||
| android:initialLayout="@layout/recents_widget" | ||
| android:resizeMode="horizontal|vertical" | ||
| android:widgetCategory="home_screen" /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,10 @@ import setupQuickActions from './setupQuickActions'; | |
| import PluginAssetsLoader from './PluginAssetsLoader'; | ||
| import AlarmService from '@joplin/lib/services/AlarmService'; | ||
| import Alarm from '@joplin/lib/models/Alarm'; | ||
| import eventManager from '@joplin/lib/eventManager'; | ||
| import time from '@joplin/lib/time'; | ||
| import Logger, { TargetType } from '@joplin/lib/Logger'; | ||
| import BaseModel, { ModelType } from '@joplin/lib/BaseModel'; | ||
| import BaseService from '@joplin/lib/services/BaseService'; | ||
| import ResourceService from '@joplin/lib/services/ResourceService'; | ||
| import KvStore from '@joplin/lib/services/KvStore'; | ||
|
|
@@ -20,6 +21,7 @@ import PoorManIntervals from '@joplin/lib/PoorManIntervals'; | |
| import reducer from '@joplin/lib/reducer'; | ||
| import ShareExtension from './utils/ShareExtension'; | ||
| import handleShared from './utils/shareHandler'; | ||
| import { updateRecentsWidgetWithDebounce } from './utils/WidgetUtils'; | ||
| import uuid from '@joplin/lib/uuid'; | ||
| import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtils'; | ||
| import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile'; | ||
|
|
@@ -754,6 +756,8 @@ class AppComponent extends React.Component { | |
|
|
||
| Linking.addEventListener('url', this.handleOpenURL_); | ||
|
|
||
| eventManager.on('itemChange', this.onItemChange_); | ||
|
|
||
| BackButtonService.initialize(this.backButtonHandler_); | ||
|
|
||
| AlarmService.setInAppNotificationHandler(async (alarmId: string) => { | ||
|
|
@@ -810,6 +814,13 @@ class AppComponent extends React.Component { | |
| return false; | ||
| } | ||
|
|
||
| private onItemChange_({ itemType }: {itemType: ModelType}) { | ||
| if (itemType !== ModelType.Note) { | ||
| return; | ||
| } | ||
| updateRecentsWidgetWithDebounce(); | ||
|
There was a problem hiding this comment. Is there any way to detect if the widget is installed, and if it's not, to skip this call? There was a problem hiding this comment. Seems the only way is to store the widget status in shared preferences and use that. However, I don't think this is a good idea:
|
||
| } | ||
|
|
||
| private async handleShareData() { | ||
| const sharedData = await ShareExtension.data(); | ||
| if (sharedData) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| const { NativeModules, Platform } = require('react-native'); | ||
|
|
||
| export interface NoteItem { | ||
| id: string; | ||
| title: string; | ||
| } | ||
|
|
||
| interface WidgetData { | ||
| notes?: NoteItem[]; | ||
| } | ||
|
|
||
| export const RecentsWidget = (Platform.OS === 'android' && NativeModules.RecentsWidget) ? | ||
| { | ||
| read: async (): Promise<WidgetData> => JSON.parse(await NativeModules.RecentsWidget.read()), | ||
| write: async (data: WidgetData) => NativeModules.RecentsWidget.write(JSON.stringify(data)), | ||
| } : | ||
| { | ||
| read: async (): Promise<WidgetData> => ({}), | ||
| write: async (_: WidgetData) => {}, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { RecentsWidget } from './RecentsWidget'; | ||
| import Note from '@joplin/lib/models/Note'; | ||
| import { reg } from '@joplin/lib/registry'; | ||
| import shim from '@joplin/lib/shim'; | ||
|
|
||
| const MAX_COUNT = 10; | ||
| const DEBOUNCE_TIMEOUT = 5000; | ||
|
|
||
| export async function updateRecentsWidget() { | ||
| reg.logger().info('updating recents widget'); | ||
| const recents = await Note.all({ | ||
| fields: ['id', 'title'], | ||
| order: [{ by: 'updated_time', dir: 'DESC' }], | ||
| limit: MAX_COUNT, | ||
| }); | ||
| return RecentsWidget.write({ | ||
| notes: recents, | ||
| }); | ||
| } | ||
|
|
||
| let hasUpdateScheduled = false; | ||
|
|
||
| export function updateRecentsWidgetWithDebounce() { | ||
| if (hasUpdateScheduled) { | ||
| return; | ||
| } | ||
| hasUpdateScheduled = true; | ||
| shim.setTimeout(async () => { | ||
| await updateRecentsWidget(); | ||
| hasUpdateScheduled = false; | ||
| }, DEBOUNCE_TIMEOUT); | ||
| } |