From 133d8e7e0d8c326c4684e91b68d9355d4c2503f8 Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Wed, 13 May 2026 12:30:48 +0200 Subject: [PATCH 1/9] Add toggle all checked functionality to list view; update header buttons layout --- app/list/[id].tsx | 60 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/app/list/[id].tsx b/app/list/[id].tsx index 0255496..5472e34 100644 --- a/app/list/[id].tsx +++ b/app/list/[id].tsx @@ -16,7 +16,7 @@ import { } from '@/lib/dataStorage'; import { useSQLiteContext } from 'expo-sqlite'; import { Stack, useRouter } from 'expo-router'; -import { CheckSquare2, PencilIcon, Square, Trash2Icon } from 'lucide-react-native'; +import { CheckSquare2, ListChecks, PencilIcon, Square, Trash2Icon } from 'lucide-react-native'; import { useCallback, useEffect, useState } from 'react'; import { Alert, Pressable, ScrollView, View } from 'react-native'; @@ -71,6 +71,14 @@ export default function ListViewScreen() { [db, listView] ); + const handleToggleAllChecked = useCallback(async () => { + if (listView === 'loading' || listView === null || listView.items.length === 0) return; + const allChecked = listView.items.every((item) => item.checked); + const updatedItems = listView.items.map((item) => ({ ...item, checked: !allChecked })); + setListView({ ...listView, items: updatedItems }); + await updateListItems(db, listView.id, updatedItems); + }, [db, listView]); + const handleDeletePress = useCallback(() => { if (listView === null || listView === 'loading') return; Alert.alert('Delete list', 'Are you sure you want to delete this list?', [ @@ -106,24 +114,38 @@ export default function ListViewScreen() { accessibilityLabel="Back to notes" /> ), - headerRight: () => ( - - - - - ), + headerRight: () => { + const allChecked = + listView.items.length > 0 && listView.items.every((item) => item.checked); + return ( + + + + + + + + ); + }, }} /> From 6eb3d34d3ca10114edaac39d9de88ac185cf2080 Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Wed, 13 May 2026 13:47:50 +0200 Subject: [PATCH 2/9] Implement Android widget for checklist functionality; add native changes documentation and update build configurations --- .gitignore | 2 - NATIVE_CHANGES.md | 109 ++++++++ android/.gitignore | 16 ++ android/app/build.gradle | 182 +++++++++++++ android/app/debug.keystore | Bin 0 -> 2257 bytes android/app/proguard-rules.pro | 14 + android/app/src/debug/AndroidManifest.xml | 7 + .../src/debugOptimized/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 70 +++++ .../com/pgarr/simplenotepad/MainActivity.kt | 65 +++++ .../pgarr/simplenotepad/MainApplication.kt | 56 ++++ .../widget/NoteListRemoteViewsFactory.kt | 62 +++++ .../simplenotepad/widget/NoteListWidget.kt | 63 +++++ .../widget/NoteListWidgetService.kt | 11 + .../simplenotepad/widget/WidgetDbHelper.kt | 116 ++++++++ .../widget/WidgetUpdateReceiver.kt | 32 +++ .../res/drawable-hdpi/splashscreen_logo.png | Bin 0 -> 2735 bytes .../res/drawable-mdpi/splashscreen_logo.png | Bin 0 -> 1750 bytes .../res/drawable-xhdpi/splashscreen_logo.png | Bin 0 -> 3838 bytes .../res/drawable-xxhdpi/splashscreen_logo.png | Bin 0 -> 6562 bytes .../drawable-xxxhdpi/splashscreen_logo.png | Bin 0 -> 10095 bytes .../res/drawable/ic_launcher_background.xml | 6 + .../res/drawable/rn_edit_text_material.xml | 37 +++ .../src/main/res/layout/widget_list_item.xml | 30 +++ .../src/main/res/layout/widget_note_list.xml | 29 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1245 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 1508 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 1934 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 840 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 973 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1302 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1556 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 2077 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 2543 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2353 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 3284 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 3839 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3026 bytes .../ic_launcher_foreground.webp | Bin 0 -> 4887 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 5032 bytes .../app/src/main/res/values-night/colors.xml | 1 + android/app/src/main/res/values/colors.xml | 6 + android/app/src/main/res/values/strings.xml | 7 + android/app/src/main/res/values/styles.xml | 14 + .../main/res/xml/note_list_widget_info.xml | 11 + android/build.gradle | 24 ++ android/gradle.properties | 65 +++++ android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + android/gradlew | 251 ++++++++++++++++++ android/gradlew.bat | 94 +++++++ android/settings.gradle | 39 +++ app/_layout.tsx | 4 +- lib/dataStorage.ts | 2 + package.json | 2 +- 57 files changed, 1446 insertions(+), 5 deletions(-) create mode 100644 NATIVE_CHANGES.md create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle create mode 100644 android/app/debug.keystore create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/debugOptimized/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/MainActivity.kt create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/MainApplication.kt create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidgetService.kt create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetUpdateReceiver.kt create mode 100644 android/app/src/main/res/drawable-hdpi/splashscreen_logo.png create mode 100644 android/app/src/main/res/drawable-mdpi/splashscreen_logo.png create mode 100644 android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png create mode 100644 android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 android/app/src/main/res/drawable/rn_edit_text_material.xml create mode 100644 android/app/src/main/res/layout/widget_list_item.xml create mode 100644 android/app/src/main/res/layout/widget_note_list.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/values-night/colors.xml create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/main/res/xml/note_list_widget_info.xml create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.jar create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100755 android/gradlew create mode 100644 android/gradlew.bat create mode 100644 android/settings.gradle diff --git a/.gitignore b/.gitignore index b059757..0b37b6e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,6 @@ dist/ web-build/ # Native -android/ -ios/ *.orig.* *.jks *.p8 diff --git a/NATIVE_CHANGES.md b/NATIVE_CHANGES.md new file mode 100644 index 0000000..b31cd21 --- /dev/null +++ b/NATIVE_CHANGES.md @@ -0,0 +1,109 @@ +# Native Changes + +This file documents all manual modifications to the `android/` folder that are **not** generated by `expo prebuild`. +Update this file whenever you add or modify native files. + +> ⚠️ Do **not** run `expo prebuild --clean` — use `expo prebuild` (no `--clean`) to preserve these changes. +> Use `npm run prebuild:reset` only when intentionally wiping native code, then re-apply all changes listed here. + +--- + +## 1. Android Home Screen Widget + +**Added:** May 2026 +**Purpose:** Displays a checklist from the app's SQLite database as an interactive Android home screen widget. Users can tap checkboxes directly in the widget to toggle item completion, which writes back to the same `content` table the app uses. + +### New files added + +#### Kotlin source files + +Location: `android/app/src/main/java/com/pgarr/simplenotepad/widget/` + +| File | Description | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `NoteListWidget.kt` | `AppWidgetProvider` — entry point, called by Android on widget add/update. Reads the latest list from SQLite and binds it to the `ListView` via `RemoteViewsService`. | +| `NoteListWidgetService.kt` | `RemoteViewsService` — Android requires a bound service to supply list row views to a widget `ListView`. Instantiates `NoteListRemoteViewsFactory`. | +| `NoteListRemoteViewsFactory.kt` | `RemoteViewsFactory` — builds each list row `RemoteViews`. Sets the checkbox icon (on/off), text, opacity for completed items, and attaches a fill-in `Intent` to each row for tap handling. | +| `WidgetDbHelper.kt` | Opens the app's SQLite database directly using Android's `SQLiteDatabase` API (not expo-sqlite). Provides `getLatestList()` and `toggleItem()`. Parses and writes the `note` column JSON in a format exactly matching the JS `parseListItems` / `stringifyListItems` functions in `db.ts`. | +| `WidgetUpdateReceiver.kt` | `BroadcastReceiver` — receives checkbox tap broadcasts, calls `WidgetDbHelper.toggleItem()`, then calls `notifyAppWidgetViewDataChanged` to re-render the widget list. | + +#### Resource files + +Location: `android/app/src/main/res/` + +| File | Description | +| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `layout/widget_note_list.xml` | Root widget layout. Contains a `TextView` for the list title and a `ListView` for the items. | +| `layout/widget_list_item.xml` | Single row layout. Contains an `ImageView` acting as a checkbox (swapped between `checkbox_on_background` / `checkbox_off_background` drawables) and a `TextView` for item text. Note: real `CheckBox` views cannot be used interactively in `RemoteViews`. | +| `xml/note_list_widget_info.xml` | `AppWidgetProviderInfo` — declares widget minimum size (250×180dp), resize behaviour, update interval, and initial layout. | + +### AndroidManifest.xml modifications + +File: `android/app/src/main/AndroidManifest.xml` + +Three entries added inside ``: + +```xml + + + + + + + + + + + + + + + + + +``` + +--- + +## Schema dependency + +The widget reads from the same database and table as the main app. + +| Property | Value | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------- | +| Database file | `notes.db` — **must match** the string passed to `openDatabaseAsync()` in JS. Defined in `WidgetDbHelper.kt` as `DB_NAME`. | +| Table | `content` | +| Relevant columns | `id`, `title`, `note` (JSON string), `type` (0 = note, 1 = list) | +| Widget reads | `SELECT * FROM content WHERE type = 1 ORDER BY id DESC LIMIT 1` | +| Widget writes | `UPDATE content SET note = ? WHERE id = ? AND type = 1` | + +The JSON format of the `note` column for lists is an array of `{ "checked": boolean, "text": string }` objects, matching `parseListItems` / `stringifyListItems` in `db.ts`. + +--- + +## Known caveats + +- **Stale app state:** If the user taps a checkbox in the widget while the app is open on the same list, the app's in-memory state will be stale until it re-fetches. Add a re-fetch in your list screen's `useEffect` on `AppState` change event to handle this. +- **Concurrent writes:** The widget and the app both write to the same SQLite file. SQLite WAL mode (enabled in migrations) handles concurrent reads safely, but avoid triggering widget updates and app writes simultaneously. In practice this is unlikely for a notepad app. +- **Which list is shown:** Currently the widget always shows the list with the highest `id`. There is no per-widget configuration (pinning a specific list). This could be added via `AppWidgetConfigureActivity` in a future iteration. +- **Update interval:** Set to 30 minutes (`updatePeriodMillis="1800000"`) in `note_list_widget_info.xml`. Android batches and throttles this — do not rely on it for real-time updates. The widget updates immediately on checkbox tap via the broadcast receiver. + +--- + +## Upgrade notes (Expo SDK upgrades) + +When upgrading the Expo SDK: + +1. Run `expo prebuild` (no `--clean`) to regenerate native files with merge. +2. Resolve any conflicts in `AndroidManifest.xml` — the three widget entries above must be preserved. +3. The Kotlin files and resource files in the `widget/` package and `res/` subfolders are entirely new additions, so they will not have conflicts. +4. Verify `WidgetDbHelper.kt`'s `DB_NAME` constant still matches the database name used in JS after the upgrade. diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..8a6be07 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,16 @@ +# OSX +# +.DS_Store + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ + +# Bundle artifacts +*.jsbundle diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..e12f7f3 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,182 @@ +apply plugin: "com.android.application" +apply plugin: "org.jetbrains.kotlin.android" +apply plugin: "com.facebook.react" + +def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() + +/** + * This is the configuration block to customize your React Native Android app. + * By default you don't need to apply any configuration, just uncomment the lines you need. + */ +react { + entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) + reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() + hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" + codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() + + enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean() + // Use Expo CLI to bundle the app, this ensures the Metro config + // works correctly with Expo projects. + cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) + bundleCommand = "export:embed" + + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '../..' + // root = file("../../") + // The folder where the react-native NPM package is. Default is ../../node_modules/react-native + // reactNativeDir = file("../../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen + // codegenDir = file("../../node_modules/@react-native/codegen") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "prodDebug"] + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + // nodeExecutableAndArgs = ["node"] + + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + // bundleAssetName = "MyApplication.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + // entryFile = file("../js/MyApplication.android.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" + // + // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" + // hermesFlags = ["-O", "-output-source-map"] + + /* Autolinking */ + autolinkLibrariesWithApp() +} + +/** + * Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization). + */ +def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean() + +/** + * The preferred build flavor of JavaScriptCore (JSC) + * + * For example, to use the international variant, you can use: + * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' + +android { + ndkVersion rootProject.ext.ndkVersion + + buildToolsVersion rootProject.ext.buildToolsVersion + compileSdk rootProject.ext.compileSdkVersion + + namespace 'com.pgarr.simplenotepad' + defaultConfig { + applicationId 'com.pgarr.simplenotepad' + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 13 + versionName "1.1.1" + + buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" + } + signingConfigs { + debug { + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + // Caution! In production, you need to generate your own keystore file. + // see https://reactnative.dev/docs/signed-apk-android. + signingConfig signingConfigs.debug + def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false' + shrinkResources enableShrinkResources.toBoolean() + minifyEnabled enableMinifyInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true' + crunchPngs enablePngCrunchInRelease.toBoolean() + } + } + packagingOptions { + jniLibs { + def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false' + useLegacyPackaging enableLegacyPackaging.toBoolean() + } + } + androidResources { + ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~' + } +} + +// Apply static values from `gradle.properties` to the `android.packagingOptions` +// Accepts values in comma delimited lists, example: +// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini +["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> + // Split option: 'foo,bar' -> ['foo', 'bar'] + def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); + // Trim all elements in place. + for (i in 0.. 0) { + println "android.packagingOptions.$prop += $options ($options.length)" + // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' + options.each { + android.packagingOptions[prop] += it + } + } +} + +dependencies { + // The version of react-native is set by the React Native Gradle Plugin + implementation("com.facebook.react:react-android") + + def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; + def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; + def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; + + if (isGifEnabled) { + // For animated gif support + implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}") + } + + if (isWebpEnabled) { + // For webp support + implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}") + if (isWebpAnimatedEnabled) { + // Animated webp support + implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}") + } + } + + if (hermesEnabled.toBoolean()) { + implementation("com.facebook.react:hermes-android") + } else { + implementation jscFlavor + } +} diff --git a/android/app/debug.keystore b/android/app/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..364e105ed39fbfd62001429a68140672b06ec0de GIT binary patch literal 2257 zcmchYXEfYt8;7T1^dLH$VOTZ%2NOdOH5j5LYLtZ0q7x-V8_6gU5)#7dkq{HTmsfNq zB3ZqcAxeY^G10@?efK?Q&)M(qInVv!xjx+IKEL}p*K@LYvIzo#AZG>st5|P)KF1_Z;y){W{<7K{nl!CPuE z_^(!C(Ol0n8 zK13*rzAtW>(wULKPRYLd7G18F8#1P`V*9`(Poj26eOXYyBVZPno~Cvvhx7vPjAuZo zF?VD!zB~QG(!zbw#qsxT8%BSpqMZ4f70ZPn-3y$L8{EVbbN9$H`B&Z1quk9tgp5FM zuxp3pJ0b8u|3+#5bkJ4SRnCF2l7#DyLYXYY8*?OuAwK4E6J{0N=O3QNVzQ$L#FKkR zi-c@&!nDvezOV$i$Lr}iF$XEcwnybQ6WZrMKuw8gCL^U#D;q3t&HpTbqyD%vG=TeDlzCT~MXUPC|Leb-Uk+ z=vnMd(|>ld?Fh>V8poP;q;;nc@en$|rnP0ytzD&fFkCeUE^kG9Kx4wUh!!rpjwKDP zyw_e|a^x_w3E zP}}@$g>*LLJ4i0`Gx)qltL}@;mDv}D*xR^oeWcWdPkW@Uu)B^X&4W1$p6}ze!zudJ zyiLg@uggoMIArBr*27EZV7djDg@W1MaL+rcZ-lrANJQ%%>u8)ZMWU@R2qtnmG(acP z0d_^!t>}5W zpT`*2NR+0+SpTHb+6Js4b;%LJB;B_-ChhnU5py}iJtku*hm5F0!iql8Hrpcy1aYbT z1*dKC5ua6pMX@@iONI?Hpr%h;&YaXp9n!ND7-=a%BD7v&g zOO41M6EbE24mJ#S$Ui0-brR5ML%@|ndz^)YLMMV1atna{Fw<;TF@>d&F|!Z>8eg>>hkFrV)W+uv=`^F9^e zzzM2*oOjT9%gLoub%(R57p-`TXFe#oh1_{&N-YN z<}artH|m=d8TQuKSWE)Z%puU|g|^^NFwC#N=@dPhasyYjoy(fdEVfKR@cXKHZV-`06HsP`|Ftx;8(YD$fFXumLWbGnu$GMqRncXYY9mwz9$ap zQtfZB^_BeNYITh^hA7+(XNFox5WMeG_LtJ%*Q}$8VKDI_p8^pqX)}NMb`0e|wgF7D zuQACY_Ua<1ri{;Jwt@_1sW9zzdgnyh_O#8y+C;LcZq6=4e^cs6KvmK@$vVpKFGbQ= z$)Eux5C|Fx;Gtmv9^#Y-g@7Rt7*eLp5n!gJmn7&B_L$G?NCN`AP>cXQEz}%F%K;vUs{+l4Q{}eWW;ATe2 zqvXzxoIDy(u;F2q1JH7Sf;{jy_j})F+cKlIOmNfjBGHoG^CN zM|Ho&&X|L-36f}Q-obEACz`sI%2f&k>z5c$2TyTSj~vmO)BW~+N^kt`Jt@R|s!){H ze1_eCrlNaPkJQhL$WG&iRvF*YG=gXd1IyYQ9ew|iYn7r~g!wOnw;@n42>enAxBv*A zEmV*N#sxdicyNM=A4|yaOC5MByts}s_Hpfj|y<6G=o=!3S@eIFKDdpR7|FY>L&Wat&oW&cm&X~ z5Bt>Fcq(fgnvlvLSYg&o6>&fY`ODg4`V^lWWD=%oJ#Kbad2u~! zLECFS*??>|vDsNR&pH=Ze0Eo`sC_G`OjoEKVHY|wmwlX&(XBE<@sx3Hd^gtd-fNwUHsylg06p`U2y_={u}Bc + + + + + diff --git a/android/app/src/debugOptimized/AndroidManifest.xml b/android/app/src/debugOptimized/AndroidManifest.xml new file mode 100644 index 0000000..3ec2507 --- /dev/null +++ b/android/app/src/debugOptimized/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..905a0af --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/MainActivity.kt b/android/app/src/main/java/com/pgarr/simplenotepad/MainActivity.kt new file mode 100644 index 0000000..5583af9 --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/MainActivity.kt @@ -0,0 +1,65 @@ +package com.pgarr.simplenotepad +import expo.modules.splashscreen.SplashScreenManager + +import android.os.Build +import android.os.Bundle + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +import expo.modules.ReactActivityDelegateWrapper + +class MainActivity : ReactActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + // Set the theme to AppTheme BEFORE onCreate to support + // coloring the background, status bar, and navigation bar. + // This is required for expo-splash-screen. + // setTheme(R.style.AppTheme); + // @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af + SplashScreenManager.registerOnActivity(this) + // @generated end expo-splashscreen + super.onCreate(null) + } + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "main" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate { + return ReactActivityDelegateWrapper( + this, + BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, + object : DefaultReactActivityDelegate( + this, + mainComponentName, + fabricEnabled + ){}) + } + + /** + * Align the back button behavior with Android S + * where moving root activities to background instead of finishing activities. + * @see onBackPressed + */ + override fun invokeDefaultOnBackPressed() { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!moveTaskToBack(false)) { + // For non-root activities, use the default implementation to finish them. + super.invokeDefaultOnBackPressed() + } + return + } + + // Use the default back button implementation on Android S + // because it's doing more than [Activity.moveTaskToBack] in fact. + super.invokeDefaultOnBackPressed() + } +} diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/MainApplication.kt b/android/app/src/main/java/com/pgarr/simplenotepad/MainApplication.kt new file mode 100644 index 0000000..cb1d9c9 --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/MainApplication.kt @@ -0,0 +1,56 @@ +package com.pgarr.simplenotepad + +import android.app.Application +import android.content.res.Configuration + +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.ReactHost +import com.facebook.react.common.ReleaseLevel +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint +import com.facebook.react.defaults.DefaultReactNativeHost + +import expo.modules.ApplicationLifecycleDispatcher +import expo.modules.ReactNativeHostWrapper + +class MainApplication : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( + this, + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + + override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + } + ) + + override val reactHost: ReactHost + get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + DefaultNewArchitectureEntryPoint.releaseLevel = try { + ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase()) + } catch (e: IllegalArgumentException) { + ReleaseLevel.STABLE + } + loadReactNative(this) + ApplicationLifecycleDispatcher.onApplicationCreate(this) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) + } +} diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt new file mode 100644 index 0000000..40458f7 --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt @@ -0,0 +1,62 @@ +package com.pgarr.simplenotepad.widget + +import android.content.Context +import android.content.Intent +import android.widget.RemoteViews +import android.widget.RemoteViewsService +import com.pgarr.simplenotepad.R + +class NoteListRemoteViewsFactory( + private val context: Context, + private val listId: Int +) : RemoteViewsService.RemoteViewsFactory { + + private var items: List = emptyList() + private var listTitle: String = "" + + override fun onCreate() {} + + override fun onDataSetChanged() { + // Called on widget refresh — re-read from SQLite + val list = WidgetDbHelper.getLatestList(context) + items = list?.items ?: emptyList() + listTitle = list?.title ?: "" + } + + override fun onDestroy() {} + + override fun getCount(): Int = items.size + + override fun getViewAt(position: Int): RemoteViews { + val item = items[position] + val rv = RemoteViews(context.packageName, R.layout.widget_list_item) + + // Set text, with strikethrough if checked + rv.setTextViewText(R.id.item_text, item.text) + + // Swap checkbox icon based on checked state + val checkboxDrawable = if (item.checked) + android.R.drawable.checkbox_on_background + else + android.R.drawable.checkbox_off_background + rv.setImageViewResource(R.id.item_checkbox, checkboxDrawable) + + // Dim completed items + rv.setFloat(R.id.item_text, "setAlpha", if (item.checked) 0.4f else 1.0f) + + // Fill-in intent carries position and listId to the broadcast receiver + val fillIntent = Intent().apply { + putExtra("item_index", position) + putExtra("list_id", listId) + } + rv.setOnClickFillInIntent(R.id.item_checkbox, fillIntent) + rv.setOnClickFillInIntent(R.id.item_text, fillIntent) + + return rv + } + + override fun getLoadingView(): RemoteViews? = null + override fun getViewTypeCount(): Int = 1 + override fun getItemId(position: Int): Long = position.toLong() + override fun hasStableIds(): Boolean = false +} \ No newline at end of file diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt new file mode 100644 index 0000000..daa28d8 --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt @@ -0,0 +1,63 @@ +package com.pgarr.simplenotepad.widget + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.widget.RemoteViews +import com.pgarr.simplenotepad.R + +class NoteListWidget : AppWidgetProvider() { + + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray + ) { + val list = WidgetDbHelper.getLatestList(context) + + for (widgetId in appWidgetIds) { + updateWidget(context, appWidgetManager, widgetId, list) + } + } + + companion object { + fun updateWidget( + context: Context, + appWidgetManager: AppWidgetManager, + widgetId: Int, + list: WidgetListItem? = null // unused directly; data flows via factory + ) { + val latestList = WidgetDbHelper.getLatestList(context) + + val rv = RemoteViews(context.packageName, R.layout.widget_note_list) + + // Set title + rv.setTextViewText(R.id.widget_title, latestList?.title ?: "No lists") + + // Wire up the ListView to the RemoteViewsService + val serviceIntent = Intent(context, NoteListWidgetService::class.java).apply { + putExtra("list_id", latestList?.id ?: -1) + // Must be unique per widget instance for Android to distinguish adapters + data = android.net.Uri.parse("widget://list/$widgetId") + } + rv.setRemoteAdapter(R.id.widget_list_view, serviceIntent) + rv.setEmptyView(R.id.widget_list_view, R.id.widget_title) + + // PendingIntent template for row taps — filled in by setOnClickFillInIntent + val toggleIntent = Intent(context, WidgetUpdateReceiver::class.java).apply { + action = WidgetUpdateReceiver.ACTION_TOGGLE_ITEM + } + val pendingIntent = PendingIntent.getBroadcast( + context, + widgetId, + toggleIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + ) + rv.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent) + + appWidgetManager.updateAppWidget(widgetId, rv) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidgetService.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidgetService.kt new file mode 100644 index 0000000..2e08ad9 --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidgetService.kt @@ -0,0 +1,11 @@ +package com.pgarr.simplenotepad.widget + +import android.content.Intent +import android.widget.RemoteViewsService + +class NoteListWidgetService : RemoteViewsService() { + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { + val listId = intent.getIntExtra("list_id", -1) + return NoteListRemoteViewsFactory(applicationContext, listId) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt new file mode 100644 index 0000000..b5c85de --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt @@ -0,0 +1,116 @@ +package com.pgarr.simplenotepad.widget + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import org.json.JSONArray + +// Mirrors your expo-sqlite DB schema exactly +data class WidgetListItem(val text: String, val checked: Boolean) +data class WidgetList(val id: Int, val title: String, val items: List) + +object WidgetDbHelper { + + // Must match the name you pass to openDatabaseAsync() in your JS code + private const val DB_NAME = "notes.db" + private const val LIST_TYPE = 1 + + private fun getDbPath(context: Context): String { + // expo-sqlite stores DBs here on Android + return context.getDatabasePath(DB_NAME).absolutePath + } + + /** + * Returns the most recently inserted list (highest id), or null if none exist. + * Opens and closes the DB on every call — safe for widget use. + */ + fun getLatestList(context: Context): WidgetList? { + val path = getDbPath(context) + return try { + val db = SQLiteDatabase.openDatabase( + path, + null, + SQLiteDatabase.OPEN_READONLY + ) + db.use { + val cursor = it.rawQuery( + "SELECT id, title, note FROM content WHERE type = ? ORDER BY id DESC LIMIT 1", + arrayOf(LIST_TYPE.toString()) + ) + cursor.use { c -> + if (!c.moveToFirst()) return null + val id = c.getInt(c.getColumnIndexOrThrow("id")) + val title = c.getString(c.getColumnIndexOrThrow("title")) + val note = c.getString(c.getColumnIndexOrThrow("note")) + WidgetList(id, title, parseItems(note)) + } + } + } catch (e: Exception) { + null + } + } + + /** + * Toggles the checked state of a single item at [itemIndex] within + * the list identified by [listId], then persists the updated JSON. + */ + fun toggleItem(context: Context, listId: Int, itemIndex: Int) { + val path = getDbPath(context) + try { + val db = SQLiteDatabase.openDatabase( + path, + null, + SQLiteDatabase.OPEN_READWRITE + ) + db.use { + // 1. Read current items + val cursor = it.rawQuery( + "SELECT note FROM content WHERE id = ? AND type = ?", + arrayOf(listId.toString(), LIST_TYPE.toString()) + ) + val currentNote = cursor.use { c -> + if (!c.moveToFirst()) return + c.getString(c.getColumnIndexOrThrow("note")) + } + + // 2. Toggle the target item + val items = parseItems(currentNote).toMutableList() + if (itemIndex < 0 || itemIndex >= items.size) return + items[itemIndex] = items[itemIndex].copy(checked = !items[itemIndex].checked) + + // 3. Write back — matches your stringifyListItems() format exactly + val newNote = stringifyItems(items) + it.execSQL( + "UPDATE content SET note = ? WHERE id = ? AND type = ?", + arrayOf(newNote, listId.toString(), LIST_TYPE.toString()) + ) + } + } catch (_: Exception) {} + } + + // Mirrors parseListItems() from your db.ts + fun parseItems(raw: String): List { + return try { + val arr = JSONArray(raw) + (0 until arr.length()).mapNotNull { i -> + val obj = arr.optJSONObject(i) ?: return@mapNotNull null + val text = obj.optString("text", "") + val checked = obj.optBoolean("checked", false) + WidgetListItem(text = text, checked = checked) + } + } catch (_: Exception) { + emptyList() + } + } + + // Mirrors stringifyListItems() from your db.ts + private fun stringifyItems(items: List): String { + val arr = JSONArray() + items.forEach { item -> + val obj = org.json.JSONObject() + obj.put("checked", item.checked) + obj.put("text", item.text.trim()) + arr.put(obj) + } + return arr.toString() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetUpdateReceiver.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetUpdateReceiver.kt new file mode 100644 index 0000000..1f06125 --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetUpdateReceiver.kt @@ -0,0 +1,32 @@ +package com.pgarr.simplenotepad.widget + +import android.appwidget.AppWidgetManager +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent + +class WidgetUpdateReceiver : BroadcastReceiver() { + + companion object { + const val ACTION_TOGGLE_ITEM = "com.pgarr.simplenotepad.TOGGLE_ITEM" + } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != ACTION_TOGGLE_ITEM) return + + val itemIndex = intent.getIntExtra("item_index", -1) + val listId = intent.getIntExtra("list_id", -1) + if (itemIndex < 0 || listId < 0) return + + // Write the toggle to SQLite + WidgetDbHelper.toggleItem(context, listId, itemIndex) + + // Notify all widget instances to re-render + val manager = AppWidgetManager.getInstance(context) + val ids = manager.getAppWidgetIds( + ComponentName(context, NoteListWidget::class.java) + ) + manager.notifyAppWidgetViewDataChanged(ids, com.pgarr.simplenotepad.R.id.widget_list_view) + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bad05de08b567852ac85e8fbfcaf7f2342fdf793 GIT binary patch literal 2735 zcmeHJ|5MUu9RHY`mZjY4R!yfg+h$ES>$)*O=%CQ`EEH-lYVCY%Ib)Rn>y~}1SI$<7+Uw&O0xpN|4aor zkDot3<7t?b%l&Eb9e$-#I$dG4$I8|#irAwy{(+W1(_*!gIO1q#nRbMc=rZ4nue1@W zEe&i<+#quBY}f=8XLQ@uQf|JGwEVZMZ+Q}jOfOb9-kL-7Y$=Y|&*;d!rJLgBQy2T~ ztKgpk{y1J-T&#d$36sa;(ZbPHOP_ZWN=pk%O5PV-JOSnYEQ7oSU_f^pa;((M&g@~{ zK96IL=9sNTuKRk~h7m?IV!ns6Qq?Y9ob@KLDq4^fvnT2GEk0+hR9#b_`R-c&soCUx zh^7IK!D6u_Wl4{gD~u3Jt8HBb^#F!r#QJMsKAE~So=Sf9E^3FP*b*Y$ zsl2-PbfnY1h>Gb7V8Y+5-9&PjXS4e%c5d)hwJ7u$m|G<#?B;0dBQVY4#@HfpcZrfT z|ESHV>Q7+Xg-M>BQ5mfgrrT-PC#aG1f$J0%HA+9m5;M1^))xrt!;!wU#t*diL2s4s zpfsIDD@LAX(3>-lQQzU*!1|{dU5J&sEX(ZDD%GCyV|4d2TxFj|GRImmEe%l_h2ndt zq^nV_6_GiziHsx`SwDss$I=n;n|n`f@s9Ax`WJ^DpvAMPCZ3$+aUn)!Pb}v`XjUF^ z04dxP7uTh&i)s~3Q`D@MagW<+)ieOFKm#h8byRTsWNuxRc_@%S38FR{j>K%_Pv&t& za&A@(>E?sH%Lc?_A7mH%w$$Kdg7wO&DG?8Vz%=mPHf2}7u%1@P&C&o=N9Cub7<&O2 z>`Ev%icCG~R)eCu1lP&qa$n2B`h;gQDXq0huD4srYiRd2We*W`Dj(}l+wQcP#D_C54DXM%5oflcFiVHd#jMh(%GoG4mXlw*mZgAl`8w@9OT`x3A0zFX=^>xB7Yd102c1OK7Pt zGd6)`vdi%dlFOl-`Jq6x1OVW@3o2B#`8*`=37uJs0ism&Qix;hB(}G^%4yTi44wDu zB!cMi73SQKh{Vk^x<@a2`3u(LjvN{0bsp<)Zc(T`ww~-_JRZmca9C+G4)atMa~EMr zI_pD5u}RSt^Xca0g>IRW=yna=nIz~h7mRLK~Gut@1t!W*RB$u+|9XfTbOPE zLe$)^+ki-NJeEe7?sX#zl);wiUW&eu!{;~oYL z!2`U#RdZo#(fGAg^3wb`fj}t8&)*vNefNp588p`4)!^&sWG{>MirvZ+{a-%OOZ3${ W%IH>InizaUp#z_!r*tOgp8E&4ZipxV literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..83233c9e4cf08c4752b6d14df3be0a2bc0a0f50a GIT binary patch literal 1750 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b1694b80ca607445b3d44e5f9e3e2bfc50acfe67 GIT binary patch literal 6562 zcmeHLSyWTk8r~EY6)DT=k}6Y?ORbklt1?K0Ig#>2ni4w67GMfeY;P6=vsH(JY=t|efHV=pT6(g z=l}jocUQfY8&)EO^bYLb=ZTPZ5cXTX3?bx}q5Bg;>rWrpxBEz}mh`dTrH_`V>;_`` z*Ke!v{M75vp2IDc_SO3>b=_M2;jd?5`HXo`ah4G%Fk5A%om7(g<3_iM&;A{}*}`Ja zemZ$c+v|->pOqx(wDuW?h(|^G38L#|W8AZOVjpi+HJrxo*a73^Z(#WF84O$QXdx7| zPaC27OJGgnYCV`V-vGlG+hNcw)PzS<6dEXKm_oyk8dU%k{C%P+Imz9n^e+$ZyCvyP7&QnwP{IlnID|D zt(^GHFtVR47Uso|H3!B^Gd-yCYcsuK=PW%#yCo?4;#1uP8zlRnr#2zp-BEa*qR8gA zZsX#vVJALwHEr9`<$tX~-vvh`bNu9_*=7%F#&DXGO*&4#|CpDT0@e!3d%`(M`p!4f zsgrkoyuFvBnim^T_X?_NlpuZ;%4$j zrdYHDG(cFqDAl`kLrkbM9D?XxA+x2;o}ZIq68DK;`I+XH7Pq${si~>tsX{`o_WKSW zxf(|uaaK>Hivz~vywd78sU0|F(Yf39D-45_(8$utARdt~ibF35>&Z+;oV$mK7nl|BIhdJDOo zot?OOSwq#loTK=4jfX%rPNUK3^!E^6E2uAi9L$G)+Gz(2>^wh^g{ zY=5#a^JRjvPrH7WhuF-po^tDg9Fkl3!#6*(BYR_quQ<*Ku9dvw+v_CZJKl8nu>U=e8jdXW->+d3NDvd z?5ALz>8;$li9sQy;p9z{Qm!*pMpJ5pw5&^%N|-+$*fcbBJiJ<3UXzpNFm!1#_u#9e z5buN?Uv(6{NR=DPrLRHv4LsQS88ZM+lvQ;V@P?EWgR6d>oP! z@ddL^JzFGfbWul8mDPMhl%cW;B}+pI*&Lf{D^^@*;S-wZC@b!0?Q|I{7^;u! z8(t-(BepXH*@?s9kO$7|l+e>D0&_B8&q(bwH(p0ea}?bu5}wyFX#%wTVNEU^gaaRa zIMsaB82d!OB2RhJI^9JbOV7%Jq;??oSP_$XYLr*(!vVD4H14zu_3`&_v> zJnHP5nhgKAH~CdV|FSG0D@3S1?OPP2vkS1~D1Fc&pX~4uXNrC)$ZI)WkV^+Ve2Un2 zf$?@%@Tu`836sxiJQs^yHQfX5-_uGoegKtT^BgEs2Sp!&x!1A=eJ(&Q&+8uMSc-A9 zHwtpyf!ImY{qf`eq-F~rU*8tXw&%}1ZK?zr5@1Z&rED#VT|t@cV6=lfIYi2^AsK{0 z?#5nF&xv0g_Gr^_mYoN0zXqE6z){|09x3?2bs38N`hV@?Plf|7*Fu4DdT7+WM*>>p z!!cFMQrIU<;wATsf-Cx>@jZyG-VSK1mXzS9`(pS-Lb_l03MUcIkJ#or4fHB0;n-*e zsZJyB7@$&8lUd41YkNK=62e|9M87%>46+JYEk$Z(Si8gUWg~z~)$2Q{q|T#WLfBq! zp$aZB0uaI0r3U_2we@jZkc0QvJ5@{p0p?D15g6ITT1RDnJQ0H&Zcb7< z$O!9Zn?VYQ{nO6d8QNtz~_rgivtek5vowVDp~U%2Z2A9wl}^tXwx WTrL}kKM&0Vbl`LMeat;U-~9*FI&`f7 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..67c1d5442ebc995ab22dad1c9f787448c735f8fd GIT binary patch literal 10095 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 Cdtasi literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..883b2a0 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rn_edit_text_material.xml b/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 0000000..5c25e72 --- /dev/null +++ b/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/widget_list_item.xml b/android/app/src/main/res/layout/widget_list_item.xml new file mode 100644 index 0000000..598f550 --- /dev/null +++ b/android/app/src/main/res/layout/widget_list_item.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/widget_note_list.xml b/android/app/src/main/res/layout/widget_note_list.xml new file mode 100644 index 0000000..e9ebc7a --- /dev/null +++ b/android/app/src/main/res/layout/widget_note_list.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..3941bea --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..3941bea --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c264af93c2e9b7d77a8959e9bb4d1acb2661a0cc GIT binary patch literal 1245 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2U|Hhn;uuoF`1YW`$K_g?10TPC z|ID-OVh5)hkN#C9j%|(`nD|{;J&&{OmK0(AYklNJh#-Ft6Mv$I%UosttG*n(sZzVI zIA*MN7MB%a<@|lwW<|}1?;19TB>dy&{r&Phng9Ov(-o@=>O2e9upIZoPPFK5*t6%2 zC>LwpzdtA4u4b8XwKAPPeOfMB!R5fIr$)#7<;`228Z%5z-MBHM{KtpHX8!m0_kXXd zuJ+v;r8?1L#>$nNR#sLVlLR(QoG1v?o2@mKtGT(EZ9W@2J5Wig@5YFfl$4eQoQ$il zW|^!C(fSd(G9)8p^;M2p488$X#p$Ofo__jc z>SFi)PcJSi1J%`W+WGqUT)2GM*=~ON?yBG4bZu>IeSCZ#On-2Ax_qPdpq;-aNXRo_27J-v-> zx!>GJS67Ft33Xn0b8m0;ot?$)r7J_auCI@Oe7sXw{lwFvHPR9i55B#t*}* z&;R(b@Y>#rk4+CBChE)ZNk=JcSrMWYxHc@)#Kz{0tCHZhZV#117cL0&uDYtVG^jI- zQ+L(%*KKOQzr8j7;}jh&-Fxfy?T@SX?w#wFo16RR)W5&KGsD(j=hYQrx_14#yNOh4 zZ%4t)OR9f%ReV%ZIy5W8#4A2t-jQ2ygKhP<4<8@9tEsDds!d)Jr1{6H_!*C}v9Smj ztKU2uPaQGk48?5@I%3HQ2@Pi_crEo<8k8e?aHEin_Q8!@f&%A1SA2ff`~Br*b>{{? zIhz+>zkQn(%A)AvAZJ(Op*Go&<-ASBhXx-tXMvUmo9b^pYd`(@S=^+=qB7HGQ|P3V zDvq3j6BaI1EPHd~U^~CO4bv1KwX=VJf9Le-XmD8^C}&@HM(c3C5B7wNmcm=aB$G3i zt&+Rlv#QEOP%uh$%Zk;jyVEXTzMNwo92{(NdvCS*TA?VB?xP*YADdX!|FcO>PQLf( z(W2$6R%os2TD6E&YSWG#FM9j?tHt$V76jVJsf%@ATqU3@B&Zj^@66fR=8@n3{w^>6 z^(Au~=VALDMNheuoK~~QuZ!RRZt3OB{`)RgbHA;N-Tmxg^8;rK8MDQU7cT({=jzS% zs|K=L4g>RD{hys&@@4PtWS*OA4U9w6&6_t*Ii0#~a)r&gwb9!vK0V>wUG~=Khe#6h zp@$nzrWoDXQP`Y+f8WQ)$N9s;!hYyWCck*~N=d9+QE81)jzfp9f>KF&`Q^KJ&;IXy h5W(3u6+7{lb^E?U@9xZN6bBYt44$rjF6*2UngF9UT4?|P literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..ba023879c5d9c8e085b2977798a8298c1fba90aa GIT binary patch literal 1508 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..76a7c0174b2d5efb5d9ac173b01c0e8488307ab3 GIT binary patch literal 840 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(F!Ou5IEGX(UOi~&T@=f3;NySG zSbhnl3aGBrP46AEc|j+S@G;mzXVY8O&+QYi@Zx^rsl9~Ql)3k!I4uRaJm+`H&N zTa!*6$MZmsjt5_7?X!JfU1z^A>%t6|VxhC1Ch8aGKp8IsmIf(K^$OGx%M4u@pa5j8 z40$odvAwLUtmF9O9Wi?E-o5)!wb$hHmoG;S9b(#+TU=c1Y$LZ^coLtjjg3ZD&h2F} zdXJaH=q2AaEA8*^@8XmEbKvr2;q>(M5G_$DozsgHXP+&qtn@Vg{{6d2#57m6$qN@Q zU*3N6X5^kY{k29Jj$5NrtE;0`g*ZQc{CLp3$$w!$$9xSDu0;VFAEusunp9gG>$*5F zQG(5nCrW^;)xyF;;1BDzT;cAcM}Ggd{{8#6g<(NKZLP1-%q8axc*4TMBqSvlDo#FG zP*~`A_t-HuW@cuSV7=*{DneI$eSHOa*fMwQ-TU;zhY49*y^Ln2Fz8CM`hgVlu zJ88P=O%F~^PHs|D;hQ&qKI?onb#>PJ6*h8?y;Fqxk2g==x6f|Llq;1satF=r=KFtm z`Shvlg98hkckkZ4YWw!-GiOQ~nwYdqe)ura&(F`o-aZ`Yd#Qu=0bUP}EO2gaZdMZO z{wUn&(BPn;@I%}K6mO2Rb8d%qcXuy}(7DvPbEoB|n`?@8#x$8pY*`ti)mdZbzx>lr zo25aXKK}lf`I^ms*4Ry(K3!9Ut0bwePHz5r_uV|Vu3Ww9YBl%Sel>V1KxMG!ZJ*6{ zdee{GX{i?UR0lq0uq zOGigX|Kaua_C96e+sN$6ed*T1 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..05e1c7039c351664bff76684b15c28ae6bb178cb GIT binary patch literal 973 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p|}=Fi-JxaSW+oe0$Kl<7%i3!-Mzp zcG{gdR-;?ft7kGnB}&yTMTDg>E%2m+kc!?Gfu=<|8G@4n8n;|fXc61F>HwGPBK8)s zpj@Y3rvioe2`d_Vn^s!tMs=L2cX@H=6k7yPiH!@E`aJ$1Gw(p^n*)42 zFMGtUJ<#kqzWhepX|Bs*%hpCbpT%y#o&TyQe1oL!|6Tmu8|0k1CQV7#yS5{aTiVdE zPWYoy)Vl9H&$N7k=2=SQ@6eo~xc}vhq#Yk;tTphgjB>j+Dc;+4QovM|kfo_5Uy@kg z9cS_}*%WtY>COHfD(gQ+EV{|aBX!eN=3v~LgA4ewU#>ZMql0(Gn%#2`hwW+oY_()* z@w)?lI!4=Mjy?Y2%Kqg=&(R-jpL=bMH*n2=yyZ;QlXB^Ohia0qtJrkN?oe4Q_cQm< z;zK+tHM)zp?OFCPQd#kQlOC)kOlPO zI}N|n?|J&9-}&?GsC0I744SvOLNq+KnDhMOu0QJ%nEw@>4c*hW85HQ{ABE)(r`B{h zTcwLnTOj}VgXA~ufasmylJ{RM7GM|E;|%q53IzwLDwuielp(wHhJeVv1Zh7lhiJ)X z53RpwUe3DH{rF$Hr(~SN{GzW9RarewZHV|Ors)vW=6ve=W+s)?8%>W-mGVT3ELh8< zUTA(TQE-w`%+bU>?ZI3Z!uo=5sN4wx+Cqhq4)P$olj@+g1FIVmnEsEy|UZyFCF z9GxK5GGgjSHC_A}AcfIkAK5^68O;?Zgy|vI@tg`>CgaF%zHc%=Mtp2EyXJ|I} zAeYS4$o_K{t@UP) z7qzra(GYQM(IV@CgtfSlG@(#I23Lafu3Sa)|0j$4eL)<@a#`fZDZ_4*Vl&obCGs$c zkLblKJjW9xEl^}(%5WI_QEX~?C`K{@r_4=0Tinp_P|n1%Q6-^Vi2>9oGSJ~Qd- z>yu8W4f*%qe`B+E0yV~oO!OPBnM8kozo$>18WRYF_4V~Z6h+3#!xj8tSm=BEP-&dn+FA__4Z+~xpv9I>r!_P*1huub zrcjA}hDE-2#yGEEzt-H`Y)BMEjvhT~!-fq;w70kG>gqD&^y$-9uU>6BeSLkJnwkvR zzJ0r@swyMizJ2S)jT^pw`=+_M+41AYZQ8WSB+j5iMr2eODva~s!Gky>ka70x*|@&G zJ}}11moMY4uC72vO-+q0Teg@^cXzj@rY1wSZrvK!)zt;YShZ?ZeEIU_AdX`X9z2LQ zZQ2x=r~+k3hDIGWiBF$C1%_?gwkHIsGg*12=%j%UxF#kIAyG6GYGJhK2@nap1rK2M!zvEKD)VLRq`zrrn{eQn{%#lC{|!rPJwn@7}#Z zXJ@B{*tKhy`}gn1`T6;Qx!WAcT5pcJySsx&j~-d5M~@!q?(PmsOH0ke`bgGFb5v4N z;>eLBlF6inC@U*dQc_}`Rz|Y&%+Zo1OSHDO`o;1>S%Y%ZU?}UO-1IS&)hjpkhO%DC zO|L>(&*i4)p{yrz)00qE5+CH24@iba-IH7H$q0?QCAZv?5gL`mL%HE0k}^WyYcx(l zL4gseRLWvYrBX%|6cm_3qhXQnb>J>44J$7%H{#NzONMORxY1%rrBW_kx@1IodAUj4 zMTcRLiR$n>@?<0u2~|~9aeI4v@b&9g=gyt;GaNj4Fis>Af$t5X&KRMIdT`7bt*x!W z!Gi~5KS@#*&|{3qsJVzg8O#nL=o0hj7)eaTSM5BQiTD z@jqNcp2ataW4LYxk=eVBe{cnr7SCPOp~oyl=B5XKU?0w)!jJP1jp#53k@@LB2g-05 z`%!G}KHwg1A!!~W3zI|=f1wP!QHsr2kCn*7AU>iOukajCkhDPm2RNU2XBJWtHvj+t M07*qoM6N<$f>&vAAOHXW literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..2ad9880b5c88d1720ecd900b9a62f53a3f8a6112 GIT binary patch literal 1556 zcma)+c{~$*9LMoEW_Ut-TuF^+JdVhN5Mj)*#zxKY6ggr!9z~4y;H8#qIg2OFB${)= z6uHS2rpd7$ues)mJj2K_*Ym9Yd7l5C{`h^rpTEB6`}<3FLBOPdNQhKEG&!=!|Jx)15i9 z-7If9`K)h)2l;ebQeVmDakn%@ABvGg{;!k^ZtLt6{`8*ghr^AJ)dZ$BL~)cp1cCG} z!r|cybj9wTp3<_if|{B~L52jwl&TOTr_j)fjW0{C7V(GFJo6Z9bCp00M!Zs6S64Qz z$AN;PGZ>E?DJ2yZw`pVALxykjEaMTTRoMe!NQPji;E>Qm6~xkf`bdG)Q3cQJ=4>OTo;m2=vyZ_ zmEA4Qv1DkXzd^4vK}wa3D0eMJa2G$QtxUBtm=#`JLbn!v0+7+raFey;b?6Urvx37apAitd+{X^mUN z?LUw=RC#y31}M*}MLKe$Uok-_H@ACe0)YU51`kXj9j~#B!iYp7)~d$wF>3$l=;$a& zUVfuIGgG##yqw!N*?dP+QgsYsqc@x(6n7n^YZMA4GCKMw5WnLJ0Tq;&6OWrk zO=r<)1`Q1j0KE0SCJ6XpV&WnOqdBC_Oeln<%$}{`&f@ z;=)23&Q$9OaXF!(B}0BNxJIG$*805DC4M4S-EU*{5vTt)N=fiHSn@YeWD@Xrk=?z$ zjC>hsfG{YSo~`4Ro-gC#?d=>Bb1vAHZ(ym)R*Cq83n$wK2h$@Cc5m|=FIMicN<b6#HEg+qSxG_J)6`HAoz9UBtL`YIRyL zMT~znVSavoa0J4X8sX~d+7!D9tg}Cr$lu|EF5fqNV3dv>8yy(|EsxI4z2I|x;VXi2 zM7D^;kyEnbqmz@VtmTiDr6nakQ!T%Tuxe8_{o~^Ds;l+0`sX2YJKR}5OxS!HEwiXd zqqwdP&CcwfS5Q&8u7(K^k6-r9! zs}1EVKvD-VEo7JBjP>8!;wYO%(R0Y;`2mtKLFz?mX>ZKV8aTrq$_7iM;XAHvPPhM{ z`|F#wiNpFOu&8YG2~p g=D%y03I&G}=)d0b2uiYd_`kh?gDnF3(%L8S9}b-B`2YX_ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..584cb724916105a3d5401dfcc80812036c2367df GIT binary patch literal 2077 zcmd6oXHXLe7RM<|P=s(n0wKYGAVrE2g&2yPRlvxBNKuFqDPg5ZkZ`oDBGMBG3G2DL zfb`(8NDzWc8wo9qxfMaABqD^A^AIFJRxfbWgZ*~jZf3vSn|bd)^Pe~KzWo04iURyG zdOA=YH8nLopUYlB`zkuzJu;tsWj4yIhg}FXb$Ei(a68lW@uv|8c=rvWC9p(r zSqiF(Q)Hh}4r z*GshT!atpdR#DL0MTji6M+=6q6iic)HDo4Hy+%=#l~XFcIr8!6N)a7_dqU?%u?oGEUo zV&m}2^ywtt_440F=v?WB+a;L^7(tI##yJ1;9IqGg9Nn0V0G=HHEXc8lHVZ*J)V?H1 zuZD@PDV-8@Ys40Rcd*@Yme;;1xAjX{DCM(@A((M-z(YT(T`{GpkwUri-%@BjVd&5C zL4hz)zjCUd^<~IBc8DdaT^&qV&w~x{3O8hy{J`2T`bAX^b!`C!#4td&kYvS%*=*sM zlx*$Qu=kV91D87$=T%E%!9(RC%WtD;yLVVnGfqSu#pew5y)vBeCRlUFnb*7K!`7vh zJ8Rh|tE;42kj09e-5Z4u1?b3U<*B}FH(Lg)MGY9h=7&R>21yqzfmX4Sf(@u@sVHZ+ zoHKxTgeEt|6Lsi$7n&GUg9M@pSw3`J)8ew#?|=M8VhLm@)N0La#XjKmk259he*?LMJ9?C zp3<%PW9#E~FL(4{tXITbc5Phj;gH29MsN7jzA=3FM`ra#zUp_QrmZi`dw#CIuOao? z_^q+G;=SVj_sxCiw}Jd`D}smNibSE=z=Rl{HWJ@99_U>XZ5cybz2H%Y&MMNTB^kjE z=otnOocb$So)4D?Ukfsfk(2|(6dUI0Pv(tqWY--8naO&$oi4Q_7=O0I<0AN5Ea6Zh zx1c9_x$nlIuBEosNWIp<2)qe&nR9R3 zGAsKLGqa@`ktY7%3?T_epX>22CUTLFKSgb09sUdgO&Nj$ zul#SK%O5hO2NN}lNp2~k+$*9}HF(<^!r^JznHB?ikN#f%r*@1-j4eEgl0ZZh;&G>W z!%7>HzN%=an8Y67&f{%oo^X2C&dCtorYzI4I?$0R$zL3dx4_ARuB04n3m6r;9LiI- zi8eQ4O1$v{7C=bRp1oyz1W0=}??yZJ7tsloy(~LPs@{%Wmje%ck9;N{|G-fH_Y+s8 z5vpC z_R6IF9G~r4YRXxe$t#nmDjfKqlquVWzc5}kvW#j4DIM##XPfr3osgeKvTy82Ing@I z-_V_wFO-9CISnFEu!MF%@XUMi-ei>LoJwyt4?QLRQv&OfUvqDACD8t5;<;9^^2^tS zCdWcj=iM@|@{Z0GjV<*sw$f#qqT{l_Qpsw&@37t%Zit`sdK|BwkY6eRoOgPoZ{=tXZ*-^?u=fGq5ZWvJ6gpTLQH#XY&UHp7*}3 zd#I97;c0HbqmK#NtpY32vsl1pJaC>@jV+M#Pi(@)beI#94BhEAG?vYww1OF9Dk&UC zO%pvEhCYs_2qhX-1wWP>Jz&AYh&Izwm?%-h*?Qs;3eH@^I}C#uzUg=vQhwJXo9txp g#~vnVe8x=8G&1-HN|_mT|Lv>!c>8;`UX0BB4>94j2><{9 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..4dfce0a0ad9df26a8696bea3f1f8338ce3554e98 GIT binary patch literal 2543 zcmV5==k=BIrX8&fzT1;1~Raqd0;#qfpf7G7ZyFjfYWUXd);?A#O*7 ztDVJu?8WD3F$zRw-(#4MIk?NnDZy;a*6%nz!G~y;B~jV&H!Q(3sE{3ZVL6tg6%E*i z4jB}cS(8wMrHB|kDzF}FuoYX-CIg}}z{P}Im@B&(- zK`4C|;{d9RZB;mc#nK=uJyzjOe;pAtpd7VsKHTfO}9%MKkn6u-6dZMA&)a)%BbGIi=yb#-+rDk`K|4gL?chA6Un^=c_tjLp)hySv*%4?Pq=eE4wC z-Q6Ah{PWKm8yh|U{PXhi@}xsoSC>Z~dBowvhn+uv-tptdef;sq@t0nDDHu0yoHYA) z{EYuK1fijuhI(oC<(FSNb?Q`LfcExw`}XaNOOyTk_iJx&H_)k5r-FU^_8B4dm}Uq< zL-#Hs(yY6?TQXf;U4b;|?Cg}x`Sa(EkO2{vrl5dYwV4+3d$`t<2I0twcx zT^oP@{r3Y&j2kyDUS3|Vy1F`e{PD-*d3kw(;aP_!*AR7mt56{YeSLl2dFLH(z4eyU zr%$^maD9O!zWVB`K>7rd*uQ^&;MxKgtzW<1#EBEFTD2}DzFN*l8CHc zz1lVW9siBc5dHSsZ=QVeNp*E~y1KdoV?<|XXYlE#p9TjH9yELQYy|}cQdEt4oRc7Q zZLc9>h2I7cN|gOHBl?Ng|ZoBrKJJ+S=M+&z?QTx;=aL zShHqLAazSINrI^4YNX)w>C-lD+~`_vx#bpxg@tiR^!D}!=gyszsHmt&etv#j8eF(= zA?WPvlxV_)3G(vt;*$9N_uqr_=g&)K-MV#NeDOtPWo1%SgJlMYy4v5cR0Rd+)tx?%cW3;iHc}vT)%-iR$a?&7M75GQGXM zKKke*H8nMU{q;XlSr<dvE$ji&KXwf32rKO&D;t7d9`|L9-SFV(z2$o=j zOF~zBMv9IeJsOwbzWeSoefo4ayBRZPm^yW;L_hxcqhWf+0HG^Ah6*X_?(PmGh{a-p zATXFP3?(ye+&JknZrnJ@gkfkfK@f<=ViI+Abs44#Jm!+nmF7#6AP5XaRaKROf&v4L zA3t7IRh4vk;DHB>A3xqe1qB7Fs;Ue{5Cqa}zDq(^nj^!?%F5K&*DEeA_NU_FV%xWG zcl+(POP7fgC)&P!yW-+ve=066R$pJQtgK9y%yCK7Wz%q%EO_dvr_7o)%gK``#bPl< zMMctW;lhPx&YbDw$&+HSn4+R0S#%esp(X0F>9VM(s7OUcg^^TLRHUM!!YDBvEuqV* zq!LUD^{bSV4V9sO_sGeHd!qW?EyKR};tQ|5@=E-N zAASgA(d5aK;~O__3?6;-QCVXJ=<1%f9~l>tNQbS-$)3J9pf1 zhYY(j)UQkiH8wWJJ3Bj#7U#~L)6~=?3(7+MN@P%BVPRmDx%JjtWkE@(-vk*nckW!1 zCr>t7OrAX1oH=u3!Gx%O1v03xu+ZMUd*honZSwv1-v{w{Ts8zj;NE-hjjvd-LSbQH zAoB{M`bA_|X=!P&X3ZL-Kp=Y}p?-aGvY{{3uSZTc^o07IlameSLjBIl$%eC`erM!l z!j-$mzp(}kNClfw#N$5%+%E^QeT@t!dGg{@0q*gS$ zBy_a~IU}jT0HLdGL!X?H(1&dX2wklMTjh*|t>`d7D7h_iM#2^eLdmsZt0BtI&yP#c z(a~Y7ID7W2MEUvoajDsgHVH!4wibPcpuD_Xq5}sGXlrYWyU86pcFZ^5d?QhLdASty zVXY)W*Vc|V3_*2uwM0EVJ?77!9~?V&%+0E;tt~!p-aI`$JrY${S4+VgXqQCP_0{4@ zRJe{sixveNHf)gS$dMx|D=R(t;Dbs^O5993IyxLUa3JXE>54Cf5-fniV+cI!a4RgI&9JiN#{Zs#q*$*REYkOG^WTJBSqq5m~)@wITW+ z{3k-!aNBLSdHU(6i6F_2EBo+8}~>JT3)`(O?LU z964ggjvev6d-pnV;)DwqE(C5O`T63Y| zS%NM8+O}YcVG7;2Wq93R)9YB~21KPtEl!{w5o2E;{(&8CP*nQtz_)l8RmQG^cmXZa zAe3G$co22QraC-`7HJYSGFD(W)}g}9uNAMNNt#8CoF+7375<5cn@=C!K&=rHHS%il zK3>C8H;=7Yi*_R@Dl^*gU)Y2iEJeiV(}%6tf;Jftm04|AhSgYtXHX&gTG4=Q=#W8C z+0lUw*nr0{A9HY*%sGxv@FALINmTYVqZunO4bxGLhfyMZ&SF3I;&ZeZ1)@fm7PMf4 zev?p%dvG`IL>Wpj0R@Pl4?Q@Cvp9oa@Dq;W2-=K7{|~}buI$Cg(j))?002ovPDHLk FV1kkE=|KPh literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..7eee34a059aea2ef34f4a93ae8ce7018ab8ef536 GIT binary patch literal 2353 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 AQvd(} literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..cd6ba834f0be0b1436fb241456c44f46c73cca6d GIT binary patch literal 4887 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 IRP0dy2k6AJ \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f387b90 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + #ffffff + #ffffff + #023c69 + #ffffff + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..ac05a2b --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + simple-notepad + contain + false + automatic + 1.1.1 + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..45a97e6 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/note_list_widget_info.xml b/android/app/src/main/res/xml/note_list_widget_info.xml new file mode 100644 index 0000000..0703e69 --- /dev/null +++ b/android/app/src/main/res/xml/note_list_widget_info.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..0554dd1 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath('com.android.tools.build:gradle') + classpath('com.facebook.react:react-native-gradle-plugin') + classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') + } +} + +allprojects { + repositories { + google() + mavenCentral() + maven { url 'https://www.jitpack.io' } + } +} + +apply plugin: "expo-root-project" +apply plugin: "com.facebook.react.rootproject" diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..8e39f82 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,65 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m +org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Enable AAPT2 PNG crunching +android.enablePngCrunchInReleaseBuilds=true + +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=true + +# Use this property to enable or disable the Hermes JS engine. +# If set to false, you will be using JSC instead. +hermesEnabled=true + +# Use this property to enable edge-to-edge display support. +# This allows your app to draw behind system bars for an immersive UI. +# Note: Only works with ReactActivity and should not be used with custom Activity. +edgeToEdgeEnabled=true + +# Enable GIF support in React Native images (~200 B increase) +expo.gif.enabled=true +# Enable webp support in React Native images (~85 KB increase) +expo.webp.enabled=true +# Enable animated webp support (~3.4 MB increase) +# Disabled by default because iOS doesn't support animated webp +expo.webp.animated=false + +# Enable network inspector +EX_DEV_CLIENT_NETWORK_INSPECTOR=true + +# Use legacy packaging to compress native libraries in the resulting APK. +expo.useLegacyPackaging=false + +# Specifies whether the app is configured to use edge-to-edge via the app config or plugin +# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge. +expo.edgeToEdgeEnabled=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..5eed7ee --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..fdd7d77 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,39 @@ +pluginManagement { + def reactNativeGradlePlugin = new File( + providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })") + }.standardOutput.asText.get().trim() + ).getParentFile().absolutePath + includeBuild(reactNativeGradlePlugin) + + def expoPluginsPath = new File( + providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })") + }.standardOutput.asText.get().trim(), + "../android/expo-gradle-plugin" + ).absolutePath + includeBuild(expoPluginsPath) +} + +plugins { + id("com.facebook.react.settings") + id("expo-autolinking-settings") +} + +extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> + if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') { + ex.autolinkLibrariesFromCommand() + } else { + ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand) + } +} +expoAutolinking.useExpoModules() + +rootProject.name = 'simple-notepad' + +expoAutolinking.useExpoVersionCatalog() + +include ':app' +includeBuild(expoAutolinking.reactNativeGradlePlugin) diff --git a/app/_layout.tsx b/app/_layout.tsx index 39e440b..0972806 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,6 +1,6 @@ import '@/global.css'; -import { migrateDbIfNeeded } from '@/lib/dataStorage'; +import { migrateDbIfNeeded, SQLITE_DATABASE_NAME } from '@/lib/dataStorage'; import { NAV_THEME } from '@/lib/theme'; import { ThemeProvider } from '@react-navigation/native'; import { PortalHost } from '@rn-primitives/portal'; @@ -19,7 +19,7 @@ export default function RootLayout() { return ( - + diff --git a/lib/dataStorage.ts b/lib/dataStorage.ts index 4f766a4..6d6906c 100644 --- a/lib/dataStorage.ts +++ b/lib/dataStorage.ts @@ -1,5 +1,7 @@ import { type SQLiteDatabase } from 'expo-sqlite'; +export const SQLITE_DATABASE_NAME = 'notes.db'; + const DATABASE_VERSION = 2; export const NOTE_TYPE = 0 as const; diff --git a/package.json b/package.json index a7413e3..85b1b30 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "main": "expo-router/entry", "version": "1.2.1", "scripts": { - "prebuild": "expo prebuild --clean", + "prebuild": "expo prebuild", "dev": "expo start", "android": "expo run:android", "ios": "expo run:ios", From 6d6eed505b8dd29bb25f931227ab991c4d9ce5d8 Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Wed, 13 May 2026 13:51:32 +0200 Subject: [PATCH 3/9] Add widget background drawable for Android; define shape and styling properties --- android/app/src/main/res/drawable/widget_background.xml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 android/app/src/main/res/drawable/widget_background.xml diff --git a/android/app/src/main/res/drawable/widget_background.xml b/android/app/src/main/res/drawable/widget_background.xml new file mode 100644 index 0000000..eff044c --- /dev/null +++ b/android/app/src/main/res/drawable/widget_background.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file From 9604eea3318b366ac68bdcbe2a5f3cb9cf43acdb Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Wed, 13 May 2026 13:54:33 +0200 Subject: [PATCH 4/9] Refactor NoteListWidget to streamline widget update process; remove unused list parameter and enhance code readability --- .../simplenotepad/widget/NoteListWidget.kt | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt index daa28d8..f3eb3d5 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt @@ -11,41 +11,34 @@ import com.pgarr.simplenotepad.R class NoteListWidget : AppWidgetProvider() { override fun onUpdate( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetIds: IntArray - ) { - val list = WidgetDbHelper.getLatestList(context) - - for (widgetId in appWidgetIds) { - updateWidget(context, appWidgetManager, widgetId, list) - } + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray + ) { + for (widgetId in appWidgetIds) { + updateWidget(context, appWidgetManager, widgetId) + } } companion object { fun updateWidget( context: Context, appWidgetManager: AppWidgetManager, - widgetId: Int, - list: WidgetListItem? = null // unused directly; data flows via factory + widgetId: Int ) { val latestList = WidgetDbHelper.getLatestList(context) - + val rv = RemoteViews(context.packageName, R.layout.widget_note_list) - - // Set title + rv.setTextViewText(R.id.widget_title, latestList?.title ?: "No lists") - - // Wire up the ListView to the RemoteViewsService + val serviceIntent = Intent(context, NoteListWidgetService::class.java).apply { putExtra("list_id", latestList?.id ?: -1) - // Must be unique per widget instance for Android to distinguish adapters data = android.net.Uri.parse("widget://list/$widgetId") } rv.setRemoteAdapter(R.id.widget_list_view, serviceIntent) rv.setEmptyView(R.id.widget_list_view, R.id.widget_title) - - // PendingIntent template for row taps — filled in by setOnClickFillInIntent + val toggleIntent = Intent(context, WidgetUpdateReceiver::class.java).apply { action = WidgetUpdateReceiver.ACTION_TOGGLE_ITEM } @@ -56,7 +49,7 @@ class NoteListWidget : AppWidgetProvider() { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE ) rv.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent) - + appWidgetManager.updateAppWidget(widgetId, rv) } } From 47b30632635e018fb898f3ac82929764128f4599 Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Wed, 13 May 2026 14:14:32 +0200 Subject: [PATCH 5/9] Update widget styling and layout; refactor database path and enhance color definitions for improved UI consistency --- .../java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt | 3 +-- android/app/src/main/res/drawable/widget_background.xml | 8 ++++++-- android/app/src/main/res/layout/widget_list_item.xml | 2 +- android/app/src/main/res/layout/widget_note_list.xml | 6 +++--- android/app/src/main/res/values/colors.xml | 4 ++++ android/app/src/main/res/xml/note_list_widget_info.xml | 8 ++++---- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt index b5c85de..7e5872e 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt @@ -15,8 +15,7 @@ object WidgetDbHelper { private const val LIST_TYPE = 1 private fun getDbPath(context: Context): String { - // expo-sqlite stores DBs here on Android - return context.getDatabasePath(DB_NAME).absolutePath + return context.filesDir.absolutePath + "/SQLite/" + DB_NAME } /** diff --git a/android/app/src/main/res/drawable/widget_background.xml b/android/app/src/main/res/drawable/widget_background.xml index eff044c..630a8ab 100644 --- a/android/app/src/main/res/drawable/widget_background.xml +++ b/android/app/src/main/res/drawable/widget_background.xml @@ -2,7 +2,11 @@ - - + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/widget_list_item.xml b/android/app/src/main/res/layout/widget_list_item.xml index 598f550..b4954ac 100644 --- a/android/app/src/main/res/layout/widget_list_item.xml +++ b/android/app/src/main/res/layout/widget_list_item.xml @@ -22,7 +22,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textSize="14sp" - android:textColor="#FFFFFF" + android:textColor="@color/widget_text_primary" android:paddingStart="10dp" android:maxLines="2" android:ellipsize="end" /> diff --git a/android/app/src/main/res/layout/widget_note_list.xml b/android/app/src/main/res/layout/widget_note_list.xml index e9ebc7a..de8de86 100644 --- a/android/app/src/main/res/layout/widget_note_list.xml +++ b/android/app/src/main/res/layout/widget_note_list.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:padding="12dp" + android:padding="8dp" android:background="@drawable/widget_background"> diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index f387b90..0602cb7 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -3,4 +3,8 @@ #ffffff #023c69 #ffffff + + #FFFFFF + #E5E5E5 + #0A0A0A \ No newline at end of file diff --git a/android/app/src/main/res/xml/note_list_widget_info.xml b/android/app/src/main/res/xml/note_list_widget_info.xml index 0703e69..1b373b9 100644 --- a/android/app/src/main/res/xml/note_list_widget_info.xml +++ b/android/app/src/main/res/xml/note_list_widget_info.xml @@ -1,10 +1,10 @@ Date: Wed, 13 May 2026 14:27:57 +0200 Subject: [PATCH 6/9] Enhance NoteListWidget functionality by adding deep link support and updating empty view handling; introduce new string resource for empty list message --- .../simplenotepad/widget/NoteListWidget.kt | 32 ++++++++++++++++++- .../src/main/res/layout/widget_note_list.xml | 22 +++++++++++-- android/app/src/main/res/values/strings.xml | 1 + 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt index f3eb3d5..17e3374 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt @@ -5,7 +5,10 @@ import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context import android.content.Intent +import android.net.Uri +import android.os.Build import android.widget.RemoteViews +import com.pgarr.simplenotepad.MainActivity import com.pgarr.simplenotepad.R class NoteListWidget : AppWidgetProvider() { @@ -31,13 +34,40 @@ class NoteListWidget : AppWidgetProvider() { val rv = RemoteViews(context.packageName, R.layout.widget_note_list) rv.setTextViewText(R.id.widget_title, latestList?.title ?: "No lists") + + val listDeepLink = + if (latestList != null) { + Uri.parse("simple-notepad:///list/${latestList.id}") + } else { + Uri.parse("simple-notepad:///") + } + val openListIntent = + Intent(Intent.ACTION_VIEW, listDeepLink).apply { + setClass(context, MainActivity::class.java) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) + } + val openListFlags = + PendingIntent.FLAG_UPDATE_CURRENT or + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_IMMUTABLE + } else { + 0 + } + val openListPendingIntent = + PendingIntent.getActivity( + context, + widgetId + 10_000, + openListIntent, + openListFlags + ) + rv.setOnClickPendingIntent(R.id.widget_title, openListPendingIntent) val serviceIntent = Intent(context, NoteListWidgetService::class.java).apply { putExtra("list_id", latestList?.id ?: -1) data = android.net.Uri.parse("widget://list/$widgetId") } rv.setRemoteAdapter(R.id.widget_list_view, serviceIntent) - rv.setEmptyView(R.id.widget_list_view, R.id.widget_title) + rv.setEmptyView(R.id.widget_list_view, R.id.widget_list_empty) val toggleIntent = Intent(context, WidgetUpdateReceiver::class.java).apply { action = WidgetUpdateReceiver.ACTION_TOGGLE_ITEM diff --git a/android/app/src/main/res/layout/widget_note_list.xml b/android/app/src/main/res/layout/widget_note_list.xml index de8de86..e7ebd58 100644 --- a/android/app/src/main/res/layout/widget_note_list.xml +++ b/android/app/src/main/res/layout/widget_note_list.xml @@ -20,10 +20,26 @@ android:ellipsize="end" android:text="My List" /> - + android:layout_weight="1"> + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index ac05a2b..1359783 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ simple-notepad + No items in this list contain false automatic From 54b29455094baa171589e340ea7202aa50493346 Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Wed, 13 May 2026 14:42:26 +0200 Subject: [PATCH 7/9] Implement database change listener for Android widgets; enhance list loading on app state change and improve SQLite database path handling --- .../pgarr/simplenotepad/MainApplication.kt | 4 +- .../widget/NoteListRemoteViewsFactory.kt | 18 +++-- .../simplenotepad/widget/NoteListWidget.kt | 10 +++ .../simplenotepad/widget/WidgetDbHelper.kt | 81 ++++++++++--------- .../widget/WidgetRefreshModule.kt | 16 ++++ .../widget/WidgetRefreshPackage.kt | 16 ++++ app/_layout.tsx | 29 ++++++- app/list/[id].tsx | 17 +++- 8 files changed, 144 insertions(+), 47 deletions(-) create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshModule.kt create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshPackage.kt diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/MainApplication.kt b/android/app/src/main/java/com/pgarr/simplenotepad/MainApplication.kt index cb1d9c9..ce4d0a0 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/MainApplication.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/MainApplication.kt @@ -13,6 +13,7 @@ import com.facebook.react.common.ReleaseLevel import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint import com.facebook.react.defaults.DefaultReactNativeHost +import com.pgarr.simplenotepad.widget.WidgetRefreshPackage import expo.modules.ApplicationLifecycleDispatcher import expo.modules.ReactNativeHostWrapper @@ -23,8 +24,7 @@ class MainApplication : Application(), ReactApplication { object : DefaultReactNativeHost(this) { override fun getPackages(): List = PackageList(this).packages.apply { - // Packages that cannot be autolinked yet can be added manually here, for example: - // add(MyReactNativePackage()) + add(WidgetRefreshPackage()) } override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt index 40458f7..679e26e 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt @@ -8,19 +8,27 @@ import com.pgarr.simplenotepad.R class NoteListRemoteViewsFactory( private val context: Context, - private val listId: Int + @Suppress("UNUSED_PARAMETER") private val listId: Int ) : RemoteViewsService.RemoteViewsFactory { private var items: List = emptyList() private var listTitle: String = "" + /** Id of the list rows are from — always aligned with [onDataSetChanged] (latest list). */ + private var boundListId: Int = -1 override fun onCreate() {} override fun onDataSetChanged() { - // Called on widget refresh — re-read from SQLite val list = WidgetDbHelper.getLatestList(context) - items = list?.items ?: emptyList() - listTitle = list?.title ?: "" + if (list == null) { + items = emptyList() + listTitle = "" + boundListId = -1 + return + } + items = list.items + listTitle = list.title + boundListId = list.id } override fun onDestroy() {} @@ -47,7 +55,7 @@ class NoteListRemoteViewsFactory( // Fill-in intent carries position and listId to the broadcast receiver val fillIntent = Intent().apply { putExtra("item_index", position) - putExtra("list_id", listId) + putExtra("list_id", boundListId) } rv.setOnClickFillInIntent(R.id.item_checkbox, fillIntent) rv.setOnClickFillInIntent(R.id.item_text, fillIntent) diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt index 17e3374..a744d32 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt @@ -3,6 +3,7 @@ package com.pgarr.simplenotepad.widget 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 @@ -24,6 +25,15 @@ class NoteListWidget : AppWidgetProvider() { } companion object { + fun refreshAllWidgets(context: Context) { + val appWidgetManager = AppWidgetManager.getInstance(context) + val component = ComponentName(context, NoteListWidget::class.java) + val ids = appWidgetManager.getAppWidgetIds(component) + for (widgetId in ids) { + updateWidget(context, appWidgetManager, widgetId) + } + } + fun updateWidget( context: Context, appWidgetManager: AppWidgetManager, diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt index 7e5872e..a5ad197 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt @@ -3,6 +3,7 @@ package com.pgarr.simplenotepad.widget import android.content.Context import android.database.sqlite.SQLiteDatabase import org.json.JSONArray +import java.io.File // Mirrors your expo-sqlite DB schema exactly data class WidgetListItem(val text: String, val checked: Boolean) @@ -14,8 +15,23 @@ object WidgetDbHelper { private const val DB_NAME = "notes.db" private const val LIST_TYPE = 1 + /** Same file expo-sqlite uses (`defaultDatabaseDirectory` + name), resolved canonically. */ private fun getDbPath(context: Context): String { - return context.filesDir.absolutePath + "/SQLite/" + DB_NAME + val file = File(File(context.filesDir, "SQLite"), DB_NAME) + return try { + file.canonicalPath + } catch (_: Exception) { + file.absolutePath + } + } + + private fun openDbWritable(context: Context): SQLiteDatabase { + val path = getDbPath(context) + return SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE).apply { + if (!isWriteAheadLoggingEnabled) { + enableWriteAheadLogging() + } + } } /** @@ -25,25 +41,22 @@ object WidgetDbHelper { fun getLatestList(context: Context): WidgetList? { val path = getDbPath(context) return try { - val db = SQLiteDatabase.openDatabase( - path, - null, - SQLiteDatabase.OPEN_READONLY - ) + val db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY) db.use { - val cursor = it.rawQuery( - "SELECT id, title, note FROM content WHERE type = ? ORDER BY id DESC LIMIT 1", - arrayOf(LIST_TYPE.toString()) - ) + val cursor = + it.rawQuery( + "SELECT id, title, note FROM content WHERE type = ? ORDER BY id DESC LIMIT 1", + arrayOf(LIST_TYPE.toString()) + ) cursor.use { c -> - if (!c.moveToFirst()) return null - val id = c.getInt(c.getColumnIndexOrThrow("id")) + if (!c.moveToFirst()) return@use null + val rowId = c.getInt(c.getColumnIndexOrThrow("id")) val title = c.getString(c.getColumnIndexOrThrow("title")) val note = c.getString(c.getColumnIndexOrThrow("note")) - WidgetList(id, title, parseItems(note)) + WidgetList(rowId, title, parseItems(note)) } } - } catch (e: Exception) { + } catch (_: Exception) { null } } @@ -53,35 +66,31 @@ object WidgetDbHelper { * the list identified by [listId], then persists the updated JSON. */ fun toggleItem(context: Context, listId: Int, itemIndex: Int) { - val path = getDbPath(context) try { - val db = SQLiteDatabase.openDatabase( - path, - null, - SQLiteDatabase.OPEN_READWRITE - ) - db.use { - // 1. Read current items - val cursor = it.rawQuery( - "SELECT note FROM content WHERE id = ? AND type = ?", - arrayOf(listId.toString(), LIST_TYPE.toString()) - ) - val currentNote = cursor.use { c -> - if (!c.moveToFirst()) return - c.getString(c.getColumnIndexOrThrow("note")) - } + openDbWritable(context).use { db -> + val currentNote = + db.rawQuery( + "SELECT note FROM content WHERE id = ? AND type = ?", + arrayOf(listId.toString(), LIST_TYPE.toString()) + ).use { c -> + if (!c.moveToFirst()) return + c.getString(c.getColumnIndexOrThrow("note")) + } - // 2. Toggle the target item val items = parseItems(currentNote).toMutableList() if (itemIndex < 0 || itemIndex >= items.size) return items[itemIndex] = items[itemIndex].copy(checked = !items[itemIndex].checked) - // 3. Write back — matches your stringifyListItems() format exactly val newNote = stringifyItems(items) - it.execSQL( - "UPDATE content SET note = ? WHERE id = ? AND type = ?", - arrayOf(newNote, listId.toString(), LIST_TYPE.toString()) - ) + val stmt = db.compileStatement("UPDATE content SET note = ? WHERE id = ? AND type = ?") + stmt.bindString(1, newNote) + stmt.bindLong(2, listId.toLong()) + stmt.bindLong(3, LIST_TYPE.toLong()) + stmt.executeUpdateDelete() + stmt.close() + + // Help other connections (expo-sqlite) see the write promptly when using WAL. + db.rawQuery("PRAGMA wal_checkpoint(PASSIVE)", null)?.close() } } catch (_: Exception) {} } diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshModule.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshModule.kt new file mode 100644 index 0000000..208b76a --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshModule.kt @@ -0,0 +1,16 @@ +package com.pgarr.simplenotepad.widget + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod + +class WidgetRefreshModule(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext) { + + override fun getName(): String = "WidgetRefresh" + + @ReactMethod + fun refreshNoteListWidgets() { + NoteListWidget.refreshAllWidgets(reactApplicationContext.applicationContext) + } +} diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshPackage.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshPackage.kt new file mode 100644 index 0000000..a7ce05b --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshPackage.kt @@ -0,0 +1,16 @@ +package com.pgarr.simplenotepad.widget + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class WidgetRefreshPackage : ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): List { + return listOf(WidgetRefreshModule(reactContext)) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return emptyList() + } +} diff --git a/app/_layout.tsx b/app/_layout.tsx index 0972806..061e032 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -4,22 +4,47 @@ import { migrateDbIfNeeded, SQLITE_DATABASE_NAME } from '@/lib/dataStorage'; import { NAV_THEME } from '@/lib/theme'; import { ThemeProvider } from '@react-navigation/native'; import { PortalHost } from '@rn-primitives/portal'; -import { SQLiteProvider } from 'expo-sqlite'; +import { addDatabaseChangeListener, SQLiteProvider, useSQLiteContext } from 'expo-sqlite'; import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { useColorScheme } from 'nativewind'; +import { useEffect } from 'react'; +import { NativeModules, Platform } from 'react-native'; export { // Catch any errors thrown by the Layout component. ErrorBoundary, } from 'expo-router'; +function AndroidHomeScreenWidgetsSync() { + const db = useSQLiteContext(); + + useEffect(() => { + if (Platform.OS !== 'android') { + return undefined; + } + const sub = addDatabaseChangeListener(({ tableName }) => { + if (tableName === 'content') { + NativeModules.WidgetRefresh?.refreshNoteListWidgets?.(); + } + }); + return () => sub.remove(); + }, [db]); + + return null; +} + export default function RootLayout() { const { colorScheme } = useColorScheme(); return ( - + + diff --git a/app/list/[id].tsx b/app/list/[id].tsx index 5472e34..90eb531 100644 --- a/app/list/[id].tsx +++ b/app/list/[id].tsx @@ -14,11 +14,12 @@ import { updateListItems, deletePosition, } from '@/lib/dataStorage'; +import { useFocusEffect } from '@react-navigation/native'; import { useSQLiteContext } from 'expo-sqlite'; import { Stack, useRouter } from 'expo-router'; import { CheckSquare2, ListChecks, PencilIcon, Square, Trash2Icon } from 'lucide-react-native'; import { useCallback, useEffect, useState } from 'react'; -import { Alert, Pressable, ScrollView, View } from 'react-native'; +import { Alert, AppState, type AppStateStatus, Pressable, ScrollView, View } from 'react-native'; type ListViewState = | 'loading' @@ -51,8 +52,20 @@ export default function ListViewScreen() { setListView({ id: listId, title: content.title, items }); }, [db, isValidId, listId]); + useFocusEffect( + useCallback(() => { + loadList(); + }, [loadList]) + ); + useEffect(() => { - loadList(); + const onAppState = (state: AppStateStatus) => { + if (state === 'active') { + loadList(); + } + }; + const sub = AppState.addEventListener('change', onAppState); + return () => sub.remove(); }, [loadList]); useHardwareBackHandler(() => { From 2f6cc3f6a37dbb4ac8f22d59c5175840b0101117 Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Wed, 13 May 2026 15:30:15 +0200 Subject: [PATCH 8/9] Refactor database handling to switch journal mode to DELETE for improved stability; implement widget refresh synchronization on note updates and deletions; enhance widget update notifications for better user experience --- .../simplenotepad/widget/NoteListWidget.kt | 3 ++ .../simplenotepad/widget/WidgetDbHelper.kt | 9 +---- .../widget/WidgetRefreshModule.kt | 11 ++++- app/_layout.tsx | 29 +------------- app/edit-list/[id].tsx | 16 +++++++- app/index.tsx | 14 ++++++- lib/__tests__/dataStorage.test.ts | 20 ++++++++-- lib/dataStorage.ts | 40 +++++++++++++++---- 8 files changed, 92 insertions(+), 50 deletions(-) diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt index a744d32..c781f71 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt @@ -32,6 +32,9 @@ class NoteListWidget : AppWidgetProvider() { for (widgetId in ids) { updateWidget(context, appWidgetManager, widgetId) } + if (ids.isNotEmpty()) { + appWidgetManager.notifyAppWidgetViewDataChanged(ids, R.id.widget_list_view) + } } fun updateWidget( diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt index a5ad197..e416628 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt @@ -27,11 +27,7 @@ object WidgetDbHelper { private fun openDbWritable(context: Context): SQLiteDatabase { val path = getDbPath(context) - return SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE).apply { - if (!isWriteAheadLoggingEnabled) { - enableWriteAheadLogging() - } - } + return SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE) } /** @@ -88,9 +84,6 @@ object WidgetDbHelper { stmt.bindLong(3, LIST_TYPE.toLong()) stmt.executeUpdateDelete() stmt.close() - - // Help other connections (expo-sqlite) see the write promptly when using WAL. - db.rawQuery("PRAGMA wal_checkpoint(PASSIVE)", null)?.close() } } catch (_: Exception) {} } diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshModule.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshModule.kt index 208b76a..6263d38 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshModule.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetRefreshModule.kt @@ -1,5 +1,7 @@ package com.pgarr.simplenotepad.widget +import android.os.Handler +import android.os.Looper import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod @@ -11,6 +13,13 @@ class WidgetRefreshModule(reactContext: ReactApplicationContext) : @ReactMethod fun refreshNoteListWidgets() { - NoteListWidget.refreshAllWidgets(reactApplicationContext.applicationContext) + val context = reactApplicationContext.applicationContext + val refresh = Runnable { NoteListWidget.refreshAllWidgets(context) } + val activity = reactApplicationContext.currentActivity + if (activity != null) { + activity.runOnUiThread(refresh) + } else { + Handler(Looper.getMainLooper()).post(refresh) + } } } diff --git a/app/_layout.tsx b/app/_layout.tsx index 061e032..0972806 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -4,47 +4,22 @@ import { migrateDbIfNeeded, SQLITE_DATABASE_NAME } from '@/lib/dataStorage'; import { NAV_THEME } from '@/lib/theme'; import { ThemeProvider } from '@react-navigation/native'; import { PortalHost } from '@rn-primitives/portal'; -import { addDatabaseChangeListener, SQLiteProvider, useSQLiteContext } from 'expo-sqlite'; +import { SQLiteProvider } from 'expo-sqlite'; import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { useColorScheme } from 'nativewind'; -import { useEffect } from 'react'; -import { NativeModules, Platform } from 'react-native'; export { // Catch any errors thrown by the Layout component. ErrorBoundary, } from 'expo-router'; -function AndroidHomeScreenWidgetsSync() { - const db = useSQLiteContext(); - - useEffect(() => { - if (Platform.OS !== 'android') { - return undefined; - } - const sub = addDatabaseChangeListener(({ tableName }) => { - if (tableName === 'content') { - NativeModules.WidgetRefresh?.refreshNoteListWidgets?.(); - } - }); - return () => sub.remove(); - }, [db]); - - return null; -} - export default function RootLayout() { const { colorScheme } = useColorScheme(); return ( - - + diff --git a/app/edit-list/[id].tsx b/app/edit-list/[id].tsx index a8314c4..a996293 100644 --- a/app/edit-list/[id].tsx +++ b/app/edit-list/[id].tsx @@ -12,7 +12,9 @@ import { } from '@/lib/dataStorage'; import { useSQLiteContext } from 'expo-sqlite'; import { Stack, useRouter } from 'expo-router'; +import { useFocusEffect } from '@react-navigation/native'; import { useCallback, useEffect, useState } from 'react'; +import { AppState, type AppStateStatus } from 'react-native'; import { ListForm } from '@/components/ListForm'; type ListEditState = @@ -49,8 +51,20 @@ export default function EditListScreen() { setList({ title: content.title, items: loadedItems }); }, [db, isValidId, listId]); + useFocusEffect( + useCallback(() => { + loadList(); + }, [loadList]) + ); + useEffect(() => { - loadList(); + const onAppState = (state: AppStateStatus) => { + if (state === 'active') { + loadList(); + } + }; + const sub = AppState.addEventListener('change', onAppState); + return () => sub.remove(); }, [loadList]); const handleBackToPreviousScreen = useCallback(() => { diff --git a/app/index.tsx b/app/index.tsx index c11f081..20e3cfd 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -7,8 +7,8 @@ import { useSQLiteContext } from 'expo-sqlite'; import { Stack, useRouter } from 'expo-router'; import { useFocusEffect } from '@react-navigation/native'; import { PlusIcon } from 'lucide-react-native'; -import { useCallback, useState } from 'react'; -import { BackHandler, Pressable, ScrollView } from 'react-native'; +import { useCallback, useEffect, useState } from 'react'; +import { AppState, type AppStateStatus, BackHandler, Pressable, ScrollView } from 'react-native'; export default function Screen() { const db = useSQLiteContext(); @@ -32,6 +32,16 @@ export default function Screen() { }, [loadNotes]) ); + useEffect(() => { + const onAppState = (state: AppStateStatus) => { + if (state === 'active') { + loadNotes(); + } + }; + const sub = AppState.addEventListener('change', onAppState); + return () => sub.remove(); + }, [loadNotes]); + return ( <> { describe('migrateDbIfNeeded', () => { it('early-returns when PRAGMA user_version >= DATABASE_VERSION', async () => { const db = makeDb(); - db.getFirstAsync.mockResolvedValue({ user_version: 2 }); + db.getFirstAsync.mockResolvedValue({ user_version: 3 }); await migrateDbIfNeeded(db as any); @@ -42,6 +42,17 @@ describe('lib/dataStorage', () => { expect(db.runAsync).not.toHaveBeenCalled(); }); + it('migrates v2 to v3 by switching journal mode off WAL', async () => { + const db = makeDb(); + db.getFirstAsync.mockResolvedValue({ user_version: 2 }); + db.execAsync.mockResolvedValue(undefined); + + await migrateDbIfNeeded(db as any); + + expect(db.execAsync).toHaveBeenCalledWith('PRAGMA journal_mode = DELETE'); + expect(db.execAsync).toHaveBeenCalledWith('PRAGMA user_version = 3'); + }); + it('creates base table and adds type column when meta is missing and type is absent', async () => { const db = makeDb(); db.getFirstAsync.mockResolvedValue(null); @@ -51,6 +62,7 @@ describe('lib/dataStorage', () => { await migrateDbIfNeeded(db as any); + expect(db.execAsync).toHaveBeenCalledWith(expect.stringContaining('PRAGMA journal_mode = DELETE')); expect(db.execAsync).toHaveBeenCalledWith(expect.stringContaining('CREATE TABLE content')); expect(db.getAllAsync).toHaveBeenCalledWith('PRAGMA table_info(content)'); expect(db.execAsync).toHaveBeenCalledWith( @@ -59,7 +71,8 @@ describe('lib/dataStorage', () => { expect(db.runAsync).toHaveBeenCalledWith( expect.stringContaining('UPDATE content SET type =') ); - expect(db.execAsync).toHaveBeenCalledWith('PRAGMA user_version = 2'); + expect(db.execAsync).toHaveBeenCalledWith('PRAGMA journal_mode = DELETE'); + expect(db.execAsync).toHaveBeenCalledWith('PRAGMA user_version = 3'); }); it('when user_version is old and type column exists, it skips ALTER TABLE but sets default types', async () => { @@ -80,7 +93,8 @@ describe('lib/dataStorage', () => { expect(db.runAsync).toHaveBeenCalledWith( expect.stringContaining('UPDATE content SET type =') ); - expect(db.execAsync).toHaveBeenCalledWith('PRAGMA user_version = 2'); + expect(db.execAsync).toHaveBeenCalledWith('PRAGMA journal_mode = DELETE'); + expect(db.execAsync).toHaveBeenCalledWith('PRAGMA user_version = 3'); }); }); diff --git a/lib/dataStorage.ts b/lib/dataStorage.ts index 6d6906c..d4dec46 100644 --- a/lib/dataStorage.ts +++ b/lib/dataStorage.ts @@ -1,8 +1,9 @@ import { type SQLiteDatabase } from 'expo-sqlite'; +import { NativeModules, Platform } from 'react-native'; export const SQLITE_DATABASE_NAME = 'notes.db'; -const DATABASE_VERSION = 2; +const DATABASE_VERSION = 3; export const NOTE_TYPE = 0 as const; export const LIST_TYPE = 1 as const; @@ -46,6 +47,12 @@ const parseListItems = (rawContent: string): ListItem[] => { const stringifyListItems = (items: ListItem[]) => JSON.stringify(items.map((item) => ({ checked: !!item.checked, text: item.text.trim() }))); +/** Home-screen widget reads `content`; refresh right after app writes (listener alone can lag behind minimize). */ +function syncAndroidNoteListWidgetFromApp(): void { + if (Platform.OS !== 'android') return; + NativeModules.WidgetRefresh?.refreshNoteListWidgets?.(); +} + export const migrateDbIfNeeded = async (db: SQLiteDatabase) => { const meta = await db.getFirstAsync<{ user_version: number; @@ -55,7 +62,7 @@ export const migrateDbIfNeeded = async (db: SQLiteDatabase) => { } if (!meta || meta.user_version === 0) { await db.execAsync(` - PRAGMA journal_mode = 'wal'; + PRAGMA journal_mode = DELETE; CREATE TABLE content (id INTEGER PRIMARY KEY NOT NULL, title TEXT NOT NULL, note TEXT NOT NULL); `); } @@ -69,6 +76,11 @@ export const migrateDbIfNeeded = async (db: SQLiteDatabase) => { } await db.runAsync(`UPDATE content SET type = ${NOTE_TYPE} WHERE type IS NULL`); } + if (!meta || meta.user_version < 3) { + // The Android widget uses android.database.sqlite on this file. WAL + two SQLite stacks can + // corrupt the DB ("disk image is malformed"); DELETE mode is safe for both readers/writers. + await db.execAsync(`PRAGMA journal_mode = DELETE`); + } await db.execAsync(`PRAGMA user_version = ${DATABASE_VERSION}`); }; @@ -82,41 +94,51 @@ export const getNoteById = async (db: SQLiteDatabase, id: number): Promise { - return await db.runAsync('INSERT INTO content (title, note, type) VALUES (?, ?, ?)', [ + const result = await db.runAsync('INSERT INTO content (title, note, type) VALUES (?, ?, ?)', [ note.title, note.note, NOTE_TYPE, ]); + syncAndroidNoteListWidgetFromApp(); + return result; }; export const updateNote = async (db: SQLiteDatabase, id: number, note: NewNote) => { - return await db.runAsync('UPDATE content SET title = ?, note = ?, type = ? WHERE id = ?', [ + const result = await db.runAsync('UPDATE content SET title = ?, note = ?, type = ? WHERE id = ?', [ note.title, note.note, NOTE_TYPE, id, ]); + syncAndroidNoteListWidgetFromApp(); + return result; }; export const deletePosition = async (db: SQLiteDatabase, id: number) => { - return await db.runAsync('DELETE FROM content WHERE id = ?', [id]); + const result = await db.runAsync('DELETE FROM content WHERE id = ?', [id]); + syncAndroidNoteListWidgetFromApp(); + return result; }; export const addList = async (db: SQLiteDatabase, list: NewList) => { - return await db.runAsync('INSERT INTO content (title, note, type) VALUES (?, ?, ?)', [ + const result = await db.runAsync('INSERT INTO content (title, note, type) VALUES (?, ?, ?)', [ list.title, stringifyListItems(list.items), LIST_TYPE, ]); + syncAndroidNoteListWidgetFromApp(); + return result; }; export const updateList = async (db: SQLiteDatabase, id: number, list: NewList) => { - return await db.runAsync('UPDATE content SET title = ?, note = ?, type = ? WHERE id = ?', [ + const result = await db.runAsync('UPDATE content SET title = ?, note = ?, type = ? WHERE id = ?', [ list.title, stringifyListItems(list.items), LIST_TYPE, id, ]); + syncAndroidNoteListWidgetFromApp(); + return result; }; export const getListItemsById = async ( @@ -132,9 +154,11 @@ export const getListItemsById = async ( }; export const updateListItems = async (db: SQLiteDatabase, id: number, items: ListItem[]) => { - return await db.runAsync('UPDATE content SET note = ? WHERE id = ? AND type = ?', [ + const result = await db.runAsync('UPDATE content SET note = ? WHERE id = ? AND type = ?', [ stringifyListItems(items), id, LIST_TYPE, ]); + syncAndroidNoteListWidgetFromApp(); + return result; }; From c0b1c5205f0ae442f78f38d776631452005fd0fc Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Wed, 13 May 2026 15:31:28 +0200 Subject: [PATCH 9/9] Update version to 1.3.0 in app.json, package.json, and package-lock.json for release; increment versionCode to 16 for Android compatibility --- app.json | 6 +++--- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app.json b/app.json index 5c657c5..da9742a 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "simple-notepad", "slug": "simple-notepad", - "version": "1.2.1", + "version": "1.3.0", "orientation": "portrait", "icon": "./assets/images/icon.png", "scheme": "simple-notepad", @@ -25,7 +25,7 @@ "backgroundColor": "#ffffff" }, "package": "com.pgarr.simplenotepad", - "versionCode": 15 + "versionCode": 16 }, "web": { "bundler": "metro", @@ -44,7 +44,7 @@ "projectId": "9e3820b7-558b-4bd2-a1b2-e49561e741e6" } }, - "runtimeVersion": "1.2.1", + "runtimeVersion": "1.3.0", "updates": { "url": "https://u.expo.dev/9e3820b7-558b-4bd2-a1b2-e49561e741e6" } diff --git a/package-lock.json b/package-lock.json index 50c1125..f3878ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simple-notepad", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simple-notepad", - "version": "1.2.1", + "version": "1.3.0", "dependencies": { "@react-navigation/native": "^7.0.0", "@rn-primitives/portal": "~1.3.0", diff --git a/package.json b/package.json index 85b1b30..8ad1f41 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "simple-notepad", "main": "expo-router/entry", - "version": "1.2.1", + "version": "1.3.0", "scripts": { "prebuild": "expo prebuild", "dev": "expo start",