Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exchanging messages with Gnokey Mobile #133

Merged
merged 23 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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