From 9e89bb34be625ced585ae93115acff696213bddc Mon Sep 17 00:00:00 2001 From: Iuri Pereira <689440+iuricmp@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:57:43 +0100 Subject: [PATCH] feat: follow --- mobile/app/[account]/index.tsx | 32 +++++++++-- mobile/app/index.tsx | 19 ++----- mobile/app/post/index.tsx | 10 ++-- mobile/redux/features/linkingSlice.ts | 72 ++++++++---------------- mobile/redux/features/profileSlice.ts | 22 ++++++++ mobile/src/hooks/use-search.ts | 15 ----- mobile/src/provider/linking-provider.tsx | 11 +--- 7 files changed, 86 insertions(+), 95 deletions(-) diff --git a/mobile/app/[account]/index.tsx b/mobile/app/[account]/index.tsx index 88af8762..262fdbf8 100644 --- a/mobile/app/[account]/index.tsx +++ b/mobile/app/[account]/index.tsx @@ -4,9 +4,9 @@ import { router, useLocalSearchParams, useNavigation } from "expo-router"; import { AccountView } from "@gno/components/view"; import { useSearch } from "@gno/hooks/use-search"; import { Following, Post, User } from "@gno/types"; -import { setPostToReply, useAppSelector } from "@gno/redux"; +import { broadcastTxCommit, clearLinking, selectQueryParamsTxJsonSigned, setPostToReply, useAppSelector } from "@gno/redux"; import { selectAccount } from "redux/features/accountSlice"; -import { setFollows } from "redux/features/profileSlice"; +import { followAndRedirectToSign, setFollows } from "redux/features/profileSlice"; import { useFeed } from "@gno/hooks/use-feed"; import { useUserCache } from "@gno/hooks/use-user-cache"; import ErrorView from "@gno/components/view/account/no-account-view"; @@ -30,6 +30,28 @@ export default function Page() { const dispatch = useDispatch(); const currentUser = useAppSelector(selectAccount); + const txJsonSigned = useAppSelector(selectQueryParamsTxJsonSigned); + + + useEffect(() => { + + (async () => { + if (txJsonSigned) { + console.log("txJsonSigned: ", txJsonSigned); + + const signedTx = decodeURIComponent(txJsonSigned as string) + try { + await dispatch(broadcastTxCommit(signedTx)).unwrap(); + } catch (error) { + console.error("on broadcastTxCommit", error); + } + + dispatch(clearLinking()); + fetchData(); + } + })(); + + }, [txJsonSigned]); useEffect(() => { const unsubscribe = navigation.addListener("focus", async () => { @@ -41,6 +63,8 @@ export default function Page() { const fetchData = async () => { if (!accountName) return; + console.log("fetching data for account: ", currentUser?.bech32); + try { setLoading("Loading account..."); const response = await search.getJsonUserByName(accountName); @@ -98,9 +122,7 @@ export default function Page() { }; const onPressFollow = async (address: string, callerAddress: Uint8Array) => { - await search.Follow(address, callerAddress); - - fetchData(); + await dispatch(followAndRedirectToSign({ address, callerAddress })).unwrap(); }; const onPressUnfollow = async (address: string, callerAddress: Uint8Array) => { diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx index b599f363..ba8f88b9 100644 --- a/mobile/app/index.tsx +++ b/mobile/app/index.tsx @@ -3,7 +3,7 @@ import Button from "@gno/components/button"; import Layout from "@gno/components/layout"; import Ruller from "@gno/components/row/Ruller"; import Text from "@gno/components/text"; -import { clearLinking, loggedIn, requestLoginForGnokeyMobile, selectAccount, selectPath, selectQueryParamsAddress, useAppDispatch, useAppSelector } from "@gno/redux"; +import { clearLinking, loggedIn, requestLoginForGnokeyMobile, selectBech32AddressSelected, useAppDispatch, useAppSelector } from "@gno/redux"; import Spacer from "@gno/components/spacer"; import * as Application from "expo-application"; import { useEffect } from "react"; @@ -12,25 +12,18 @@ import { useRouter } from "expo-router"; export default function Root() { const dispatch = useAppDispatch(); const route = useRouter(); - const path = useAppSelector(selectPath) - const bech32 = useAppSelector(selectQueryParamsAddress) - const account = useAppSelector(selectAccount) + const bech32AddressSelected = useAppSelector(selectBech32AddressSelected) const appVersion = Application.nativeApplicationVersion; useEffect(() => { - if (account) { - route.replace("/home"); - } - }, [account]); - - useEffect(() => { - if (path === "login-callback" && bech32) { - dispatch(loggedIn({ bech32: bech32 as string })); + console.log("bech32AddressSelected on index", bech32AddressSelected); + if (bech32AddressSelected) { + dispatch(loggedIn({ bech32: bech32AddressSelected as string })); dispatch(clearLinking()); setTimeout(() => route.replace("/home"), 500); } - }, [path, bech32]); + }, [bech32AddressSelected]); diff --git a/mobile/app/post/index.tsx b/mobile/app/post/index.tsx index 8c6b763c..8cf229c1 100644 --- a/mobile/app/post/index.tsx +++ b/mobile/app/post/index.tsx @@ -6,7 +6,7 @@ import TextInput from "@gno/components/textinput"; import { Stack, useNavigation, useRouter } from "expo-router"; import { useEffect, useState } from "react"; import { KeyboardAvoidingView, Platform } from "react-native"; -import { broadcastTxCommit, hasParam, makeCallTxAndRedirectToSign, selectAccount, selectQueryParams, useAppDispatch, useAppSelector } from "@gno/redux"; +import { broadcastTxCommit, makeCallTxAndRedirectToSign, selectAccount, selectQueryParamsTxJsonSigned, useAppDispatch, useAppSelector } from "@gno/redux"; export default function Search() { const [postContent, setPostContent] = useState(""); @@ -18,13 +18,13 @@ export default function Search() { const dispatch = useAppDispatch(); const account = useAppSelector(selectAccount); - const queryParams = useAppSelector(selectQueryParams); + const txJsonSigned = useAppSelector(selectQueryParamsTxJsonSigned); // hook to handle the signed tx from the Gnokey and broadcast it useEffect(() => { - if (queryParams && hasParam("tx", queryParams)) { + if (txJsonSigned) { - const signedTx = decodeURIComponent(queryParams.tx as string) + const signedTx = decodeURIComponent(txJsonSigned as string) console.log("signedTx: ", signedTx); try { @@ -38,7 +38,7 @@ export default function Search() { setLoading(false); } } - }, [queryParams]); + }, [txJsonSigned]); useEffect(() => { const unsubscribe = navigation.addListener("focus", async () => { diff --git a/mobile/redux/features/linkingSlice.ts b/mobile/redux/features/linkingSlice.ts index 4b17e833..1e0b7be1 100644 --- a/mobile/redux/features/linkingSlice.ts +++ b/mobile/redux/features/linkingSlice.ts @@ -4,26 +4,18 @@ import * as Linking from 'expo-linking'; import { ThunkExtra } from "redux/redux-provider"; interface State { - linkingParsedURl: Linking.ParsedURL | undefined; - queryParams: Linking.QueryParams | undefined; - path: string | undefined; - hostname: string | undefined; + txJsonSigned: string | undefined; + bech32AddressSelected: string | undefined; } const initialState: State = { - linkingParsedURl: undefined, - queryParams: undefined, - path: undefined, - hostname: undefined, + txJsonSigned: undefined, + bech32AddressSelected: undefined, }; -export const hasParam = (param: string, queryParams: Linking.QueryParams | undefined): boolean => { - return Boolean(queryParams && queryParams[param] !== undefined); -} - export const requestLoginForGnokeyMobile = createAsyncThunk("tx/requestLoginForGnokeyMobile", async () => { console.log("requesting login for GnokeyMobile"); - const callback = encodeURIComponent('tech.berty.dsocial:///login-callback'); + const callback = encodeURIComponent('tech.berty.dsocial://login-callback'); return await Linking.openURL(`land.gno.gnokey://tologin?callback=${callback}`); }) @@ -41,7 +33,7 @@ export const makeCallTxAndRedirectToSign = createAsyncThunk { const params = [`tx=${encodeURIComponent(res.txJson)}`, `address=${callerAddressBech32}`, `client_name=dSocial`, `reason=Post a message`]; @@ -51,7 +43,7 @@ export const makeCallTxAndRedirectToSign = createAsyncThunk("tx/makeCallTx", async (props, thunkAPI) => { - const {packagePath, fnc, callerAddressBech32, gasFee, gasWanted, args } = props; +export const makeCallTx = createAsyncThunk("tx/makeCallTx", async (props, thunkAPI) => { + const { packagePath, fnc, callerAddressBech32, gasFee, gasWanted, args } = props; console.log("making a tx for: ", callerAddressBech32); @@ -77,52 +69,34 @@ export const broadcastTxCommit = createAsyncThunk("tx/ console.log("broadcasting tx: ", signedTx); const gnonative = thunkAPI.extra.gnonative; - await gnonative.broadcastTxCommit(signedTx); + const res = await gnonative.broadcastTxCommit(signedTx); + console.log("broadcasted tx: ", res); }); -type SetLinkingResponse = Partial; - -export const setLinkingParsedURL = createAsyncThunk("tx/setLinkingParsedURL", async (linkingParsedURl, thunkAPI) => { - const { hostname, path, queryParams } = linkingParsedURl; - - return { - linkingParsedURl, - queryParams: queryParams || undefined, - path: path || undefined, - hostname: hostname || undefined, - } -}) - /** * Slice to handle linking between the app and the GnokeyMobile app */ export const linkingSlice = createSlice({ name: "linking", initialState, - extraReducers: (builder) => { - builder.addCase(setLinkingParsedURL.fulfilled, (state, action) => { - state.linkingParsedURl = action.payload.linkingParsedURl; - state.queryParams = action.payload.queryParams; - state.path = action.payload.path; - state.hostname = action.payload.hostname; - }) - }, reducers: { + setLinkingData: (state, action) => { + const queryParams = action.payload.queryParams + + state.bech32AddressSelected = queryParams?.address ? queryParams.address as string : undefined + state.txJsonSigned = queryParams?.tx ? queryParams.tx as string : undefined + }, clearLinking: (state) => { - state.linkingParsedURl = undefined; - state.queryParams = undefined; - state.path = undefined; - state.hostname = undefined; + console.log("clearing linking data"); + state = { ...initialState }; } }, selectors: { - selectPath: (state: State) => state.path, - selectQueryParams: (state: State) => state.queryParams, - selectLinkingParsedURL: (state: State) => state.linkingParsedURl, - selectQueryParamsAddress: (state: State) => state.linkingParsedURl?.queryParams?.address as string | undefined, + selectQueryParamsTxJsonSigned: (state: State) => state.txJsonSigned as string | undefined, + selectBech32AddressSelected: (state: State) => state.bech32AddressSelected as string | undefined, }, }); -export const { clearLinking } = linkingSlice.actions; +export const { clearLinking, setLinkingData } = linkingSlice.actions; -export const { selectLinkingParsedURL, selectQueryParams, selectQueryParamsAddress, selectPath } = linkingSlice.selectors; +export const { selectQueryParamsTxJsonSigned, selectBech32AddressSelected } = linkingSlice.selectors; diff --git a/mobile/redux/features/profileSlice.ts b/mobile/redux/features/profileSlice.ts index 22aef762..2b52fcd9 100644 --- a/mobile/redux/features/profileSlice.ts +++ b/mobile/redux/features/profileSlice.ts @@ -1,5 +1,8 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import { makeCallTx } from "./linkingSlice"; import { Following } from "@gno/types"; +import { ThunkExtra } from "redux/redux-provider"; +import * as Linking from 'expo-linking'; export interface ProfileState { following: Following[]; @@ -18,6 +21,25 @@ interface FollowsProps { followers: Following[]; } +export const followAndRedirectToSign = createAsyncThunk("profile/follow", async ({ address, callerAddress }, thunkAPI) => { + console.log("Follow user: %s", address); + const gnonative = thunkAPI.extra.gnonative; + + const packagePath = "gno.land/r/berty/social"; + const fnc = "Follow"; + const args: Array = [address]; + const gasFee = "1000000ugnot"; + const gasWanted = BigInt(10000000); + const callerAddressBech32 = await gnonative.addressToBech32(callerAddress); + + const res = await thunkAPI.dispatch(makeCallTx({ packagePath, fnc, args, gasFee, gasWanted, callerAddressBech32 })).unwrap(); + + setTimeout(() => { + const params = [`tx=${encodeURIComponent(res.txJson)}`, `address=${callerAddressBech32}`, 'client_name=dSocial', 'reason=Folow a user', `callback=${encodeURIComponent('tech.berty.dsocial://account')}`]; + Linking.openURL('land.gno.gnokey://tosign?' + params.join('&')) + }, 500) +}); + export const setFollows = createAsyncThunk("profile/setFollows", async ({ following, followers }: FollowsProps, _) => { return { following, followers }; }); diff --git a/mobile/src/hooks/use-search.ts b/mobile/src/hooks/use-search.ts index 07bf7326..61bb78a4 100644 --- a/mobile/src/hooks/use-search.ts +++ b/mobile/src/hooks/use-search.ts @@ -6,20 +6,6 @@ const MAX_RESULT = 10; export const useSearch = () => { const { gnonative } = useGnoNativeContext(); - async function Follow(address: string, callerAddress: Uint8Array) { - - try { - const gasFee = "1000000ugnot"; - const gasWanted = BigInt(10000000); - const args: Array = [address]; - for await (const response of await gnonative.call("gno.land/r/berty/social", "Follow", args, gasFee, gasWanted, callerAddress)) { - console.log("response: ", JSON.stringify(response)); - } - } catch (error) { - console.error("error registering account", error); - } - } - async function Unfollow(address: string, callerAddress: Uint8Array) { try { @@ -103,7 +89,6 @@ export const useSearch = () => { GetJsonFollowersCount, GetJsonFollowing, GetJsonFollowers, - Follow, Unfollow, }; }; diff --git a/mobile/src/provider/linking-provider.tsx b/mobile/src/provider/linking-provider.tsx index ce8ec8e0..af3babd0 100644 --- a/mobile/src/provider/linking-provider.tsx +++ b/mobile/src/provider/linking-provider.tsx @@ -1,6 +1,6 @@ import * as Linking from 'expo-linking'; import { useEffect } from 'react'; -import { useAppDispatch, setLinkingParsedURL } from "@gno/redux"; +import { setLinkingData, useAppDispatch } from "@gno/redux"; const LinkingProvider = ({ children }: { children: React.ReactNode }) => { @@ -12,14 +12,9 @@ const LinkingProvider = ({ children }: { children: React.ReactNode }) => { (async () => { if (url) { const linkingParsedURL = Linking.parse(url); - const { hostname, path, queryParams } = linkingParsedURL; + console.log("link url received", url); - console.log("link url", url); - console.log("link hostname", hostname); - console.log("link path", path); - console.log("link queryParams", queryParams); - - await dispatch(setLinkingParsedURL(linkingParsedURL)).unwrap(); + await dispatch(setLinkingData(linkingParsedURL)); } })(); }, [url]);