Skip to content

Commit

Permalink
fix: quest onboarding flow
Browse files Browse the repository at this point in the history
  • Loading branch information
oreHGA committed Nov 12, 2024
1 parent 39ae5df commit 5a15799
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 86 deletions.
2 changes: 1 addition & 1 deletion mobile/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ missingDimensionStrategy "store", "play"
applicationId 'com.neurofusion.fusion'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 93
versionCode 95
versionName "2.1.0"

buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
Expand Down
8 changes: 4 additions & 4 deletions mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
ios: {
supportsTablet: true,
bundleIdentifier: "com.neurofusion.fusion",
buildNumber: "93",
buildNumber: "95",
backgroundColor: "#0B0816",
config: {
usesNonExemptEncryption: false,
Expand All @@ -36,7 +36,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
backgroundColor: "#0B0816",
},
package: "com.neurofusion.fusion",
versionCode: 93,
versionCode: 95,
softwareKeyboardLayoutMode: "pan",
permissions: [
"android.permission.health.READ_STEPS",
Expand All @@ -58,8 +58,8 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
"InstrumentationKey=5a52ca8a-bd71-4c4c-84f6-d51429acbe03;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/",
fusionBackendUrl:
process.env.FUSION_BACKEND_API_URL ??
// "https://neurofusionbackendprd.azurewebsites.net",
"https://neurofusion-backend.azurewebsites.net",
"https://neurofusionbackendprd.azurewebsites.net",
// "https://neurofusion-backend.azurewebsites.net",
fusionRelayUrl: "wss://relay.usefusion.ai",
fusionNostrPublicKey:
"5f3a52d8027cdde03a41857e98224dafd69495204d93071199aa86921aa02674",
Expand Down
4 changes: 2 additions & 2 deletions mobile/ios/Fusion.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.neurofusion.fusion;
PRODUCT_NAME = FusionCopilot;
PRODUCT_NAME = "FusionCopilot";
SWIFT_OBJC_BRIDGING_HEADER = "Fusion/Fusion-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
Expand Down Expand Up @@ -431,7 +431,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.neurofusion.fusion;
PRODUCT_NAME = FusionCopilot;
PRODUCT_NAME = "FusionCopilot";
SWIFT_OBJC_BRIDGING_HEADER = "Fusion/Fusion-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down
2 changes: 1 addition & 1 deletion mobile/ios/Fusion/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>93</string>
<string>95</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
Expand Down
112 changes: 50 additions & 62 deletions mobile/src/pages/quest-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { HealthCard } from "~/components/health-details";
import { AccountContext } from "~/contexts";
import { useCreatePrompt, useCreateQuest } from "~/hooks";
import { RouteProp } from "~/navigation";
import { promptService } from "~/services";
import { questService } from "~/services/quest.service";
import { appInsights, getApiService } from "~/utils";

Expand All @@ -26,106 +25,102 @@ export function QuestDetailScreen() {

const route = useRoute<RouteProp<"QuestDetailScreen">>();

const [, setAddQuestPrompts] = React.useState<Prompt[]>([]);

const { mutateAsync: createQuest } = useCreateQuest();

const { mutateAsync: createPrompt } = useCreatePrompt();

const [isSubscribed, setIsSubscribed] = React.useState(false);
const [joiningQuest, setJoiningQuest] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);

const [quest, setQuest] = React.useState<Quest | undefined>(
route.params.quest
);

/**
* Fetch quest details from remote server and updates the data cache after a period of time
* Fetch quest details from remote server and
* updates the data cache after a period of time
*/
const handleFetchQuestFromRemote = async () => {
try {
const apiService = await getApiService();
if (apiService === null) {
console.log("apiService is null");
return;
}
const response = await apiService.get(`/quest/detail`, {
params: { questId: route.params.quest.guid },
});
const updatedQuest = response?.data?.quest as Quest;

const savedQuest = await questService.saveQuest(updatedQuest);

if (savedQuest) {
setQuest(savedQuest);
const updatedQuest = {
...response?.data?.quest,
prompts: JSON.parse(response?.data?.quest?.config)?.prompts,
} as Quest;

if (
quest?.title !== updatedQuest.title ||
quest?.description !== updatedQuest.description ||
quest?.config !== updatedQuest.config
) {
setQuest(updatedQuest);
// only save if the user is subscribed
if (isSubscribed) {
await questService.saveQuest(updatedQuest);
}
}
} catch (error) {
console.log("error --->", error);
}
};

/**
* Fetch the prompts user has saved for this quest
*/
const handleFetchQuestPrompts = async () => {
const questPrompts = await questService.fetchQuestPrompts(
route.params.quest.guid
);
if (questPrompts) {
// it'll be slow for now but find prompt from db & set it
const fetchPrompts = async () => {
const fetchedPrompts = await Promise.all(
questPrompts.map(async (qp) => {
const prompt = await promptService.getPrompt(qp.promptId);
if (prompt) {
return prompt;
}
})
);
const filteredPrompts = fetchedPrompts.filter(
(prompt) => prompt !== undefined
);
if (filteredPrompts) setAddQuestPrompts(filteredPrompts);
};

fetchPrompts();
}
};

React.useEffect(() => {
appInsights.trackPageView({
name: "QuestDetail",
properties: {
questGuid: quest?.guid,
userNpub: accountContext?.userNpub,
},
});

// TODO: fix this!
if (quest) {
quest.prompts = JSON.parse(quest?.config ?? "{}")?.prompts;
}

getQuestSubscriptionStatus();
handleFetchQuestFromRemote();
handleFetchQuestPrompts();
pushQuestData();
}, []);
}, [quest]);

useEffect(() => {
if (isSubscribed) {
(async () => {
await handleFetchQuestFromRemote();
await pushQuestData();
})();
}
}, [isSubscribed]);

/**
* Push quest data to remote storage if the user is subscribed
*/
const pushQuestData = async () => {
if (quest?.guid) {
if (quest?.guid && isSubscribed) {
await questService.uploadQuestDataset(quest.guid);
}
};

const getQuestSubscriptionStatus = async () => {
/**
* Combination of it quest is saved locally & if api returns user subscription status
*/
try {
const getQuest = await questService.getSingleQuest(
route.params.quest.guid
);
if (getQuest) {
console.log("Quest is saved locally so user is subscribed", getQuest);
setIsLoading(true);
const questSubscriptionStatus =
await questService.fetchRemoteSubscriptionStatus(
route.params.quest.guid
);
if (questSubscriptionStatus) {
setIsSubscribed(true);
} else {
setIsSubscribed(false);
}
} catch (error) {
console.error("Failed to get quest subscription status", error);
} finally {
setIsLoading(false);
}
};

Expand Down Expand Up @@ -266,7 +261,6 @@ export function QuestDetailScreen() {
)}
<HealthCard />
<View className="mt-5">
{/* TODO: display the list of prompts that are required for the quest */}
<Text className="text-white font-sans text-lg px-5">Prompts</Text>
{quest?.prompts &&
quest.prompts.length > 0 &&
Expand All @@ -276,12 +270,6 @@ export function QuestDetailScreen() {
prompt={prompt}
variant="detail"
displayFrequency
onClick={() => {
if (isSubscribed !== false) {
handlePromptExpandSheet(prompt);
// handle prompt bottom sheet
}
}}
/>
</View>
))}
Expand All @@ -294,7 +282,7 @@ export function QuestDetailScreen() {
</ScrollView>

{/* if the user is not subscribed, show 'Get Started' */}
{isSubscribed === false && (
{isLoading === false && isSubscribed === false && (
<View className="mt-5">
<Button
title="Complete Onboarding"
Expand All @@ -306,7 +294,7 @@ export function QuestDetailScreen() {
</View>
)}

{isSubscribed && (
{isLoading === false && isSubscribed && (
<View className="mt-5 space-y-2">
<Button
title="View Leaderboard"
Expand Down
73 changes: 57 additions & 16 deletions mobile/src/services/quest.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import dayjs from "dayjs";

import { promptService } from "./prompt.service";

import { Quest, Prompt, QuestPrompt } from "~/@types";
import { db } from "~/lib";
import { buildHealthDataset, getApiService } from "~/utils";
Expand Down Expand Up @@ -212,7 +214,8 @@ class QuestService {
(_, { rows }) => {
const quest = rows._array[0] as Quest;
if (quest) {
quest.prompts = JSON.parse(quest.config!) as Prompt[];
const configObject = JSON.parse(quest.config!);
quest.prompts = configObject?.prompts as Prompt[];
resolve(quest);
} else {
resolve(null);
Expand All @@ -235,23 +238,29 @@ class QuestService {
}

async fetchRemoteSubscriptionStatus(questId: string) {
console.log("Quest is not saved locally, checking subscription status");
const apiService = await getApiService();
if (apiService === null) {
throw new Error("Failed to get api service");
}
try {
console.log("Quest is not saved locally, checking subscription status");
const apiService = await getApiService();
if (apiService === null) {
throw new Error("Failed to get api service");
}

const response = await apiService.get(`/quest/userSubscription`, {
params: {
questId,
},
});
const response = await apiService.get(`/quest/userSubscription`, {
params: {
questId,
},
});

if (response.status >= 200 && response.status < 300) {
console.log("Subscription status", response.data);
return response.data;
} else {
console.log(response.status);
if (response.status >= 200 && response.status < 300) {
console.log("Subscription status", response.data);
return response.data;
} else {
console.log(response.status);
return false;
}
} catch (error) {
console.error(error);
return false;
}
}

Expand Down Expand Up @@ -294,6 +303,38 @@ class QuestService {
// remove quest_prompts
// remove prompt
// remove quest
try {
const questPrompts = await this.fetchQuestPrompts(questId);
// delete prompts
await Promise.all(
questPrompts.map((questPrompt) =>
promptService.deletePrompt(questPrompt.promptId)
)
);
// delete quest
const deleteQuest = () =>
new Promise<boolean>((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
`DELETE FROM quests WHERE guid = ?`,
[questId],
(_, { rowsAffected }) => {
if (rowsAffected > 0) {
resolve(true);
} else {
resolve(false);
}
}
);
});
});

const deleteStatus = await deleteQuest();
return deleteStatus;
} catch (error) {
console.error(error);
return false;
}
}

async uploadQuestDataset(questId: string) {
Expand Down

0 comments on commit 5a15799

Please sign in to comment.