From 335b0b8add338c7acff30e55610579a0baae6db1 Mon Sep 17 00:00:00 2001 From: Pablo Carballeda Date: Thu, 26 Jan 2023 20:24:44 +0100 Subject: [PATCH 01/15] Create SkeletonNote component Component for content loading --- .../Components/SkeletonNote/SkeletonNote.tsx | 153 ++++++++++++++++++ package.json | 1 + yarn.lock | 5 + 3 files changed, 159 insertions(+) create mode 100644 frontend/Components/SkeletonNote/SkeletonNote.tsx diff --git a/frontend/Components/SkeletonNote/SkeletonNote.tsx b/frontend/Components/SkeletonNote/SkeletonNote.tsx new file mode 100644 index 0000000..2de917a --- /dev/null +++ b/frontend/Components/SkeletonNote/SkeletonNote.tsx @@ -0,0 +1,153 @@ +import * as React from 'react' +import {StyleSheet, View} from 'react-native' +import {Card, useTheme, IconButton} from 'react-native-paper' +import ContentLoader, {Rect, Circle} from "react-content-loader/native" +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' + +export const SkeletonNote: React.FC = () => { + const theme = useTheme() + const skeletonBackgroundColor = theme.colors.elevation.level2 + const skeletonForegroundColor = theme.colors.elevation.level5 + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +const styles = StyleSheet.create({ + container: {}, + header: { + flexDirection: 'row', + padding: 16, + }, + headerContent: { + flex: 1, + }, + content: { + borderTopWidth: 1, + borderBottomWidth: 1, + padding: 16 + }, + footer: { + padding: 16, + flexDirection: 'row', + justifyContent: 'space-around', + }, + action: { + flexDirection: 'row', + alignItems: 'center', + }, + actionIcon: { + marginRight: 8, + } +}) \ No newline at end of file diff --git a/package.json b/package.json index 97eecbf..ccdf50c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "lodash.debounce": "^4.0.8", "nostr-tools": "^1.1.1", "react": "18.1.0", + "react-content-loader": "^6.2.0", "react-i18next": "^12.1.4", "react-native": "0.70.6", "react-native-action-button": "^2.8.5", diff --git a/yarn.lock b/yarn.lock index 50fd17d..17dda1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7167,6 +7167,11 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +react-content-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/react-content-loader/-/react-content-loader-6.2.0.tgz#cd8fee8160b8fda6610d0c69ce5aee7b8094cba6" + integrity sha512-r1dI6S+uHNLW68qraLE2njJYOuy6976PpCExuCZUcABWbfnF3FMcmuESRI8L4Bj45wnZ7n8g71hkPLzbma7/Cw== + react-devtools-core@4.24.0: version "4.24.0" resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.24.0.tgz#7daa196bdc64f3626b3f54f2ff2b96f7c4fdf017" From 4be05fd4103814a7efb24f29bf5e0d7629ec3f71 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 09:07:40 +0100 Subject: [PATCH 02/15] Remove non-FOSS libary and add validator to release workflow --- apktool.jar | 1 + frontend/Pages/AboutPage/index.tsx | 3 +-- package.json | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) create mode 120000 apktool.jar diff --git a/apktool.jar b/apktool.jar new file mode 120000 index 0000000..2154086 --- /dev/null +++ b/apktool.jar @@ -0,0 +1 @@ +apktool_2.5.0.jar \ No newline at end of file diff --git a/frontend/Pages/AboutPage/index.tsx b/frontend/Pages/AboutPage/index.tsx index 6965348..7043fc5 100644 --- a/frontend/Pages/AboutPage/index.tsx +++ b/frontend/Pages/AboutPage/index.tsx @@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next' import { FlatList, Linking, ListRenderItem, StyleSheet } from 'react-native' import { Divider, List, Text, useTheme } from 'react-native-paper' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' -import DeviceInfo from 'react-native-device-info' interface ItemList { key: number @@ -82,7 +81,7 @@ export const AboutPage: React.FC = () => { )} /> ), - right: {DeviceInfo.getVersion()}, + right: v0.2.1.4-alpha, onPress: () => {}, }, ], diff --git a/package.json b/package.json index b0e746a..97eecbf 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "react-native": "0.70.6", "react-native-action-button": "^2.8.5", "react-native-bidirectional-infinite-scroll": "^0.3.3", - "react-native-device-info": "^10.3.0", "react-native-gesture-handler": "^2.8.0", "react-native-multithreading": "^1.1.1", "react-native-pager-view": "^6.1.2", From 3aafb20e5423b3d673d1e9a75a351c5f29772284 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 09:08:23 +0100 Subject: [PATCH 03/15] Remove non-FOSS libary and add validator to release workflow --- apktool.jar | 1 - 1 file changed, 1 deletion(-) delete mode 120000 apktool.jar diff --git a/apktool.jar b/apktool.jar deleted file mode 120000 index 2154086..0000000 --- a/apktool.jar +++ /dev/null @@ -1 +0,0 @@ -apktool_2.5.0.jar \ No newline at end of file From 5c7466ff54716bee8c1b108b98cee4bfdfa086d4 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 09:15:21 +0100 Subject: [PATCH 04/15] validator to release workflow --- .github/workflows/android-build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 6a339b7..a87a583 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -31,6 +31,14 @@ jobs: cd android ./gradlew assembleRelease + - name: 'Check for non-FOSS libraries' + run: | + wget https://github.com/iBotPeaches/Apktool/releases/download/v2.5.0/apktool_2.5.0.jar + wget https://github.com/iBotPeaches/Apktool/raw/master/scripts/linux/apktool + chmod u+x apktool + ln -s apktool_2.5.0.jar apktool.jar + scanapk android/app/build/outputs/apk/release/app-universal-release.apk + - name: 'Get Commit Hash' id: commit uses: pr-mpt/actions-commit-hash@v1 From fe19711b26f37c1172e20d15711000d2c1d30322 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 09:36:15 +0100 Subject: [PATCH 05/15] validator to release workflow 2 --- .github/workflows/android-build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index a87a583..a7dea53 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -37,6 +37,14 @@ jobs: wget https://github.com/iBotPeaches/Apktool/raw/master/scripts/linux/apktool chmod u+x apktool ln -s apktool_2.5.0.jar apktool.jar + ./apktool d android/app/build/outputs/apk/release/app-universal-release.apk + # clone the repo + git clone https://gitlab.com/IzzyOnDroid/repo.git + # create a directory for Apktool and move the apktool* files there + mkdir -p repo/lib/radar/tool + mv test/apktool* repo/lib/radar/tool + # create an alias for ease of use + alias scanapk=$(pwd)/repo/bin/scanapk.php scanapk android/app/build/outputs/apk/release/app-universal-release.apk - name: 'Get Commit Hash' From 5bba834400371034fccea11d4f5e10d4f8e8257f Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 10:01:26 +0100 Subject: [PATCH 06/15] validator to release workflow 4 --- .github/workflows/android-build.yml | 2 +- repo | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 repo diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index a7dea53..bb48806 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -42,7 +42,7 @@ jobs: git clone https://gitlab.com/IzzyOnDroid/repo.git # create a directory for Apktool and move the apktool* files there mkdir -p repo/lib/radar/tool - mv test/apktool* repo/lib/radar/tool + mv app-universal-release* repo/lib/radar/tool # create an alias for ease of use alias scanapk=$(pwd)/repo/bin/scanapk.php scanapk android/app/build/outputs/apk/release/app-universal-release.apk diff --git a/repo b/repo new file mode 160000 index 0000000..3ec6717 --- /dev/null +++ b/repo @@ -0,0 +1 @@ +Subproject commit 3ec67176872422cae1ac1cf31c9348a751df37b0 From 16d7b1c4247479df6af57a4c9d5346c7b01e185a Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 10:01:42 +0100 Subject: [PATCH 07/15] Revert "validator to release workflow 4" This reverts commit 5bba834400371034fccea11d4f5e10d4f8e8257f. --- .github/workflows/android-build.yml | 2 +- repo | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 160000 repo diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index bb48806..a7dea53 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -42,7 +42,7 @@ jobs: git clone https://gitlab.com/IzzyOnDroid/repo.git # create a directory for Apktool and move the apktool* files there mkdir -p repo/lib/radar/tool - mv app-universal-release* repo/lib/radar/tool + mv test/apktool* repo/lib/radar/tool # create an alias for ease of use alias scanapk=$(pwd)/repo/bin/scanapk.php scanapk android/app/build/outputs/apk/release/app-universal-release.apk diff --git a/repo b/repo deleted file mode 160000 index 3ec6717..0000000 --- a/repo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3ec67176872422cae1ac1cf31c9348a751df37b0 From d43bd241da1c78a187e3a231b168c4ffcfa637a8 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 10:02:23 +0100 Subject: [PATCH 08/15] validator to release workflow 4 --- .github/workflows/android-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index a7dea53..bb48806 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -42,7 +42,7 @@ jobs: git clone https://gitlab.com/IzzyOnDroid/repo.git # create a directory for Apktool and move the apktool* files there mkdir -p repo/lib/radar/tool - mv test/apktool* repo/lib/radar/tool + mv app-universal-release* repo/lib/radar/tool # create an alias for ease of use alias scanapk=$(pwd)/repo/bin/scanapk.php scanapk android/app/build/outputs/apk/release/app-universal-release.apk From 55ea01e84d5172a9cce9439894c43baee8a6025c Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 10:20:01 +0100 Subject: [PATCH 09/15] validator to release workflow 5 --- .github/workflows/android-build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index bb48806..b2d331e 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -44,8 +44,7 @@ jobs: mkdir -p repo/lib/radar/tool mv app-universal-release* repo/lib/radar/tool # create an alias for ease of use - alias scanapk=$(pwd)/repo/bin/scanapk.php - scanapk android/app/build/outputs/apk/release/app-universal-release.apk + repo/bin/scanapk.php android/app/build/outputs/apk/release/app-universal-release.apk - name: 'Get Commit Hash' id: commit From 4d790e54766b47728a48f7857410487346a0ee39 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 10:34:03 +0100 Subject: [PATCH 10/15] New version release --- android/app/build.gradle | 4 ++-- frontend/Pages/AboutPage/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f3ade80..23c5f38 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -139,8 +139,8 @@ android { applicationId "com.nostros" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 30 - versionName "0.2.1.4-alpha" + versionCode 31 + versionName "0.2.1.5-alpha" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { diff --git a/frontend/Pages/AboutPage/index.tsx b/frontend/Pages/AboutPage/index.tsx index 7043fc5..f78998a 100644 --- a/frontend/Pages/AboutPage/index.tsx +++ b/frontend/Pages/AboutPage/index.tsx @@ -81,7 +81,7 @@ export const AboutPage: React.FC = () => { )} /> ), - right: v0.2.1.4-alpha, + right: v0.2.1.5-alpha, onPress: () => {}, }, ], From 21065a924edbac23f6ed379858456a616e94675a Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 08:58:37 +0100 Subject: [PATCH 11/15] New websockets library --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 2 +- .../main/java/com/nostros/classes/Relay.java | 7 +- .../java/com/nostros/classes/Websocket.java | 69 +++++++++++-------- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 23c5f38..3dd8482 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -252,7 +252,7 @@ dependencies { implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" - implementation 'com.neovisionaries:nv-websocket-client:2.14' + implementation 'org.java-websocket:Java-WebSocket:1.5.3' implementation 'com.facebook.fresco:animated-webp:2.6.0' implementation 'com.facebook.fresco:webpsupport:2.6.0' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 907e7fe..0deb2f7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ - + diff --git a/android/app/src/main/java/com/nostros/classes/Relay.java b/android/app/src/main/java/com/nostros/classes/Relay.java index 1b0e63b..17db2d6 100644 --- a/android/app/src/main/java/com/nostros/classes/Relay.java +++ b/android/app/src/main/java/com/nostros/classes/Relay.java @@ -9,6 +9,7 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.nostros.modules.DatabaseModule; import java.io.IOException; +import java.net.URISyntaxException; public class Relay { private Websocket webSocket; @@ -38,7 +39,11 @@ public class Relay { } public void connect(String userPubKey) throws IOException { - webSocket.connect(userPubKey); + try { + webSocket.connect(userPubKey); + } catch (URISyntaxException e) { + e.printStackTrace(); + } } public void save(SQLiteDatabase database) { diff --git a/android/app/src/main/java/com/nostros/classes/Websocket.java b/android/app/src/main/java/com/nostros/classes/Websocket.java index 31bb01c..ed54c29 100644 --- a/android/app/src/main/java/com/nostros/classes/Websocket.java +++ b/android/app/src/main/java/com/nostros/classes/Websocket.java @@ -6,19 +6,21 @@ import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.neovisionaries.ws.client.WebSocket; -import com.neovisionaries.ws.client.WebSocketAdapter; -import com.neovisionaries.ws.client.WebSocketFactory; -import com.neovisionaries.ws.client.WebSocketFrame; import com.nostros.modules.DatabaseModule; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; public class Websocket { - private WebSocket webSocket; + private WebSocketClient webSocket; private DatabaseModule database; private String url; private String pubKey; @@ -36,48 +38,57 @@ public class Websocket { if (!webSocket.isOpen()) { try { this.connect(pubKey); - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { e.printStackTrace(); } } - webSocket.sendText(message); + webSocket.send(message); } } public void disconnect() { if (webSocket != null) { - webSocket.disconnect(); + webSocket.close(); } } - public void connect(String userPubKey) throws IOException { - WebSocketFactory factory = new WebSocketFactory(); + public void connect(String userPubKey) throws IOException, URISyntaxException { pubKey = userPubKey; - webSocket = factory.createSocket(url); - webSocket.setMissingCloseFrameAllowed(true); - webSocket.setPingInterval(25 * 1000); - - webSocket.addListener(new WebSocketAdapter() { + webSocket = new WebSocketClient(new URI(url)) { @Override - public void onTextMessage(WebSocket websocket, String message) throws Exception { + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { Log.d("Websocket", "RECEIVE URL:" + url + " __ " + message); - JSONArray jsonArray = new JSONArray(message); - String messageType = jsonArray.get(0).toString(); - if (messageType.equals("EVENT")) { - JSONObject data = jsonArray.getJSONObject(2); - database.saveEvent(data, userPubKey); - reactNativeEvent(data.getString("id")); - } else if (messageType.equals("OK")) { - reactNativeConfirmation(jsonArray.get(1).toString()); + JSONArray jsonArray; + try { + jsonArray = new JSONArray(message); + String messageType = jsonArray.get(0).toString(); + if (messageType.equals("EVENT")) { + JSONObject data = jsonArray.getJSONObject(2); + database.saveEvent(data, userPubKey); + reactNativeEvent(data.getString("id")); + } else if (messageType.equals("OK")) { + reactNativeConfirmation(jsonArray.get(1).toString()); + } + } catch (JSONException e) { + e.printStackTrace(); } } + @Override - public void onDisconnected(WebSocket ws, WebSocketFrame serverCloseFrame, - WebSocketFrame clientCloseFrame, boolean closedByServer) { - ws.connectAsynchronously(); + public void onClose(int code, String reason, boolean remote) { + this.connect(); } - }); - webSocket.connectAsynchronously(); + + @Override + public void onError(Exception ex) { + ex.printStackTrace(); + } + }; } public void reactNativeEvent(String eventId) { From fc6e6e9bdf8d84c0ed60df2f4f1fe28b016c84c1 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 09:47:47 +0100 Subject: [PATCH 12/15] validator to release workflow 3 --- .../java/com/nostros/classes/Websocket.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/android/app/src/main/java/com/nostros/classes/Websocket.java b/android/app/src/main/java/com/nostros/classes/Websocket.java index ed54c29..9ef218b 100644 --- a/android/app/src/main/java/com/nostros/classes/Websocket.java +++ b/android/app/src/main/java/com/nostros/classes/Websocket.java @@ -62,33 +62,34 @@ public class Websocket { @Override public void onMessage(String message) { - Log.d("Websocket", "RECEIVE URL:" + url + " __ " + message); - JSONArray jsonArray; - try { - jsonArray = new JSONArray(message); - String messageType = jsonArray.get(0).toString(); - if (messageType.equals("EVENT")) { - JSONObject data = jsonArray.getJSONObject(2); - database.saveEvent(data, userPubKey); - reactNativeEvent(data.getString("id")); - } else if (messageType.equals("OK")) { - reactNativeConfirmation(jsonArray.get(1).toString()); - } - } catch (JSONException e) { - e.printStackTrace(); - } +// Log.d("Websocket", "RECEIVE URL:" + url + " __ " + message); +// JSONArray jsonArray; +// try { +// jsonArray = new JSONArray(message); +// String messageType = jsonArray.get(0).toString(); +// if (messageType.equals("EVENT")) { +// JSONObject data = jsonArray.getJSONObject(2); +// database.saveEvent(data, userPubKey); +// reactNativeEvent(data.getString("id")); +// } else if (messageType.equals("OK")) { +// reactNativeConfirmation(jsonArray.get(1).toString()); +// } +// } catch (JSONException e) { +// e.printStackTrace(); +// } } @Override public void onClose(int code, String reason, boolean remote) { - this.connect(); +// this.connect(); } @Override public void onError(Exception ex) { - ex.printStackTrace(); +// ex.printStackTrace(); } }; + webSocket.connect(); } public void reactNativeEvent(String eventId) { From e01051a8c5e981b61c84d73e5a200dad40e100b0 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 12:32:17 +0100 Subject: [PATCH 13/15] Library working --- .../java/com/nostros/classes/Websocket.java | 46 +++++++++---------- .../com/nostros/modules/DatabaseModule.java | 5 ++ .../DatabaseFunctions/Notes/index.ts | 5 +- yarn.lock | 5 -- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/android/app/src/main/java/com/nostros/classes/Websocket.java b/android/app/src/main/java/com/nostros/classes/Websocket.java index 9ef218b..466915c 100644 --- a/android/app/src/main/java/com/nostros/classes/Websocket.java +++ b/android/app/src/main/java/com/nostros/classes/Websocket.java @@ -9,6 +9,7 @@ import com.facebook.react.modules.core.DeviceEventManagerModule; import com.nostros.modules.DatabaseModule; import org.java_websocket.client.WebSocketClient; +import org.java_websocket.exceptions.WebsocketNotConnectedException; import org.java_websocket.handshake.ServerHandshake; import org.json.JSONArray; @@ -35,14 +36,11 @@ public class Websocket { public void send(String message) { if (webSocket != null) { Log.d("Websocket", "SEND URL:" + url + " __ " + message); - if (!webSocket.isOpen()) { - try { - this.connect(pubKey); - } catch (IOException | URISyntaxException e) { - e.printStackTrace(); - } + try { + webSocket.send(message); + } catch (WebsocketNotConnectedException e) { + } - webSocket.send(message); } } @@ -62,31 +60,31 @@ public class Websocket { @Override public void onMessage(String message) { -// Log.d("Websocket", "RECEIVE URL:" + url + " __ " + message); -// JSONArray jsonArray; -// try { -// jsonArray = new JSONArray(message); -// String messageType = jsonArray.get(0).toString(); -// if (messageType.equals("EVENT")) { -// JSONObject data = jsonArray.getJSONObject(2); -// database.saveEvent(data, userPubKey); -// reactNativeEvent(data.getString("id")); -// } else if (messageType.equals("OK")) { -// reactNativeConfirmation(jsonArray.get(1).toString()); -// } -// } catch (JSONException e) { -// e.printStackTrace(); -// } + Log.d("Websocket", "RECEIVE URL:" + url + " __ " + message); + JSONArray jsonArray; + try { + jsonArray = new JSONArray(message); + String messageType = jsonArray.get(0).toString(); + if (messageType.equals("EVENT")) { + JSONObject data = jsonArray.getJSONObject(2); + database.saveEvent(data, userPubKey); + reactNativeEvent(data.getString("id")); + } else if (messageType.equals("OK")) { + reactNativeConfirmation(jsonArray.get(1).toString()); + } + } catch (JSONException e) { + e.printStackTrace(); + } } @Override public void onClose(int code, String reason, boolean remote) { -// this.connect(); + webSocket.connect(); } @Override public void onError(Exception ex) { -// ex.printStackTrace(); + ex.printStackTrace(); } }; webSocket.connect(); diff --git a/android/app/src/main/java/com/nostros/modules/DatabaseModule.java b/android/app/src/main/java/com/nostros/modules/DatabaseModule.java index 26028af..1d5d103 100644 --- a/android/app/src/main/java/com/nostros/modules/DatabaseModule.java +++ b/android/app/src/main/java/com/nostros/modules/DatabaseModule.java @@ -106,6 +106,11 @@ public class DatabaseModule { try { database.execSQL("ALTER TABLE nostros_relays ADD COLUMN active BOOLEAN DEFAULT TRUE;"); } catch (SQLException e) { } + try { + database.execSQL("CREATE INDEX nostros_direct_messages_created_at_index ON nostros_direct_messages(created_at); "); + database.execSQL("CREATE INDEX nostros_notes_user_mentioned_index ON nostros_notes(user_mentioned); "); + database.execSQL("CREATE INDEX nostros_notes_created_at_index ON nostros_notes(created_at); "); + } catch (SQLException e) { } } public void saveEvent(JSONObject data, String userPubKey) throws JSONException { diff --git a/frontend/Functions/DatabaseFunctions/Notes/index.ts b/frontend/Functions/DatabaseFunctions/Notes/index.ts index df53b17..0de863f 100644 --- a/frontend/Functions/DatabaseFunctions/Notes/index.ts +++ b/frontend/Functions/DatabaseFunctions/Notes/index.ts @@ -51,10 +51,11 @@ export const getMentionNotes: ( nostros_notes.*, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes LEFT JOIN nostros_users ON nostros_users.id = nostros_notes.pubkey - WHERE (nostros_notes.reply_event_id IN ( + WHERE + nostros_notes.pubkey != '${pubKey}' + AND (nostros_notes.reply_event_id IN ( SELECT nostros_notes.id FROM nostros_notes WHERE pubkey = '${pubKey}' ) OR nostros_notes.user_mentioned = 1) - AND nostros_notes.pubkey != '${pubKey}' ORDER BY created_at DESC LIMIT ${limit} ` diff --git a/yarn.lock b/yarn.lock index d0af661..17dda1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7230,11 +7230,6 @@ react-native-codegen@^0.70.6: jscodeshift "^0.13.1" nullthrows "^1.1.1" -react-native-device-info@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-10.3.0.tgz#6bab64d84d3415dd00cc446c73ec5e2e61fddbe7" - integrity sha512-/ziZN1sA1REbJTv5mQZ4tXggcTvSbct+u5kCaze8BmN//lbxcTvWsU6NQd4IihLt89VkbX+14IGc9sVApSxd/w== - react-native-gesture-handler@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.8.0.tgz#ef9857871c10663c95a51546225b6e00cd4740cf" From 39ff0f9565afccdc3d89248a2bef989ca618484e Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 12:31:54 +0000 Subject: [PATCH 14/15] Revert "New websockets library" --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 2 +- .../main/java/com/nostros/classes/Relay.java | 7 +- .../java/com/nostros/classes/Websocket.java | 76 ++++++++----------- .../com/nostros/modules/DatabaseModule.java | 5 -- .../DatabaseFunctions/Notes/index.ts | 5 +- yarn.lock | 5 ++ 7 files changed, 43 insertions(+), 59 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 3dd8482..23c5f38 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -252,7 +252,7 @@ dependencies { implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" - implementation 'org.java-websocket:Java-WebSocket:1.5.3' + implementation 'com.neovisionaries:nv-websocket-client:2.14' implementation 'com.facebook.fresco:animated-webp:2.6.0' implementation 'com.facebook.fresco:webpsupport:2.6.0' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0deb2f7..907e7fe 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ - + diff --git a/android/app/src/main/java/com/nostros/classes/Relay.java b/android/app/src/main/java/com/nostros/classes/Relay.java index 17db2d6..1b0e63b 100644 --- a/android/app/src/main/java/com/nostros/classes/Relay.java +++ b/android/app/src/main/java/com/nostros/classes/Relay.java @@ -9,7 +9,6 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.nostros.modules.DatabaseModule; import java.io.IOException; -import java.net.URISyntaxException; public class Relay { private Websocket webSocket; @@ -39,11 +38,7 @@ public class Relay { } public void connect(String userPubKey) throws IOException { - try { - webSocket.connect(userPubKey); - } catch (URISyntaxException e) { - e.printStackTrace(); - } + webSocket.connect(userPubKey); } public void save(SQLiteDatabase database) { diff --git a/android/app/src/main/java/com/nostros/classes/Websocket.java b/android/app/src/main/java/com/nostros/classes/Websocket.java index 466915c..31bb01c 100644 --- a/android/app/src/main/java/com/nostros/classes/Websocket.java +++ b/android/app/src/main/java/com/nostros/classes/Websocket.java @@ -6,22 +6,19 @@ import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.neovisionaries.ws.client.WebSocket; +import com.neovisionaries.ws.client.WebSocketAdapter; +import com.neovisionaries.ws.client.WebSocketFactory; +import com.neovisionaries.ws.client.WebSocketFrame; import com.nostros.modules.DatabaseModule; -import org.java_websocket.client.WebSocketClient; -import org.java_websocket.exceptions.WebsocketNotConnectedException; -import org.java_websocket.handshake.ServerHandshake; - import org.json.JSONArray; -import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; public class Websocket { - private WebSocketClient webSocket; + private WebSocket webSocket; private DatabaseModule database; private String url; private String pubKey; @@ -36,58 +33,51 @@ public class Websocket { public void send(String message) { if (webSocket != null) { Log.d("Websocket", "SEND URL:" + url + " __ " + message); - try { - webSocket.send(message); - } catch (WebsocketNotConnectedException e) { - + if (!webSocket.isOpen()) { + try { + this.connect(pubKey); + } catch (IOException e) { + e.printStackTrace(); + } } + webSocket.sendText(message); } } public void disconnect() { if (webSocket != null) { - webSocket.close(); + webSocket.disconnect(); } } - public void connect(String userPubKey) throws IOException, URISyntaxException { + public void connect(String userPubKey) throws IOException { + WebSocketFactory factory = new WebSocketFactory(); pubKey = userPubKey; - webSocket = new WebSocketClient(new URI(url)) { - @Override - public void onOpen(ServerHandshake handshakedata) { - - } + webSocket = factory.createSocket(url); + webSocket.setMissingCloseFrameAllowed(true); + webSocket.setPingInterval(25 * 1000); + webSocket.addListener(new WebSocketAdapter() { @Override - public void onMessage(String message) { + public void onTextMessage(WebSocket websocket, String message) throws Exception { Log.d("Websocket", "RECEIVE URL:" + url + " __ " + message); - JSONArray jsonArray; - try { - jsonArray = new JSONArray(message); - String messageType = jsonArray.get(0).toString(); - if (messageType.equals("EVENT")) { - JSONObject data = jsonArray.getJSONObject(2); - database.saveEvent(data, userPubKey); - reactNativeEvent(data.getString("id")); - } else if (messageType.equals("OK")) { - reactNativeConfirmation(jsonArray.get(1).toString()); - } - } catch (JSONException e) { - e.printStackTrace(); + JSONArray jsonArray = new JSONArray(message); + String messageType = jsonArray.get(0).toString(); + if (messageType.equals("EVENT")) { + JSONObject data = jsonArray.getJSONObject(2); + database.saveEvent(data, userPubKey); + reactNativeEvent(data.getString("id")); + } else if (messageType.equals("OK")) { + reactNativeConfirmation(jsonArray.get(1).toString()); } } - @Override - public void onClose(int code, String reason, boolean remote) { - webSocket.connect(); + public void onDisconnected(WebSocket ws, WebSocketFrame serverCloseFrame, + WebSocketFrame clientCloseFrame, boolean closedByServer) { + ws.connectAsynchronously(); } - - @Override - public void onError(Exception ex) { - ex.printStackTrace(); - } - }; - webSocket.connect(); + }); + webSocket.connectAsynchronously(); } public void reactNativeEvent(String eventId) { diff --git a/android/app/src/main/java/com/nostros/modules/DatabaseModule.java b/android/app/src/main/java/com/nostros/modules/DatabaseModule.java index 1d5d103..26028af 100644 --- a/android/app/src/main/java/com/nostros/modules/DatabaseModule.java +++ b/android/app/src/main/java/com/nostros/modules/DatabaseModule.java @@ -106,11 +106,6 @@ public class DatabaseModule { try { database.execSQL("ALTER TABLE nostros_relays ADD COLUMN active BOOLEAN DEFAULT TRUE;"); } catch (SQLException e) { } - try { - database.execSQL("CREATE INDEX nostros_direct_messages_created_at_index ON nostros_direct_messages(created_at); "); - database.execSQL("CREATE INDEX nostros_notes_user_mentioned_index ON nostros_notes(user_mentioned); "); - database.execSQL("CREATE INDEX nostros_notes_created_at_index ON nostros_notes(created_at); "); - } catch (SQLException e) { } } public void saveEvent(JSONObject data, String userPubKey) throws JSONException { diff --git a/frontend/Functions/DatabaseFunctions/Notes/index.ts b/frontend/Functions/DatabaseFunctions/Notes/index.ts index 0de863f..df53b17 100644 --- a/frontend/Functions/DatabaseFunctions/Notes/index.ts +++ b/frontend/Functions/DatabaseFunctions/Notes/index.ts @@ -51,11 +51,10 @@ export const getMentionNotes: ( nostros_notes.*, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes LEFT JOIN nostros_users ON nostros_users.id = nostros_notes.pubkey - WHERE - nostros_notes.pubkey != '${pubKey}' - AND (nostros_notes.reply_event_id IN ( + WHERE (nostros_notes.reply_event_id IN ( SELECT nostros_notes.id FROM nostros_notes WHERE pubkey = '${pubKey}' ) OR nostros_notes.user_mentioned = 1) + AND nostros_notes.pubkey != '${pubKey}' ORDER BY created_at DESC LIMIT ${limit} ` diff --git a/yarn.lock b/yarn.lock index 17dda1d..d0af661 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7230,6 +7230,11 @@ react-native-codegen@^0.70.6: jscodeshift "^0.13.1" nullthrows "^1.1.1" +react-native-device-info@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-10.3.0.tgz#6bab64d84d3415dd00cc446c73ec5e2e61fddbe7" + integrity sha512-/ziZN1sA1REbJTv5mQZ4tXggcTvSbct+u5kCaze8BmN//lbxcTvWsU6NQd4IihLt89VkbX+14IGc9sVApSxd/w== + react-native-gesture-handler@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.8.0.tgz#ef9857871c10663c95a51546225b6e00cd4740cf" From d195f5d6984b442cd68478dc9831954c8508f718 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 27 Jan 2023 22:03:09 +0100 Subject: [PATCH 15/15] Config page and global feed --- .../main/java/com/nostros/classes/Event.java | 2 +- .../com/nostros/modules/DatabaseModule.java | 27 ++ frontend/Components/LnPayment/index.tsx | 23 +- frontend/Components/MenuItems/index.tsx | 18 +- frontend/Components/NoteCard/index.tsx | 75 +++-- frontend/Components/ProfileCard/index.tsx | 51 +++- .../Components/SkeletonNote/SkeletonNote.tsx | 286 +++++++++--------- frontend/Contexts/AppContext.tsx | 67 +++- frontend/Contexts/UserContext.tsx | 23 +- .../DatabaseFunctions/Config/index.ts | 34 +++ .../DatabaseFunctions/Notes/index.ts | 39 ++- .../DatabaseFunctions/Users/index.ts | 12 + frontend/Locales/en.json | 15 +- frontend/Locales/es.json | 15 +- frontend/Locales/ru.json | 15 +- frontend/Pages/ConfigPage/index.tsx | 100 ++++++ frontend/Pages/ContactsFeed/index.tsx | 7 +- frontend/Pages/FeedNavigator/index.tsx | 2 + frontend/Pages/GlobalFeed/index.tsx | 190 ++++++++++++ frontend/Pages/HomeFeed/index.tsx | 267 +++++++--------- frontend/Pages/HomeNavigator/index.tsx | 2 + frontend/Pages/MyFeed/index.tsx | 192 ++++++++++++ frontend/Pages/ProfileConfigPage/index.tsx | 12 +- frontend/Pages/ProfileLoadPage/index.tsx | 11 +- frontend/Pages/RelaysPage/index.tsx | 77 +++-- yarn.lock | 5 - 26 files changed, 1122 insertions(+), 445 deletions(-) create mode 100644 frontend/Functions/DatabaseFunctions/Config/index.ts create mode 100644 frontend/Pages/ConfigPage/index.tsx create mode 100644 frontend/Pages/GlobalFeed/index.tsx create mode 100644 frontend/Pages/MyFeed/index.tsx diff --git a/android/app/src/main/java/com/nostros/classes/Event.java b/android/app/src/main/java/com/nostros/classes/Event.java index 5792c0f..19b21d5 100644 --- a/android/app/src/main/java/com/nostros/classes/Event.java +++ b/android/app/src/main/java/com/nostros/classes/Event.java @@ -66,7 +66,7 @@ public class Event { } protected boolean isValid() { - return !id.isEmpty() && !sig.isEmpty(); + return !id.isEmpty() && !sig.isEmpty() && created_at <= System.currentTimeMillis() / 1000L; } protected String getMainEventId() { diff --git a/android/app/src/main/java/com/nostros/modules/DatabaseModule.java b/android/app/src/main/java/com/nostros/modules/DatabaseModule.java index 26028af..8ab3695 100644 --- a/android/app/src/main/java/com/nostros/modules/DatabaseModule.java +++ b/android/app/src/main/java/com/nostros/modules/DatabaseModule.java @@ -68,6 +68,7 @@ public class DatabaseModule { try { database.execSQL("ALTER TABLE nostros_users ADD COLUMN created_at INT DEFAULT 0;"); } catch (SQLException e) { } + try { database.execSQL("CREATE TABLE IF NOT EXISTS nostros_reactions(\n" + " id TEXT PRIMARY KEY NOT NULL, \n" + " content TEXT NOT NULL,\n" + @@ -80,6 +81,7 @@ public class DatabaseModule { " reacted_event_id TEXT,\n" + " reacted_user_id TEXT\n" + " );"); + } catch (SQLException e) { } try { database.execSQL("CREATE INDEX nostros_notes_pubkey_index ON nostros_notes(pubkey); "); database.execSQL("CREATE INDEX nostros_notes_main_event_id_index ON nostros_notes(main_event_id); "); @@ -106,6 +108,31 @@ public class DatabaseModule { try { database.execSQL("ALTER TABLE nostros_relays ADD COLUMN active BOOLEAN DEFAULT TRUE;"); } catch (SQLException e) { } + try { + database.execSQL("CREATE INDEX nostros_notes_main_index ON nostros_notes(pubkey, main_event_id, created_at);"); + database.execSQL("CREATE INDEX nostros_notes_kind_index ON nostros_notes(repost_id, pubkey, created_at); "); + database.execSQL("CREATE INDEX nostros_notes_notifications_index ON nostros_notes(pubkey, user_mentioned, reply_event_id, created_at); "); + database.execSQL("CREATE INDEX nostros_notes_repost_id_index ON nostros_notes(pubkey, repost_id); "); + database.execSQL("CREATE INDEX nostros_notes_reply_event_id_count_index ON nostros_notes(created_at, reply_event_id); "); + + database.execSQL("CREATE INDEX nostros_direct_messages_created_at_index ON nostros_direct_messages(created_at); "); + database.execSQL("CREATE INDEX nostros_direct_messages_created_at_conversation_id_index ON nostros_direct_messages(created_at, conversation_id); "); + + database.execSQL("CREATE INDEX nostros_reactions_created_at_reacted_event_id_index ON nostros_reactions(created_at, reacted_event_id); "); + + database.execSQL("CREATE INDEX nostros_users_contact_index ON nostros_users(contact, follower); "); + database.execSQL("CREATE INDEX nostros_users_contact_index ON nostros_users(id, name); "); + } catch (SQLException e) { } + try { + database.execSQL("CREATE TABLE IF NOT EXISTS nostros_config(\n" + + " satoshi TEXT NOT NULL,\n" + + " show_public_images BOOLEAN DEFAULT FALSE,\n" + + " show_sensitive BOOLEAN DEFAULT FALSE\n" + + " );"); + } catch (SQLException e) { } + try { + database.execSQL("ALTER TABLE nostros_users ADD COLUMN blocked BOOLEAN DEFAULT FALSE;"); + } catch (SQLException e) { } } public void saveEvent(JSONObject data, String userPubKey) throws JSONException { diff --git a/frontend/Components/LnPayment/index.tsx b/frontend/Components/LnPayment/index.tsx index ef628db..ac48716 100644 --- a/frontend/Components/LnPayment/index.tsx +++ b/frontend/Components/LnPayment/index.tsx @@ -8,6 +8,7 @@ import Clipboard from '@react-native-clipboard/clipboard' import { useTranslation } from 'react-i18next' import RBSheet from 'react-native-raw-bottom-sheet' import { Button, Card, IconButton, Text, TextInput, useTheme } from 'react-native-paper' +import { AppContext } from '../../Contexts/AppContext' interface TextContentProps { open: boolean @@ -19,6 +20,7 @@ interface TextContentProps { export const LnPayment: React.FC = ({ open, setOpen, event, user }) => { const theme = useTheme() const { t } = useTranslation('common') + const { getSatoshiSymbol } = React.useContext(AppContext) const bottomSheetLnPaymentRef = React.useRef(null) const bottomSheetInvoiceRef = React.useRef(null) const [monto, setMonto] = useState('') @@ -91,22 +93,13 @@ export const LnPayment: React.FC = ({ open, setOpen, event, us = ({ open, setOpen, event, us - - s - - {monto} + {monto} + {getSatoshiSymbol(23)} {comment && ( diff --git a/frontend/Components/MenuItems/index.tsx b/frontend/Components/MenuItems/index.tsx index e39c97b..95b8e15 100644 --- a/frontend/Components/MenuItems/index.tsx +++ b/frontend/Components/MenuItems/index.tsx @@ -34,10 +34,12 @@ export const MenuItems: React.FC = () => { setDrawerItemIndex(index) if (key === 'relays') { navigate('Relays') - } else if (key === 'config') { - navigate('Feed', { page: 'Config' }) + } else if (key === 'configProfile') { + navigate('Feed', { page: 'ProfileConfig' }) } else if (key === 'about') { navigate('About') + } else if (key === 'config') { + navigate('Config') } } @@ -89,7 +91,7 @@ export const MenuItems: React.FC = () => { )} {publicKey && ( - + ( @@ -117,6 +119,16 @@ export const MenuItems: React.FC = () => { /> )} + + onPressItem('config', 1)} + onTouchEnd={() => setDrawerItemIndex(-1)} + /> + void + showAvatarImage?: boolean showAnswerData?: boolean showAction?: boolean + showActionCount?: boolean showPreview?: boolean showRepostPreview?: boolean numberOfLines?: number @@ -48,8 +50,10 @@ interface NoteCardProps { export const NoteCard: React.FC = ({ note, + showAvatarImage = true, showAnswerData = true, showAction = true, + showActionCount = true, showPreview = true, showRepostPreview = true, onPressUser = () => {}, @@ -59,7 +63,7 @@ export const NoteCard: React.FC = ({ const theme = useTheme() const { publicKey, privateKey } = React.useContext(UserContext) const { relayPool, lastEventId } = useContext(RelayPoolContext) - const { database } = useContext(AppContext) + const { database, showSensitive } = useContext(AppContext) const [relayAdded, setRelayAdded] = useState(false) const [positiveReactions, setPositiveReactions] = useState(0) const [negaiveReactions, setNegativeReactions] = useState(0) @@ -72,7 +76,7 @@ export const NoteCard: React.FC = ({ const [repost, setRepost] = useState() useEffect(() => { - if (database && publicKey && showAction && note?.id) { + if (database && publicKey && showAction && showActionCount && note?.id) { getReactions(database, { eventId: note.id }).then((result) => { const total = result.length let positive = 0 @@ -152,7 +156,7 @@ export const NoteCard: React.FC = ({ )} - {hide ? ( + {hide && !showSensitive ? ( @@ -208,7 +212,7 @@ export const NoteCard: React.FC = ({ {!relayAdded && note && REGEX_SOCKET_LINK.test(note.content) && ( - + )} @@ -217,8 +221,33 @@ export const NoteCard: React.FC = ({ ) } + const blockedContent: () => JSX.Element = () => { + return ( + + + + + + + + {t('noteCard.userBlocked')} + + + + + ) + } + const getNoteContent: () => JSX.Element | undefined = () => { - if (note?.kind === Kind.Text) { + if (note?.blocked) { + return blockedContent() + } else if (note?.kind === Kind.Text) { return textNote() } else if (note?.kind === Kind.RecommendRelay) return recommendServer() } @@ -233,24 +262,24 @@ export const NoteCard: React.FC = ({ validNip05={note?.valid_nip05} nip05={note?.nip05} lud06={note?.lnurl} - picture={note?.picture} + picture={showAvatarImage ? note?.picture : undefined} timestamp={note?.created_at} avatarSize={56} /> - - {showAction && ( + {showAction && ( + onPressUser({ id: note.pubkey, name: note.name })} /> - )} - + + )} {getNoteContent()} - {showAction && ( - + {showAction && !note?.blocked && ( + )} @@ -335,6 +368,10 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', alignContent: 'center', }, + userBlocked: { + justifyContent: 'center', + textAlign: 'center', + }, titleUser: { flexDirection: 'row', alignContent: 'center', @@ -348,12 +385,16 @@ const styles = StyleSheet.create({ padding: 16, justifyContent: 'space-between', }, - actions: { + bottomActions: { paddingTop: 16, flexDirection: 'row', justifyContent: 'space-around', borderTopWidth: 1, }, + topAction: { + flexDirection: 'row', + justifyContent: 'space-around', + }, relayActions: { paddingTop: 16, flexDirection: 'row', diff --git a/frontend/Components/ProfileCard/index.tsx b/frontend/Components/ProfileCard/index.tsx index a5472d5..9581798 100644 --- a/frontend/Components/ProfileCard/index.tsx +++ b/frontend/Components/ProfileCard/index.tsx @@ -6,7 +6,13 @@ import { Card, IconButton, Snackbar, Text, useTheme } from 'react-native-paper' import { AppContext } from '../../Contexts/AppContext' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { UserContext } from '../../Contexts/UserContext' -import { getUser, updateUserContact, User } from '../../Functions/DatabaseFunctions/Users' +import { + addUser, + getUser, + updateUserBlock, + updateUserContact, + User, +} from '../../Functions/DatabaseFunctions/Users' import { populatePets, usernamePubKey } from '../../Functions/RelayFunctions/Users' import LnPayment from '../LnPayment' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' @@ -18,14 +24,20 @@ import ProfileData from '../ProfileData' interface ProfileCardProps { userPubKey: string bottomSheetRef: React.RefObject + showImages: boolean } -export const ProfileCard: React.FC = ({ userPubKey, bottomSheetRef }) => { +export const ProfileCard: React.FC = ({ + userPubKey, + bottomSheetRef, + showImages = true, +}) => { const theme = useTheme() const { database } = React.useContext(AppContext) const { publicKey } = React.useContext(UserContext) const { relayPool } = React.useContext(RelayPoolContext) const [user, setUser] = React.useState() + const [blocked, setBlocked] = React.useState() const [openLn, setOpenLn] = React.useState(false) const [isContact, setIsContact] = React.useState() const [showNotification, setShowNotification] = React.useState() @@ -36,6 +48,15 @@ export const ProfileCard: React.FC = ({ userPubKey, bottomShee loadUser() }, []) + const onChangeBlockUser: () => void = () => { + if (database && blocked !== undefined) { + updateUserBlock(userPubKey, database, !blocked).then(() => { + setBlocked(!blocked) + loadUser() + }) + } + } + const removeContact: () => void = () => { if (relayPool && database && publicKey) { updateUserContact(userPubKey, database, false).then(() => { @@ -61,7 +82,13 @@ export const ProfileCard: React.FC = ({ userPubKey, bottomShee getUser(userPubKey, database).then((result) => { if (result) { setUser(result) + setBlocked(result.blocked) setIsContact(result?.contact) + } else { + addUser(userPubKey, database).then(() => { + setUser({ id: userPubKey }) + setBlocked(false) + }) } }) } @@ -80,11 +107,11 @@ export const ProfileCard: React.FC = ({ userPubKey, bottomShee @@ -118,6 +145,14 @@ export const ProfileCard: React.FC = ({ userPubKey, bottomShee {isContact ? t('profileCard.unfollow') : t('profileCard.follow')} )} + + + {t(blocked ? 'profileCard.unblock' : 'profileCard.block')} + = ({ userPubKey, bottomShee /> {t('profileCard.copyNPub')} - - {user?.lnurl && ( + {user?.lnurl && ( + <> = ({ userPubKey, bottomShee /> {t('profileCard.invoice')} - )} - + + )} {showNotification && ( { - const theme = useTheme() - const skeletonBackgroundColor = theme.colors.elevation.level2 - const skeletonForegroundColor = theme.colors.elevation.level5 + const theme = useTheme() + const skeletonBackgroundColor = theme.colors.elevation.level2 + const skeletonForegroundColor = theme.colors.elevation.level5 - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } const styles = StyleSheet.create({ - container: {}, - header: { - flexDirection: 'row', - padding: 16, - }, - headerContent: { - flex: 1, - }, - content: { - borderTopWidth: 1, - borderBottomWidth: 1, - padding: 16 - }, - footer: { - padding: 16, - flexDirection: 'row', - justifyContent: 'space-around', - }, - action: { - flexDirection: 'row', - alignItems: 'center', - }, - actionIcon: { - marginRight: 8, - } -}) \ No newline at end of file + container: {}, + header: { + flexDirection: 'row', + padding: 16, + }, + headerContent: { + flex: 1, + }, + content: { + borderTopWidth: 1, + borderBottomWidth: 1, + padding: 16, + }, + footer: { + padding: 16, + flexDirection: 'row', + justifyContent: 'space-around', + }, + action: { + flexDirection: 'row', + alignItems: 'center', + }, + actionIcon: { + marginRight: 8, + }, +}) diff --git a/frontend/Contexts/AppContext.tsx b/frontend/Contexts/AppContext.tsx index 722c034..e307123 100644 --- a/frontend/Contexts/AppContext.tsx +++ b/frontend/Contexts/AppContext.tsx @@ -2,12 +2,21 @@ import React, { useEffect, useState } from 'react' import { QuickSQLiteConnection } from 'react-native-quick-sqlite' import { initDatabase } from '../Functions/DatabaseFunctions' import SInfo from 'react-native-sensitive-info' -import { Linking } from 'react-native' +import { Linking, StyleSheet } from 'react-native' +import { Config, getConfig, updateConfig } from '../Functions/DatabaseFunctions/Config' +import { Text } from 'react-native-paper' export interface AppContextProps { init: () => void loadingDb: boolean database: QuickSQLiteConnection | null + showPublicImages: boolean + setShowPublicImages: (showPublicImages: boolean) => void + showSensitive: boolean + setShowSensitive: (showPublicImages: boolean) => void + satoshi: 'kebab' | 'sats' + setSatoshi: (showPublicImages: 'kebab' | 'sats') => void + getSatoshiSymbol: (fontSize?: number) => JSX.Element } export interface AppContextProviderProps { @@ -18,9 +27,21 @@ export const initialAppContext: AppContextProps = { init: () => {}, loadingDb: true, database: null, + showPublicImages: false, + setShowPublicImages: () => {}, + showSensitive: false, + setShowSensitive: () => {}, + satoshi: 'kebab', + setSatoshi: () => {}, + getSatoshiSymbol: () => <>, } export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => { + const [showPublicImages, setShowPublicImages] = React.useState( + initialAppContext.showPublicImages, + ) + const [showSensitive, setShowSensitive] = React.useState(initialAppContext.showSensitive) + const [satoshi, setSatoshi] = React.useState<'kebab' | 'sats'>(initialAppContext.satoshi) const [database, setDatabase] = useState(null) const [loadingDb, setLoadingDb] = useState(initialAppContext.loadingDb) @@ -35,14 +56,52 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E }) } + const getSatoshiSymbol: (fontSize?: number) => JSX.Element = (fontSize) => { + return satoshi === 'sats' ? ( + Sats + ) : ( + s + ) + } + useEffect(init, []) + useEffect(() => { + if (database) { + getConfig(database).then((result) => { + if (result) { + setShowPublicImages(result.show_public_images ?? initialAppContext.showPublicImages) + setShowSensitive(result.show_sensitive ?? initialAppContext.showSensitive) + setSatoshi(result.satoshi) + } + }) + } + }, [database]) + + useEffect(() => { + if (database) { + const config: Config = { + show_public_images: showPublicImages, + show_sensitive: showSensitive, + satoshi, + } + updateConfig(config, database) + } + }, [database]) + return ( {children} @@ -50,4 +109,10 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E ) } +const styles = StyleSheet.create({ + satoshi: { + fontFamily: 'Satoshi-Symbol', + }, +}) + export const AppContext = React.createContext(initialAppContext) diff --git a/frontend/Contexts/UserContext.tsx b/frontend/Contexts/UserContext.tsx index 954642f..7f32d4c 100644 --- a/frontend/Contexts/UserContext.tsx +++ b/frontend/Contexts/UserContext.tsx @@ -2,12 +2,7 @@ import React, { useContext, useEffect, useState } from 'react' import SInfo from 'react-native-sensitive-info' import { RelayPoolContext } from './RelayPoolContext' import { AppContext } from './AppContext' -import { - getContactsCount, - getFollowersCount, - getUser, - User, -} from '../Functions/DatabaseFunctions/Users' +import { getUser, User } from '../Functions/DatabaseFunctions/Users' import { getPublicKey } from 'nostr-tools' import { dropTables } from '../Functions/DatabaseFunctions' import { navigate } from '../lib/Navigation' @@ -27,10 +22,6 @@ export interface UserContextProps { setPrivateKey: (privateKey: string | undefined) => void setUser: (user: User) => void user?: User - contactsCount: number - followersCount: number - setContantsCount: (count: number) => void - setFollowersCount: (count: number) => void reloadUser: () => void logout: () => void } @@ -47,10 +38,6 @@ export const initialUserContext: UserContextProps = { setUser: () => {}, reloadUser: () => {}, logout: () => {}, - setContantsCount: () => {}, - setFollowersCount: () => {}, - contactsCount: 0, - followersCount: 0, } export const UserContextProvider = ({ children }: UserContextProviderProps): JSX.Element => { @@ -63,8 +50,6 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX const [privateKey, setPrivateKey] = useState() const [user, setUser] = React.useState() const [clipboardLoads, setClipboardLoads] = React.useState([]) - const [contactsCount, setContantsCount] = React.useState(0) - const [followersCount, setFollowersCount] = React.useState(0) const reloadUser: () => void = () => { if (database && publicKey) { @@ -78,8 +63,6 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX } checkClipboard() }) - getContactsCount(database).then(setContantsCount) - getFollowersCount(database).then(setFollowersCount) } } @@ -169,12 +152,8 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX privateKey, setPrivateKey, user, - contactsCount, - followersCount, reloadUser, logout, - setContantsCount, - setFollowersCount, }} > {children} diff --git a/frontend/Functions/DatabaseFunctions/Config/index.ts b/frontend/Functions/DatabaseFunctions/Config/index.ts new file mode 100644 index 0000000..c9d239c --- /dev/null +++ b/frontend/Functions/DatabaseFunctions/Config/index.ts @@ -0,0 +1,34 @@ +import { getItems } from '..' +import { QuickSQLiteConnection, QueryResult } from 'react-native-quick-sqlite' + +export interface Config { + satoshi: 'kebab' | 'sats' + show_public_images?: boolean + show_sensitive?: boolean +} + +const databaseToEntity: (object: object) => Config = (object) => { + return object as Config +} + +export const getConfig: (db: QuickSQLiteConnection) => Promise = async (db) => { + const userQuery = `SELECT * FROM nostros_config LIMIT 1;` + const resultSet = await db.execute(userQuery) + + if (resultSet.rows && resultSet.rows?.length > 0) { + const items: object[] = getItems(resultSet) + const user: Config = databaseToEntity(items[0]) + return user + } else { + return null + } +} + +export const updateConfig: ( + config: Config, + db: QuickSQLiteConnection, +) => Promise = async (config, db) => { + const configQuery = `UPDATE nostros_config SET satoshi = ?, show_public_images = ?, show_sensitive = ?` + + return db.execute(configQuery, [config.satoshi, config.show_public_images, config.show_sensitive]) +} diff --git a/frontend/Functions/DatabaseFunctions/Notes/index.ts b/frontend/Functions/DatabaseFunctions/Notes/index.ts index df53b17..36d3642 100644 --- a/frontend/Functions/DatabaseFunctions/Notes/index.ts +++ b/frontend/Functions/DatabaseFunctions/Notes/index.ts @@ -11,6 +11,7 @@ export interface Note extends Event { nip05: string valid_nip05: boolean repost_id: string + blocked: boolean } const databaseToEntity: (object: any) => Note = (object = {}) => { @@ -22,14 +23,26 @@ export const getMainNotes: ( db: QuickSQLiteConnection, pubKey: string, limit: number, -) => Promise = async (db, pubKey, limit) => { - const notesQuery = ` + contants: boolean, + filters?: { + until?: number + }, +) => Promise = async (db, pubKey, limit, contants, filters) => { + let notesQuery = ` SELECT - nostros_notes.*, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes + nostros_notes.*, nostros_users.nip05, nostros_users.blocked, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes LEFT JOIN nostros_users ON nostros_users.id = nostros_notes.pubkey - WHERE (nostros_users.contact = 1 OR nostros_notes.pubkey = '${pubKey}') - AND nostros_notes.main_event_id IS NULL + WHERE + ` + + if (contants) + notesQuery += `(nostros_users.contact = 1 OR nostros_notes.pubkey = '${pubKey}') AND ` + + if (filters?.until) notesQuery += `nostros_notes.created_at < ${filters?.until} AND ` + + notesQuery += ` + nostros_notes.main_event_id IS NULL ORDER BY created_at DESC LIMIT ${limit} ` @@ -41,6 +54,22 @@ export const getMainNotes: ( return notes } +export const getMainNotesCount: ( + db: QuickSQLiteConnection, + from: number, +) => Promise = async (db, from) => { + const repliesQuery = ` + SELECT + COUNT(*) + FROM nostros_notes + WHERE created_at > "${from}" + ` + const resultSet = db.execute(repliesQuery) + const item: { 'COUNT(*)': number } = resultSet?.rows?.item(0) + + return item['COUNT(*)'] ?? 0 +} + export const getMentionNotes: ( db: QuickSQLiteConnection, pubKey: string, diff --git a/frontend/Functions/DatabaseFunctions/Users/index.ts b/frontend/Functions/DatabaseFunctions/Users/index.ts index b9f2e76..a1a5c39 100644 --- a/frontend/Functions/DatabaseFunctions/Users/index.ts +++ b/frontend/Functions/DatabaseFunctions/Users/index.ts @@ -13,6 +13,7 @@ export interface User { nip05?: string created_at?: number valid_nip05?: boolean + blocked?: boolean } const databaseToEntity: (object: object) => User = (object) => { @@ -30,6 +31,17 @@ export const updateUserContact: ( return db.execute(userQuery, [contact ? 1 : 0, userId]) } +export const updateUserBlock: ( + userId: string, + db: QuickSQLiteConnection, + blocked: boolean, +) => Promise = async (userId, db, blocked) => { + const userQuery = `UPDATE nostros_users SET blocked = ? WHERE id = ?` + + await addUser(userId, db) + return db.execute(userQuery, [blocked ? 1 : 0, userId]) +} + export const getUser: (pubkey: string, db: QuickSQLiteConnection) => Promise = async ( pubkey, db, diff --git a/frontend/Locales/en.json b/frontend/Locales/en.json index b25f064..af50925 100644 --- a/frontend/Locales/en.json +++ b/frontend/Locales/en.json @@ -23,6 +23,7 @@ "Note": "Note", "Profile": "Profile", "About": "About", + "Config": "Config", "Send": "Send", "Relays": "Relays", "ProfileConfig": "My profile" @@ -57,9 +58,15 @@ "about": "About", "logout": "Logout" }, + "configPage": { + "showPublicImages": "Show images on public feed", + "showSensitive": "Show sensitive notes", + "satoshi": "Satoshi symbol" + }, "noteCard": { "answering": "Answer to {{pubkey}}", "reposting": "Reposted {{pubkey}}", + "userBlocked": "User blocked", "seeParent": "See note", "contentWarning": "Sensitive content" }, @@ -128,7 +135,11 @@ "homeFeed": { "emptyTitle": "You are not following anyone.", "emptyDescription": "Follow other profiles to see content.", - "emptyButton": "Go to contacts" + "emptyButton": "Go to contacts", + "globalFeed": "Global feed", + "myFeed": "My feed", + "newMessage": "{{newNotesCount}} new note. Pull to refresh.", + "newMessages": "{{newNotesCount}} new notes. Pull to refresh." }, "relaysPage": { "labelAdd": "Relay address", @@ -204,6 +215,8 @@ "message": "Message", "follow": "Follow", "unfollow": "Following", + "block": "Block", + "unblock": "Unblock", "copyNPub": "Copy key" }, "conversationsFeed": { diff --git a/frontend/Locales/es.json b/frontend/Locales/es.json index 9493eeb..a9acb4f 100644 --- a/frontend/Locales/es.json +++ b/frontend/Locales/es.json @@ -23,6 +23,7 @@ "Note": "Nota", "Profile": "Perfil", "About": "About", + "Config": "Config", "Send": "Send", "Relays": "Relays", "ProfileConfig": "My profile" @@ -57,10 +58,16 @@ "about": "Sobre Nostros", "logout": "Salir" }, + "configPage": { + "showPublicImages": "Show images on public feed", + "showSensitive": "Show sensitive notes", + "satoshi": "Satoshi symbol" + }, "noteCard": { "answering": "Responder a {{pubkey}}", "reposting": "Reposted {{pubkey}}", "seeParent": "See note", + "userBlocked": "User blocked", "contentWarning": "Sensitive content" }, "lnPayment": { @@ -129,7 +136,11 @@ "homeFeed": { "emptyTitle": "No sigues a nadie", "emptyDescription": "Sigue otros perfiles para ver aquí contenido.", - "emptyButton": "Ir a contactos" + "emptyButton": "Ir a contactos", + "globalFeed": "Fee global", + "newMessage": "{{newNotesCount}} nota nueva. Desliza para recargar.", + "newMessages": "{{newNotesCount}} notas nuevas. Desliza para refrescar.", + "myFeed": "Mi feed" }, "relaysPage": { "labelAdd": "Dirección de relay", @@ -202,6 +213,8 @@ "invoice": "Propina", "message": "Mensaje", "follow": "Seguir", + "block": "Bloquear", + "unblock": "Desbloquear", "unfollow": "Siguiendo", "copyNPub": "Copiar clave" }, diff --git a/frontend/Locales/ru.json b/frontend/Locales/ru.json index 4ea2cbd..fe7ee2a 100644 --- a/frontend/Locales/ru.json +++ b/frontend/Locales/ru.json @@ -23,6 +23,7 @@ "Repost": "Поделиться", "Profile": "Профиль", "About": "О нас", + "Config": "Config", "Send": "Отпраить", "Relays": "Реле", "ProfileConfig": "Мой профиль" @@ -57,10 +58,16 @@ "about": "Подроблее", "logout": "Выйти" }, + "configPage": { + "showPublicImages": "Show images on public feed", + "showSensitive": "Show sensitive notes", + "satoshi": "Satoshi symbol" + }, "noteCard": { "answering": "Ответить {{pubkey}}", "reposting": "Reposted {{pubkey}}", "seeParent": "See note", + "userBlocked": "User blocked", "contentWarning": "Деликатный контент" }, "lnPayment": { @@ -128,7 +135,11 @@ "homeFeed": { "emptyTitle": "You are not following anyone.", "emptyDescription": "Follow other profiles to see content.", - "emptyButton": "Go to contacts" + "emptyButton": "Go to contacts", + "globalFeed": "Global feed", + "newMessage": "{{newNotesCount}} nota nueva. Desliza para recargar.", + "newMessages": "{{newNotesCount}} notas nuevas. Desliza para refrescar.", + "myFeed": "My feed" }, "relaysPage": { "labelAdd": "Relay address", @@ -201,6 +212,8 @@ "invoice": "Tip", "message": "Message", "follow": "Follow", + "block": "Block", + "unblock": "Desbloquear", "unfollow": "Following", "copyNPub": "Copy key" }, diff --git a/frontend/Pages/ConfigPage/index.tsx b/frontend/Pages/ConfigPage/index.tsx new file mode 100644 index 0000000..d4abd7c --- /dev/null +++ b/frontend/Pages/ConfigPage/index.tsx @@ -0,0 +1,100 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { FlatList, StyleSheet, Text } from 'react-native' +import { Divider, List, Switch, useTheme } from 'react-native-paper' +import RBSheet from 'react-native-raw-bottom-sheet' +import { AppContext } from '../../Contexts/AppContext' + +export const ConfigPage: React.FC = () => { + const theme = useTheme() + const { t } = useTranslation('common') + const { + getSatoshiSymbol, + showPublicImages, + setShowPublicImages, + showSensitive, + setShowSensitive, + satoshi, + setSatoshi, + } = React.useContext(AppContext) + const bottomSheetRef = React.useRef(null) + + React.useEffect(() => {}, [showPublicImages, showSensitive, satoshi]) + + const createOptions = React.useMemo(() => { + return [ + { + key: 1, + title: s, + onPress: () => { + setSatoshi('kebab') + bottomSheetRef.current?.close() + }, + }, + { + key: 2, + title: 'sats', + onPress: () => { + setSatoshi('sats') + bottomSheetRef.current?.close() + }, + }, + ] + }, []) + + const bottomSheetStyles = React.useMemo(() => { + return { + container: { + backgroundColor: theme.colors.background, + padding: 16, + borderTopRightRadius: 28, + borderTopLeftRadius: 28, + }, + } + }, []) + + return ( + <> + ( + setShowPublicImages(value)} /> + )} + /> + ( + setShowSensitive(value)} /> + )} + /> + bottomSheetRef.current?.open()} + right={() => getSatoshiSymbol(25)} + /> + + { + return + }} + ItemSeparatorComponent={Divider} + /> + + + ) +} + +const styles = StyleSheet.create({ + satoshi: { + fontFamily: 'Satoshi-Symbol', + fontSize: 25, + }, +}) + +export default ConfigPage diff --git a/frontend/Pages/ContactsFeed/index.tsx b/frontend/Pages/ContactsFeed/index.tsx index b4a5e59..da15d06 100644 --- a/frontend/Pages/ContactsFeed/index.tsx +++ b/frontend/Pages/ContactsFeed/index.tsx @@ -44,8 +44,7 @@ export const ContactsFeed: React.FC = () => { const { t } = useTranslation('common') const initialPageSize = 20 const { database } = useContext(AppContext) - const { privateKey, publicKey, setContantsCount, setFollowersCount, nPub } = - React.useContext(UserContext) + const { privateKey, publicKey, nPub } = React.useContext(UserContext) const { relayPool, lastEventId } = useContext(RelayPoolContext) const theme = useTheme() const [pageSize, setPageSize] = useState(initialPageSize) @@ -94,8 +93,6 @@ export const ContactsFeed: React.FC = () => { ]) setFollowers(followers) setFollowing(following) - setContantsCount(following.length) - setFollowersCount(followers.length) } }) } @@ -434,7 +431,7 @@ const styles = StyleSheet.create({ alignContent: 'center', }, tabActive: { - borderBottomWidth: 5, + borderBottomWidth: 3, }, tabText: { textAlign: 'center', diff --git a/frontend/Pages/FeedNavigator/index.tsx b/frontend/Pages/FeedNavigator/index.tsx index 9afbd29..336e235 100644 --- a/frontend/Pages/FeedNavigator/index.tsx +++ b/frontend/Pages/FeedNavigator/index.tsx @@ -14,6 +14,7 @@ import ProfileCard from '../../Components/ProfileCard' import NotePage from '../NotePage' import SendPage from '../SendPage' import ConversationPage from '../ConversationPage' +import ConfigPage from '../ConfigPage' export const HomeNavigator: React.FC = () => { const theme = useTheme() @@ -102,6 +103,7 @@ export const HomeNavigator: React.FC = () => { + diff --git a/frontend/Pages/GlobalFeed/index.tsx b/frontend/Pages/GlobalFeed/index.tsx new file mode 100644 index 0000000..b7373c8 --- /dev/null +++ b/frontend/Pages/GlobalFeed/index.tsx @@ -0,0 +1,190 @@ +import React, { useCallback, useContext, useState, useEffect } from 'react' +import { + ListRenderItem, + NativeScrollEvent, + NativeSyntheticEvent, + RefreshControl, + ScrollView, + StyleSheet, + View, +} from 'react-native' +import { AppContext } from '../../Contexts/AppContext' +import { getMainNotes, getMainNotesCount, Note } from '../../Functions/DatabaseFunctions/Notes' +import { handleInfinityScroll } from '../../Functions/NativeFunctions' +import { UserContext } from '../../Contexts/UserContext' +import { RelayPoolContext } from '../../Contexts/RelayPoolContext' +import { Kind } from 'nostr-tools' +import { RelayFilters } from '../../lib/nostr/RelayPool/intex' +import { ActivityIndicator, Banner, Button, Text } from 'react-native-paper' +import NoteCard from '../../Components/NoteCard' +import { useTheme } from '@react-navigation/native' +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' +import { t } from 'i18next' +import { FlatList } from 'react-native-gesture-handler' +import { getUnixTime } from 'date-fns' + +interface GlobalFeedProps { + navigation: any + setProfileCardPubKey: (profileCardPubKey: string) => void +} + +export const GlobalFeed: React.FC = ({ navigation, setProfileCardPubKey }) => { + const theme = useTheme() + const { database, showPublicImages } = useContext(AppContext) + const { publicKey } = useContext(UserContext) + const { lastEventId, relayPool, lastConfirmationtId } = useContext(RelayPoolContext) + const initialPageSize = 10 + const [notes, setNotes] = useState([]) + const [lastLoadAt, setLastLoadAt] = useState(0) + const [newNotesCount, setNewNotesCount] = useState(0) + const [pageSize, setPageSize] = useState(initialPageSize) + const [refreshing, setRefreshing] = useState(false) + + useEffect(() => { + subscribeNotes() + }, []) + + useEffect(() => { + if (relayPool && publicKey) { + loadNotes() + } + }, [lastEventId, lastConfirmationtId, lastLoadAt]) + + useEffect(() => { + if (pageSize > initialPageSize) { + subscribeNotes(true) + } + }, [pageSize]) + + const updateLastLoad: () => void = () => { + setLastLoadAt(getUnixTime(new Date())) + } + + const onRefresh = useCallback(() => { + setRefreshing(true) + updateLastLoad() + setNewNotesCount(0) + }, []) + + const subscribeNotes: (past?: boolean) => void = async (past) => { + if (!database || !publicKey) return + + const message: RelayFilters = { + kinds: [Kind.Text, Kind.RecommendRelay], + limit: pageSize, + } + + if (past) message.until = notes[0].created_at + + relayPool?.subscribe('homepage-global-main', [message]) + setRefreshing(false) + updateLastLoad() + } + + const onScroll: (event: NativeSyntheticEvent) => void = (event) => { + if (handleInfinityScroll(event)) { + setPageSize(pageSize + initialPageSize) + } + } + + const loadNotes: () => void = () => { + if (database && publicKey) { + if (lastLoadAt > 0) { + getMainNotesCount(database, lastLoadAt).then(setNewNotesCount) + } + getMainNotes(database, publicKey, pageSize, false, { until: lastLoadAt }).then((results) => { + setRefreshing(false) + if (results.length > 0) { + setNotes(results) + relayPool?.subscribe('homepage-contacts-meta', [ + { + kinds: [Kind.Metadata], + authors: notes.map((note) => note.pubkey ?? ''), + }, + ]) + } + }) + } + } + + const renderItem: ListRenderItem = ({ item, index }) => { + return ( + + { + setProfileCardPubKey(user.id) + }} + showPreview={showPublicImages} + /> + + ) + } + + return ( + + {notes && notes.length > 0 ? ( + } + > + 0} + actions={[]} + icon={() => } + > + {t(newNotesCount < 2 ? 'homeFeed.newMessage' : 'homeFeed.newMessages', { + newNotesCount, + })} + + + {notes.length >= 10 && ( + + )} + + ) : ( + + + + {t('homeFeed.emptyTitle')} + + + {t('homeFeed.emptyDescription')} + + + + )} + + ) +} + +const styles = StyleSheet.create({ + noteCard: { + marginTop: 16, + }, + center: { + alignContent: 'center', + textAlign: 'center', + }, + blank: { + justifyContent: 'space-between', + height: 220, + marginTop: 91, + }, + activityIndicator: { + padding: 16, + }, +}) + +export default GlobalFeed diff --git a/frontend/Pages/HomeFeed/index.tsx b/frontend/Pages/HomeFeed/index.tsx index 4f04dba..56fc737 100644 --- a/frontend/Pages/HomeFeed/index.tsx +++ b/frontend/Pages/HomeFeed/index.tsx @@ -1,32 +1,16 @@ -import React, { useCallback, useContext, useState, useEffect } from 'react' -import { getUsers, User } from '../../Functions/DatabaseFunctions/Users' -import { - Dimensions, - ListRenderItem, - NativeScrollEvent, - NativeSyntheticEvent, - RefreshControl, - ScrollView, - StyleSheet, - View, -} from 'react-native' -import { AppContext } from '../../Contexts/AppContext' -import { getLastReply, getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes' -import { handleInfinityScroll } from '../../Functions/NativeFunctions' +import React, { useContext, useState } from 'react' +import { Dimensions, StyleSheet, View } from 'react-native' import { UserContext } from '../../Contexts/UserContext' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' -import { Kind } from 'nostr-tools' -import { RelayFilters } from '../../lib/nostr/RelayPool/intex' -import { ActivityIndicator, AnimatedFAB, Button, Text } from 'react-native-paper' -import NoteCard from '../../Components/NoteCard' +import { AnimatedFAB, Text, TouchableRipple } from 'react-native-paper' import RBSheet from 'react-native-raw-bottom-sheet' import ProfileCard from '../../Components/ProfileCard' import { useFocusEffect, useTheme } from '@react-navigation/native' import { navigate } from '../../lib/Navigation' -import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import { t } from 'i18next' -import { FlatList } from 'react-native-gesture-handler' -import { getLastReaction } from '../../Functions/DatabaseFunctions/Reactions' +import GlobalFeed from '../GlobalFeed' +import MyFeed from '../MyFeed' +import { AppContext } from '../../Contexts/AppContext' interface HomeFeedProps { navigation: any @@ -34,24 +18,19 @@ interface HomeFeedProps { export const HomeFeed: React.FC = ({ navigation }) => { const theme = useTheme() - const { database } = useContext(AppContext) - const { publicKey, privateKey } = useContext(UserContext) - const { lastEventId, relayPool, lastConfirmationtId } = useContext(RelayPoolContext) - const initialPageSize = 10 - const [notes, setNotes] = useState([]) - const [pageSize, setPageSize] = useState(initialPageSize) - const [refreshing, setRefreshing] = useState(false) + const { showPublicImages } = useContext(AppContext) + const { privateKey } = useContext(UserContext) + const { relayPool } = useContext(RelayPoolContext) + const [tabKey, setTabKey] = React.useState('myFeed') const [profileCardPubkey, setProfileCardPubKey] = useState() const bottomSheetProfileRef = React.useRef(null) useFocusEffect( React.useCallback(() => { - subscribeNotes() - loadNotes() - return () => relayPool?.unsubscribe([ - 'homepage-main', + 'homepage-global-main', + 'homepage-contacts-main', 'homepage-reactions', 'homepage-contacts-meta', 'homepage-replies', @@ -59,104 +38,6 @@ export const HomeFeed: React.FC = ({ navigation }) => { }, []), ) - useEffect(() => { - if (relayPool && publicKey) { - loadNotes() - } - }, [lastEventId, lastConfirmationtId]) - - useEffect(() => { - if (pageSize > initialPageSize) { - subscribeNotes(true) - } - }, [pageSize]) - - const onRefresh = useCallback(() => { - setRefreshing(true) - subscribeNotes() - }, []) - - const subscribeNotes: (past?: boolean) => void = async (past) => { - if (!database || !publicKey) return - - const users: User[] = await getUsers(database, { contacts: true, order: 'created_at DESC' }) - const authors: string[] = [...users.map((user) => user.id), publicKey] - - const lastNotes: Note[] = await getMainNotes(database, publicKey, initialPageSize) - const lastNote: Note = lastNotes[lastNotes.length - 1] - - const message: RelayFilters = { - kinds: [Kind.Text, Kind.RecommendRelay], - authors, - } - if (lastNote && lastNotes.length >= pageSize && !past) { - message.since = lastNote?.created_at - } else { - message.limit = pageSize + initialPageSize - } - relayPool?.subscribe('homepage-main', [message]) - relayPool?.subscribe('homepage-contacts-meta', [ - { - kinds: [Kind.Metadata], - authors, - }, - ]) - setRefreshing(false) - } - - const onScroll: (event: NativeSyntheticEvent) => void = (event) => { - if (handleInfinityScroll(event)) { - setPageSize(pageSize + initialPageSize) - } - } - - const loadNotes: () => void = () => { - if (database && publicKey) { - getMainNotes(database, publicKey, pageSize).then((notes) => { - setNotes(notes) - if (notes.length > 0) { - const notedIds = notes.map((note) => note.id ?? '') - getLastReaction(database, { eventIds: notes.map((note) => note.id ?? '') }).then( - (lastReaction) => { - relayPool?.subscribe('homepage-reactions', [ - { - kinds: [Kind.Reaction], - '#e': notedIds, - since: lastReaction?.created_at ?? 0, - }, - ]) - }, - ) - getLastReply(database, { eventIds: notes.map((note) => note.id ?? '') }).then( - (lastReply) => { - relayPool?.subscribe('homepage-replies', [ - { - kinds: [Kind.Text], - '#e': notedIds, - since: lastReply?.created_at ?? 0, - }, - ]) - }, - ) - } - }) - } - } - - const renderItem: ListRenderItem = ({ item, index }) => { - return ( - - { - setProfileCardPubKey(user.id) - bottomSheetProfileRef.current?.open() - }} - /> - - ) - } - const bottomSheetStyles = React.useMemo(() => { return { container: { @@ -168,37 +49,74 @@ export const HomeFeed: React.FC = ({ navigation }) => { } }, []) + const renderScene: Record = { + globalFeed: ( + { + setProfileCardPubKey(value) + bottomSheetProfileRef.current?.open() + }} + /> + ), + myFeed: ( + { + setProfileCardPubKey(value) + bottomSheetProfileRef.current?.open() + }} + /> + ), + } + return ( - - {notes && notes.length > 0 ? ( - } + + + - - {notes.length >= 10 && } - - ) : ( - - - - {t('homeFeed.emptyTitle')} - - - {t('homeFeed.emptyDescription')} - - + { + relayPool?.unsubscribe([ + 'homepage-contacts-main', + 'homepage-reactions', + 'homepage-contacts-meta', + 'homepage-replies', + ]) + setTabKey('globalFeed') + }} + > + {t('homeFeed.globalFeed')} + - )} + + { + relayPool?.unsubscribe(['homepage-global-main']) + setTabKey('myFeed') + }} + > + {t('homeFeed.myFeed')} + + + + {renderScene[tabKey]} {privateKey && ( = ({ navigation }) => { height={280} customStyles={bottomSheetStyles} > - + ) @@ -231,9 +153,7 @@ const styles = StyleSheet.create({ position: 'absolute', }, container: { - paddingLeft: 16, - paddingRight: 16, - flex: 1, + padding: 16, }, center: { alignContent: 'center', @@ -244,6 +164,27 @@ const styles = StyleSheet.create({ height: 220, marginTop: 91, }, + tab: { + flex: 1, + height: '100%', + justifyContent: 'center', + alignContent: 'center', + }, + tabText: { + textAlign: 'center', + paddingTop: 25, + height: '100%', + }, + tabsNavigator: { + flexDirection: 'row', + justifyContent: 'space-between', + height: 70, + }, + feed: { + paddingBottom: 140, + paddingLeft: 16, + paddingRight: 16, + }, }) export default HomeFeed diff --git a/frontend/Pages/HomeNavigator/index.tsx b/frontend/Pages/HomeNavigator/index.tsx index a69943f..c878bb0 100644 --- a/frontend/Pages/HomeNavigator/index.tsx +++ b/frontend/Pages/HomeNavigator/index.tsx @@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next' import ProfileCreatePage from '../../Pages/ProfileCreatePage' import { DrawerNavigationProp } from '@react-navigation/drawer' import RelaysPage from '../RelaysPage' +import ConfigPage from '../ConfigPage' export const HomeNavigator: React.FC = () => { const theme = useTheme() @@ -107,6 +108,7 @@ export const HomeNavigator: React.FC = () => { + void +} + +export const MyFeed: React.FC = ({ navigation, setProfileCardPubKey }) => { + const theme = useTheme() + const { database } = useContext(AppContext) + const { publicKey } = useContext(UserContext) + const { lastEventId, relayPool, lastConfirmationtId } = useContext(RelayPoolContext) + const initialPageSize = 10 + const [notes, setNotes] = useState([]) + const [pageSize, setPageSize] = useState(initialPageSize) + const [refreshing, setRefreshing] = useState(false) + + useEffect(() => { + subscribeNotes() + loadNotes() + }, []) + + useEffect(() => { + if (relayPool && publicKey) { + loadNotes() + } + }, [lastEventId, lastConfirmationtId]) + + useEffect(() => { + if (pageSize > initialPageSize) { + subscribeNotes(true) + } + }, [pageSize]) + + const onRefresh = useCallback(() => { + setRefreshing(true) + subscribeNotes() + }, []) + + const subscribeNotes: (past?: boolean) => void = async (past) => { + if (!database || !publicKey) return + + const users: User[] = await getUsers(database, { contacts: true, order: 'created_at DESC' }) + const authors: string[] = [...users.map((user) => user.id), publicKey] + + const message: RelayFilters = { + kinds: [Kind.Text, Kind.RecommendRelay], + authors, + limit: pageSize, + } + relayPool?.subscribe('homepage-contacts-main', [message]) + setRefreshing(false) + } + + const onScroll: (event: NativeSyntheticEvent) => void = (event) => { + if (handleInfinityScroll(event)) { + setPageSize(pageSize + initialPageSize) + } + } + + const loadNotes: () => void = () => { + if (database && publicKey) { + getMainNotes(database, publicKey, pageSize, true).then((notes) => { + setNotes(notes) + if (notes.length > 0) { + relayPool?.subscribe('homepage-contacts-meta', [ + { + kinds: [Kind.Metadata], + authors: notes.map((note) => note.pubkey ?? ''), + }, + ]) + const notedIds = notes.map((note) => note.id ?? '') + getLastReaction(database, { eventIds: notes.map((note) => note.id ?? '') }).then( + (lastReaction) => { + relayPool?.subscribe('homepage-reactions', [ + { + kinds: [Kind.Reaction], + '#e': notedIds, + since: lastReaction?.created_at ?? 0, + }, + ]) + }, + ) + getLastReply(database, { eventIds: notes.map((note) => note.id ?? '') }).then( + (lastReply) => { + relayPool?.subscribe('homepage-replies', [ + { + kinds: [Kind.Text], + '#e': notedIds, + since: lastReply?.created_at ?? 0, + }, + ]) + }, + ) + } + }) + } + } + + const renderItem: ListRenderItem = ({ item, index }) => { + return ( + + { + setProfileCardPubKey(user.id) + }} + /> + + ) + } + + return ( + + {notes && notes.length > 0 ? ( + } + > + + {notes.length >= initialPageSize && ( + + )} + + ) : ( + + + + {t('homeFeed.emptyTitle')} + + + {t('homeFeed.emptyDescription')} + + + + )} + + ) +} + +const styles = StyleSheet.create({ + noteCard: { + marginTop: 16, + }, + center: { + alignContent: 'center', + textAlign: 'center', + }, + blank: { + justifyContent: 'space-between', + height: 220, + marginTop: 91, + }, + activityIndicator: { + padding: 16, + }, +}) + +export default MyFeed diff --git a/frontend/Pages/ProfileConfigPage/index.tsx b/frontend/Pages/ProfileConfigPage/index.tsx index 36f733c..662a66a 100644 --- a/frontend/Pages/ProfileConfigPage/index.tsx +++ b/frontend/Pages/ProfileConfigPage/index.tsx @@ -30,8 +30,7 @@ export const ProfileConfigPage: React.FC = () => { const bottomSheetLud06Ref = React.useRef(null) const { database } = useContext(AppContext) const { relayPool, lastEventId } = useContext(RelayPoolContext) - const { user, publicKey, nPub, nSec, contactsCount, followersCount, setUser } = - useContext(UserContext) + const { user, publicKey, nPub, nSec, setUser } = useContext(UserContext) // State const [name, setName] = useState() const [picture, setPicture] = useState() @@ -256,14 +255,6 @@ export const ProfileConfigPage: React.FC = () => { )} - - - - { useEffect(() => { loadPets() reloadUser() + if (user?.created_at) { + setProfileFound(true) + loadPets() + } }, [lastEventId]) useEffect(() => { if (publicKey && relayPoolReady) loadMeta() }, [publicKey, relayPoolReady]) - useEffect(() => { - if (user) { - setProfileFound(true) - loadPets() - } - }, [user]) - const loadMeta: () => void = () => { if (publicKey && relayPoolReady) { relayPool?.subscribe('profile-load-meta-pets', [ diff --git a/frontend/Pages/RelaysPage/index.tsx b/frontend/Pages/RelaysPage/index.tsx index 2d058fc..8bb4ca1 100644 --- a/frontend/Pages/RelaysPage/index.tsx +++ b/frontend/Pages/RelaysPage/index.tsx @@ -1,5 +1,5 @@ import React, { useContext, useState } from 'react' -import { FlatList, ListRenderItem, ScrollView, StyleSheet, View } from 'react-native' +import { ScrollView, StyleSheet, View } from 'react-native' import Clipboard from '@react-native-clipboard/clipboard' import { useTranslation } from 'react-i18next' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' @@ -71,27 +71,6 @@ export const RelaysPage: React.FC = () => { } } - const relayToggle: (relay: Relay) => JSX.Element = (relay) => { - return ( - (relay.active ? desactiveRelay(relay) : activeRelay(relay))} - /> - ) - } - - const renderItem: ListRenderItem = ({ index, item }) => ( - relayToggle(item)} - onPress={() => { - setSelectedRelay(item) - bottomSheetEditRef.current?.open() - }} - /> - ) - const rbSheetCustomStyles = React.useMemo(() => { return { container: { @@ -113,22 +92,54 @@ export const RelaysPage: React.FC = () => { {t('relaysPage.myList')} - + {myRelays.length > 0 && + myRelays.map((relay, index) => { + return ( + ( + + relay.active ? desactiveRelay(relay) : activeRelay(relay) + } + /> + )} + onPress={() => { + setSelectedRelay(relay) + bottomSheetEditRef.current?.open() + }} + /> + ) + })} )} {t('relaysPage.recommended')} - { - return { - url, - active: relays.find((relay) => relay.url === url) !== undefined, - } - })} - renderItem={renderItem} - /> + {defaultRelays.map((url, index) => { + const relay = { + url, + active: relays.find((relay) => relay.url === url && relay.active) !== undefined, + } + return ( + ( + (relay.active ? desactiveRelay(relay) : activeRelay(relay))} + /> + )} + onPress={() => { + setSelectedRelay(relay) + bottomSheetEditRef.current?.open() + }} + /> + ) + })}