From b4db41e762adec6c6316e70b804d92704ed2bcc2 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Tue, 24 May 2022 22:37:19 +0430 Subject: [PATCH 01/17] fix the screen scroll issue --- app/components/screen/screen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/screen/screen.tsx b/app/components/screen/screen.tsx index 447e236c..1501033d 100644 --- a/app/components/screen/screen.tsx +++ b/app/components/screen/screen.tsx @@ -42,7 +42,7 @@ function ScreenWithScrolling(props: ScreenProps) { From 533fea22ca719be4fa160f949a9aac0d801d6bc4 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Tue, 24 May 2022 22:38:20 +0430 Subject: [PATCH 02/17] add box entity in realmdb --- app/realmdb/entities/box.ts | 19 ++++++ app/realmdb/entities/index.ts | 3 +- app/realmdb/realm.ts | 8 +-- app/realmdb/schemas/box.ts | 22 +++++++ app/realmdb/schemas/index.ts | 3 +- app/services/localdb/boxs.ts | 106 ++++++++++++++++++++++++++++++++++ app/services/localdb/index.ts | 3 +- 7 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 app/realmdb/entities/box.ts create mode 100644 app/realmdb/schemas/box.ts create mode 100644 app/services/localdb/boxs.ts diff --git a/app/realmdb/entities/box.ts b/app/realmdb/entities/box.ts new file mode 100644 index 00000000..2c5a99e0 --- /dev/null +++ b/app/realmdb/entities/box.ts @@ -0,0 +1,19 @@ +export type BoxEntity = { + /** + * Box peerId + */ + peerId: string + /** + * box nickname. + */ + name: string | undefined + /** + * Box address + */ + address: string | undefined + + /** + * Determin the default device for connectio + */ + isDefault: boolean +} diff --git a/app/realmdb/entities/index.ts b/app/realmdb/entities/index.ts index 946a5dd1..61fa2a39 100644 --- a/app/realmdb/entities/index.ts +++ b/app/realmdb/entities/index.ts @@ -1 +1,2 @@ -export * from './asset'; \ No newline at end of file +export * from "./asset" +export * from "./box" diff --git a/app/realmdb/realm.ts b/app/realmdb/realm.ts index eeb1770d..dfbc08c6 100644 --- a/app/realmdb/realm.ts +++ b/app/realmdb/realm.ts @@ -1,10 +1,10 @@ import Realm from "realm" -import { Asset } from "./schemas" +import { Asset, Box } from "./schemas" export const RealmDB = async () => { return await Realm.open({ - schema: [Asset], - schemaVersion: 1, - deleteRealmIfMigrationNeeded:true + schema: [Asset, Box], + schemaVersion: 2, + deleteRealmIfMigrationNeeded: true, }) } diff --git a/app/realmdb/schemas/box.ts b/app/realmdb/schemas/box.ts new file mode 100644 index 00000000..b92bbbac --- /dev/null +++ b/app/realmdb/schemas/box.ts @@ -0,0 +1,22 @@ +export const Box = { + name: "Box", + primaryKey: "peerId", + properties: { + /** + * Box peerId + */ + peerId: "string", + /** + * Filename of the asset. + */ + name: "string?", + /** + * URI that points to the asset. `assets://*` (iOS), `file://*` (Android) + */ + address: "string?", + /** + * Determin the default device for connectio + */ + isDefault: { type: "bool", default: false }, + }, +} diff --git a/app/realmdb/schemas/index.ts b/app/realmdb/schemas/index.ts index 946a5dd1..c824583d 100644 --- a/app/realmdb/schemas/index.ts +++ b/app/realmdb/schemas/index.ts @@ -1 +1,2 @@ -export * from './asset'; \ No newline at end of file +export * from './asset'; +export * from './box'; \ No newline at end of file diff --git a/app/services/localdb/boxs.ts b/app/services/localdb/boxs.ts new file mode 100644 index 00000000..349e4287 --- /dev/null +++ b/app/services/localdb/boxs.ts @@ -0,0 +1,106 @@ +import { Entities, RealmDB, Schemas } from "../../realmdb" + +export const getAll = (): Promise> => { + return RealmDB() + .then((realm) => { + const boxs = realm + .objects(Schemas.Box.name) + return boxs + }) + .catch((error) => { + console.error("RealmDB getAll Boxs error!", error) + throw error + }) +} + +export const removeAll = (): Promise => { + return RealmDB() + .then((realm) => { + return realm.write(() => { + + const boxs = realm + .objects(Schemas.Box.name); + + // Delete all instances of Assets from the realm. + return realm.delete(boxs); + }); + + }) + .catch((error) => { + console.error("RealmDB removeAll Boxs error!", error) + throw error + }) +} + +export const addOrUpdate = (boxs: Entities.BoxEntity[]): Promise => { + return RealmDB() + .then((realm) => { + try { + const result = [] + realm.write(() => { + for (const box of boxs) { + result.push( + realm.create( + Schemas.Box.name, + { + ...box, + }, + Realm.UpdateMode.Modified, + ), + ) + } + return result + }) + } catch (error) { + console.error("addOrUpdate Box error!", error) + throw error + } + }) + .catch((error) => { + console.error("RealmDB addOrUpdateAssets error!", error) + throw error + }) +} + +export const remove = (peerIds: string[]): Promise => { + return RealmDB() + .then((realm) => { + try { + const idsQuery = peerIds.map(id => `peerId = '${id}'`).join(' OR '); + const boxs = realm + .objects(Schemas.Box.name) + .filtered(idsQuery) + realm.write(() => { + realm.delete(boxs) + }); + } catch (error) { + console.error("remove Boxs error!", error) + throw error + } + }) + .catch((error) => { + console.error("RealmDB remove Boxs error!", error) + throw error + }) +} + +export const removeByAddress = (address:string): Promise => { + return RealmDB() + .then((realm) => { + try { + const boxs = realm + .objects(Schemas.Box.name) + .filtered(`address endsWith '${address}'`) + realm.write(() => { + realm.delete(boxs) + }); + } catch (error) { + console.error("removeByAddress box error!", error) + throw error + } + }) + .catch((error) => { + console.error("RealmDB removeByAddress box error!", error) + throw error + }) +} diff --git a/app/services/localdb/index.ts b/app/services/localdb/index.ts index 634f6bb0..1520c182 100644 --- a/app/services/localdb/index.ts +++ b/app/services/localdb/index.ts @@ -1 +1,2 @@ -export * as Assets from './assets' \ No newline at end of file +export * as Assets from './assets' +export * as Boxs from './boxs' \ No newline at end of file From c71974a3d4b1d6eb2e5c89df5d24310f477ff169 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Tue, 24 May 2022 22:38:58 +0430 Subject: [PATCH 03/17] Add box list screen Add box addUpdate screen. --- app/navigators/app-navigator.tsx | 14 +++- app/screens/asset-list/asset-list-screen.tsx | 4 + app/screens/box/box-add-update-screen.tsx | 58 +++++++++++++ app/screens/box/box-list-screen.tsx | 86 ++++++++++++++++++++ app/screens/index.ts | 2 + 5 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 app/screens/box/box-add-update-screen.tsx create mode 100644 app/screens/box/box-list-screen.tsx diff --git a/app/navigators/app-navigator.tsx b/app/navigators/app-navigator.tsx index c29d16fd..f85300c5 100644 --- a/app/navigators/app-navigator.tsx +++ b/app/navigators/app-navigator.tsx @@ -5,7 +5,7 @@ import { NavigationContainer } from "@react-navigation/native" import { createSharedElementStackNavigator } from "react-navigation-shared-element" import { navigationRef } from "./navigation-utilities" -import { PhotoScreen, LibraryAssetsScreen } from "../screens" +import { PhotoScreen, LibraryAssetsScreen, BoxListScreen, BoxAddUpdateScreen } from "../screens" import { HomeNavigator } from "./home-navigator" import { ThemeContext } from '../theme'; enableScreens() @@ -17,7 +17,9 @@ export type NavigatorParamList = { export enum AppNavigationNames { HomeScreen = "home", PhotoScreen = "photo", - LibraryAssets = "LibraryAssets" + LibraryAssets = "LibraryAssets", + BoxList = "BoxList", + BoxAddUpdate = "BoxAddUpdate" } const Stack = createSharedElementStackNavigator() @@ -38,6 +40,14 @@ const AppStack = () => { name={AppNavigationNames.LibraryAssets} component={LibraryAssetsScreen} /> + + ; medias: Asset[]; @@ -104,6 +105,9 @@ export const AssetListScreen: React.FC = ({ navigation, medias, defaultHe toggleTheme() }} /> } + rightComponent={ + navigation.navigate(AppNavigationNames.BoxList)} /> + } />) } } diff --git a/app/screens/box/box-add-update-screen.tsx b/app/screens/box/box-add-update-screen.tsx new file mode 100644 index 00000000..a89a491b --- /dev/null +++ b/app/screens/box/box-add-update-screen.tsx @@ -0,0 +1,58 @@ +import React from "react" +import { StyleProp, StyleSheet, View, ViewStyle } from "react-native" +import { Icon, Input, Text } from "@rneui/themed" +import Animated from "react-native-reanimated" + +import { NativeStackNavigationProp } from "@react-navigation/native-stack" +import { HomeNavigationParamList, HomeNavigationTypes } from "../../navigators/home-navigator" +import { Header, HeaderLeftContainer, HeaderArrowBack } from "../../components/header" +import { StyleProps } from "react-native-reanimated" +import { Screen } from "../../components" + +interface Props { + navigation: NativeStackNavigationProp +} + +export const BoxAddUpdateScreen: React.FC = ({ navigation }) => { + const renderHeader = (style?: StyleProp>>) => { + return (
Add box} + leftComponent={} + />) + } + return ( + + {renderHeader()} + <> + { + console.log("onKeyPress:", e.nativeEvent.text) + }} + placeholder='Box name' + leftIcon={{ type: 'material-community', name: 'alpha-f-box-outline' }} + + /> + + + + + ) +} + +const styles = StyleSheet.create({ + screen: { + justifyContent: "center", + paddingTop: 120 + } +}) \ No newline at end of file diff --git a/app/screens/box/box-list-screen.tsx b/app/screens/box/box-list-screen.tsx new file mode 100644 index 00000000..9a09f4df --- /dev/null +++ b/app/screens/box/box-list-screen.tsx @@ -0,0 +1,86 @@ +import React, { useEffect, useState, } from "react" +import { StyleSheet, View, ListRenderItemInfo, Pressable, Image } from "react-native" +import { useRecoilState } from "recoil" +import { Icon, ListItem, Text, useTheme } from "@rneui/themed" + +import { Screen } from "../../components" +import { Boxs } from "../../services/localdb" +import { NativeStackNavigationProp } from "@react-navigation/native-stack" +import { HomeNavigationParamList, HomeNavigationTypes } from "../../navigators/home-navigator" +import { Header, HeaderArrowBack, HeaderLogo, HeaderRightContainer } from "../../components/header" +import { FlatList } from "react-native-gesture-handler" +import { AppNavigationNames } from "../../navigators" +import { BoxEntity } from "../../realmdb/entities" +interface Props { + navigation: NativeStackNavigationProp +} +export const BoxListScreen: React.FC = ({ navigation }) => { + const [boxs, setBoxs] = useState([]) + + useEffect(() => { + loadBoxs() + }, []); + + const loadBoxs = async () => { + const boxs = await Boxs.getAll(); + setBoxs(boxs); + } + const renderHeader = () => { + return (
Boxs} + leftComponent={} + rightComponent={ + navigation.navigate(AppNavigationNames.BoxAddUpdate)} /> + } + />) + } + + const onItemPress = (box: BoxEntity) => { + navigation.push(AppNavigationNames.BoxAddUpdate) + } + + const renderItem = (box: BoxEntity) => { + return onItemPress(box)}> + + {box.name || "No name"} + {box.address} + + + } + return ( + + {renderHeader()} + {} + + ) +} + +const styles = StyleSheet.create({ + screen: { + flex: 1, + justifyContent: "center", + }, + listContainer: { + flex: 1 + }, + card: { + flex: 1, + padding: 10 + }, + cardImage: { + aspectRatio: 1, + width: '100%', + flex: 1, + marginBottom: 8, + borderRadius: 15 + }, +}) \ No newline at end of file diff --git a/app/screens/index.ts b/app/screens/index.ts index a37fe19c..43c87c30 100644 --- a/app/screens/index.ts +++ b/app/screens/index.ts @@ -2,6 +2,8 @@ export * from "./home/home-screen" export * from "./library/library-screen" export * from "./library/library-assets-screen" export * from "./asset-list/asset-list-screen" +export * from "./box/box-list-screen" +export * from "./box/box-add-update-screen" export * from "./error/error-boundary" export * from "./photo" // export other screens here From 4f39c4d3465a7f95db87fa3f4412494ee1a59e1b Mon Sep 17 00:00:00 2001 From: Mati Q Date: Wed, 25 May 2022 16:15:49 +0430 Subject: [PATCH 04/17] add/update/remove box are done. --- app/components/screen/screen.tsx | 4 +- app/navigators/app-navigator.tsx | 17 +++-- app/screens/box/box-add-update-screen.tsx | 90 +++++++++++++++++++---- app/screens/box/box-list-screen.tsx | 55 ++++++++++---- app/services/localdb/boxs.ts | 4 +- app/store/index.ts | 6 ++ 6 files changed, 134 insertions(+), 42 deletions(-) diff --git a/app/components/screen/screen.tsx b/app/components/screen/screen.tsx index 1501033d..d314657e 100644 --- a/app/components/screen/screen.tsx +++ b/app/components/screen/screen.tsx @@ -21,7 +21,7 @@ function ScreenWithoutScrolling(props: ScreenProps) { keyboardVerticalOffset={offsets[props.keyboardOffset || "none"]} > - {props.children} + {props.children} ) } @@ -42,7 +42,7 @@ function ScreenWithScrolling(props: ScreenProps) { diff --git a/app/navigators/app-navigator.tsx b/app/navigators/app-navigator.tsx index f85300c5..f6aec035 100644 --- a/app/navigators/app-navigator.tsx +++ b/app/navigators/app-navigator.tsx @@ -8,20 +8,23 @@ import { navigationRef } from "./navigation-utilities" import { PhotoScreen, LibraryAssetsScreen, BoxListScreen, BoxAddUpdateScreen } from "../screens" import { HomeNavigator } from "./home-navigator" import { ThemeContext } from '../theme'; +import { BoxEntity } from "../realmdb/entities" enableScreens() -export type NavigatorParamList = { - home: undefined - photo: { section: RecyclerAssetListSection } - settings: undefined +export type RootStackParamList = { + Home: undefined + Photo: { section: RecyclerAssetListSection } + Settings: undefined, + BoxList: undefined, + BoxAddUpdate: { box: BoxEntity } } export enum AppNavigationNames { - HomeScreen = "home", - PhotoScreen = "photo", + HomeScreen = "Home", + PhotoScreen = "Photo", LibraryAssets = "LibraryAssets", BoxList = "BoxList", BoxAddUpdate = "BoxAddUpdate" } -const Stack = createSharedElementStackNavigator() +const Stack = createSharedElementStackNavigator() const AppStack = () => { return ( diff --git a/app/screens/box/box-add-update-screen.tsx b/app/screens/box/box-add-update-screen.tsx index a89a491b..5c4d2ccb 100644 --- a/app/screens/box/box-add-update-screen.tsx +++ b/app/screens/box/box-add-update-screen.tsx @@ -1,26 +1,70 @@ -import React from "react" -import { StyleProp, StyleSheet, View, ViewStyle } from "react-native" +import React, { useEffect, useRef, useState } from "react" +import { Alert, StyleProp, StyleSheet, View, ViewStyle } from "react-native" import { Icon, Input, Text } from "@rneui/themed" import Animated from "react-native-reanimated" -import { NativeStackNavigationProp } from "@react-navigation/native-stack" +import { NativeStackNavigationProp, NativeStackScreenProps } from "@react-navigation/native-stack" import { HomeNavigationParamList, HomeNavigationTypes } from "../../navigators/home-navigator" import { Header, HeaderLeftContainer, HeaderArrowBack } from "../../components/header" import { StyleProps } from "react-native-reanimated" import { Screen } from "../../components" +import { Boxs } from "../../services/localdb" +import { BoxEntity } from "../../realmdb/entities" +import { RootStackParamList, AppNavigationNames } from "../../navigators" -interface Props { - navigation: NativeStackNavigationProp +type Props = NativeStackScreenProps; +interface AddUpdateForm { + peerId?: string | undefined + name: string | undefined + address: string | undefined } +export const BoxAddUpdateScreen: React.FC = ({ navigation, route }) => { -export const BoxAddUpdateScreen: React.FC = ({ navigation }) => { - const renderHeader = (style?: StyleProp>>) => { + const pressed = useRef(false) + const [form, setForm] = useState(null) + useEffect(() => { + if (route.params?.box?.peerId) { + setForm({ + peerId: route.params?.box?.peerId, + name: route.params?.box?.name, + address: route.params?.box?.address + }) + } else { + setForm({ + peerId: undefined, + name: "", + address: "" + }) + } + }, []) + const renderHeader = () => { return (
Add box} + centerComponent={{form && form.peerId?"Edit box":"Add box"}} leftComponent={} + rightComponent={} />) } + const addUpdate = async () => { + try { + pressed.current = true + if (!form.address || !form.name) { + Alert.alert("Warning", "Please fill all the fields!") + return + } + const box = { + name: form.name, + address: form.address, + peerId: form.peerId + } as BoxEntity + await Boxs.addOrUpdate([box]); + navigation.pop(); + } catch (error) { + console.log(error) + } finally { + pressed.current = false + } + + } return ( = ({ navigation }) => { {renderHeader()} <> { - console.log("onKeyPress:", e.nativeEvent.text) - }} - placeholder='Box name' + placeholder='Choose a nickname for your box' leftIcon={{ type: 'material-community', name: 'alpha-f-box-outline' }} - + containerStyle={{ marginTop: 20 }} + onChangeText={(text) => { + setForm(prev => ({ + ...prev, + name: text + })) + }} + errorProps /> { + setForm(prev => ({ + ...prev, + address: text + })) + }} /> @@ -53,6 +111,6 @@ export const BoxAddUpdateScreen: React.FC = ({ navigation }) => { const styles = StyleSheet.create({ screen: { justifyContent: "center", - paddingTop: 120 + paddingTop: 100 } }) \ No newline at end of file diff --git a/app/screens/box/box-list-screen.tsx b/app/screens/box/box-list-screen.tsx index 9a09f4df..8a154777 100644 --- a/app/screens/box/box-list-screen.tsx +++ b/app/screens/box/box-list-screen.tsx @@ -1,50 +1,73 @@ -import React, { useEffect, useState, } from "react" +import React, { useEffect, useRef, useState, } from "react" import { StyleSheet, View, ListRenderItemInfo, Pressable, Image } from "react-native" import { useRecoilState } from "recoil" import { Icon, ListItem, Text, useTheme } from "@rneui/themed" import { Screen } from "../../components" import { Boxs } from "../../services/localdb" -import { NativeStackNavigationProp } from "@react-navigation/native-stack" +import { boxsState } from "../../store" +import { NativeStackNavigationProp, NativeStackScreenProps } from "@react-navigation/native-stack" import { HomeNavigationParamList, HomeNavigationTypes } from "../../navigators/home-navigator" import { Header, HeaderArrowBack, HeaderLogo, HeaderRightContainer } from "../../components/header" import { FlatList } from "react-native-gesture-handler" -import { AppNavigationNames } from "../../navigators" +import { AppNavigationNames, RootStackParamList } from "../../navigators" import { BoxEntity } from "../../realmdb/entities" -interface Props { - navigation: NativeStackNavigationProp -} + +type Props = NativeStackScreenProps; + export const BoxListScreen: React.FC = ({ navigation }) => { - const [boxs, setBoxs] = useState([]) + const pressed = useRef(false); + const [boxs, setBoxs] = useRecoilState(boxsState) useEffect(() => { - loadBoxs() + navigation.addListener("focus", loadBoxs) + loadBoxs(); + return () => { + navigation.removeListener("focus") + } }, []); const loadBoxs = async () => { const boxs = await Boxs.getAll(); - setBoxs(boxs); + setBoxs(boxs.map(m => m.toJSON())); + } + const deleteBox = async (box: BoxEntity) => { + try { + pressed.current = true; + await Boxs.remove([box.peerId]) + setBoxs(prev => { + return prev.filter(item => item.peerId != box.peerId) + }) + } catch (error) { + console.log(error) + } finally { + pressed.current = false + } } const renderHeader = () => { return (
Boxs} leftComponent={} rightComponent={ - navigation.navigate(AppNavigationNames.BoxAddUpdate)} /> + navigation.navigate(AppNavigationNames.BoxAddUpdate)} /> } />) } const onItemPress = (box: BoxEntity) => { - navigation.push(AppNavigationNames.BoxAddUpdate) + console.log("onItemPress",box) + navigation.navigate(AppNavigationNames.BoxAddUpdate, { + box + }) } - const renderItem = (box: BoxEntity) => { - return onItemPress(box)}> + const renderItem = ({ item }: { item: BoxEntity }) => { + return onItemPress(item)}> - {box.name || "No name"} - {box.address} + {item.name || "No name"} + {item.address} + deleteBox(item)} /> } return ( @@ -66,8 +89,8 @@ export const BoxListScreen: React.FC = ({ navigation }) => { const styles = StyleSheet.create({ screen: { - flex: 1, justifyContent: "center", + paddingTop: 100 }, listContainer: { flex: 1 diff --git a/app/services/localdb/boxs.ts b/app/services/localdb/boxs.ts index 349e4287..97488db1 100644 --- a/app/services/localdb/boxs.ts +++ b/app/services/localdb/boxs.ts @@ -1,5 +1,5 @@ import { Entities, RealmDB, Schemas } from "../../realmdb" - +const { UUID } = Realm.BSON; export const getAll = (): Promise> => { return RealmDB() .then((realm) => { @@ -33,6 +33,7 @@ export const removeAll = (): Promise => { } export const addOrUpdate = (boxs: Entities.BoxEntity[]): Promise => { + return RealmDB() .then((realm) => { try { @@ -44,6 +45,7 @@ export const addOrUpdate = (boxs: Entities.BoxEntity[]): Promise({ @@ -14,4 +15,9 @@ export const recyclerSectionsState = atom({ export const selectedLibraryState = atom({ key: "selectedLibraryState", default: null, +}) + +export const boxsState = atom({ + key: "boxsState", + default: null, }) \ No newline at end of file From f86bcf0f0aa314da1e50fc8d8e1f95ab03ac4ffe Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sat, 28 May 2022 16:16:22 +0430 Subject: [PATCH 05/17] add react-native-fula package. --- package.json | 1 + yarn.lock | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c9d4ec00..4ad71061 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "react": "17.0.2", "react-native": "0.66.3", "react-native-fast-image": "^8.5.11", + "react-native-fula": "^0.2.0", "react-native-gesture-handler": "^2.4.1", "react-native-keychain": "6.2.0", "react-native-reanimated": "^2.8.0", diff --git a/yarn.lock b/yarn.lock index 2b142d5a..fcf6e614 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1196,7 +1196,7 @@ pirates "^4.0.5" source-map-support "^0.5.16" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2", "@babel/runtime@~7.17.2": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== @@ -1638,6 +1638,23 @@ lodash.pick "^4.4.0" lodash.template "^4.5.0" +"@functionland/file-protocol@^0.3.6": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@functionland/file-protocol/-/file-protocol-0.3.6.tgz#655254599b248182052ad53e3840dece8eb84435" + integrity sha512-dt36pB4igjX0wD8VAYPvOhGB7kj8mVQAPfPNxNr6CDB4URRXLbrvzja0UpbOG5+gdzhFrDuG25S9tH3F7GhoWA== + dependencies: + "@babel/runtime" "~7.17.2" + "@protobuf-ts/runtime" "~2.2.2" + aes-js "^3.1.2" + +"@functionland/graph-protocol@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@functionland/graph-protocol/-/graph-protocol-0.2.2.tgz#6620fb389aa030d5f23bdf4747e14965527a1538" + integrity sha512-ege7ou9RoU6YfEfrDe304eCjHCD5dVoItJEdkKMTqbyqA4kYPwt6rVZCpUNWLCnPnlIhJjPRzhRPjwxwPnortA== + dependencies: + "@babel/runtime" "~7.17.2" + "@protobuf-ts/runtime" "~2.2.2" + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -2278,6 +2295,11 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64" integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw== +"@protobuf-ts/runtime@~2.2.2": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime/-/runtime-2.2.4.tgz#820b2f4db8c98f31517a9404be9569999fcc1d9c" + integrity sha512-v6A67t27p56qG2j3Dtag7Lkj6F8VotXtlwCUs9YiL/Sv+Vxp0uGOwSNuBI798YnEozt5NTbmkWszp84j5cSNdQ== + "@reach/router@^1.2.1", "@reach/router@^1.3.3": version "1.3.4" resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c" @@ -3956,6 +3978,11 @@ address@1.1.2, address@^1.0.1: resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== +aes-js@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" + integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== + agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -7785,6 +7812,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastestsmallesttextencoderdecoder@^1.0.22: + version "1.0.22" + resolved "https://registry.yarnpkg.com/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz#59b47e7b965f45258629cc6c127bf783281c5e93" + integrity sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw== + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -13626,6 +13658,15 @@ react-native-flipper@^0.34.0: resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d" integrity sha512-48wgm29HJTOlZ0DibBsvXueEOY0EPIVL0wWKbwRfgrk86+luSEuLW3aZC50oJa95zSFb9qYShTV/6dWqh4Jamg== +react-native-fula@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/react-native-fula/-/react-native-fula-0.2.0.tgz#ac666adaab692e36f6586d708c78bc91b4ca6491" + integrity sha512-TbU/UAXTgqcWM3CdC5YLeqs9Kbl68rCIxMIa+ioRMJUgg/hX4ygDQvRjiuSuhGFDLWjHzB8BmWBeb0nzGhsKHw== + dependencies: + "@functionland/file-protocol" "^0.3.6" + "@functionland/graph-protocol" "^0.2.2" + fastestsmallesttextencoderdecoder "^1.0.22" + react-native-gesture-handler@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.4.1.tgz#f4cb1784b6dcdf41ae35b4fff6022ec21038e2cd" From 8a12b540fa0426ea1c18d39f056d91ff51aa3891 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sat, 28 May 2022 16:37:31 +0430 Subject: [PATCH 06/17] AssetEntity is changed. --- app/realmdb/entities/asset.ts | 4 ++++ app/realmdb/schemas/asset.ts | 4 ++++ app/types/types.ts | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/realmdb/entities/asset.ts b/app/realmdb/entities/asset.ts index 8e3cb720..70572795 100644 --- a/app/realmdb/entities/asset.ts +++ b/app/realmdb/entities/asset.ts @@ -52,4 +52,8 @@ export type AssetEntity = { * content id in the box */ cid: string | undefined + /** + * Deleted from storage + */ + isDeleted: boolean } diff --git a/app/realmdb/schemas/asset.ts b/app/realmdb/schemas/asset.ts index 2e857e5a..dc73fb62 100644 --- a/app/realmdb/schemas/asset.ts +++ b/app/realmdb/schemas/asset.ts @@ -55,5 +55,9 @@ export const Asset = { * content id in the box */ cid: "string?", + /** + * Deleted form storage + */ + isDeleted:{ type: "bool", default: false }, }, } diff --git a/app/types/types.ts b/app/types/types.ts index e8e0c674..63c9221e 100644 --- a/app/types/types.ts +++ b/app/types/types.ts @@ -1,6 +1,6 @@ import { Asset as ExpoAsset } from "expo-media-library" - -export type Asset = ExpoAsset & {} +import { AssetEntity} from "../realmdb/entities" +export type Asset = ExpoAsset & AssetEntity export type AssetStory = { id: string data: Asset[] From 09c536e3a60fb8a8c89088d42e8cd965a29abecd Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sat, 28 May 2022 19:59:05 +0430 Subject: [PATCH 07/17] Add cloud icon on the synced asstes. --- .../asset-items/asset-item.tsx | 33 ++++++-- .../asset-items/recycler-section-item.tsx | 4 +- app/components/photo-screen-header/index.tsx | 7 +- app/screens/asset-list/asset-list-screen.tsx | 5 +- app/screens/home/home-screen.tsx | 7 ++ app/screens/photo/index.tsx | 83 ++++++++++++++++--- app/services/localdb/assets.ts | 40 +++++---- 7 files changed, 138 insertions(+), 41 deletions(-) diff --git a/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx b/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx index f0bbf3ba..b1e60948 100644 --- a/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx +++ b/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx @@ -11,7 +11,8 @@ import { convertDurationToTime } from "../../../../utils/helper" interface Props { asset: Asset selected: boolean - selectionMode: boolean + selectionMode: boolean, + isSynced: boolean, onError?: (error: NativeSyntheticEvent) => void } const AssetItem = (props: Props): JSX.Element => { @@ -39,6 +40,7 @@ const AssetItem = (props: Props): JSX.Element => { scaleSharedValue.value = 1 } }, [selected]) + return ( { {convertDurationToTime(asset?.duration)} } + {asset?.isSynced && + + + } {selectionMode ? : null} ) @@ -94,24 +100,41 @@ const styles = StyleSheet.create({ videoIconContainer: { right: 10, position: "absolute", - top: 10, + bottom: 10, zIndex: 99, flexDirection: "row", justifyContent: "center", - alignItems: "center" + alignItems: "center", + backgroundColor:"white", + borderRadius:5, + opacity:.8 }, videoDurationText: { color: "gray", fontSize: 10, padding: 1 - } + }, + syncIconContainer: { + right: 10, + position: "absolute", + top: 10, + zIndex: 99, + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + backgroundColor:"white", + borderRadius:5, + padding:1, + opacity:.7 + }, }) const areEqual = (prev: Props, next: Props) => { return ( prev?.asset?.id === next?.asset?.id && prev?.selectionMode === next?.selectionMode && - prev?.selected === next?.selected + prev?.selected === next?.selected && + prev?.isSynced === next?.isSynced ) } export default memo(AssetItem, areEqual) diff --git a/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx b/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx index ac8600a8..75bd73f9 100644 --- a/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx +++ b/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx @@ -8,7 +8,7 @@ import HeaderItem from "./header-item" interface Props { section: RecyclerAssetListSection selectionMode: boolean - selected: boolean + selected: boolean, onLongPress: (section: RecyclerAssetListSection) => void onPress: (section: RecyclerAssetListSection) => void onAssetLoadError?: (error: NativeSyntheticEvent) => void @@ -24,7 +24,7 @@ const getSectionByType = ( return } case ViewType.ASSET: { - return + return } case ViewType.MONTH: { const groupHeader: GroupHeader = section.data diff --git a/app/components/photo-screen-header/index.tsx b/app/components/photo-screen-header/index.tsx index baf78a55..5a0b0ce3 100644 --- a/app/components/photo-screen-header/index.tsx +++ b/app/components/photo-screen-header/index.tsx @@ -16,14 +16,19 @@ export const PhotoScreenHeader: React.FC = ({ goBack }) + + + ) } const styles = StyleSheet.create({ container: { + flexDirection:"row", + paddingTop:30, height: Constants.HeaderHeight, - justifyContent: "center", + justifyContent: "space-between", paddingHorizontal: 15, position: "absolute", width: widthPercentageToDP(100), diff --git a/app/screens/asset-list/asset-list-screen.tsx b/app/screens/asset-list/asset-list-screen.tsx index 43511b0a..20d860af 100644 --- a/app/screens/asset-list/asset-list-screen.tsx +++ b/app/screens/asset-list/asset-list-screen.tsx @@ -55,7 +55,10 @@ export const AssetListScreen: React.FC = ({ navigation, medias, defaultHe const deleted = await AssetService.deleteAssets(selectedItems); if (deleted) { assetListRef?.current?.resetSelectedItems(); - await Assets.remove(selectedItems); + await Assets.addOrUpdate(selectedItems.map(id=>({ + id, + isDeleted:true + }))); cancelSelectionMode() } } catch (error) { diff --git a/app/screens/home/home-screen.tsx b/app/screens/home/home-screen.tsx index f5d51581..b36b6000 100644 --- a/app/screens/home/home-screen.tsx +++ b/app/screens/home/home-screen.tsx @@ -67,6 +67,13 @@ export const HomeScreen: React.FC = ({ navigation }) => { }) return assets; } + if(changes.newModifications?.length){ + assets = [] + for (const asset of collection) { + assets.push(asset) + } + return assets; + } return prev }) } diff --git a/app/screens/photo/index.tsx b/app/screens/photo/index.tsx index 60c9e252..98f4b641 100644 --- a/app/screens/photo/index.tsx +++ b/app/screens/photo/index.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React, { useEffect, useState } from "react" import Animated, { runOnJS, withTiming, @@ -9,7 +9,7 @@ import Animated, { useAnimatedGestureHandler, } from "react-native-reanimated" import { snapPoint } from "react-native-redash" -import { Dimensions, StyleSheet } from "react-native" +import { ActivityIndicator, Alert, Dimensions, StyleSheet } from "react-native" import { SharedElement } from "react-navigation-shared-element" import { TapGestureHandler, @@ -19,11 +19,16 @@ import { } from "react-native-gesture-handler" import { widthPercentageToDP } from "react-native-responsive-screen" import { RouteProp, NavigationProp } from "@react-navigation/native" - +import { file, fula } from "react-native-fula" import { Asset } from "../../types" import { palette } from "../../theme" -import { PhotoScreenHeader } from "../../components" +import { Header } from "../../components" import { HomeNavigationParamList } from "../../navigators" +import { Icon } from "@rneui/themed" +import { HeaderArrowBack } from "../../components/header" +import { useRecoilState } from "recoil" +import { boxsState } from "../../store" +import { Assets, Boxs } from "../../services/localdb" const { height } = Dimensions.get("window") @@ -33,14 +38,34 @@ interface PhotoScreenProps { } export const PhotoScreen: React.FC = ({ navigation, route }) => { - const img = route.params.section.data as Asset - + const [asset, setAsset] = useState(JSON.parse(JSON.stringify(route.params.section?.data)) as Asset) const translateX = useSharedValue(0) const translateY = useSharedValue(0) const imageScale = useSharedValue(1) const isPanGestureActive = useSharedValue(false) const isPinchGestureActive = useSharedValue(false) const animatedOpacity = useSharedValue(1) + const [, setBoxs] = useRecoilState(boxsState) + const [loading, setLoading] = useState(false) + + useEffect(() => { + loadBoxs(); + }, []); + + const loadBoxs = async () => { + try { + const boxs = await Boxs.getAll(); + if (boxs && boxs.length) { + boxs.map(item => { + fula.addBox(item.address) + }) + } + setBoxs(boxs.map(m => m.toJSON())); + } catch (error) { + Alert.alert("Error", "Unable to connect to the box!") + } + + } const wrapperAnimatedStyle = useAnimatedStyle(() => { return { @@ -62,6 +87,36 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = const goBack = () => { navigation.goBack() } + const uploadOrDownload = async () => { + if (!asset?.isSynced && !asset?.isDeleted) { + setLoading(true); + setTimeout(async () => { + try { + + const _filePath = asset.uri?.split('file:')[1]; + const result = await file.send(decodeURI(_filePath)) + Assets.addOrUpdate([{ + id: asset.id, + cid: result, + isSynced: true, + syncDate: new Date(), + }]); + setAsset(prev => ({ + ...prev, + isSynced: true, + cid: result + })) + console.log("CID:", result) + + } catch (error) { + console.log("uploadOrDownload", error) + Alert.alert("Error", "Unable to send the file") + } finally { + setLoading(false) + } + }, 0); + } + } const onPanGesture = useAnimatedGestureHandler({ onStart: () => { isPanGestureActive.value = true @@ -133,20 +188,26 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = } const imageContainerStyle = { - height: (widthPercentageToDP(100) * img.height) / img.width, + height: (widthPercentageToDP(100) * asset.height) / asset.width, width: widthPercentageToDP(100), } - + const renderHeader = () => { + return (
} + rightComponent={loading ? : } + />) + } return ( - goBack()} /> + {renderHeader()} onDoubleTap()} numberOfTaps={2}> - + > => { return RealmDB() .then((realm) => { - const assets = realm + let assets = realm .objects(Schemas.Asset.name) .sorted(descriptor, orderby === "desc") + if (filter) + assets = assets.filtered(filter) return assets }) .catch((error) => { @@ -21,14 +24,11 @@ export const removeAll = (): Promise => { return RealmDB() .then((realm) => { return realm.write(() => { - - const assets = realm - .objects(Schemas.Asset.name); - + const assets = realm.objects(Schemas.Asset.name) + // Delete all instances of Assets from the realm. - return realm.delete(assets); - }); - + return realm.delete(assets) + }) }) .catch((error) => { console.error("RealmDB removeAll error!", error) @@ -58,7 +58,7 @@ export const addOrUpdate = (assets: Entities.AssetEntity[]): Promise { console.error("RealmDB addOrUpdateAssets error!", error) @@ -70,17 +70,15 @@ export const remove = (assetIds: string[]): Promise => { return RealmDB() .then((realm) => { try { - const idsQuery = assetIds.map(id => `id = '${id}'`).join(' OR '); - const assets = realm - .objects(Schemas.Asset.name) - .filtered(idsQuery) + const idsQuery = assetIds.map((id) => `id = '${id}'`).join(" OR ") + const assets = realm.objects(Schemas.Asset.name).filtered(idsQuery) realm.write(() => { realm.delete(assets) - }); + }) } catch (error) { console.error("removeAssets error!", error) throw error - } + } }) .catch((error) => { console.error("RealmDB removeAssets error!", error) @@ -88,21 +86,21 @@ export const remove = (assetIds: string[]): Promise => { }) } -export const removeByUri = (uri:string): Promise => { +export const removeByUri = (uri: string): Promise => { return RealmDB() .then((realm) => { try { const assets = realm - .objects(Schemas.Asset.name) - .filtered(`uri endsWith '${uri}'`) - console.log("removeByUri",assets.length) + .objects(Schemas.Asset.name) + .filtered(`uri endsWith '${uri}'`) + console.log("removeByUri", assets.length) realm.write(() => { realm.delete(assets) - }); + }) } catch (error) { console.error("removeAssets error!", error) throw error - } + } }) .catch((error) => { console.error("RealmDB removeAssets error!", error) From cc9b3f696e745c6a0d1a10eb09c784e14ecdf7e7 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sat, 28 May 2022 20:51:33 +0430 Subject: [PATCH 08/17] upload/download file to box is done. --- .../asset-items/asset-item.tsx | 41 +++++++++++-------- .../asset-items/recycler-section-item.tsx | 8 +++- app/screens/photo/index.tsx | 18 +++++++- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx b/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx index b1e60948..537c63a4 100644 --- a/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx +++ b/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx @@ -13,6 +13,7 @@ interface Props { selected: boolean selectionMode: boolean, isSynced: boolean, + isDeleted: boolean, onError?: (error: NativeSyntheticEvent) => void } const AssetItem = (props: Props): JSX.Element => { @@ -40,7 +41,7 @@ const AssetItem = (props: Props): JSX.Element => { scaleSharedValue.value = 1 } }, [selected]) - + return ( { }]}> - + {props?.isDeleted && props?.isSynced ? + + : } {asset?.mediaType === "video" && @@ -96,6 +99,7 @@ const styles = StyleSheet.create({ }, sharedElementContainer: { flex: 1, + justifyContent:"center" }, videoIconContainer: { right: 10, @@ -105,9 +109,9 @@ const styles = StyleSheet.create({ flexDirection: "row", justifyContent: "center", alignItems: "center", - backgroundColor:"white", - borderRadius:5, - opacity:.8 + backgroundColor: "white", + borderRadius: 5, + opacity: .8 }, videoDurationText: { color: "gray", @@ -122,10 +126,10 @@ const styles = StyleSheet.create({ flexDirection: "row", justifyContent: "center", alignItems: "center", - backgroundColor:"white", - borderRadius:5, - padding:1, - opacity:.7 + backgroundColor: "white", + borderRadius: 5, + padding: 1, + opacity: .7 }, }) @@ -134,7 +138,8 @@ const areEqual = (prev: Props, next: Props) => { prev?.asset?.id === next?.asset?.id && prev?.selectionMode === next?.selectionMode && prev?.selected === next?.selected && - prev?.isSynced === next?.isSynced + prev?.isSynced === next?.isSynced && + prev?.isDeleted === next?.isDeleted ) } export default memo(AssetItem, areEqual) diff --git a/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx b/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx index 75bd73f9..8dcbdd59 100644 --- a/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx +++ b/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx @@ -24,7 +24,13 @@ const getSectionByType = ( return } case ViewType.ASSET: { - return + return } case ViewType.MONTH: { const groupHeader: GroupHeader = section.data diff --git a/app/screens/photo/index.tsx b/app/screens/photo/index.tsx index 98f4b641..13c050a1 100644 --- a/app/screens/photo/index.tsx +++ b/app/screens/photo/index.tsx @@ -92,7 +92,6 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = setLoading(true); setTimeout(async () => { try { - const _filePath = asset.uri?.split('file:')[1]; const result = await file.send(decodeURI(_filePath)) Assets.addOrUpdate([{ @@ -115,6 +114,23 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = setLoading(false) } }, 0); + } else if (asset?.isSynced && asset?.isDeleted) { + setLoading(true); + setTimeout(async () => { + try { + const result = await file.receive(asset?.cid, false) + setAsset(prev => ({ + ...prev, + uri: result.uri, + isDeleted: false + })) + } catch (error) { + console.log("uploadOrDownload", error) + Alert.alert("Error", "Unable to receive the file") + } finally { + setLoading(false) + } + }, 0); } } const onPanGesture = useAnimatedGestureHandler({ From 2993e82e8e31a9b33dc100ff6b10169743eca905 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sun, 29 May 2022 19:35:09 +0430 Subject: [PATCH 09/17] download asset form box --- .../asset-items/asset-item.tsx | 29 +++--- app/screens/photo/index.tsx | 88 ++++++++++++------- 2 files changed, 68 insertions(+), 49 deletions(-) diff --git a/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx b/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx index 537c63a4..766ed2e3 100644 --- a/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx +++ b/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx @@ -1,9 +1,9 @@ import React, { memo } from "react" import { StyleSheet, View, Image, NativeSyntheticEvent, ImageErrorEventData } from "react-native" import { Icon, Text, useTheme } from "@rneui/themed" + import { Asset } from "../../../../types" import { Checkbox } from "../../../checkbox/checkbox" - import Animated, { useSharedValue, useAnimatedStyle, withTiming } from "react-native-reanimated" import { SharedElement } from "react-navigation-shared-element" import { convertDurationToTime } from "../../../../utils/helper" @@ -48,19 +48,17 @@ const AssetItem = (props: Props): JSX.Element => { borderColor: theme.colors.background }]}> - - {props?.isDeleted && props?.isSynced ? - - : } - + {props?.isDeleted && props?.isSynced ? : + + } {asset?.mediaType === "video" && @@ -96,10 +94,11 @@ const styles = StyleSheet.create({ flex: 1, overflow: "hidden", zIndex: 0, + justifyContent:"center" }, sharedElementContainer: { flex: 1, - justifyContent:"center" + justifyContent: "center" }, videoIconContainer: { right: 10, diff --git a/app/screens/photo/index.tsx b/app/screens/photo/index.tsx index 13c050a1..172163e4 100644 --- a/app/screens/photo/index.tsx +++ b/app/screens/photo/index.tsx @@ -9,7 +9,7 @@ import Animated, { useAnimatedGestureHandler, } from "react-native-reanimated" import { snapPoint } from "react-native-redash" -import { ActivityIndicator, Alert, Dimensions, StyleSheet } from "react-native" +import { ActivityIndicator, Alert, Dimensions, StyleSheet, View } from "react-native" import { SharedElement } from "react-navigation-shared-element" import { TapGestureHandler, @@ -21,10 +21,10 @@ import { widthPercentageToDP } from "react-native-responsive-screen" import { RouteProp, NavigationProp } from "@react-navigation/native" import { file, fula } from "react-native-fula" import { Asset } from "../../types" -import { palette } from "../../theme" +import { Constants, palette } from "../../theme" import { Header } from "../../components" import { HomeNavigationParamList } from "../../navigators" -import { Icon } from "@rneui/themed" +import { Card, Icon } from "@rneui/themed" import { HeaderArrowBack } from "../../components/header" import { useRecoilState } from "recoil" import { boxsState } from "../../store" @@ -69,6 +69,7 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = const wrapperAnimatedStyle = useAnimatedStyle(() => { return { + paddingTop: Constants.HeaderHeight, backgroundColor: palette.black, flex: 1, opacity: animatedOpacity.value, @@ -87,7 +88,33 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = const goBack = () => { navigation.goBack() } - const uploadOrDownload = async () => { + const downloadFromBox = async () => { + if (asset?.isSynced && asset?.isDeleted) { + setLoading(true); + setTimeout(async () => { + try { + const result = await file.receive(asset?.cid, false) + console.log("downloadFromBox:",result) + setAsset(prev => ({ + ...prev, + uri: result.uri, + isDeleted: false + })) + Assets.addOrUpdate([{ + id: asset.id, + uri:result.uri, + isDeleted:false + }]); + } catch (error) { + console.log("uploadOrDownload", error) + Alert.alert("Error", "Unable to receive the file") + } finally { + setLoading(false) + } + }, 0); + } + } + const uploadToBox = async () => { if (!asset?.isSynced && !asset?.isDeleted) { setLoading(true); setTimeout(async () => { @@ -114,23 +141,6 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = setLoading(false) } }, 0); - } else if (asset?.isSynced && asset?.isDeleted) { - setLoading(true); - setTimeout(async () => { - try { - const result = await file.receive(asset?.cid, false) - setAsset(prev => ({ - ...prev, - uri: result.uri, - isDeleted: false - })) - } catch (error) { - console.log("uploadOrDownload", error) - Alert.alert("Error", "Unable to receive the file") - } finally { - setLoading(false) - } - }, 0); } } const onPanGesture = useAnimatedGestureHandler({ @@ -210,9 +220,16 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = const renderHeader = () => { return (
} - rightComponent={loading ? : } - />) + rightComponent={loading ? : + (asset?.isSynced ? + : (!asset?.isSynced && !asset?.isDeleted ? : null)) + } />) + } + const renderDownloadSection = () => { + return ( + + Tap to download + ) } return ( @@ -220,16 +237,19 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = {renderHeader()} onDoubleTap()} numberOfTaps={2}> - - - - - + + {asset?.isSynced && asset?.isDeleted && renderDownloadSection()} + {!asset?.isDeleted && + + + + } + From 62bfbbaf09f86cf763db5891a17a7390e1a730c7 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Mon, 30 May 2022 16:42:17 +0430 Subject: [PATCH 10/17] update react-native-fula package. --- app/screens/box/box-add-update-screen.tsx | 4 +++- app/screens/photo/index.tsx | 13 +++++++++---- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/screens/box/box-add-update-screen.tsx b/app/screens/box/box-add-update-screen.tsx index 5c4d2ccb..af31a3d1 100644 --- a/app/screens/box/box-add-update-screen.tsx +++ b/app/screens/box/box-add-update-screen.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from "react" import { Alert, StyleProp, StyleSheet, View, ViewStyle } from "react-native" import { Icon, Input, Text } from "@rneui/themed" -import Animated from "react-native-reanimated" +import {fula} from "react-native-fula" import { NativeStackNavigationProp, NativeStackScreenProps } from "@react-navigation/native-stack" import { HomeNavigationParamList, HomeNavigationTypes } from "../../navigators/home-navigator" @@ -51,6 +51,7 @@ export const BoxAddUpdateScreen: React.FC = ({ navigation, route }) => { Alert.alert("Warning", "Please fill all the fields!") return } + await fula.addBox(form.address); const box = { name: form.name, address: form.address, @@ -60,6 +61,7 @@ export const BoxAddUpdateScreen: React.FC = ({ navigation, route }) => { navigation.pop(); } catch (error) { console.log(error) + Alert.alert("Error", "Make sure the address format is correct!") } finally { pressed.current = false } diff --git a/app/screens/photo/index.tsx b/app/screens/photo/index.tsx index 172163e4..7a315763 100644 --- a/app/screens/photo/index.tsx +++ b/app/screens/photo/index.tsx @@ -56,12 +56,17 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = try { const boxs = await Boxs.getAll(); if (boxs && boxs.length) { - boxs.map(item => { - fula.addBox(item.address) + boxs.map(async item => { + try { + await fula.addBox(item.address) + } catch (error) { + console.log(error) + } }) } setBoxs(boxs.map(m => m.toJSON())); } catch (error) { + console.log(error) Alert.alert("Error", "Unable to connect to the box!") } @@ -107,7 +112,7 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = }]); } catch (error) { console.log("uploadOrDownload", error) - Alert.alert("Error", "Unable to receive the file") + Alert.alert("Error", "Unable to receive the file, make sure your box is available!") } finally { setLoading(false) } @@ -136,7 +141,7 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = } catch (error) { console.log("uploadOrDownload", error) - Alert.alert("Error", "Unable to send the file") + Alert.alert("Error", "Unable to send the file, make sure your box is available!") } finally { setLoading(false) } diff --git a/package.json b/package.json index 4ad71061..fb128fe2 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "react": "17.0.2", "react-native": "0.66.3", "react-native-fast-image": "^8.5.11", - "react-native-fula": "^0.2.0", + "react-native-fula": "^0.2.2", "react-native-gesture-handler": "^2.4.1", "react-native-keychain": "6.2.0", "react-native-reanimated": "^2.8.0", diff --git a/yarn.lock b/yarn.lock index fcf6e614..a72b55b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13658,10 +13658,10 @@ react-native-flipper@^0.34.0: resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d" integrity sha512-48wgm29HJTOlZ0DibBsvXueEOY0EPIVL0wWKbwRfgrk86+luSEuLW3aZC50oJa95zSFb9qYShTV/6dWqh4Jamg== -react-native-fula@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/react-native-fula/-/react-native-fula-0.2.0.tgz#ac666adaab692e36f6586d708c78bc91b4ca6491" - integrity sha512-TbU/UAXTgqcWM3CdC5YLeqs9Kbl68rCIxMIa+ioRMJUgg/hX4ygDQvRjiuSuhGFDLWjHzB8BmWBeb0nzGhsKHw== +react-native-fula@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/react-native-fula/-/react-native-fula-0.2.2.tgz#30a8a08d0edee26e0c5f97558fefee366e6da5bb" + integrity sha512-47VWpEZfFaquDlWwMK+BiBGOuRlVZvkdFJGfb7Ut5huTkFjUkeYQNUSdTGCgvQTUQf/qS2cN7PLWCBPuhrw2cg== dependencies: "@functionland/file-protocol" "^0.3.6" "@functionland/graph-protocol" "^0.2.2" From 639ec223dc0320ae63222d469c9336c271485b8c Mon Sep 17 00:00:00 2001 From: Mati Q Date: Mon, 30 May 2022 20:03:54 +0430 Subject: [PATCH 11/17] add react-native-background-actions package. --- android/app/src/main/AndroidManifest.xml | 3 +++ package.json | 1 + yarn.lock | 9 ++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 91f4069f..4ff0fb44 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,8 @@ + + + diff --git a/package.json b/package.json index fb128fe2..9c257082 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "moment": "^2.29.1", "react": "17.0.2", "react-native": "0.66.3", + "react-native-background-actions": "^2.6.7", "react-native-fast-image": "^8.5.11", "react-native-fula": "^0.2.2", "react-native-gesture-handler": "^2.4.1", diff --git a/yarn.lock b/yarn.lock index a72b55b5..89ba8efc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7403,7 +7403,7 @@ event-target-shim@^5.0.0, event-target-shim@^5.0.1: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@^4.0.0: +eventemitter3@^4.0.0, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -13634,6 +13634,13 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-native-background-actions@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/react-native-background-actions/-/react-native-background-actions-2.6.7.tgz#01637518814aa957b877bf8117cee01266d67f43" + integrity sha512-qjHeks9EbLIXeb2xn3AR0eLVlUh/sAboKt0tI3ZN8leEEL1xmZUvpbk0fN6RKRpxpv7ATVHgWjGMK1FNoYh+Ug== + dependencies: + eventemitter3 "^4.0.7" + react-native-clean-project@^3.6.3: version "3.6.7" resolved "https://registry.yarnpkg.com/react-native-clean-project/-/react-native-clean-project-3.6.7.tgz#6d22ad22fe3a1e6efdb040eb66f9bdfb2273ac2e" From 57394a6a64805f20b95d18ef1e8ad0438aca9b08 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Mon, 30 May 2022 20:05:04 +0430 Subject: [PATCH 12/17] Asset schema is changed. --- .../asset-items/asset-item.tsx | 2 +- .../asset-items/recycler-section-item.tsx | 15 ++++++------ app/realmdb/entities/asset.ts | 4 +++- app/realmdb/schemas/asset.ts | 2 +- app/screens/photo/index.tsx | 23 ++++++++++--------- app/services/localdb/assets.ts | 2 +- app/types/enums.ts | 6 +++++ 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx b/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx index 766ed2e3..ef16f158 100644 --- a/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx +++ b/app/components/asset-list/recycler-asset-list/asset-items/asset-item.tsx @@ -65,7 +65,7 @@ const AssetItem = (props: Props): JSX.Element => { {convertDurationToTime(asset?.duration)} } - {asset?.isSynced && + {props?.isSynced && } diff --git a/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx b/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx index 8dcbdd59..5c51bc58 100644 --- a/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx +++ b/app/components/asset-list/recycler-asset-list/asset-items/recycler-section-item.tsx @@ -1,6 +1,6 @@ import React from "react" import { TouchableHighlight, StyleSheet, NativeSyntheticEvent, ImageErrorEventData } from "react-native" -import { GroupHeader, RecyclerAssetListSection, ViewType } from "../../../../types" +import { Asset, GroupHeader, RecyclerAssetListSection, SyncStatus, ViewType } from "../../../../types" import StoryListItem from "./story-list-item" import AssetItem from "./asset-item" import HeaderItem from "./header-item" @@ -24,12 +24,13 @@ const getSectionByType = ( return } case ViewType.ASSET: { - return } case ViewType.MONTH: { diff --git a/app/realmdb/entities/asset.ts b/app/realmdb/entities/asset.ts index 70572795..730cacb8 100644 --- a/app/realmdb/entities/asset.ts +++ b/app/realmdb/entities/asset.ts @@ -1,3 +1,5 @@ +import { SyncStatus } from "../../types" + export type AssetEntity = { id: string /** @@ -43,7 +45,7 @@ export type AssetEntity = { /** * Sync statis with box */ - isSynced: boolean + syncStatus: SyncStatus /** * Sync date with box */ diff --git a/app/realmdb/schemas/asset.ts b/app/realmdb/schemas/asset.ts index dc73fb62..95c72d49 100644 --- a/app/realmdb/schemas/asset.ts +++ b/app/realmdb/schemas/asset.ts @@ -46,7 +46,7 @@ export const Asset = { /** * Sync statis with box */ - isSynced: { type: "bool", default: false }, + syncStatus: { type: "int", default: 0 }, /** * Sync date with box */ diff --git a/app/screens/photo/index.tsx b/app/screens/photo/index.tsx index 7a315763..7388a6fb 100644 --- a/app/screens/photo/index.tsx +++ b/app/screens/photo/index.tsx @@ -20,7 +20,7 @@ import { import { widthPercentageToDP } from "react-native-responsive-screen" import { RouteProp, NavigationProp } from "@react-navigation/native" import { file, fula } from "react-native-fula" -import { Asset } from "../../types" +import { Asset, SyncStatus } from "../../types" import { Constants, palette } from "../../theme" import { Header } from "../../components" import { HomeNavigationParamList } from "../../navigators" @@ -94,12 +94,12 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = navigation.goBack() } const downloadFromBox = async () => { - if (asset?.isSynced && asset?.isDeleted) { + if (asset?.syncStatus === SyncStatus.SYNCED && asset?.isDeleted) { setLoading(true); setTimeout(async () => { try { const result = await file.receive(asset?.cid, false) - console.log("downloadFromBox:",result) + console.log("downloadFromBox:", result) setAsset(prev => ({ ...prev, uri: result.uri, @@ -107,8 +107,8 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = })) Assets.addOrUpdate([{ id: asset.id, - uri:result.uri, - isDeleted:false + uri: result.uri, + isDeleted: false }]); } catch (error) { console.log("uploadOrDownload", error) @@ -120,21 +120,22 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = } } const uploadToBox = async () => { - if (!asset?.isSynced && !asset?.isDeleted) { + if (asset?.syncStatus === SyncStatus.NOTSYNCED && !asset?.isDeleted) { setLoading(true); setTimeout(async () => { try { const _filePath = asset.uri?.split('file:')[1]; const result = await file.send(decodeURI(_filePath)) + console.log("result:",result) Assets.addOrUpdate([{ id: asset.id, cid: result, - isSynced: true, + syncStatus: SyncStatus.SYNCED, syncDate: new Date(), }]); setAsset(prev => ({ ...prev, - isSynced: true, + syncStatus: SyncStatus.SYNCED, cid: result })) console.log("CID:", result) @@ -226,8 +227,8 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = return (
} rightComponent={loading ? : - (asset?.isSynced ? - : (!asset?.isSynced && !asset?.isDeleted ? : null)) + (asset?.syncStatus === SyncStatus.SYNCED ? + : (asset?.syncStatus === SyncStatus.NOTSYNCED && !asset?.isDeleted ? : null)) } />) } const renderDownloadSection = () => { @@ -243,7 +244,7 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = onDoubleTap()} numberOfTaps={2}> - {asset?.isSynced && asset?.isDeleted && renderDownloadSection()} + {asset?.syncStatus===SyncStatus.SYNCED && asset?.isDeleted && renderDownloadSection()} {!asset?.isDeleted && > => { return RealmDB() .then((realm) => { diff --git a/app/types/enums.ts b/app/types/enums.ts index 41a2aee4..838ede23 100644 --- a/app/types/enums.ts +++ b/app/types/enums.ts @@ -4,3 +4,9 @@ export enum ViewType { MONTH = "MONTH", DAY = "DAY", } + +export enum SyncStatus { + NOTSYNCED = 0, + SYNC = 1, // Selected for sync + SYNCED = 2, +} \ No newline at end of file From 1e1aceeae04913df6382c5694b14c56cfe71ce9b Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sat, 4 Jun 2022 12:56:05 +0430 Subject: [PATCH 13/17] Added netInfo package. Added react-native-background-fetch package. --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 11 +++++++++++ android/build.gradle | 4 ++++ package.json | 3 +++ yarn.lock | 15 +++++++++++++++ 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index bfcb07bd..6b67f8b5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -228,7 +228,7 @@ dependencies { } } - implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { exclude group:'com.facebook.fbjni' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4ff0fb44..2d867b29 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,11 +1,13 @@ + + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index aae2877c..7028b0e3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -25,6 +25,10 @@ allprojects { repositories { google() mavenCentral() + maven { + // react-native-background-fetch + url("${project(':react-native-background-fetch').projectDir}/libs") + } mavenLocal() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm diff --git a/package.json b/package.json index 9c257082..98a58053 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "@react-native-async-storage/async-storage": "^1.14.1", "@react-native-community/masked-view": "0.1.10", + "@react-native-community/netinfo": "^8.3.1", "@react-navigation/bottom-tabs": "^6.3.1", "@react-navigation/native": "^6.0.10", "@react-navigation/native-stack": "^6.6.2", @@ -50,6 +51,7 @@ "react": "17.0.2", "react-native": "0.66.3", "react-native-background-actions": "^2.6.7", + "react-native-background-fetch": "^4.1.0", "react-native-fast-image": "^8.5.11", "react-native-fula": "^0.2.2", "react-native-gesture-handler": "^2.4.1", @@ -60,6 +62,7 @@ "react-native-safe-area-context": "^4.1.2", "react-native-screens": "^3.13.1", "react-native-shared-element": "^0.8.4", + "react-native-toast-message": "^2.1.5", "react-native-vector-icons": "^9.0.0", "react-navigation-collapsible": "^6.3.0", "react-navigation-shared-element": "^3.1.3", diff --git a/yarn.lock b/yarn.lock index 89ba8efc..703d9d46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2459,6 +2459,11 @@ resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.10.tgz#5dda643e19e587793bc2034dd9bf7398ad43d401" integrity sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ== +"@react-native-community/netinfo@^8.3.1": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-8.3.1.tgz#c880407c660475d1fa0afa92fa259aae0e9e2dc9" + integrity sha512-CYZXW3wZJ8Ac7FQjrDfqITFZ6Ex+dCOOKQv0LvjQUI811otSBATyEZrBVWc+1GpZExbKTlulyE0X3W4v5YN1hQ== + "@react-native/assets@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@react-native/assets/-/assets-1.0.0.tgz#c6f9bf63d274bafc8e970628de24986b30a55c8e" @@ -13641,6 +13646,11 @@ react-native-background-actions@^2.6.7: dependencies: eventemitter3 "^4.0.7" +react-native-background-fetch@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/react-native-background-fetch/-/react-native-background-fetch-4.1.0.tgz#6b5a4a85c9fcf18d3108bdca1d15a92c1166fe9a" + integrity sha512-LWl1xuLNfHbD9MQXV31RvpCia9hMxOW+gwjp5yzW7BbiZWTWV2TQ9szuHaPn4NBWHDdh+MtI5pb6JV9P/LCaOQ== + react-native-clean-project@^3.6.3: version "3.6.7" resolved "https://registry.yarnpkg.com/react-native-clean-project/-/react-native-clean-project-3.6.7.tgz#6d22ad22fe3a1e6efdb040eb66f9bdfb2273ac2e" @@ -13764,6 +13774,11 @@ react-native-swipe-gestures@^1.0.4: resolved "https://registry.yarnpkg.com/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz#a172cb0f3e7478ccd681fd36b8bfbcdd098bde7c" integrity sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw== +react-native-toast-message@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/react-native-toast-message/-/react-native-toast-message-2.1.5.tgz#550acb9c6c0f1ee49c6b57b65a56d34e297eb723" + integrity sha512-mk3rELtBEhrhWBCN6CTaw0gypgL9ZNauX3xx1LUs4uee9vc0pVsghrKxO57vroUCcNL2hDeZSLJWdQNMCkGeaQ== + react-native-vector-icons@^9.0.0: version "9.1.0" resolved "https://registry.yarnpkg.com/react-native-vector-icons/-/react-native-vector-icons-9.1.0.tgz#52eaa205f5954d567b7048eb93d58ac854a2c59e" From 4e019a0dcbada9ba392c893029e84011b7419128 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sat, 4 Jun 2022 12:57:49 +0430 Subject: [PATCH 14/17] setup background sync action --- app/app.tsx | 26 +++++- app/navigators/app-navigator.tsx | 2 + app/screens/library/library-screen.tsx | 12 ++- app/screens/photo/index.tsx | 122 ++++++++++++++++-------- app/services/localdb/assets.ts | 14 ++- app/services/sync-service.ts | 124 +++++++++++++++++++++++++ 6 files changed, 255 insertions(+), 45 deletions(-) create mode 100644 app/services/sync-service.ts diff --git a/app/app.tsx b/app/app.tsx index 958dad98..e57bc71b 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -11,7 +11,7 @@ */ import "./i18n" import "./utils/ignore-warnings" -import React from "react" +import React, { useRef } from "react" import { useColorScheme } from "react-native" import { GestureHandlerRootView } from "react-native-gesture-handler" import { SafeAreaProvider, initialWindowMetrics } from "react-native-safe-area-context" @@ -26,6 +26,8 @@ import { ErrorBoundary } from "./screens/error/error-boundary" import * as MediaLibrary from "expo-media-library" import { ThemeProvider } from './theme'; import { RneLightTheme, RneDarkTheme } from "./theme" +import NetInfo from "@react-native-community/netinfo"; +import { AddBoxs, uploadAssetsInBackground } from "./services/sync-service" // This puts screens in a native ViewController or Activity. If you want fully native // stack navigation, use `createNativeStackNavigator` in place of `createStackNavigator`: @@ -39,7 +41,7 @@ export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE" function App() { const scheme = useColorScheme(); - + const netInfoTimer = useRef(null); useBackButtonHandler(canExit) const { onNavigationStateChange, isRestored: isNavigationStateRestored } = useNavigationPersistence(storage, NAVIGATION_PERSISTENCE_KEY) @@ -50,8 +52,26 @@ function App() { ; (async () => { await getPermissions() await initFonts() - })() + })(); + // Subscribe + const unsubscribeNetInfo = subscribeNetInfo(); + return () => { + // Unsubscribe + unsubscribeNetInfo(); + } }, []) + const subscribeNetInfo = () => { + return NetInfo.addEventListener(state => { + if (netInfoTimer.current) + clearTimeout(netInfoTimer.current); + netInfoTimer.current = setTimeout(async () => { + if (state.isConnected) + await AddBoxs(); + uploadAssetsInBackground(); + }, 1000); + }); + } + // Before we show the app, we have to wait for our state to be ready. // In the meantime, don't render anything. This will be the background diff --git a/app/navigators/app-navigator.tsx b/app/navigators/app-navigator.tsx index f6aec035..4c303beb 100644 --- a/app/navigators/app-navigator.tsx +++ b/app/navigators/app-navigator.tsx @@ -3,6 +3,7 @@ import Animated from "react-native-reanimated" import { enableScreens } from "react-native-screens" import { NavigationContainer } from "@react-navigation/native" import { createSharedElementStackNavigator } from "react-navigation-shared-element" +import Toast from 'react-native-toast-message' import { navigationRef } from "./navigation-utilities" import { PhotoScreen, LibraryAssetsScreen, BoxListScreen, BoxAddUpdateScreen } from "../screens" @@ -103,6 +104,7 @@ export const AppNavigator = (props: NavigationProps) => { > + ) } diff --git a/app/screens/library/library-screen.tsx b/app/screens/library/library-screen.tsx index 1771e97a..947a64ab 100644 --- a/app/screens/library/library-screen.tsx +++ b/app/screens/library/library-screen.tsx @@ -21,13 +21,19 @@ interface HomeScreenProps { const AnimatedFlatList = Animated.createAnimatedComponent(FlatList) export const LibraryScreen: React.FC = ({ navigation }) => { const [medias] = useRecoilState(mediasState) - const [, setSelectedLibrary] = useRecoilState(selectedLibraryState) + const [selectedLibrary, setSelectedLibrary] = useRecoilState(selectedLibraryState) const { theme } = useTheme(); const [libraies, setLibraries] = useState(null) // Get a custom hook to animate the header const [scrollY, headerStyles] = useFloatHederAnimation(60) useEffect(() => { - setLibraries(AssetService.getLibraries(medias)) + const libs = AssetService.getLibraries(medias) + setLibraries(libs) + if (selectedLibrary){ + const lib=libs.find(lib=>lib.title === selectedLibrary.title) + if(lib) + setSelectedLibrary(lib) + } }, [medias]); const scrollHandler = useAnimatedScrollHandler({ onScroll: (event) => { @@ -40,7 +46,7 @@ export const LibraryScreen: React.FC = ({ navigation }) => { centerComponent={} />) } - + const onLibraryPress = (item: Library) => { setSelectedLibrary(item); navigation.push(AppNavigationNames.LibraryAssets) diff --git a/app/screens/photo/index.tsx b/app/screens/photo/index.tsx index 7388a6fb..bf8b505c 100644 --- a/app/screens/photo/index.tsx +++ b/app/screens/photo/index.tsx @@ -8,6 +8,7 @@ import Animated, { useAnimatedStyle, useAnimatedGestureHandler, } from "react-native-reanimated" +import Toast from 'react-native-toast-message' import { snapPoint } from "react-native-redash" import { ActivityIndicator, Alert, Dimensions, StyleSheet, View } from "react-native" import { SharedElement } from "react-navigation-shared-element" @@ -18,6 +19,7 @@ import { PinchGestureHandlerGestureEvent, } from "react-native-gesture-handler" import { widthPercentageToDP } from "react-native-responsive-screen" +import { useNetInfo } from "@react-native-community/netinfo" import { RouteProp, NavigationProp } from "@react-navigation/native" import { file, fula } from "react-native-fula" import { Asset, SyncStatus } from "../../types" @@ -29,6 +31,7 @@ import { HeaderArrowBack } from "../../components/header" import { useRecoilState } from "recoil" import { boxsState } from "../../store" import { Assets, Boxs } from "../../services/localdb" +import { AddBoxs, downloadAsset, uploadAsset, uploadAssetsInBackground } from "../../services/sync-service" const { height } = Dimensions.get("window") @@ -47,31 +50,12 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = const animatedOpacity = useSharedValue(1) const [, setBoxs] = useRecoilState(boxsState) const [loading, setLoading] = useState(false) - + const netInfoState = useNetInfo() useEffect(() => { - loadBoxs(); - }, []); - - const loadBoxs = async () => { - try { - const boxs = await Boxs.getAll(); - if (boxs && boxs.length) { - boxs.map(async item => { - try { - await fula.addBox(item.address) - } catch (error) { - console.log(error) - } - }) - } - setBoxs(boxs.map(m => m.toJSON())); - } catch (error) { - console.log(error) - Alert.alert("Error", "Unable to connect to the box!") + return () => { + Toast.hide(); } - - } - + }) const wrapperAnimatedStyle = useAnimatedStyle(() => { return { paddingTop: Constants.HeaderHeight, @@ -98,7 +82,13 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = setLoading(true); setTimeout(async () => { try { - const result = await file.receive(asset?.cid, false) + try { + await AddBoxs() + } catch (error) { + Alert.alert("Warning", error) + return; + } + const result = await downloadAsset(asset?.cid) console.log("downloadFromBox:", result) setAsset(prev => ({ ...prev, @@ -124,22 +114,60 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = setLoading(true); setTimeout(async () => { try { - const _filePath = asset.uri?.split('file:')[1]; - const result = await file.send(decodeURI(_filePath)) - console.log("result:",result) - Assets.addOrUpdate([{ + // const _filePath = asset.uri?.split('file:')[1]; + // const result = await file.send(decodeURI(_filePath)) + // console.log("result:",result) + await Assets.addOrUpdate([{ id: asset.id, - cid: result, - syncStatus: SyncStatus.SYNCED, - syncDate: new Date(), + syncStatus: SyncStatus.SYNC, }]); setAsset(prev => ({ ...prev, - syncStatus: SyncStatus.SYNCED, - cid: result + syncStatus: SyncStatus.SYNC, })) - console.log("CID:", result) + if (!netInfoState.isConnected) { + Toast.show({ + type: 'info', + text1: 'Will upload when connected', + position: "bottom", + bottomOffset: 0, + }); + return + } + try { + await AddBoxs() + } catch (error) { + Alert.alert("Warning", error) + return; + } + try { + Toast.show({ + type: 'info', + text1: 'Upload...', + position: "bottom", + bottomOffset: 0, + }); + await uploadAssetsInBackground({ + callback: (success) => { + if (success) + setAsset(prev => ({ + ...prev, + syncStatus: SyncStatus.SYNCED, + })) + else + Toast.show({ + type: 'error', + text1: 'Will upload when connected', + position: "bottom", + bottomOffset: 0, + }); + + } + }); + } catch (error) { + Alert.alert("Error", "Unable to send the file now, will upload when connected") + } } catch (error) { console.log("uploadOrDownload", error) Alert.alert("Error", "Unable to send the file, make sure your box is available!") @@ -147,8 +175,27 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = setLoading(false) } }, 0); + } else if (asset?.syncStatus === SyncStatus.SYNC) { + cancelUpdate(); } } + const cancelUpdate = () => { + Alert.alert("Waiting for connection", "Will upload when connected", [{ + text: "Cancel update", + onPress: async () => { + await Assets.addOrUpdate([{ + id: asset.id, + syncStatus: SyncStatus.NOTSYNCED, + }]); + setAsset(prev => ({ + ...prev, + syncStatus: SyncStatus.NOTSYNCED, + })) + } + }, { + text: "OK" + }]) + } const onPanGesture = useAnimatedGestureHandler({ onStart: () => { isPanGestureActive.value = true @@ -227,8 +274,9 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = return (
} rightComponent={loading ? : - (asset?.syncStatus === SyncStatus.SYNCED ? - : (asset?.syncStatus === SyncStatus.NOTSYNCED && !asset?.isDeleted ? : null)) + (asset?.syncStatus === SyncStatus.SYNCED && !asset?.isDeleted ? + : (asset?.syncStatus === SyncStatus.NOTSYNCED && !asset?.isDeleted ? + : asset?.syncStatus === SyncStatus.SYNC ? : null)) } />) } const renderDownloadSection = () => { @@ -244,7 +292,7 @@ export const PhotoScreen: React.FC = ({ navigation, route }) = onDoubleTap()} numberOfTaps={2}> - {asset?.syncStatus===SyncStatus.SYNCED && asset?.isDeleted && renderDownloadSection()} + {asset?.syncStatus === SyncStatus.SYNCED && asset?.isDeleted && renderDownloadSection()} {!asset?.isDeleted && (Schemas.Asset.name) .sorted(descriptor, orderby === "desc") - if (filter) - assets = assets.filtered(filter) + if (filter) assets = assets.filtered(filter) + return assets + }) + .catch((error) => { + console.error("RealmDB getAllAssets error!", error) + throw error + }) +} +export const getAllNeedToSync = (): Promise> => { + return RealmDB() + .then((realm) => { + const assets = realm.objects(Schemas.Asset.name).filtered("syncStatus=1") return assets }) .catch((error) => { diff --git a/app/services/sync-service.ts b/app/services/sync-service.ts new file mode 100644 index 00000000..e09eb82e --- /dev/null +++ b/app/services/sync-service.ts @@ -0,0 +1,124 @@ +import { Platform } from "react-native" +import BackgroundJob, { BackgroundTaskOptions } from "react-native-background-actions" +import { file, fula } from "react-native-fula" +import { AssetEntity } from "../realmdb/entities" + +import { SyncStatus } from "../types" +import { Assets, Boxs } from "./localdb/index" +type TaskParams = { + callback: (success: boolean) => void + assets: AssetEntity[] +} +const defaultOptions = { + taskName: "BackgroundSyncTask", + taskTitle: "Preparing upload...", + taskDesc: "", + taskIcon: { + name: "ic_launcher", + type: "mipmap", + }, + color: "#2196f3", + linkingURI: "exampleScheme://chat/jane", +} as BackgroundTaskOptions + +const backgroundTask = async (taskParameters:TaskParams) => { + if (Platform.OS === "ios") { + console.warn( + "This task will not keep your app alive in the background by itself, use other library like react-native-track-player that use audio,", + "geolocalization, etc. to keep your app alive in the background while you excute the JS from this library.", + ) + } + const { callback = null,assets=[] } = taskParameters + await new Promise(async (resolve) => { + try { + // const assets = await (await Assets.getAllNeedToSync()).toJSON() + console.log("syncAssets assets.length", assets.length, callback) + + for (let index = 0; index < assets.length; index++) { + const asset = assets[index] + BackgroundJob.updateNotification({ + taskTitle: `Upload asset #${index + 1}`, + taskDesc: `Totla: ${assets.length}`, + progressBar: { + max: assets.length, + value: index, + indeterminate: assets.length == 1, + }, + }) + const result = await uploadAsset(asset) + Assets.addOrUpdate([ + { + id: asset.id, + cid: result, + syncDate: new Date(), + syncStatus: SyncStatus.SYNCED, + }, + ]) + try { + callback?.(true) + } catch {} + console.log("result:", result) + } + } catch (error) { + console.log("backgroundTask:", error) + try { + callback?.(false) + } catch {} + } + await BackgroundJob.stop() + }) +} +/** + * You need to make sure the box addresses are added and then call this method + * @param options + */ +export const uploadAssetsInBackground = async (options: { + callback?: (success: boolean) => void +}) => { + try { + const assets = (await Assets.getAllNeedToSync())?.toJSON() + if (assets.length) { + if (!BackgroundJob.isRunning()) + await BackgroundJob.start(backgroundTask, { + ...defaultOptions, + parameters: { + callback: options?.callback, + assets, + }, + }) + } + } catch (e) { + console.log("Error", e) + await BackgroundJob.stop() + } +} + +export const uploadAsset = async (asset: AssetEntity) => { + const _filePath = asset.uri?.split("file:")[1] + return await file.send(decodeURI(_filePath)) +} + +export const downloadAsset = async (cid: string) => { + return await file.receive(cid, false) +} + +export const AddBoxs = async () => { + try { + const boxs = await Boxs.getAll() + console.log("boxs:", boxs) + if (boxs && boxs.length) { + boxs.map(async (item) => { + try { + await fula.addBox(item.address) + } catch (error) { + console.log(error) + } + }) + } else { + throw "There is no box, please first add a box in the box list!" + } + } catch (error) { + console.log(error) + throw error + } +} From 66046a39f8de7c2023b81ec016b673a32c1873c9 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sat, 4 Jun 2022 16:53:39 +0430 Subject: [PATCH 15/17] configed BackgroundFetch. --- app/app.tsx | 2 ++ app/screens/photo/index.tsx | 4 +-- app/services/index.ts | 1 + app/services/sync-service.ts | 62 ++++++++++++++++++++++++++++++++---- index.js | 6 ++++ 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/app/app.tsx b/app/app.tsx index e57bc71b..4bd417c5 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -28,6 +28,7 @@ import { ThemeProvider } from './theme'; import { RneLightTheme, RneDarkTheme } from "./theme" import NetInfo from "@react-native-community/netinfo"; import { AddBoxs, uploadAssetsInBackground } from "./services/sync-service" +import { SyncService } from "./services" // This puts screens in a native ViewController or Activity. If you want fully native // stack navigation, use `createNativeStackNavigator` in place of `createStackNavigator`: @@ -52,6 +53,7 @@ function App() { ; (async () => { await getPermissions() await initFonts() + await SyncService.initBackgroundFetch(); })(); // Subscribe const unsubscribeNetInfo = subscribeNetInfo(); diff --git a/app/screens/photo/index.tsx b/app/screens/photo/index.tsx index bf8b505c..c737fbd9 100644 --- a/app/screens/photo/index.tsx +++ b/app/screens/photo/index.tsx @@ -30,8 +30,8 @@ import { Card, Icon } from "@rneui/themed" import { HeaderArrowBack } from "../../components/header" import { useRecoilState } from "recoil" import { boxsState } from "../../store" -import { Assets, Boxs } from "../../services/localdb" -import { AddBoxs, downloadAsset, uploadAsset, uploadAssetsInBackground } from "../../services/sync-service" +import { Assets } from "../../services/localdb" +import { AddBoxs, downloadAsset, uploadAssetsInBackground } from "../../services/sync-service" const { height } = Dimensions.get("window") diff --git a/app/services/index.ts b/app/services/index.ts index b211f273..4314e844 100644 --- a/app/services/index.ts +++ b/app/services/index.ts @@ -1,2 +1,3 @@ export * as AssetService from './asset-service' export * as LocalDbService from './localdb' +export * as SyncService from './sync-service' diff --git a/app/services/sync-service.ts b/app/services/sync-service.ts index e09eb82e..b6ad26cf 100644 --- a/app/services/sync-service.ts +++ b/app/services/sync-service.ts @@ -1,5 +1,6 @@ import { Platform } from "react-native" import BackgroundJob, { BackgroundTaskOptions } from "react-native-background-actions" +import BackgroundFetch, { HeadlessEvent } from "react-native-background-fetch" import { file, fula } from "react-native-fula" import { AssetEntity } from "../realmdb/entities" @@ -21,19 +22,16 @@ const defaultOptions = { linkingURI: "exampleScheme://chat/jane", } as BackgroundTaskOptions -const backgroundTask = async (taskParameters:TaskParams) => { +const backgroundTask = async (taskParameters: TaskParams) => { if (Platform.OS === "ios") { console.warn( "This task will not keep your app alive in the background by itself, use other library like react-native-track-player that use audio,", "geolocalization, etc. to keep your app alive in the background while you excute the JS from this library.", ) } - const { callback = null,assets=[] } = taskParameters + const { callback = null, assets = [] } = taskParameters await new Promise(async (resolve) => { try { - // const assets = await (await Assets.getAllNeedToSync()).toJSON() - console.log("syncAssets assets.length", assets.length, callback) - for (let index = 0; index < assets.length; index++) { const asset = assets[index] BackgroundJob.updateNotification({ @@ -57,7 +55,6 @@ const backgroundTask = async (taskParameters:TaskParams) => { try { callback?.(true) } catch {} - console.log("result:", result) } } catch (error) { console.log("backgroundTask:", error) @@ -70,7 +67,7 @@ const backgroundTask = async (taskParameters:TaskParams) => { } /** * You need to make sure the box addresses are added and then call this method - * @param options + * @param options */ export const uploadAssetsInBackground = async (options: { callback?: (success: boolean) => void @@ -122,3 +119,54 @@ export const AddBoxs = async () => { throw error } } + +/// Configure BackgroundFetch. +/// +export const initBackgroundFetch = async () => { + return await BackgroundFetch.configure( + { + minimumFetchInterval: 15, // <-- minutes (15 is minimum allowed) + stopOnTerminate: false, + enableHeadless: true, + startOnBoot: true, + // Android options + forceAlarmManager: false, // <-- Set true to bypass JobScheduler. + requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY, // Default + requiresCharging: false, // Default + requiresDeviceIdle: false, // Default + requiresBatteryNotLow: true, // Default + requiresStorageNotLow: false, // Default + }, + async (taskId: string) => { + console.log("[BackgroundFetch (configure)] taskId", taskId) + await backgroundFetchHeadlessTask({ taskId, timeout: false }) + + BackgroundFetch.finish(taskId) + }, + (taskId: string) => { + // Oh No! Our task took too long to complete and the OS has signalled + // that this task must be finished immediately. + console.log("[Fetch] TIMEOUT taskId:", taskId) + BackgroundFetch.finish(taskId) + }, + ) +} + +/// BackgroundFetch Android Headless Event Receiver. +/// Called when the Android app is terminated. +/// +export const backgroundFetchHeadlessTask = async (event: HeadlessEvent) => { + if (event.timeout) { + console.log("[BackgroundFetch] 💀 HeadlessTask TIMEOUT: ", event.taskId) + BackgroundFetch.finish(event.taskId) + return + } + + console.log("[BackgroundFetch] 💀 HeadlessTask start: ", event.taskId) + await uploadAssetsInBackground() + + // Required: Signal to native code that your task is complete. + // If you don't do this, your app could be terminated and/or assigned + // battery-blame for consuming too much time in background. + BackgroundFetch.finish(event.taskId) +} diff --git a/index.js b/index.js index daabcd28..e0f12b39 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,12 @@ // It's easier just to leave it here. import App from "./app/app.tsx" import { AppRegistry } from "react-native" +import BackgroundFetch from "react-native-background-fetch" +import { SyncService } from "./app/services" AppRegistry.registerComponent("fotos", () => App) + +/// register the backgroyndFetch handler. +BackgroundFetch.registerHeadlessTask(SyncService.backgroundFetchHeadlessTask) + export default App From 918e1a9ba922cc20e3a35393943673f65ec813b7 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Sun, 5 Jun 2022 14:38:25 +0430 Subject: [PATCH 16/17] Clean the AndroidManifiest. --- android/app/src/main/AndroidManifest.xml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2d867b29..4403319c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,14 +29,5 @@ - - - - - - - From 5c9b6965eddb98072c4062b75b050aa5f5dad595 Mon Sep 17 00:00:00 2001 From: Mati Q Date: Mon, 6 Jun 2022 16:39:20 +0430 Subject: [PATCH 17/17] v0.4.0 --- android/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 6b67f8b5..2a05288a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -138,8 +138,8 @@ android { applicationId "land.fx.fotos" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 3 - versionName "0.3.0" + versionCode 4 + versionName "0.4.0" } splits { abi {