diff --git a/EditSet.tsx b/EditSet.tsx index 825894c..9e03ff7 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -3,31 +3,32 @@ import {StyleSheet, Text, View} from 'react-native'; import {Button, Modal, Portal, TextInput} from 'react-native-paper'; import {getDb} from './db'; import Set from './set'; +import {format} from 'date-fns'; export default function EditSet({ id, onSave, show, setShow, - onRemove, + clearId, }: { id?: number; + clearId: () => void; onSave: () => void; show: boolean; setShow: (visible: boolean) => void; - onRemove: () => void; }) { const [name, setName] = useState(''); const [reps, setReps] = useState(''); const [weight, setWeight] = useState(''); const [unit, setUnit] = useState(''); - const [created, setCreated] = useState(new Date()); + const [created, setCreated] = useState(new Date(new Date().toUTCString())); const weightRef = useRef(null); const repsRef = useRef(null); const unitRef = useRef(null); useEffect(() => { - if (!id) return; + if (!id) return setCreated(new Date(new Date().toUTCString())); getDb().then(async db => { const [result] = await db.executeSql(`SELECT * FROM sets WHERE id = ?`, [ id, @@ -59,21 +60,13 @@ export default function EditSet({ onSave(); }; - const remove = async () => { - if (!id) return; - const db = await getDb(); - await db.executeSql(`DELETE FROM sets WHERE id = ?`, [id]); - setShow(false); - onRemove(); - }; - return ( setShow(false)}> - Add a set + {id ? `Edit "${name}"` : 'Add a set'} - + {format(created, 'PPPP p')} - + {id && ( + + )} diff --git a/Exercises.tsx b/Exercises.tsx index a1a70f2..cfcdf38 100644 --- a/Exercises.tsx +++ b/Exercises.tsx @@ -11,6 +11,7 @@ export default function Exercises({ }: NativeStackScreenProps) { const [exercises, setExercises] = useState([]); const [search, setSearch] = useState(''); + const [refreshing, setRefresing] = useState(false); const refresh = async () => { const db = await getDb(); @@ -41,7 +42,16 @@ export default function Exercises({ return ( - + { + setRefresing(true); + await refresh(); + setRefresing(false); + }} + renderItem={renderItem} + data={exercises} + /> ); } diff --git a/Home.tsx b/Home.tsx index eeff3a2..e752470 100644 --- a/Home.tsx +++ b/Home.tsx @@ -1,4 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; import { FlatList, @@ -8,7 +9,7 @@ import { View, } from 'react-native'; import {AnimatedFAB, Searchbar} from 'react-native-paper'; -import {getDb} from './db'; +import {getSets} from './db'; import EditSet from './EditSet'; import Set from './set'; @@ -22,12 +23,13 @@ export default function Home() { const [offset, setOffset] = useState(0); const [showEdit, setShowEdit] = useState(false); const [search, setSearch] = useState(''); + const [refreshing, setRefresing] = useState(false); + const navigation = useNavigation(); const refresh = async () => { - const db = await getDb(); - const [result] = await db.executeSql( - `SELECT * from sets WHERE name LIKE ? ORDER BY created DESC LIMIT ? OFFSET ?`, - [`%${search}%`, limit, 0], + setRefresing(true); + const [result] = await getSets({search, limit, offset: 0}).finally(() => + setRefresing(false), ); if (!result) return setSets([]); setSets(result.rows.raw()); @@ -38,6 +40,8 @@ export default function Home() { refresh(); }, [search]); + useEffect(() => navigation.addListener('focus', refresh), [navigation]); + const renderItem = ({item}: {item: Set}) => ( { + setRefresing(true); const newOffset = offset + limit; - const db = await getDb(); - const [result] = await db.executeSql( - `SELECT * from sets WHERE name LIKE ? LIMIT ? OFFSET ?`, - [`%${search}%`, limit, newOffset], + const [result] = await getSets({search, limit, offset: newOffset}).finally( + () => setRefresing(false), ); if (result.rows.length === 0) return; if (!sets) return; - if (sets.filter(set => set.id === result.rows.item(0).id)) return; setSets([...sets, ...result.rows.raw()]); setOffset(newOffset); }; @@ -76,19 +78,21 @@ export default function Home() { set.id.toString()} - onScrollEndDrag={next} + onEndReached={next} + refreshing={refreshing} + onRefresh={refresh} /> setId(undefined)} id={id} show={showEdit} setShow={setShowEdit} onSave={save} - onRemove={refresh} /> diff --git a/Settings.tsx b/Settings.tsx index 89f97f5..2bf7205 100644 --- a/Settings.tsx +++ b/Settings.tsx @@ -2,7 +2,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import React, {useEffect, useState} from 'react'; import {NativeModules, StyleSheet, Text, View} from 'react-native'; -import {Button, Switch, TextInput} from 'react-native-paper'; +import {Button, Snackbar, Switch, TextInput} from 'react-native-paper'; import {RootStackParamList} from './App'; import {getDb} from './db'; @@ -12,6 +12,7 @@ export default function Settings({ const [minutes, setMinutes] = useState(''); const [seconds, setSeconds] = useState(''); const [alarmEnabled, setAlarmEnabled] = useState(true); + const [snackbar, setSnackbar] = useState(''); useEffect(() => { (async () => { @@ -28,6 +29,8 @@ export default function Settings({ }, [minutes, seconds, alarmEnabled]); const clear = async () => { + setSnackbar('Deleting all data...'); + setTimeout(() => setSnackbar(''), 5000); const db = await getDb(); await db.executeSql(`DELETE FROM sets`); }; @@ -36,6 +39,10 @@ export default function Settings({ NativeModules.ExportModule.sets(); }; + const importSets = () => { + NativeModules.ImportModule.sets(); + }; + return ( - Download + Export + + + + setSnackbar('')} + action={{label: 'Close', onPress: () => setSnackbar('')}}> + {snackbar} + ); } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 23a7fdc..dc4086e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,10 +30,12 @@ - + + + diff --git a/android/app/src/main/java/com/massive/AlarmService.kt b/android/app/src/main/java/com/massive/AlarmService.kt index 602da58..d2505da 100644 --- a/android/app/src/main/java/com/massive/AlarmService.kt +++ b/android/app/src/main/java/com/massive/AlarmService.kt @@ -17,6 +17,10 @@ class AlarmService : Service(), OnPreparedListener { @RequiresApi(api = Build.VERSION_CODES.O) override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + if (intent.action == "stop") { + onDestroy() + return START_STICKY + } mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon) mediaPlayer?.start() mediaPlayer?.setOnCompletionListener { vibrator?.cancel() } diff --git a/android/app/src/main/java/com/massive/ExportActivity.kt b/android/app/src/main/java/com/massive/ExportActivity.kt index 1a93193..302de12 100644 --- a/android/app/src/main/java/com/massive/ExportActivity.kt +++ b/android/app/src/main/java/com/massive/ExportActivity.kt @@ -2,21 +2,13 @@ package com.massive import android.annotation.SuppressLint import android.app.Activity -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.content.Context import android.content.Intent import android.database.Cursor import android.net.Uri -import android.os.Build import android.os.Bundle -import android.os.Environment -import android.provider.DocumentsContract import android.provider.OpenableColumns import android.util.Log -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import java.io.* @@ -24,10 +16,8 @@ class ExportActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d("ExportActivity", "Started ExportActivity.") - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) type = "text/csv" putExtra(Intent.EXTRA_TITLE, "sets.csv") } @@ -61,7 +51,6 @@ class ExportActivity : Activity() { } } } - } @Throws(IOException::class) @@ -111,13 +100,6 @@ class ExportActivity : Activity() { companion object { - private const val CHANNEL_ID = "Exports" - private const val NOTIFICATION_ID = 2 - - // Request code for selecting a PDF document. - const val PICK_PDF_FILE = 2 - - // Request code for creating a PDF document. const val CREATE_FILE = 1 } } \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/ExportModule.kt b/android/app/src/main/java/com/massive/ExportModule.kt index 7ba4828..63404fe 100644 --- a/android/app/src/main/java/com/massive/ExportModule.kt +++ b/android/app/src/main/java/com/massive/ExportModule.kt @@ -18,6 +18,7 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import java.io.File +import java.io.FileReader import java.io.FileWriter import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -40,7 +41,7 @@ class ExportModule internal constructor(context: ReactApplicationContext?) : val file = File(dir, "sets-$formatted.csv") file.createNewFile() val writer = FileWriter(file) - writer.write("id,name,reps,weight,created,unit\n") + writer.appendLine("id,name,reps,weight,created,unit") val db = MassiveHelper(reactApplicationContext).readableDatabase db.use { with(it.query("sets", null, null, null, null, null, null)) { @@ -51,7 +52,7 @@ class ExportModule internal constructor(context: ReactApplicationContext?) : val weight = getInt(getColumnIndex("weight")) val created = getString(getColumnIndex("created")) val unit = getString(getColumnIndex("unit")) - writer.appendLine("$id,$name,$reps,$weight,$created,$unit\n") + writer.appendLine("$id,$name,$reps,$weight,$created,$unit") } } } diff --git a/android/app/src/main/java/com/massive/ImportActivity.kt b/android/app/src/main/java/com/massive/ImportActivity.kt new file mode 100644 index 0000000..9117704 --- /dev/null +++ b/android/app/src/main/java/com/massive/ImportActivity.kt @@ -0,0 +1,59 @@ +package com.massive + +import android.annotation.SuppressLint +import android.app.* +import android.content.ContentValues +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.annotation.RequiresApi +import java.io.* + +class ImportActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Log.d("ImportActivity", "Started ImportActivity.") + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "*/*" + } + startActivityForResult(intent, OPEN_FILE, null) + } + + @RequiresApi(Build.VERSION_CODES.M) + @SuppressLint("Range") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + Log.d("ImportActivity", "Got activity result: requestCode=$requestCode,resultCode=$resultCode") + val db = MassiveHelper(applicationContext).readableDatabase + data?.data?.also { uri -> + contentResolver.openInputStream(uri)?.use { inputStream -> + BufferedReader(InputStreamReader(inputStream)).use { reader -> + reader.readLine() + var line: String? = reader.readLine() + while (line != null) { + Log.d("ImportActivity", "line: $line") + val split = line.split(",") + if (split.isEmpty()) continue + val set = ContentValues().apply { + put("name", split[1]) + put("reps", split[2]) + put("weight", split[3]) + put("created", split[4]) + put("unit", split[5]) + } + db.insert("sets", null, set) + line = reader.readLine() + } + } + } + } + val mainIntent = Intent(applicationContext, MainActivity::class.java) + mainIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + applicationContext.startActivity(mainIntent) + } + + companion object { + const val OPEN_FILE = 1 + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/ImportModule.kt b/android/app/src/main/java/com/massive/ImportModule.kt new file mode 100644 index 0000000..fe9cf98 --- /dev/null +++ b/android/app/src/main/java/com/massive/ImportModule.kt @@ -0,0 +1,20 @@ +package com.massive + +import android.content.Intent +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod + +class ImportModule internal constructor(context: ReactApplicationContext?) : + ReactContextBaseJavaModule(context) { + override fun getName(): String { + return "ImportModule" + } + + @ReactMethod() + fun sets() { + val intent = Intent(reactApplicationContext, ImportActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + reactApplicationContext.startActivity(intent) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/MassivePackage.kt b/android/app/src/main/java/com/massive/MassivePackage.kt index 70049dc..ee27580 100644 --- a/android/app/src/main/java/com/massive/MassivePackage.kt +++ b/android/app/src/main/java/com/massive/MassivePackage.kt @@ -18,6 +18,7 @@ class MassivePackage : ReactPackage { val modules: MutableList = ArrayList() modules.add(AlarmModule(reactContext)) modules.add(ExportModule(reactContext)) + modules.add(ImportModule(reactContext)) return modules } } \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/AlarmActivity.kt b/android/app/src/main/java/com/massive/StopAlarm.kt similarity index 96% rename from android/app/src/main/java/com/massive/AlarmActivity.kt rename to android/app/src/main/java/com/massive/StopAlarm.kt index 9a89ec9..d4696a7 100644 --- a/android/app/src/main/java/com/massive/AlarmActivity.kt +++ b/android/app/src/main/java/com/massive/StopAlarm.kt @@ -13,7 +13,7 @@ import androidx.annotation.RequiresApi import com.massive.AlarmService import com.massive.MainActivity -class AlarmActivity : Activity() { +class StopAlarm : Activity() { @RequiresApi(Build.VERSION_CODES.O_MR1) override fun onCreate(savedInstanceState: Bundle?) { Log.d("AlarmActivity", "Call to AlarmActivity") diff --git a/android/app/src/main/java/com/massive/StopTimer.kt b/android/app/src/main/java/com/massive/StopTimer.kt index 92811a8..6a4011b 100644 --- a/android/app/src/main/java/com/massive/StopTimer.kt +++ b/android/app/src/main/java/com/massive/StopTimer.kt @@ -3,6 +3,7 @@ package com.massive import android.app.Service import android.content.Intent import android.os.IBinder +import android.util.Log class StopTimer : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { diff --git a/android/app/src/main/java/com/massive/TimerBroadcast.kt b/android/app/src/main/java/com/massive/TimerBroadcast.kt new file mode 100644 index 0000000..35e6b4b --- /dev/null +++ b/android/app/src/main/java/com/massive/TimerBroadcast.kt @@ -0,0 +1,90 @@ +package com.massive + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import kotlin.math.floor + +class TimerBroadcast : BroadcastReceiver() { + @RequiresApi(api = Build.VERSION_CODES.O) + override fun onReceive(context: Context, intent: Intent) { + val notificationManager = getManager(context) + val builder = getBuilder(context) + if (intent.action == "tick") { + val endMs = intent.extras!!.getInt("endMs") + val currentMs = intent.extras!!.getLong("currentMs") + val seconds = floor((currentMs / 1000).toDouble() % 60) + .toInt().toString().padStart(2, '0') + val minutes = floor((currentMs / 1000).toDouble() / 60) + .toInt().toString().padStart(2, '0') + builder.setContentText("$minutes:$seconds") + .setAutoCancel(false) + .setDefaults(0) + .setProgress(endMs, currentMs.toInt(), false) + .setCategory(NotificationCompat.CATEGORY_PROGRESS) + .priority = NotificationCompat.PRIORITY_LOW + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } + else if (intent.action === "finish") { + Log.d("TimerBroadcast", "Finishing...") + val finishIntent = Intent(context, StopAlarm::class.java) + val finishPending = + PendingIntent.getActivity(context, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE) + builder.setContentText("Timer finished.") + .clearActions() + .setAutoCancel(true) + .setOngoing(false) + .setContentIntent(finishPending) + .setCategory(NotificationCompat.CATEGORY_ALARM) + .priority = NotificationCompat.PRIORITY_HIGH + notificationManager.notify(NOTIFICATION_ID, builder.build()) + context.startService(Intent(context, AlarmService::class.java)) + } + else if (intent.action === "stop") { + notificationManager.cancel(NOTIFICATION_ID) + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun getBuilder(context: Context): NotificationCompat.Builder { + val contentIntent = Intent(context, MainActivity::class.java) + val pendingContent = + PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE) + val actionIntent = Intent(context, StopTimer::class.java) + val pendingAction = + PendingIntent.getService(context, 0, actionIntent, PendingIntent.FLAG_IMMUTABLE) + return NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24) + .setContentTitle("Resting") + .setContentIntent(pendingContent) + .addAction(R.drawable.ic_baseline_stop_24, "STOP", pendingAction) + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun getManager(context: Context): NotificationManager { + val importance = NotificationManager.IMPORTANCE_LOW + val channel = NotificationChannel( + CHANNEL_ID, + CHANNEL_ID, importance + ) + channel.description = "Alarms for rest timings." + val notificationManager = context.getSystemService( + NotificationManager::class.java + ) + notificationManager.createNotificationChannel(channel) + return notificationManager + } + + companion object { + private const val CHANNEL_ID = "MassiveTimer" + private const val NOTIFICATION_ID = 1 + } +} + diff --git a/android/app/src/main/java/com/massive/TimerService.kt b/android/app/src/main/java/com/massive/TimerService.kt index 74bf02e..61909c9 100644 --- a/android/app/src/main/java/com/massive/TimerService.kt +++ b/android/app/src/main/java/com/massive/TimerService.kt @@ -1,8 +1,5 @@ package com.massive -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.app.Service import android.content.Intent import android.os.Build @@ -10,68 +7,30 @@ import android.os.CountDownTimer import android.os.IBinder import android.util.Log import androidx.annotation.RequiresApi -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import kotlin.math.floor class TimerService : Service() { - private lateinit var notificationManager: NotificationManagerCompat - private lateinit var countdownTimer: CountDownTimer + private var countdownTimer: CountDownTimer? = null @RequiresApi(Build.VERSION_CODES.M) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.d("TimerService", "Started timer service.") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val importance = NotificationManager.IMPORTANCE_LOW - val channel = NotificationChannel(CHANNEL_ID, CHANNEL_ID, importance) - channel.description = "Alarms for rest timings." - val notificationManager = applicationContext.getSystemService( - NotificationManager::class.java - ) - notificationManager.createNotificationChannel(channel) - } - - val contentIntent = Intent(applicationContext, MainActivity::class.java) - val pendingContent = - PendingIntent.getActivity(applicationContext, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE) - val actionIntent = Intent(applicationContext, StopTimer::class.java) - val pendingAction = - PendingIntent.getService(applicationContext, 0, actionIntent, PendingIntent.FLAG_IMMUTABLE) - val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24) - .setContentTitle("Resting") - .setContentIntent(pendingContent) - .addAction(R.drawable.ic_baseline_stop_24, "STOP", pendingAction) - val endMs = intent!!.extras!!.getInt("milliseconds") - notificationManager = NotificationManagerCompat.from(applicationContext) + countdownTimer?.cancel() countdownTimer = object : CountDownTimer(endMs.toLong(), 1000) { override fun onTick(currentMs: Long) { - val seconds = floor((currentMs / 1000).toDouble() % 60) - .toInt().toString().padStart(2, '0') - val minutes = floor((currentMs / 1000).toDouble() / 60) - .toInt().toString().padStart(2, '0') - builder.setContentText("$minutes:$seconds") - .setAutoCancel(false) - .setDefaults(0) - .setProgress(endMs, currentMs.toInt(), false) - .setCategory(NotificationCompat.CATEGORY_PROGRESS) - .priority = NotificationCompat.PRIORITY_LOW - notificationManager.notify(ALARM_ID, builder.build()) + val broadcastIntent = Intent(applicationContext, TimerBroadcast::class.java) + broadcastIntent.putExtra("endMs", endMs) + broadcastIntent.putExtra("currentMs", currentMs) + broadcastIntent.action = "tick" + sendBroadcast(broadcastIntent) } override fun onFinish() { - builder.setContentText("Timer finished.") - .clearActions() - .setAutoCancel(true) - .setOngoing(false) - .setCategory(NotificationCompat.CATEGORY_ALARM) - .priority = NotificationCompat.PRIORITY_HIGH - notificationManager.notify(ALARM_ID, builder.build()) - applicationContext.startService(Intent(applicationContext, AlarmService::class.java)) + val broadcastIntent = Intent(applicationContext, TimerBroadcast::class.java) + broadcastIntent.action = "finish" + sendBroadcast(broadcastIntent) } } - - countdownTimer.start() + countdownTimer!!.start() return super.onStartCommand(intent, flags, startId) } @@ -80,13 +39,11 @@ class TimerService : Service() { } override fun onDestroy() { - countdownTimer.cancel() - notificationManager.cancel(ALARM_ID) + Log.d("TimerService", "Destroying...") + countdownTimer?.cancel() + val broadcastIntent = Intent(applicationContext, TimerBroadcast::class.java) + broadcastIntent.action = "stop" + sendBroadcast(broadcastIntent) super.onDestroy() } - - companion object { - private const val CHANNEL_ID = "Alarms" - private const val ALARM_ID = 1 - } } \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_baseline_arrow_upward_24.xml b/android/app/src/main/res/drawable/ic_baseline_arrow_upward_24.xml new file mode 100644 index 0000000..46d5d58 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_baseline_arrow_upward_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/db.ts b/db.ts index 82d11c6..8d93e07 100644 --- a/db.ts +++ b/db.ts @@ -15,3 +15,20 @@ const schema = ` `; export const setupSchema = () => getDb().then(db => db.executeSql(schema)); + +const select = ` + SELECT * from sets + WHERE name LIKE ? + ORDER BY created DESC + LIMIT ? OFFSET ? +`; + +export const getSets = ({ + search, + limit, + offset, +}: { + search: string; + limit: number; + offset: number; +}) => getDb().then(db => db.executeSql(select, [`%${search}%`, limit, offset])); diff --git a/package.json b/package.json index 076c64a..559b840 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,11 @@ "@react-navigation/native-stack": "^6.6.2", "@types/react-native-sqlite-storage": "^5.0.2", "@types/react-native-vector-icons": "^6.4.11", + "date-fns": "^2.28.0", "react": "18.0.0", "react-devtools": "^4.24.7", "react-native": "0.69.1", + "react-native-document-picker": "^8.1.1", "react-native-gesture-handler": "^2.5.0", "react-native-pager-view": "^5.4.24", "react-native-paper": "^4.12.2", diff --git a/yarn.lock b/yarn.lock index 790fc8e..b2fbeac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3035,6 +3035,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns@^2.28.0: + version "2.28.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" + integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== + dayjs@^1.8.15: version "1.11.3" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.3.tgz#4754eb694a624057b9ad2224b67b15d552589258" @@ -6632,6 +6637,13 @@ react-native-codegen@^0.69.1: jscodeshift "^0.13.1" nullthrows "^1.1.1" +react-native-document-picker@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/react-native-document-picker/-/react-native-document-picker-8.1.1.tgz#642bbe25752cc428b96416318f8dc07cef29ee10" + integrity sha512-mH0oghd7ndgU9/1meVJdqts1sAkOfUQW1qbrqTTsvR5f2K9r0BAj/X02dve5IBMOMZvlGd7qWrNVuIFg5AUXWg== + dependencies: + invariant "^2.2.4" + react-native-gesture-handler@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.5.0.tgz#61385583570ed0a45a9ed142425e35f8fe8274fb"