diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 956d12c..129123a 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> + + - \ No newline at end of file + diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html index 7cc5bc4..944f4d8 100644 --- a/app/src/main/assets/index.html +++ b/app/src/main/assets/index.html @@ -133,6 +133,20 @@

Fonts


Add Notes To Bottom + + +
+ Enable Quick Note + +
+
+ + +
+ + + +
@@ -550,7 +564,62 @@

Recycle Bin

try { mergeNotes(JSON.parse(jsonString)); } catch(err) {} }; +// Status bar notes integration +function loadNotificationSettings() { + let notifEnabled = localStorage.getItem("notificationEnabled") === "true"; + document.getElementById('notification-toggle').checked = notifEnabled; + + let savedTab = localStorage.getItem("inboxTabName") || "Inbox"; + document.getElementById('inbox-tab-name').value = savedTab; + // Ensure the tab exists without switching to it + ensureTabExists(savedTab); + // Sync to Android + if (window.Android) { + window.Android.setInboxTabName(savedTab); + if (notifEnabled) window.Android.toggleNotification(true); + } +} + +function toggleNotification(enable) { + localStorage.setItem("notificationEnabled", enable); + if (window.Android) { + window.Android.toggleNotification(enable); + } +} + +function updateInboxTab(name) { + name = name.trim() || "Inbox"; + localStorage.setItem("inboxTabName", name); + ensureTabExists(name); + if (window.Android) { + window.Android.setInboxTabName(name); + } +} + +// Helper: add a tab if it doesn't exist, without changing active category +function ensureTabExists(name) { + if (!noteCategories.includes(name)) { + noteCategories.push(name); + saveData(); + renderTabs(); + } +} + +// Modified note receiver: adds note to the specified tab +window.syncNoteFromAndroid = function(text, tabName) { + tabName = tabName || 'Inbox'; + ensureTabExists(tabName); + let note = { id: Date.now(), text: text, completed: false, category: tabName }; + notes.unshift(note); + saveData(); + if (activeCategory === tabName) { + renderNotes(); + } + // Also update stats if needed (but it's okay) +}; + init(); +loadNotificationSettings(); - \ No newline at end of file + diff --git a/app/src/main/java/io/github/ronynn/karui/MainActivity.java b/app/src/main/java/io/github/ronynn/karui/MainActivity.java index 0c066c3..af2b341 100644 --- a/app/src/main/java/io/github/ronynn/karui/MainActivity.java +++ b/app/src/main/java/io/github/ronynn/karui/MainActivity.java @@ -1,11 +1,24 @@ package io.github.ronynn.karui; +import android.Manifest; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.RemoteInput; import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.drawable.Icon; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.view.View; import android.view.animation.DecelerateInterpolator; @@ -26,173 +39,531 @@ import java.io.InputStreamReader; import java.io.OutputStream; -public class MainActivity extends Activity { +public class MainActivity extends Activity +{ + public static final String ACTION_NOTE_ADDED = "io.github.ronynn.karui.ACTION_NOTE_ADDED"; - private static final int CREATE_FILE_REQUEST_CODE = 1; - private static final int IMPORT_FILE_REQUEST_CODE = 2; - private static final int FILECHOOSER_RESULTCODE = 3; + private static final int CREATE_FILE_REQUEST_CODE = 1; + private static final int IMPORT_FILE_REQUEST_CODE = 2; + private static final int FILECHOOSER_RESULTCODE = 3; + private static final int NOTIFICATION_PERMISSION_REQUEST = 100; + private static final String CHANNEL_ID = "note_reply_channel"; + private static final int NOTIFICATION_ID = 1; - private WebView mWebView; - private View splashScreen; + private WebView mWebView; + private View splashScreen; - private String pendingFileName; - private String pendingFileData; - private String pendingFileType; - - private ValueCallback mFilePathCallback; + private String pendingFileName; + private String pendingFileData; + private String pendingFileType; + private ValueCallback mFilePathCallback; + + private boolean isNotificationActive = false; + private boolean isPageLoaded = false; + private String inboxTabName = "Inbox"; + + private final BroadcastReceiver noteReceiver = new BroadcastReceiver() + { @Override - @SuppressLint({"SetJavaScriptEnabled", "AllowFileAccess"}) - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - mWebView = findViewById(R.id.activity_main_webview); - splashScreen = findViewById(R.id.splash_screen); - - WebSettings webSettings = mWebView.getSettings(); - webSettings.setJavaScriptEnabled(true); - webSettings.setDomStorageEnabled(true); - webSettings.setAllowFileAccess(true); - webSettings.setAllowContentAccess(true); - webSettings.setAllowFileAccessFromFileURLs(true); - webSettings.setAllowUniversalAccessFromFileURLs(true); - - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.setAcceptCookie(true); - cookieManager.setAcceptThirdPartyCookies(mWebView, true); - - mWebView.addJavascriptInterface(new WebAppInterface(), "Android"); - - mWebView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - try { - view.getContext().startActivity(intent); - } catch (ActivityNotFoundException e) { - Toast.makeText(view.getContext(), R.string.no_app_to_open_link, Toast.LENGTH_SHORT).show(); - } - return true; - } + public void onReceive(Context context, Intent intent) + { + if (isPageLoaded) + { + injectPendingNotes(); + } + } + }; - @Override - public void onPageFinished(WebView view, String url) { - ObjectAnimator fadeOut = ObjectAnimator.ofFloat(splashScreen, "alpha", 1f, 0f); - fadeOut.setInterpolator(new DecelerateInterpolator()); - fadeOut.setDuration(500); - fadeOut.start(); + @Override + @SuppressLint({"SetJavaScriptEnabled", "AllowFileAccess"}) + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); - splashScreen.setVisibility(View.GONE); - mWebView.setVisibility(View.VISIBLE); - } - }); - - mWebView.setWebChromeClient(new WebChromeClient() { - @Override - public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { - if (mFilePathCallback != null) { - mFilePathCallback.onReceiveValue(null); - } - mFilePathCallback = filePathCallback; - Intent intent = fileChooserParams.createIntent(); - try { - startActivityForResult(intent, FILECHOOSER_RESULTCODE); - } catch (Exception e) { - mFilePathCallback = null; - return false; - } - return true; - } - }); + mWebView = findViewById(R.id.activity_main_webview); + splashScreen = findViewById(R.id.splash_screen); - mWebView.loadUrl("file:///android_asset/index.html"); - } + WebSettings webSettings = mWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setDomStorageEnabled(true); + webSettings.setAllowFileAccess(true); + webSettings.setAllowContentAccess(true); + webSettings.setAllowFileAccessFromFileURLs(true); + webSettings.setAllowUniversalAccessFromFileURLs(true); - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == RESULT_OK) { - if (data != null && data.getData() != null && pendingFileData != null) { - try { - OutputStream outputStream = getContentResolver().openOutputStream(data.getData()); - if (outputStream != null) { - outputStream.write(pendingFileData.getBytes()); - outputStream.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } else if (requestCode == IMPORT_FILE_REQUEST_CODE && resultCode == RESULT_OK) { - if (data != null && data.getData() != null) { - try { - InputStream inputStream = getContentResolver().openInputStream(data.getData()); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - } - reader.close(); - String jsonContent = sb.toString(); - - String jsCode = "if(window.setAndroidNotes) window.setAndroidNotes(" + JSONObject.quote(jsonContent) + ");"; - mWebView.evaluateJavascript(jsCode, null); - } catch (IOException e) { - e.printStackTrace(); - } - } - } else if (requestCode == FILECHOOSER_RESULTCODE) { - if (mFilePathCallback == null) return; - Uri[] results = null; - if (resultCode == Activity.RESULT_OK && data != null) { - String dataString = data.getDataString(); - if (dataString != null) { - results = new Uri[]{Uri.parse(dataString)}; - } else if (data.getClipData() != null) { - int count = data.getClipData().getItemCount(); - results = new Uri[count]; - for (int i = 0; i < count; i++) { - results[i] = data.getClipData().getItemAt(i).getUri(); - } - } - } - mFilePathCallback.onReceiveValue(results); - mFilePathCallback = null; + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptCookie(true); + cookieManager.setAcceptThirdPartyCookies(mWebView, true); + + mWebView.addJavascriptInterface(new WebAppInterface(), "Android"); + + mWebView.setWebViewClient(new WebViewClient() + { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) + { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + try + { + view.getContext().startActivity(intent); + } + catch (ActivityNotFoundException e) + { + Toast.makeText(view.getContext(), R.string.no_app_to_open_link, Toast.LENGTH_SHORT).show(); } + return true; + } + + @Override + public void onPageFinished(WebView view, String url) + { + ObjectAnimator fadeOut = ObjectAnimator.ofFloat(splashScreen, "alpha", 1f, 0f); + fadeOut.setInterpolator(new DecelerateInterpolator()); + fadeOut.setDuration(500); + fadeOut.start(); + + splashScreen.setVisibility(View.GONE); + mWebView.setVisibility(View.VISIBLE); + + isPageLoaded = true; + injectPendingNotes(); + } + }); + + mWebView.setWebChromeClient(new WebChromeClient() + { + @Override + public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) + { + if (mFilePathCallback != null) + { + mFilePathCallback.onReceiveValue(null); + } + mFilePathCallback = filePathCallback; + Intent intent = fileChooserParams.createIntent(); + try + { + startActivityForResult(intent, FILECHOOSER_RESULTCODE); + } + catch (Exception e) + { + mFilePathCallback = null; + return false; + } + return true; + } + }); + + mWebView.loadUrl("file:///android_asset/index.html"); + + SharedPreferences prefs = getSharedPreferences("note_queue", MODE_PRIVATE); + inboxTabName = prefs.getString("inbox_tab_name", "Inbox"); + + createNotificationChannel(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + { + registerReceiver(noteReceiver, new IntentFilter(ACTION_NOTE_ADDED), Context.RECEIVER_NOT_EXPORTED); } + else + { + registerReceiver(noteReceiver, new IntentFilter(ACTION_NOTE_ADDED)); + } + } - @Override - public void onBackPressed() { - if (mWebView.canGoBack()) { - mWebView.goBack(); - } else { - super.onBackPressed(); + // ---------- NOTIFICATION HANDLING ---------- + + private void createNotificationChannel() + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID, "Quick Note", NotificationManager.IMPORTANCE_LOW); + channel.setDescription("Add notes from status bar"); + NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (manager != null) + { + manager.createNotificationChannel(channel); + } + } + } + + private void showNotification() + { + createNotificationChannel(); + + NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (manager == null) + { + Toast.makeText(this, "Notification service unavailable", Toast.LENGTH_SHORT).show(); + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !manager.areNotificationsEnabled()) + { + Toast.makeText(this, "Notifications are disabled. Please enable them in system settings.", Toast.LENGTH_LONG).show(); + try + { + Intent intent = new Intent(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + intent.setAction(android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(android.provider.Settings.EXTRA_APP_PACKAGE, getPackageName()); } + else + { + intent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + getPackageName())); + } + startActivity(intent); + } + catch (Exception ignored) + { + } + return; } - public class WebAppInterface { - @JavascriptInterface - public void saveFile(String fileName, String fileData, String fileType) { - pendingFileName = fileName; - pendingFileData = fileData; - pendingFileType = fileType; + try + { + Intent openAppIntent = new Intent(this, MainActivity.class); + int openFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + openFlags |= PendingIntent.FLAG_IMMUTABLE; + } + PendingIntent openPendingIntent = PendingIntent.getActivity(this, 0, openAppIntent, openFlags); + + RemoteInput remoteInput = new RemoteInput.Builder(NoteReplyReceiver.KEY_TEXT_REPLY) + .setLabel("Add Note") + .build(); + + Intent replyIntent = new Intent(this, NoteReplyReceiver.class); + int replyFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + replyFlags |= PendingIntent.FLAG_MUTABLE; + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + replyFlags |= PendingIntent.FLAG_IMMUTABLE; + } + PendingIntent replyPendingIntent = PendingIntent.getBroadcast(this, 1, replyIntent, replyFlags); + + Notification.Action replyAction; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + replyAction = new Notification.Action.Builder( + Icon.createWithResource(this, R.drawable.ic_note), + "Add Note", + replyPendingIntent) + .addRemoteInput(remoteInput) + .build(); + } + else + { + replyAction = new Notification.Action.Builder( + R.drawable.ic_note, + "Add Note", + replyPendingIntent) + .addRemoteInput(remoteInput) + .build(); + } + + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + builder = new Notification.Builder(this, CHANNEL_ID); + } + else + { + builder = new Notification.Builder(this); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + builder.setSmallIcon(Icon.createWithResource(this, R.drawable.ic_note)); + } + else + { + builder.setSmallIcon(R.drawable.ic_note); + } - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType(fileType); - intent.putExtra(Intent.EXTRA_TITLE, fileName); - startActivityForResult(intent, CREATE_FILE_REQUEST_CODE); + builder.setContentTitle("Quick Note") + .setContentText("Swipe down to add a note") + .setContentIntent(openPendingIntent) + .addAction(replyAction) + .setOngoing(true); + + Notification notification = builder.build(); + manager.notify(NOTIFICATION_ID, notification); + isNotificationActive = true; + } + catch (Exception e) + { + String msg = e.getClass().getSimpleName() + ": " + e.getMessage(); + if (e.getCause() != null) + { + msg += "\nCause: " + e.getCause().toString(); + } + Toast.makeText(this, "Couldn't show notification:\n" + msg, Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + } + + private void cancelNotification() + { + NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (manager != null) + { + manager.cancel(NOTIFICATION_ID); + } + isNotificationActive = false; + } + + // ---------- DATA INJECTION ---------- + + private void injectPendingNotes() + { + if (!isPageLoaded) + { + return; + } + + SharedPreferences prefs = getSharedPreferences("note_queue", MODE_PRIVATE); + String pendingNotes = prefs.getString("pending_notes", ""); + String pendingTabs = prefs.getString("pending_notes_tabs", ""); + if (pendingNotes.isEmpty()) + { + return; + } + + prefs.edit().remove("pending_notes").remove("pending_notes_tabs").apply(); + + String[] notes = pendingNotes.split("\n"); + String[] tabs = pendingTabs.split("\n"); + + for (int i = 0; i < notes.length; i++) + { + if (notes[i].trim().isEmpty()) + { + continue; + } + String tabName = (i < tabs.length) ? tabs[i] : inboxTabName; + String escapedNote = notes[i] + .replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\n", "\\n"); + String escapedTab = tabName + .replace("\\", "\\\\") + .replace("'", "\\'"); + String js = String.format("syncNoteFromAndroid('%s', '%s')", escapedNote, escapedTab); + mWebView.evaluateJavascript(js, null); + } + } + + @Override + protected void onResume() + { + super.onResume(); + if (isPageLoaded) + { + injectPendingNotes(); + } + } + + // ---------- ACTIVITY RESULTS & LIFECYCLE ---------- + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == RESULT_OK) + { + if (data != null && data.getData() != null && pendingFileData != null) + { + try + { + OutputStream outputStream = getContentResolver().openOutputStream(data.getData()); + if (outputStream != null) + { + outputStream.write(pendingFileData.getBytes()); + outputStream.close(); + } } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + else if (requestCode == IMPORT_FILE_REQUEST_CODE && resultCode == RESULT_OK) + { + if (data != null && data.getData() != null) + { + try + { + InputStream inputStream = getContentResolver().openInputStream(data.getData()); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) + { + sb.append(line); + } + reader.close(); + String jsonContent = sb.toString(); - @JavascriptInterface - public void importJsonFile() { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("application/json"); - startActivityForResult(intent, IMPORT_FILE_REQUEST_CODE); + String jsCode = "if(window.setAndroidNotes) window.setAndroidNotes(" + JSONObject.quote(jsonContent) + ");"; + mWebView.evaluateJavascript(jsCode, null); + } + catch (IOException e) + { + e.printStackTrace(); } + } + } + else if (requestCode == FILECHOOSER_RESULTCODE) + { + if (mFilePathCallback == null) + { + return; + } + Uri[] results = null; + if (resultCode == Activity.RESULT_OK && data != null) + { + String dataString = data.getDataString(); + if (dataString != null) + { + results = new Uri[]{Uri.parse(dataString)}; + } + else if (data.getClipData() != null) + { + int count = data.getClipData().getItemCount(); + results = new Uri[count]; + for (int i = 0; i < count; i++) + { + results[i] = data.getClipData().getItemAt(i).getUri(); + } + } + } + mFilePathCallback.onReceiveValue(results); + mFilePathCallback = null; + } + } + + @Override + public void onBackPressed() + { + if (mWebView.canGoBack()) + { + mWebView.goBack(); + } + else + { + super.onBackPressed(); + } + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + try + { + unregisterReceiver(noteReceiver); + } + catch (Exception ignored) + { + } + + if (mWebView != null) + { + mWebView.destroy(); + } + } + + // ---------- JAVASCRIPT INTERFACE ---------- + + public class WebAppInterface + { + @JavascriptInterface + public void saveFile(String fileName, String fileData, String fileType) + { + pendingFileName = fileName; + pendingFileData = fileData; + pendingFileType = fileType; + + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(fileType); + intent.putExtra(Intent.EXTRA_TITLE, fileName); + startActivityForResult(intent, CREATE_FILE_REQUEST_CODE); + } + + @JavascriptInterface + public void importJsonFile() + { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); + startActivityForResult(intent, IMPORT_FILE_REQUEST_CODE); + } + + @JavascriptInterface + public void toggleNotification(boolean enable) + { + runOnUiThread(() -> { + if (enable) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + { + if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) + { + requestPermissions( + new String[]{Manifest.permission.POST_NOTIFICATIONS}, + NOTIFICATION_PERMISSION_REQUEST); + return; + } + } + showNotification(); + } + else + { + cancelNotification(); + } + }); + } + + @JavascriptInterface + public void setInboxTabName(String tabName) + { + if (tabName == null || tabName.trim().isEmpty()) + { + tabName = "Inbox"; + } + inboxTabName = tabName.trim(); + getSharedPreferences("note_queue", MODE_PRIVATE) + .edit().putString("inbox_tab_name", inboxTabName).apply(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) + { + if (requestCode == NOTIFICATION_PERMISSION_REQUEST) + { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + { + showNotification(); + } + else + { + Toast.makeText(this, "Notification permission denied", Toast.LENGTH_SHORT).show(); + } } + } } diff --git a/app/src/main/java/io/github/ronynn/karui/MyWebViewClient.java b/app/src/main/java/io/github/ronynn/karui/MyWebViewClient.java deleted file mode 100644 index e1ff130..0000000 --- a/app/src/main/java/io/github/ronynn/karui/MyWebViewClient.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.ronynn.karui; - -import android.content.Intent; -import android.net.Uri; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -class MyWebViewClient extends WebViewClient { - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - String hostname; - - // YOUR HOSTNAME - hostname = "example.com"; - - Uri uri = Uri.parse(url); - if (url.startsWith("file:") || uri.getHost() != null && uri.getHost().endsWith(hostname)) { - return false; - } - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - view.getContext().startActivity(intent); - return true; - } -} diff --git a/app/src/main/java/io/github/ronynn/karui/NoteReplyReceiver.java b/app/src/main/java/io/github/ronynn/karui/NoteReplyReceiver.java new file mode 100644 index 0000000..f805673 --- /dev/null +++ b/app/src/main/java/io/github/ronynn/karui/NoteReplyReceiver.java @@ -0,0 +1,148 @@ +package io.github.ronynn.karui; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.drawable.Icon; +import android.os.Build; +import android.os.Bundle; + +public class NoteReplyReceiver extends BroadcastReceiver +{ + public static final String KEY_TEXT_REPLY = "key_text_reply"; + private static final String CHANNEL_ID = "note_reply_channel"; + private static final int NOTIFICATION_ID = 1; + + @Override + public void onReceive(Context context, Intent intent) + { + Bundle remoteInputResult = RemoteInput.getResultsFromIntent(intent); + if (remoteInputResult != null) + { + CharSequence input = remoteInputResult.getCharSequence(KEY_TEXT_REPLY); + if (input != null && input.length() > 0) + { + String noteText = input.toString().trim(); + if (!noteText.isEmpty()) + { + saveNoteToPreferences(context, noteText); + + Intent updateIntent = new Intent(MainActivity.ACTION_NOTE_ADDED); + updateIntent.setPackage(context.getPackageName()); + context.sendBroadcast(updateIntent); + } + } + } + + updateNotification(context); + } + + private void saveNoteToPreferences(Context context, String noteText) + { + SharedPreferences prefs = context.getSharedPreferences("note_queue", Context.MODE_PRIVATE); + String existingNotes = prefs.getString("pending_notes", ""); + String existingTabs = prefs.getString("pending_notes_tabs", ""); + String inboxTab = prefs.getString("inbox_tab_name", "Inbox"); + + if (existingNotes.isEmpty()) + { + existingNotes = noteText; + existingTabs = inboxTab; + } + else + { + existingNotes += "\n" + noteText; + existingTabs += "\n" + inboxTab; + } + + prefs.edit() + .putString("pending_notes", existingNotes) + .putString("pending_notes_tabs", existingTabs) + .apply(); + } + + private void updateNotification(Context context) + { + NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (manager == null) + { + return; + } + + Intent openAppIntent = new Intent(context, MainActivity.class); + int openFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + openFlags |= PendingIntent.FLAG_IMMUTABLE; + } + PendingIntent openPendingIntent = PendingIntent.getActivity(context, 0, openAppIntent, openFlags); + + RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY) + .setLabel("Add Note") + .build(); + + Intent replyIntent = new Intent(context, NoteReplyReceiver.class); + int replyFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + replyFlags |= PendingIntent.FLAG_MUTABLE; + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + replyFlags |= PendingIntent.FLAG_IMMUTABLE; + } + PendingIntent replyPendingIntent = PendingIntent.getBroadcast(context, 1, replyIntent, replyFlags); + + Notification.Action replyAction; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + replyAction = new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_note), + "Add Note", + replyPendingIntent) + .addRemoteInput(remoteInput) + .build(); + } + else + { + replyAction = new Notification.Action.Builder( + R.drawable.ic_note, + "Add Note", + replyPendingIntent) + .addRemoteInput(remoteInput) + .build(); + } + + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + builder = new Notification.Builder(context, CHANNEL_ID); + } + else + { + builder = new Notification.Builder(context); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + builder.setSmallIcon(Icon.createWithResource(context, R.drawable.ic_note)); + } + else + { + builder.setSmallIcon(R.drawable.ic_note); + } + + builder.setContentTitle("Quick Note") + .setContentText("Note saved! Swipe down to add another") + .setContentIntent(openPendingIntent) + .addAction(replyAction) + .setOngoing(true); + + manager.notify(NOTIFICATION_ID, builder.build()); + } +} diff --git a/app/src/main/res/drawable/ic_note.xml b/app/src/main/res/drawable/ic_note.xml new file mode 100644 index 0000000..6dc065f --- /dev/null +++ b/app/src/main/res/drawable/ic_note.xml @@ -0,0 +1,10 @@ + + + + diff --git a/diagram.svg b/diagram.svg index 3aefe53..99120cb 100644 --- a/diagram.svg +++ b/diagram.svg @@ -1 +1 @@ -srcsrcgradle/wrappergradle/wrapperfastlane/metadata/android/en-USfastlane/metadata/android/en-USappappcomponentscomponentsassets_archiveassets_archiveassetsassetsimagesimageschangelogschangelogssrc/mainsrc/mainresresjava/io/github/ronynn/karuijava/io/github/ronynn/karuivaluesvaluesApp.svelteApp.svelteApp.sveltelib/Counte...lib/Counte...lib/Counte...gradle-wrapper...gradle-wrapper...gradle-wrapper...README.mdREADME.mdREADME.mdLICENSELICENSELICENSEgradlewgradlewgradlewgradlew.batgradlew.batgradlew.batLaterData...LaterData...LaterData...MainScree...MainScree...MainScree...alpine.cdn.min.jsalpine.cdn.min.jsalpine.cdn.min.jsmilligram.cssmilligram.cssmilligram.cssmain.jsmain.jsmain.jsindex.htmlindex.htmlindex.htmlnormalize.cssnormalize.cssnormalize.cssmilligram.cssmilligram.cssmilligram.cssnormalize.cssnormalize.cssnormalize.cssassets/ind...assets/ind...assets/ind...mipmap-any...mipmap-any...mipmap-any...layout/act...layout/act...layout/act...MainActivity....MainActivity....MainActivity.....bat.css.gitignore.gradle.html.java.js.md.properties.res.svelte.ts.txt.xmleach dot sized by file size \ No newline at end of file +srcsrcgradle/wrappergradle/wrapperfastlane/metadata/android/en-USfastlane/metadata/android/en-USappappcomponentscomponentsassets_archiveassets_archiveassetsassetsimagesimageschangelogschangelogssrc/mainsrc/mainresresjava/io/github/ronynn/karuijava/io/github/ronynn/karuivaluesvaluesApp.svelteApp.svelteApp.sveltelib/Counte...lib/Counte...lib/Counte...gradle-wrappe...gradle-wrappe...gradle-wrappe...README.mdREADME.mdREADME.mdLICENSELICENSELICENSEgradlewgradlewgradlewLaterData...LaterData...LaterData...MainScre...MainScre...MainScre...alpine.cdn.min.jsalpine.cdn.min.jsalpine.cdn.min.jsmilligram.cssmilligram.cssmilligram.cssmain.jsmain.jsmain.jsindex.htmlindex.htmlindex.htmlnormalize.cssnormalize.cssnormalize.cssmilligram.cssmilligram.cssmilligram.cssnormalize.cssnormalize.cssnormalize.cssassets/ind...assets/ind...assets/ind...mipmap-any...mipmap-any...mipmap-any...layout/act...layout/act...layout/act...drawable/i...drawable/i...drawable/i...MainActivity.javaMainActivity.javaMainActivity.javaNoteReplyR...NoteReplyR...NoteReplyR....bat.css.gitignore.gradle.html.java.js.md.properties.res.svelte.svg.ts.txt.xmleach dot sized by file size \ No newline at end of file