Skip to content

Commit

Permalink
Noe/text input deeplink (#921)
Browse files Browse the repository at this point in the history
* feat: dm with text input, even from frames

* make nav helper more robust for dm deeplinks

* Deep link to group, handle group not found

* Allow converse deep link in frames

* handle converse://?text= deeplink

* add /group app link

* use URL to parse frame action target

* Add test
  • Loading branch information
nmalzieu authored Oct 10, 2024
1 parent 8d0dc6b commit 19e925e
Show file tree
Hide file tree
Showing 16 changed files with 329 additions and 53 deletions.
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
<data android:host="dev.converse.xyz" android:pathPrefix="/dm" android:scheme="https"/>
<data android:host="dev.getconverse.app" android:pathPrefix="/group-invite" android:scheme="https"/>
<data android:host="dev.converse.xyz" android:pathPrefix="/group-invite" android:scheme="https"/>
<data android:host="dev.getconverse.app" android:pathPrefix="/group" android:scheme="https"/>
<data android:host="dev.converse.xyz" android:pathPrefix="/group" android:scheme="https"/>
<data android:host="dev.getconverse.app" android:pathPrefix="/coinbase" android:scheme="https"/>
<data android:host="dev.converse.xyz" android:pathPrefix="/coinbase" android:scheme="https"/>
<data android:host="dev.getconverse.app" android:pathPrefix="/desktopconnect" android:scheme="https"/>
Expand Down
11 changes: 9 additions & 2 deletions components/Chat/ChatPlaceholder/ChatPlaceholder.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { translate } from "@i18n";
import { actionSheetColors, textPrimaryColor } from "@styles/colors";
import { isGroupTopic } from "@utils/groupUtils/groupId";
import {
Keyboard,
Platform,
Expand Down Expand Up @@ -31,6 +32,7 @@ type Props = {
};

export default function ChatPlaceholder({ messagesCount }: Props) {
const topic = useConversationContext("topic");
const conversation = useConversationContext("conversation");
const isBlockedPeer = useConversationContext("isBlockedPeer");
const onReadyToFocus = useConversationContext("onReadyToFocus");
Expand Down Expand Up @@ -62,9 +64,13 @@ export default function ChatPlaceholder({ messagesCount }: Props) {
>
{!conversation && (
<View>
<ActivityIndicator style={{ marginBottom: 20 }} />
{!topic && <ActivityIndicator style={{ marginBottom: 20 }} />}
<Text style={styles.chatPlaceholderText}>
Opening your conversation
{topic
? isGroupTopic(topic)
? translate("group_not_found")
: translate("conversation_not_found")
: translate("opening_conversation")}
</Text>
</View>
)}
Expand Down Expand Up @@ -152,6 +158,7 @@ const useStyles = () => {
textAlign: "center",
fontSize: Platform.OS === "android" ? 16 : 17,
color: textPrimaryColor(colorScheme),
paddingHorizontal: 30,
},
cta: {
alignSelf: "center",
Expand Down
19 changes: 13 additions & 6 deletions components/Chat/Frame/FramePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,21 @@ export default function FramePreview({
const onButtonPress = useCallback(
async (button: FrameButtonType) => {
if (button.action === "link") {
if (!button.target) return;
const link = button.target;
if (
!link ||
!link.startsWith("http") ||
!(await Linking.canOpenURL(link))
)
try {
const url = new URL(link);
if (
(url.protocol === "http:" ||
url.protocol === "https:" ||
url.protocol === `${config.scheme}:`) &&
(await Linking.canOpenURL(link))
) {
Linking.openURL(link);
}
} catch {
return;
Linking.openURL(link);
}
return;
}
if (!conversation) return;
Expand Down
2 changes: 1 addition & 1 deletion components/ConversationListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ const ConversationListItem = memo(function ConversationListItem({
}
navigate("Conversation", {
topic: conversationTopic,
message: routeParams?.frameURL,
text: routeParams?.frameURL,
});
}, [
isGroupConversation,
Expand Down
13 changes: 1 addition & 12 deletions components/StateHandlers/InitialStateHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,16 @@ import * as Linking from "expo-linking";
import { useCallback, useEffect, useRef } from "react";
import { useColorScheme } from "react-native";

import config from "../../config";
import { useAppStore } from "../../data/store/appStore";
import { useOnboardingStore } from "../../data/store/onboardingStore";
import { useSelect } from "../../data/store/storeHelpers";
import {
getSchemedURLFromUniversalURL,
navigateToTopicWithRetry,
topicToNavigateTo,
} from "../../utils/navigation";
import { hideSplashScreen } from "../../utils/splash/splash";

const getSchemedURLFromUniversalURL = (url: string) => {
let schemedURL = url;
// Handling universal links by saving a schemed URI
config.universalLinks.forEach((prefix) => {
if (schemedURL.startsWith(prefix)) {
schemedURL = Linking.createURL(schemedURL.replace(prefix, ""));
}
});
return schemedURL;
};

const isDevelopmentClientURL = (url: string) => {
return url.includes("expo-development-client");
};
Expand Down
4 changes: 4 additions & 0 deletions i18n/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ const en = {
if_you_block_contact:
"If you block this contact, you will not receive messages from them anymore",
opening_conversation: "Opening your conversation",
conversation_not_found:
"We couldn't find this conversation. Please try again",
group_not_found:
"We couldn't find this group. Please try again or ask someone to invite you to this group",
say_hi: "Say hi",
do_you_trust_this_contact: "Do you trust this contact?",
do_you_want_to_join_this_group: "Do you want to join this group?",
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "./wdyr";
import "react-native-gesture-handler";
import "react-native-url-polyfill/auto";
import { registerRootComponent } from "expo";

import App from "./App";
Expand Down
5 changes: 3 additions & 2 deletions screens/Conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ const Conversation = ({
const mediaPreviewRef = useRef<MediaPreview>();

const messageToPrefill = useMemo(
() => route.params?.message || conversation?.messageDraft || "",
[conversation?.messageDraft, route.params?.message]
() => route.params?.text || conversation?.messageDraft || "",
[conversation?.messageDraft, route.params?.text]
);
const mediaPreviewToPrefill = useMemo(
() => conversation?.mediaPreview || null,
Expand Down Expand Up @@ -289,6 +289,7 @@ const Conversation = ({
{route.params?.topic || route.params?.mainConversationWithPeer ? (
<ConversationContext.Provider
value={{
topic: conversationTopic,
conversation,
messageToPrefill,
inputRef: textInputRef,
Expand Down
2 changes: 1 addition & 1 deletion screens/Navigation/ConversationNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Conversation from "../Conversation";

export type ConversationNavParams = {
topic?: string;
message?: string;
text?: string;
focus?: boolean;
mainConversationWithPeer?: string;
};
Expand Down
2 changes: 1 addition & 1 deletion screens/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const linking = {
WebviewPreview: WebviewPreviewScreenConfig,
},
},
getStateFromPath: getConverseStateFromPath,
getStateFromPath: getConverseStateFromPath("fullStackNavigation"),
getInitialURL: getConverseInitialURL,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const linking = {
WebviewPreview: WebviewPreviewScreenConfig,
},
},
getStateFromPath: getConverseStateFromPath,
getStateFromPath: getConverseStateFromPath("splitRightStack"),
getInitialURL: getConverseInitialURL,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const linking = {
Conversation: ConversationScreenConfig,
},
},
getStateFromPath: getConverseStateFromPath,
getStateFromPath: getConverseStateFromPath("splitScreen"),
getInitialURL: getConverseInitialURL,
};

Expand Down
134 changes: 134 additions & 0 deletions screens/Navigation/navHelpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { getConverseStateFromPath } from "./navHelpers";

jest.mock("../../components/StateHandlers/InitialStateHandler", () => ({
initialURL: "",
}));

describe("getConverseStateFromPath", () => {
const navConfig = {
initialRouteName: "Chats",
screens: {
Chats: "/",
Conversation: {
path: "/conversation",
},
NewConversation: {
path: "/newConversation",
},
Profile: {
path: "/profile",
},
Group: {
path: "/group",
},
GroupLink: {
path: "/groupLink/:groupLinkId",
},
GroupInvite: {
path: "/group-invite/:groupInviteId",
},
ShareProfile: {
path: "/shareProfile",
},
WebviewPreview: {
path: "/webviewPreview",
},
},
};

it("should parse simple dm deeplink", () => {
const state = getConverseStateFromPath("testNav")(
"dm?peer=0xno12.eth",
navConfig
);
const route = state?.routes[state.routes.length - 1];
expect(route?.name).toBe("Conversation");
expect((route?.params as any).mainConversationWithPeer).toBe("0xno12.eth");
expect((route?.params as any).text).toBeUndefined();
});

it("should parse simple dm universal link", () => {
const state = getConverseStateFromPath("testNav")(
"dm/0xno12.eth",
navConfig
);
const route = state?.routes[state.routes.length - 1];
expect(route?.name).toBe("Conversation");
expect((route?.params as any).mainConversationWithPeer).toBe("0xno12.eth");
expect((route?.params as any).text).toBeUndefined();
});

it("should parse simple group deeplink", () => {
const state = getConverseStateFromPath("testNav")(
"group?groupId=f7349f84925aaeee5816d02b7798efbc",
navConfig
);
const route = state?.routes[state.routes.length - 1];
expect(route?.name).toBe("Conversation");
expect((route?.params as any).topic).toBe(
"/xmtp/mls/1/g-f7349f84925aaeee5816d02b7798efbc/proto"
);
expect((route?.params as any).text).toBeUndefined();
});

it("should parse simple group universal link", () => {
const state = getConverseStateFromPath("testNav")(
"group/f7349f84925aaeee5816d02b7798efbc",
navConfig
);
const route = state?.routes[state.routes.length - 1];
expect(route?.name).toBe("Conversation");
expect((route?.params as any).topic).toBe(
"/xmtp/mls/1/g-f7349f84925aaeee5816d02b7798efbc/proto"
);
expect((route?.params as any).text).toBeUndefined();
});

it("should parse simple dm deeplink with text", () => {
const state = getConverseStateFromPath("testNav")(
"dm?peer=0xno12.eth&text=hello",
navConfig
);
const route = state?.routes[state.routes.length - 1];
expect(route?.name).toBe("Conversation");
expect((route?.params as any).mainConversationWithPeer).toBe("0xno12.eth");
expect((route?.params as any).text).toBe("hello");
});

it("should parse simple dm universal link with text", () => {
const state = getConverseStateFromPath("testNav")(
"dm/0xno12.eth?text=hello",
navConfig
);
const route = state?.routes[state.routes.length - 1];
expect(route?.name).toBe("Conversation");
expect((route?.params as any).mainConversationWithPeer).toBe("0xno12.eth");
expect((route?.params as any).text).toBe("hello");
});

it("should parse simple group deeplink with text", () => {
const state = getConverseStateFromPath("testNav")(
"group?groupId=f7349f84925aaeee5816d02b7798efbc&text=hello",
navConfig
);
const route = state?.routes[state.routes.length - 1];
expect(route?.name).toBe("Conversation");
expect((route?.params as any).topic).toBe(
"/xmtp/mls/1/g-f7349f84925aaeee5816d02b7798efbc/proto"
);
expect((route?.params as any).text).toBe("hello");
});

it("should parse simple group universal link with text", () => {
const state = getConverseStateFromPath("testNav")(
"group/f7349f84925aaeee5816d02b7798efbc?text=hello",
navConfig
);
const route = state?.routes[state.routes.length - 1];
expect(route?.name).toBe("Conversation");
expect((route?.params as any).topic).toBe(
"/xmtp/mls/1/g-f7349f84925aaeee5816d02b7798efbc/proto"
);
expect((route?.params as any).text).toBe("hello");
});
});
Loading

0 comments on commit 19e925e

Please sign in to comment.