@sqliteai/todoapp 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/.env.example +2 -1
- package/README.md +2 -0
- package/app.json +1 -1
- package/components/TaskRow.js +1 -1
- package/hooks/useCategories.js +20 -5
- package/package.json +18 -5
- package/plugins/CloudSyncSetup.js +14 -4
- package/screens/Categories.js +25 -5
- package/screens/Home.js +1 -1
package/.env.example
CHANGED
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A simple Expo example demonstrating SQLite synchronization with CloudSync. Build cross-platform apps that sync data seamlessly across devices.
|
|
4
4
|
|
|
5
|
+
<img src="https://github.com/user-attachments/assets/86db5c25-d8ff-4c31-b157-8dff178e1720" width="40%" height="40%">
|
|
6
|
+
|
|
5
7
|
## 🚀 Quick Start
|
|
6
8
|
|
|
7
9
|
### 1. Clone the template
|
package/app.json
CHANGED
package/components/TaskRow.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useRef, useEffect } from "react";
|
|
2
2
|
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
|
|
3
|
-
import Icon from "react-native-vector-icons/
|
|
3
|
+
import Icon from "@react-native-vector-icons/fontawesome";
|
|
4
4
|
import { Swipeable } from "react-native-gesture-handler";
|
|
5
5
|
|
|
6
6
|
export default TaskRow = ({ task, updateTask, handleDelete }) => {
|
package/hooks/useCategories.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
3
|
import { db } from "../db/dbConnection";
|
|
4
|
-
import { CONNECTION_STRING } from "@env";
|
|
4
|
+
import { ANDROID_CONNECTION_STRING, CONNECTION_STRING, API_TOKEN } from "@env";
|
|
5
5
|
import { getDylibPath } from "@op-engineering/op-sqlite";
|
|
6
6
|
import { randomUUID } from 'expo-crypto';
|
|
7
7
|
import { useSyncContext } from '../components/SyncContext';
|
|
@@ -30,6 +30,16 @@ const useCategories = () => {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const removeCategory = async categoryName => {
|
|
34
|
+
try {
|
|
35
|
+
await db.execute('DELETE FROM tags WHERE name = ?', [categoryName])
|
|
36
|
+
db.execute('SELECT cloudsync_network_send_changes();')
|
|
37
|
+
setMoreCategories(prevCategories => prevCategories.filter(cat => cat !== categoryName))
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error removing category', error)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
const initializeTables = async () => {
|
|
34
44
|
let extensionPath;
|
|
35
45
|
|
|
@@ -55,14 +65,18 @@ const useCategories = () => {
|
|
|
55
65
|
await db.execute('CREATE TABLE IF NOT EXISTS tags (uuid TEXT NOT NULL PRIMARY KEY, name TEXT, UNIQUE(name));')
|
|
56
66
|
await db.execute('CREATE TABLE IF NOT EXISTS tasks_tags (uuid TEXT NOT NULL PRIMARY KEY, task_uuid TEXT, tag_uuid TEXT, FOREIGN KEY (task_uuid) REFERENCES tasks(uuid), FOREIGN KEY (tag_uuid) REFERENCES tags(uuid));')
|
|
57
67
|
|
|
58
|
-
await db.execute(`SELECT cloudsync_init('
|
|
68
|
+
await db.execute(`SELECT cloudsync_init('tasks');`);
|
|
69
|
+
await db.execute(`SELECT cloudsync_init('tags');`);
|
|
70
|
+
await db.execute(`SELECT cloudsync_init('tasks_tags');`);
|
|
71
|
+
|
|
59
72
|
await db.execute('INSERT OR IGNORE INTO tags (uuid, name) VALUES (?, ?)', [randomUUID(), 'Work'])
|
|
60
73
|
await db.execute('INSERT OR IGNORE INTO tags (uuid, name) VALUES (?, ?)', [randomUUID(), 'Personal'])
|
|
61
74
|
|
|
62
|
-
if (CONNECTION_STRING &&
|
|
63
|
-
await db.execute(`SELECT cloudsync_network_init('${CONNECTION_STRING}');`);
|
|
75
|
+
if ((ANDROID_CONNECTION_STRING || CONNECTION_STRING) && API_TOKEN) {
|
|
76
|
+
await db.execute(`SELECT cloudsync_network_init('${Platform.OS == 'android' && ANDROID_CONNECTION_STRING ? ANDROID_CONNECTION_STRING : CONNECTION_STRING}');`);
|
|
77
|
+
await db.execute(`SELECT cloudsync_network_set_token('${API_TOKEN}');`)
|
|
64
78
|
} else {
|
|
65
|
-
throw new Error('No valid CONNECTION_STRING provided, cloudsync_network_init will not be called');
|
|
79
|
+
throw new Error('No valid CONNECTION_STRING or API_TOKEN provided, cloudsync_network_init will not be called');
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
db.execute('SELECT cloudsync_network_sync(100, 10);')
|
|
@@ -86,6 +100,7 @@ const useCategories = () => {
|
|
|
86
100
|
return {
|
|
87
101
|
moreCategories,
|
|
88
102
|
addCategory,
|
|
103
|
+
removeCategory,
|
|
89
104
|
getCategories
|
|
90
105
|
}
|
|
91
106
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqliteai/todoapp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "An Expo template for building apps with the SQLite CloudSync extension",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/sqliteai/sqlite-sync.git"
|
|
8
8
|
},
|
|
9
9
|
"author": "SQLiteAI",
|
|
10
|
-
"keywords": [
|
|
10
|
+
"keywords": [
|
|
11
|
+
"expo-template",
|
|
12
|
+
"sqlite",
|
|
13
|
+
"cloudsync",
|
|
14
|
+
"todo",
|
|
15
|
+
"sync",
|
|
16
|
+
"react-native",
|
|
17
|
+
"expo",
|
|
18
|
+
"template"
|
|
19
|
+
],
|
|
11
20
|
"main": "expo/AppEntry.js",
|
|
12
21
|
"scripts": {
|
|
13
22
|
"start": "expo start",
|
|
@@ -21,19 +30,23 @@
|
|
|
21
30
|
"dependencies": {
|
|
22
31
|
"@op-engineering/op-sqlite": "14.1.4",
|
|
23
32
|
"@react-native-picker/picker": "2.11.1",
|
|
33
|
+
"@react-native-vector-icons/fontawesome": "^12.4.0",
|
|
34
|
+
"@react-native-vector-icons/material-design-icons": "^12.4.0",
|
|
24
35
|
"@react-navigation/native": "^7.1.17",
|
|
25
36
|
"@react-navigation/stack": "^7.4.7",
|
|
26
37
|
"expo": "^53.0.22",
|
|
38
|
+
"expo-asset": "^12.0.12",
|
|
39
|
+
"expo-constants": "^18.0.13",
|
|
27
40
|
"expo-crypto": "~14.1.5",
|
|
28
41
|
"expo-status-bar": "~2.2.3",
|
|
42
|
+
"prop-types": "^15.8.1",
|
|
29
43
|
"react": "19.0.0",
|
|
30
|
-
"react-native": "0.79.
|
|
44
|
+
"react-native": "0.79.6",
|
|
31
45
|
"react-native-gesture-handler": "~2.24.0",
|
|
32
46
|
"react-native-paper": "5.14.5",
|
|
33
47
|
"react-native-picker-select": "^9.3.1",
|
|
34
48
|
"react-native-safe-area-context": "5.4.0",
|
|
35
|
-
"react-native-screens": "~4.11.1"
|
|
36
|
-
"react-native-vector-icons": "^10.3.0"
|
|
49
|
+
"react-native-screens": "~4.11.1"
|
|
37
50
|
},
|
|
38
51
|
"devDependencies": {
|
|
39
52
|
"@babel/core": "7.28.3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { withDangerousMod, withXcodeProject } = require('@expo/config-plugins');
|
|
1
|
+
const { withDangerousMod, withXcodeProject, withInfoPlist } = require('@expo/config-plugins');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const https = require('https');
|
|
@@ -50,7 +50,7 @@ async function getLatestReleaseUrl(asset_pattern) {
|
|
|
50
50
|
return new Promise((resolve, reject) => {
|
|
51
51
|
const options = {
|
|
52
52
|
hostname: 'api.github.com',
|
|
53
|
-
path: '/repos/sqliteai/sqlite-sync/releases/latest',
|
|
53
|
+
path: '/repos/sqliteai/sqlite-sync-dev/releases/latest',
|
|
54
54
|
headers: {
|
|
55
55
|
'User-Agent': 'expo-cloudsync-plugin'
|
|
56
56
|
}
|
|
@@ -277,8 +277,18 @@ const withCloudSync = (config) => {
|
|
|
277
277
|
|
|
278
278
|
// iOS setup - add to Xcode project
|
|
279
279
|
config = withCloudSyncFramework(config);
|
|
280
|
-
|
|
280
|
+
|
|
281
|
+
// iOS setup - register icon fonts in Info.plist
|
|
282
|
+
config = withInfoPlist(config, (config) => {
|
|
283
|
+
const fonts = config.modResults.UIAppFonts || [];
|
|
284
|
+
if (!fonts.includes('FontAwesome.ttf')) {
|
|
285
|
+
fonts.push('FontAwesome.ttf');
|
|
286
|
+
}
|
|
287
|
+
config.modResults.UIAppFonts = fonts;
|
|
288
|
+
return config;
|
|
289
|
+
});
|
|
290
|
+
|
|
281
291
|
return config;
|
|
282
292
|
};
|
|
283
293
|
|
|
284
|
-
module.exports = withCloudSync;
|
|
294
|
+
module.exports = withCloudSync;
|
package/screens/Categories.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
-
import { ScrollView, StyleSheet, View } from "react-native";
|
|
2
|
+
import { ScrollView, StyleSheet, View, Alert } from "react-native";
|
|
3
3
|
import { Avatar, Card, Text, Modal, Portal, Button, TextInput } from "react-native-paper";
|
|
4
|
+
import Icon from '@react-native-vector-icons/material-design-icons';
|
|
4
5
|
import { useFocusEffect } from '@react-navigation/native';
|
|
5
6
|
import useCategories from "../hooks/useCategories";
|
|
6
7
|
import { useSyncContext } from "../components/SyncContext";
|
|
@@ -22,7 +23,7 @@ const Categories = ({ navigation }) => {
|
|
|
22
23
|
day: "numeric",
|
|
23
24
|
});
|
|
24
25
|
|
|
25
|
-
const { moreCategories, addCategory } = useCategories();
|
|
26
|
+
const { moreCategories, addCategory, removeCategory } = useCategories();
|
|
26
27
|
const { setSync } = useSyncContext();
|
|
27
28
|
|
|
28
29
|
const [newCategory, setNewCategory] = useState("");
|
|
@@ -45,6 +46,24 @@ const Categories = ({ navigation }) => {
|
|
|
45
46
|
hideModal();
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function handleRemoveCategory(categoryName) {
|
|
50
|
+
Alert.alert(
|
|
51
|
+
"Delete Category",
|
|
52
|
+
`Are you sure you want to delete "${categoryName}"?`,
|
|
53
|
+
[
|
|
54
|
+
{
|
|
55
|
+
text: "Cancel",
|
|
56
|
+
style: "cancel"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
text: "Delete",
|
|
60
|
+
style: "destructive",
|
|
61
|
+
onPress: () => removeCategory(categoryName)
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
return (
|
|
49
68
|
<>
|
|
50
69
|
<Portal>
|
|
@@ -108,7 +127,7 @@ const Categories = ({ navigation }) => {
|
|
|
108
127
|
>
|
|
109
128
|
<Card.Title
|
|
110
129
|
left={(props) => (
|
|
111
|
-
<
|
|
130
|
+
<Icon
|
|
112
131
|
{...props}
|
|
113
132
|
icon="inbox-outline"
|
|
114
133
|
color={styles.icon.color}
|
|
@@ -127,11 +146,12 @@ const Categories = ({ navigation }) => {
|
|
|
127
146
|
key={index}
|
|
128
147
|
style={styles.card}
|
|
129
148
|
onPress={() => navigation.navigate("Tasks", { category })}
|
|
149
|
+
onLongPress={() => handleRemoveCategory(category)}
|
|
130
150
|
mode="contained"
|
|
131
151
|
>
|
|
132
152
|
<Card.Title
|
|
133
153
|
left={(props) => (
|
|
134
|
-
<
|
|
154
|
+
<Icon
|
|
135
155
|
{...props}
|
|
136
156
|
icon="tag-outline"
|
|
137
157
|
color={styles.icon.color}
|
|
@@ -149,7 +169,7 @@ const Categories = ({ navigation }) => {
|
|
|
149
169
|
<Card style={styles.addCard} onPress={showModal} mode="contained">
|
|
150
170
|
<Card.Title
|
|
151
171
|
left={(props) => (
|
|
152
|
-
<
|
|
172
|
+
<Icon
|
|
153
173
|
{...props}
|
|
154
174
|
icon="plus-circle-outline"
|
|
155
175
|
color={styles.addIcon.color}
|
package/screens/Home.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { View, Text, StyleSheet, FlatList, Alert } from "react-native";
|
|
3
3
|
import { Button } from "react-native-paper";
|
|
4
|
-
import Icon from "react-native-vector-icons/
|
|
4
|
+
import Icon from "@react-native-vector-icons/fontawesome";
|
|
5
5
|
import TaskRow from "../components/TaskRow";
|
|
6
6
|
import AddTaskModal from "../components/AddTaskModal";
|
|
7
7
|
import useTasks from "../hooks/useTasks"
|