Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android: adds "recent notes" widget #5571

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintignore
Expand Up @@ -858,6 +858,10 @@ packages/app-mobile/utils/setupNotifications.js.map
packages/app-mobile/utils/shareHandler.d.ts
packages/app-mobile/utils/shareHandler.js
packages/app-mobile/utils/shareHandler.js.map
packages/app-mobile/utils/RecentsWidget.js
packages/app-mobile/utils/RecentsWidget.js.map
packages/app-mobile/utils/WidgetUtils.js
packages/app-mobile/utils/WidgetUtils.js.map
packages/fork-htmlparser2/src/CollectingHandler.d.ts
packages/fork-htmlparser2/src/CollectingHandler.js
packages/fork-htmlparser2/src/CollectingHandler.js.map
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -841,6 +841,10 @@ packages/app-mobile/utils/setupNotifications.js.map
packages/app-mobile/utils/shareHandler.d.ts
packages/app-mobile/utils/shareHandler.js
packages/app-mobile/utils/shareHandler.js.map
packages/app-mobile/utils/RecentsWidget.js
packages/app-mobile/utils/RecentsWidget.js.map
packages/app-mobile/utils/WidgetUtils.js
packages/app-mobile/utils/WidgetUtils.js.map
packages/fork-htmlparser2/src/CollectingHandler.d.ts
packages/fork-htmlparser2/src/CollectingHandler.js
packages/fork-htmlparser2/src/CollectingHandler.js.map
Expand Down
11 changes: 11 additions & 0 deletions packages/app-mobile/android/app/src/main/AndroidManifest.xml
Expand Up @@ -63,6 +63,17 @@
</intent-filter>
</activity>

<receiver android:name=".widgets.recents.RecentsWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/recents_widget_provider_info" />
</receiver>
<service android:name=".widgets.recents.RecentsWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />

<!-- SHARE EXTENSION -->
<activity
android:noHistory="true"
Expand Down
Expand Up @@ -17,6 +17,7 @@
import net.cozic.joplin.share.SharePackage;
import net.cozic.joplin.ssl.SslPackage;
import net.cozic.joplin.textinput.TextInputPackage;
import net.cozic.joplin.widgets.WidgetDataPackage;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -46,6 +47,7 @@ protected List<ReactPackage> getPackages() {
packages.add(new SharePackage());
packages.add(new SslPackage());
packages.add(new TextInputPackage());
packages.add(new WidgetDataPackage());
return packages;
}

Expand Down
@@ -0,0 +1,80 @@
package net.cozic.joplin.widgets;

import android.content.Context;
import android.content.SharedPreferences;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import org.json.JSONException;
import org.json.JSONObject;

public abstract class WidgetData {
private static final String NAME = "widget_data";
private SharedPreferences sharedPreferences;

protected Context context;

protected abstract String getKey();

protected WidgetData(Context context) {
this.context = context;
sharedPreferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE);
}

protected JSONObject readJSON() {
try {
return new JSONObject(readString());
} catch (JSONException e) {
return new JSONObject();
}
}

protected void writeJSON(JSONObject value) {
writeString(value.toString());
}

protected String readString() {
return sharedPreferences.getString(getKey(), "{}");
}

protected void writeString(String value) {
sharedPreferences.edit().putString(getKey(), value).apply();
}

public ReactModule createReactModule(String name) {
return new ReactModule((ReactApplicationContext) context, name, this);
}

private final static class ReactModule extends ReactContextBaseJavaModule {
private String name;
private WidgetData widgetData;

private ReactModule(@NonNull ReactApplicationContext reactContext, String name, WidgetData widgetData) {
super(reactContext);
this.name = name;
this.widgetData = widgetData;
}

@NonNull
@Override
public String getName() {
return name;
}

@ReactMethod
public void write(String value) {
widgetData.writeString(value);
}

@ReactMethod
public void read(Promise promise) {
promise.resolve(widgetData.readString());
}

}
}
@@ -0,0 +1,29 @@
package net.cozic.joplin.widgets;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import net.cozic.joplin.widgets.recents.RecentsWidgetData;

import java.util.Collections;
import java.util.List;

public class WidgetDataPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return Collections.singletonList(
new RecentsWidgetData(reactContext).createReactModule("RecentsWidget")
);
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
@@ -0,0 +1,78 @@
package net.cozic.joplin.widgets.recents;

import android.content.Context;
import android.content.Intent;

import net.cozic.joplin.widgets.WidgetData;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class RecentsWidgetData extends WidgetData {
public RecentsWidgetData(Context context) {
super(context);
}

private void broadcastUpdate() {
Intent intent = new Intent(context, RecentsWidgetProvider.class);
intent.setAction(RecentsWidgetProvider.UPDATE_ACTION);
context.sendBroadcast(intent);
}

public List<NoteItem> readRecents() {
JSONObject data = readJSON();
try {
JSONArray notes = data.getJSONArray("notes");
List<NoteItem> result = new ArrayList<>(notes.length());
for (int i = 0; i < notes.length(); i++) {
result.add(NoteItem.fromJSONObject(notes.getJSONObject(i)));
}
return result;
} catch (JSONException e) {
return Collections.emptyList();
}
}

@Override
protected void writeString(String value) {
super.writeString(value);
broadcastUpdate();
}

@Override
protected String getKey() {
return "RecentsWidget";
}

public static final class NoteItem {
private String id;
private String title;

public static NoteItem fromJSONObject(JSONObject obj) throws JSONException {
return new NoteItem(obj.getString("id"), obj.getString("title"));
}

public NoteItem(String id, String title) {
this.id = id;
this.title = title;
}

public String getId() {
return id;
}

public String getTitle() {
return title;
}

@Override
public int hashCode() {
return getId().hashCode();
}
}
}
@@ -0,0 +1,79 @@
package net.cozic.joplin.widgets.recents;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.RemoteViews;

import net.cozic.joplin.R;

public class RecentsWidgetProvider extends AppWidgetProvider {
public static final String CLICK_ACTION = "RECENTS_WIDGET_CLICK_ACTION";
public static final String UPDATE_ACTION = "RECENTS_WIDGET_UPDATE_ACTION";

public static final String NOTE_ID = "RECENTS_WIDGET_NOTE_ID";

private static final int listViewId = R.id.list_view;

private void handleClick(Context context, String noteId) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("joplin://x-callback-url/openNote?id=" + noteId));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}

private void handleUpdate(Context context) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] ids = appWidgetManager.getAppWidgetIds(new ComponentName(context, getClass()));
appWidgetManager.notifyAppWidgetViewDataChanged(ids, listViewId);
}

@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
String action = intent.getAction();
if (action == null) {
return;
}
switch (action) {
case CLICK_ACTION:
String noteId = intent.getStringExtra(NOTE_ID);
if (noteId != null) {
handleClick(context, noteId);
}
break;
case UPDATE_ACTION:
handleUpdate(context);
break;
}
}

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.recents_widget);
rv.setRemoteAdapter(listViewId, widgetIntent(context, appWidgetId));
rv.setEmptyView(listViewId, R.id.empty_view);
rv.setPendingIntentTemplate(listViewId, pendingIntentTemplate(context, appWidgetId));
appWidgetManager.updateAppWidget(appWidgetId, rv);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
}

private Intent widgetIntent(Context context, int appWidgetId) {
Intent intent = new Intent(context, RecentsWidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
return intent;
}

private PendingIntent pendingIntentTemplate(Context context, int appWidgetId) {
Intent intent = new Intent(context, RecentsWidgetProvider.class);
intent.setAction(RecentsWidgetProvider.CLICK_ACTION);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}