Skip to content

Commit

Permalink
Exchanging messages with Gnokey Mobile (#133)
Browse files Browse the repository at this point in the history
* feat: upgrading gnonative to signTx

* feat: inter app communication

* feat: sending and receiving - temp

* refactor: using gnokey

* refactor: revert

* chore: eslint

* feat: login at gnokey

* chore: posting with gnokey key

* refactor: cleaning issues

* refactor: onboard feature goes to Gnokey

* chore: messaging improvments

* refactor: generic makeCallTx

* feat: follow

* fix: In onPressFollow, need to pass the bech32 address

Signed-off-by: Jeff Thompson <[email protected]>

* fix: isFollowed detection

* fix: follow and unfollow

* refactoring

* feat: repost a message

* fix: gnods are good

* fix: changing avatar

* fix: reposting

* fix: adding a delay to list the new post

* Update mobile/redux/features/accountSlice.ts

Co-authored-by: Jeff Thompson <[email protected]>

---------

Signed-off-by: Jeff Thompson <[email protected]>
Co-authored-by: Jeff Thompson <[email protected]>
  • Loading branch information
iuricmp and jefft0 authored Oct 22, 2024
1 parent 2941eb0 commit f00e4a0
Show file tree
Hide file tree
Showing 31 changed files with 545 additions and 946 deletions.
5 changes: 5 additions & 0 deletions mobile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
The dSocial mobile app uses Expo. You can review the general expo requirements:

- Expo Requiments: https://docs.expo.dev/get-started/installation/
- Configure buff registry: `$ npm config set @buf:registry https://buf.build/gen/npm/v1/`

Here are specific steps to install the requirements on your platform.

Expand Down Expand Up @@ -143,3 +144,7 @@ The manual release process uses the [`eas`](https://docs.expo.dev/build/setup/#i
3. After the build is complete, submit it to the Play Store running `eas submit --platform android`
You'll need to have a [service account json file](https://developers.google.com/android/management/service-account) to authenticate with Google Play Store.

## Opening the App using Links

You can open this app using [Linking](https://docs.expo.dev/guides/linking/).
To understand the URL format, please refer to the 'expo-linking' usage in this project.
10 changes: 6 additions & 4 deletions mobile/app/[account]/followers.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { selectFollowers } from "redux/features/profileSlice";
import { useAppSelector } from "@gno/redux";
import { selectFollowers, setProfileAccountName } from "redux/features/profileSlice";
import { useAppDispatch, useAppSelector } from "@gno/redux";
import { Following } from "@gno/types";
import FollowModalContent from "@gno/components/view/follow";
import { useRouter } from "expo-router";

export default function FollowersPage() {
const data = useAppSelector(selectFollowers);
const router = useRouter();
const dispatch = useAppDispatch();

const onPress = (item: Following) => {
router.navigate({ pathname: "account", params: { accountName: item.user?.name } });
const onPress = async (item: Following) => {
await dispatch(setProfileAccountName(item.user?.name));
router.navigate({ pathname: "account"});
};

return <FollowModalContent data={data} onPress={onPress} />;
Expand Down
9 changes: 5 additions & 4 deletions mobile/app/[account]/following.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { useAppSelector } from "@gno/redux";
import { useAppDispatch, useAppSelector, selectFollowing, setProfileAccountName } from "@gno/redux";
import { Following } from "@gno/types";
import { selectFollowing } from "redux/features/profileSlice";
import FollowModalContent from "@gno/components/view/follow";
import { useRouter } from "expo-router";

export default function FollowingPage() {
const data = useAppSelector(selectFollowing);
const router = useRouter();
const dispatch = useAppDispatch();

const onPress = (item: Following) => {
router.navigate({ pathname: "account", params: { accountName: item.user?.name } });
const onPress = async (item: Following) => {
await dispatch(setProfileAccountName(item.user?.name));
router.navigate({ pathname: "account" });
};

return <FollowModalContent data={data} onPress={onPress} />;
Expand Down
59 changes: 36 additions & 23 deletions mobile/app/[account]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { router, useLocalSearchParams, useNavigation } from "expo-router";
import { router, useNavigation, usePathname } 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 { selectAccount } from "redux/features/accountSlice";
import { setFollows } from "redux/features/profileSlice";
import { broadcastTxCommit, clearLinking, selectQueryParamsTxJsonSigned, setPostToReply, useAppSelector, selectAccount, gnodTxAndRedirectToSign } from "@gno/redux";
import { followTxAndRedirectToSign, selectProfileAccountName, setFollows, unfollowTxAndRedirectToSign } 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";
import Layout from "@gno/components/layout";
import { colors } from "@gno/styles/colors";

export default function Page() {
const { accountName } = useLocalSearchParams<{ accountName: string }>();
const accountName = useAppSelector(selectProfileAccountName)

const [loading, setLoading] = useState<string | undefined>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
Expand All @@ -30,6 +29,28 @@ export default function Page() {
const dispatch = useDispatch();

const currentUser = useAppSelector(selectAccount);
const txJsonSigned = useAppSelector(selectQueryParamsTxJsonSigned);

const pathName = usePathname();

useEffect(() => {

(async () => {
if (txJsonSigned) {
console.log("txJsonSigned in [account] page: ", 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 () => {
Expand All @@ -38,9 +59,12 @@ export default function Page() {
return unsubscribe;
}, [accountName]);

const fetchData = async () => {
const fetchData = useCallback(async () => {
console.log("fetchData", accountName);
if (!accountName) return;

console.log("fetching data for account: ", currentUser?.bech32);

try {
setLoading("Loading account...");
const response = await search.getJsonUserByName(accountName);
Expand Down Expand Up @@ -87,7 +111,7 @@ export default function Page() {
} finally {
setLoading(undefined);
}
};
}, [accountName]);

const onPressFollowing = () => {
router.navigate({ pathname: "account/following" });
Expand All @@ -98,15 +122,11 @@ export default function Page() {
};

const onPressFollow = async (address: string, callerAddress: Uint8Array) => {
await search.Follow(address, callerAddress);

fetchData();
await dispatch(followTxAndRedirectToSign({ address, callerAddress })).unwrap();
};

const onPressUnfollow = async (address: string, callerAddress: Uint8Array) => {
await search.Unfollow(address as string, callerAddress);

fetchData();
await dispatch(unfollowTxAndRedirectToSign({ address, callerAddress })).unwrap();
};

const onGnod = async (post: Post) => {
Expand All @@ -115,18 +135,11 @@ export default function Page() {

if (!currentUser) throw new Error("No active account");

try {
await feed.onGnod(post, currentUser.address);
await fetchData();
} catch (error) {
console.error("Error while adding reaction: " + error);
} finally {
setLoading(undefined);
}
dispatch(gnodTxAndRedirectToSign({ post, callerAddressBech32: currentUser.bech32, callbackPath: pathName })).unwrap();
};

const onPressPost = async (item: Post) => {
await dispatch(setPostToReply({ post: item }));
await dispatch(setPostToReply(item));
// Posts come from the indexer, the address is a bech32 address.
router.navigate({ pathname: "/post/[post_id]", params: { post_id: item.id, address: String(item.user.address) } });
};
Expand Down
25 changes: 14 additions & 11 deletions mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GnoNativeProvider } from "@gnolang/gnonative";
import { IndexerProvider } from "@gno/provider/indexer-provider";
import { NotificationProvider } from "@gno/provider/notification-provider";
import { ReduxProvider } from "redux/redux-provider";
import { LinkingProvider } from "@gno/provider/linking-provider";

const gnoDefaultConfig = {
// @ts-ignore
Expand All @@ -30,17 +31,19 @@ export default function AppLayout() {
<NotificationProvider config={notificationDefaultConfig}>
<IndexerProvider config={indexerDefaultConfig}>
<ReduxProvider>
<ThemeProvider value={DefaultTheme}>
<Guard>
<Stack
screenOptions={{
headerShown: false,
headerLargeTitle: true,
headerBackVisible: false,
}}
/>
</Guard>
</ThemeProvider>
<LinkingProvider>
<ThemeProvider value={DefaultTheme}>
<Guard>
<Stack
screenOptions={{
headerShown: false,
headerLargeTitle: true,
headerBackVisible: false,
}}
/>
</Guard>
</ThemeProvider>
</LinkingProvider>
</ReduxProvider>
</IndexerProvider>
</NotificationProvider>
Expand Down
27 changes: 8 additions & 19 deletions mobile/app/home/feed.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ActivityIndicator, FlatList, Platform, StyleSheet, View, Alert as RNAlert, SafeAreaView } from "react-native";
import React, { useEffect, useRef, useState } from "react";
import { useNavigation, useRouter } from "expo-router";
import { useNavigation, usePathname, useRouter } from "expo-router";
import { useFeed } from "@gno/hooks/use-feed";
import Layout from "@gno/components/layout";
import useScrollToTop from "@gno/components/utils/useScrollToTopWithOffset";
import Button from "@gno/components/button";
import { Post } from "@gno/types";
import { selectAccount, setPostToReply, useAppDispatch, useAppSelector } from "@gno/redux";
import { gnodTxAndRedirectToSign, selectAccount, setPostToReply, useAppDispatch, useAppSelector } from "@gno/redux";
import Alert from "@gno/components/alert";
import { FeedView } from "@gno/components/view";

Expand All @@ -22,15 +22,13 @@ export default function Page() {
const dispatch = useAppDispatch();

const account = useAppSelector(selectAccount);
const pathName = usePathname();

useScrollToTop(ref, Platform.select({ ios: -150, default: 0 }));

useEffect(() => {
const unsubscribe = navigation.addListener("focus", async () => {
if (!account) {
RNAlert.alert("No user found.");
return;
}
if (!account) return;
setError(undefined);
setIsLoading(true);
try {
Expand All @@ -50,23 +48,14 @@ export default function Page() {
router.navigate({ pathname: "/post" });
};

const onPress = async (item: Post) => {
await dispatch(setPostToReply({ post: item }));
router.navigate({ pathname: "/post/[post_id]", params: { post_id: item.id, address: item.user.bech32 } });
const onPress = async (p: Post) => {
await dispatch(setPostToReply(p));
router.navigate({ pathname: "/post/[post_id]" });
};

const onGnod = async (post: Post) => {
setIsLoading(true);

if (!account) throw new Error("No active account");

try {
await feed.onGnod(post, account.address);
} catch (error) {
RNAlert.alert("Error", "Error while adding reaction: " + error);
} finally {
setIsLoading(false);
}
dispatch(gnodTxAndRedirectToSign({ post, callerAddressBech32: account.bech32, callbackPath: pathName })).unwrap();
};

if (isLoading)
Expand Down
67 changes: 46 additions & 21 deletions mobile/app/home/profile.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
import { Alert, ScrollView, StyleSheet, View } from "react-native";
import { router, useNavigation } from "expo-router";
import { router, useNavigation, usePathname } from "expo-router";
import { useEffect, useState } from "react";
import { useGnoNativeContext } from "@gnolang/gnonative";
import { logedOut, selectAccount, useAppDispatch, useAppSelector } from "@gno/redux";
import { avatarTxAndRedirectToSign, broadcastTxCommit, clearLinking, logedOut, reloadAvatar, selectAccount, selectQueryParamsTxJsonSigned, useAppDispatch, useAppSelector } from "@gno/redux";
import Button from "@gno/components/button";
import Layout from "@gno/components/layout";
import { LoadingModal } from "@gno/components/loading";
import { AccountBalance } from "@gno/components/settings";
import Text from "@gno/components/text";
import { useSearch } from "@gno/hooks/use-search";
import { useNotificationContext } from "@gno/provider/notification-provider";
import { onboarding } from "redux/features/signupSlice";
import AvatarPicker from "@gno/components/avatar/avatar-picker";
import { ProgressViewModal } from "@gno/components/view/progress";
import { compressImage } from '@gno/utils/file-utils';
import { useUserCache } from "@gno/hooks/use-user-cache";

export default function Page() {
const [loading, setLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [chainID, setChainID] = useState("");
const [remote, setRemote] = useState("");
const [followersCount, setFollowersCount] = useState({ n_followers: 0, n_following: 0 });
const pathName = usePathname();

const account = useAppSelector(selectAccount);
const { gnonative } = useGnoNativeContext();
const search = useSearch();
const navigation = useNavigation();
const dispatch = useAppDispatch();
const push = useNotificationContext();
const txJsonSigned = useAppSelector(selectQueryParamsTxJsonSigned);

const userCache = useUserCache();

useEffect(() => {
const unsubscribe = navigation.addListener("focus", async () => {
Expand All @@ -39,22 +44,6 @@ export default function Page() {
return unsubscribe;
}, [navigation]);

const onboard = async () => {
if (!account) {
console.log("No active account");
return;
}
setLoading(true);
try {
await dispatch(onboarding({ account })).unwrap();
fetchAccountData();
} catch (error) {
console.log("Error on onboard", JSON.stringify(error));
} finally {
setLoading(false);
}
};

const onPressNotification = async () => {
if (!account) {
throw new Error("No active account");
Expand Down Expand Up @@ -101,13 +90,50 @@ export default function Page() {
dispatch(logedOut());
};

const onAvatarChanged = async (imagePath: string, mimeType?: string) => {
const imageCompressed = await compressImage(imagePath)
if (!imageCompressed || !mimeType || !imageCompressed.base64) {
console.log("Error compressing image or missing data");
return;
}

if (!account) throw new Error("No account found");
await dispatch(avatarTxAndRedirectToSign({ mimeType, base64: imageCompressed.base64, callerAddressBech32: account.bech32, callbackPath: pathName })).unwrap();
}

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());
userCache.invalidateCache();

setTimeout(() => {
console.log("reloading avatar");
dispatch(reloadAvatar());
}, 500);
}
})();

}, [txJsonSigned]);

return (
<>
<Layout.Container>
<Layout.Body>
<ScrollView >
<View style={{ paddingBottom: 20 }}>
<AvatarPicker />
<AvatarPicker onChanged={onAvatarChanged} />
</View>
<>
<AccountBalance activeAccount={account} />
Expand All @@ -124,7 +150,6 @@ export default function Page() {
<Layout.Footer>
<ProgressViewModal visible={modalVisible} onRequestClose={() => setModalVisible(false)} />
<Button.TouchableOpacity title="Logs" onPress={() => setModalVisible(true)} variant="primary" />
<Button.TouchableOpacity title="Onboard the current user" onPress={onboard} variant="primary" />
<Button.TouchableOpacity
title="Register to the notification service"
onPress={onPressNotification}
Expand Down
Loading

0 comments on commit f00e4a0

Please sign in to comment.