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

FEATURE: Audible integration #89

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions apps/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"expo": "^45.0.0",
"expo-av": "~11.2.3",
"expo-clipboard": "~3.0.1",
"expo-crypto": "~10.2.0",
"expo-dev-client": "~1.0.1",
"expo-document-picker": "~10.2.1",
"expo-font": "~10.1.0",
Expand Down
64 changes: 64 additions & 0 deletions packages/app/api/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,13 @@ export type EntryLikesQuery = {
} | null;
};

export type AudibleTokenQueryVariables = Exact<{ [key: string]: never }>;

export type AudibleTokenQuery = {
__typename?: "Query";
getAudibleToken?: { __typename?: "Token"; token?: string | null } | null;
};

export type GetIssuerQueryVariables = Exact<{
cid: Scalars["String"];
}>;
Expand Down Expand Up @@ -1633,6 +1640,63 @@ export type EntryLikesQueryResult = Apollo.QueryResult<
EntryLikesQuery,
EntryLikesQueryVariables
>;
export const AudibleTokenDocument = gql`
query audibleToken {
getAudibleToken {
token
}
}
`;

/**
* __useAudibleTokenQuery__
*
* To run a query within a React component, call `useAudibleTokenQuery` and pass it any options that fit your needs.
* When your component renders, `useAudibleTokenQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useAudibleTokenQuery({
* variables: {
* },
* });
*/
export function useAudibleTokenQuery(
baseOptions?: Apollo.QueryHookOptions<
AudibleTokenQuery,
AudibleTokenQueryVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<AudibleTokenQuery, AudibleTokenQueryVariables>(
AudibleTokenDocument,
options
);
}
export function useAudibleTokenLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
AudibleTokenQuery,
AudibleTokenQueryVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<AudibleTokenQuery, AudibleTokenQueryVariables>(
AudibleTokenDocument,
options
);
}
export type AudibleTokenQueryHookResult = ReturnType<
typeof useAudibleTokenQuery
>;
export type AudibleTokenLazyQueryHookResult = ReturnType<
typeof useAudibleTokenLazyQuery
>;
export type AudibleTokenQueryResult = Apollo.QueryResult<
AudibleTokenQuery,
AudibleTokenQueryVariables
>;
export const GetIssuerDocument = gql`
query GetIssuer($cid: String!) {
getIssuer(cid: $cid)
Expand Down
5 changes: 5 additions & 0 deletions packages/app/api/queries/get-audible-token.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query audibleToken {
getAudibleToken {
token
}
}
19 changes: 8 additions & 11 deletions packages/app/features/dashboard/profile/edit/changeUserAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import React, { useEffect } from "react";
import useMediaLibraryPermission from "app/hooks/useMediaLibraryPermission";
import usePickMedia from "app/hooks/usePickMedia";
import { validateProfilePicture } from "app/validation";
import { ChangeAvatarImg } from "app/types";
import { MediaFileInfo } from "app/types";
import { useToast } from "react-native-toast-notifications";

type ChangeUserAvatarProps = {
avatarImg: ChangeAvatarImg;
avatarImg: string;
displayName?: string | null;
onChange: (_avatar: ChangeAvatarImg) => void;
onChange: (avatar: MediaFileInfo) => void;
disable?: boolean;
};

Expand All @@ -22,19 +22,16 @@ export function ChangeUserAvatar({
}: ChangeUserAvatarProps) {
const toast = useToast();
useMediaLibraryPermission();
const { pickMedia, loading, error, data, url } = usePickMedia(
const { pickMedia, loading, error, media } = usePickMedia(
"image",
validateProfilePicture
);

useEffect(() => {
if (data && url) {
onChange({
blob: data,
url,
});
if (media) {
onChange(media);
}
}, [data, url, onChange]);
}, [media, onChange]);

useEffect(() => {
if (error) {
Expand All @@ -48,7 +45,7 @@ export function ChangeUserAvatar({
<ActivityIndicator size="large" />
) : (
<UserAvatar
avatarUrl={avatarImg.url}
avatarUrl={avatarImg}
displayName={displayName}
size="large"
/>
Expand Down
20 changes: 12 additions & 8 deletions packages/app/features/dashboard/profile/edit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { userAtom } from "app/state/user";
import { FormInputWithIcon } from "app/ui/inputs/FormInputWithIcon";
import { ScrollView } from "app/design-system/ScrollView";
import { useRouter } from "solito/router";
import { ChangeAvatarImg, EditProfileForm } from "app/types";
import { EditProfileForm, MediaFileInfo } from "app/types";
import * as assert from "assert";
import { editProfileFormSchema } from "app/validation";
import useUploadFileToNFTStorage from "app/hooks/useUploadFileToNFTStorage";
Expand All @@ -27,9 +27,10 @@ import { ChangeWallet } from "./ChangeWallet";
export default function EditProfileScreen() {
const [user, setUser] = useRecoilState(userAtom);
assert.ok(user, "Unauthorized access on EditProfileScreen");
const [avatar, setAvatar] = useState<ChangeAvatarImg>({
url: user.avatarUrl ?? "",
});
const [avatar, setAvatar] = useState<string>(user.avatarUrl ?? "");
const [changedAvatar, setChangedAvatar] = useState<MediaFileInfo | null>(
null
);
const [updateUser, { data, loading }] = useUpdateUserMutation();
const { uploadFile, progress } = useUploadFileToNFTStorage();
const { back } = useRouter();
Expand All @@ -40,8 +41,8 @@ export default function EditProfileScreen() {

let avatarUrl = "";
try {
if (avatar.blob) {
const cid = await uploadFile(avatar.blob);
if (changedAvatar) {
const cid = await uploadFile(changedAvatar);
avatarUrl = `${ipfsProtocol}${cid}`;
}
await updateUser({
Expand Down Expand Up @@ -92,7 +93,10 @@ export default function EditProfileScreen() {
<ChangeUserAvatar
avatarImg={avatar}
displayName={user!.displayName}
onChange={setAvatar}
onChange={(file) => {
setAvatar(file.uri);
setChangedAvatar(file);
}}
disable={loading}
/>
<FormInputWithIcon
Expand Down Expand Up @@ -142,7 +146,7 @@ export default function EditProfileScreen() {
size="large"
onPress={handleSubmit}
className="mb-5 md:mb-0 md:mr-5"
disabled={!isValid}
disabled={!isValid || loading}
loading={loading || !!progress}
/>
<Button
Expand Down
27 changes: 14 additions & 13 deletions packages/app/features/dashboard/profile/mint/MintScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { tw } from "app/design-system/tailwind";
import { FormInputWithIcon } from "app/ui/inputs/FormInputWithIcon";
import InfoIcon from "app/ui/icons/info-circle";
import { Formik, FormikProps } from "formik";
import { MintForm } from "app/types";
import { MediaFileInfo, MintForm } from "app/types";
import { Button, Text, View } from "app/design-system";
import { Switch } from "react-native";
import DollarIcon from "app/ui/icons/dollar";
Expand All @@ -22,8 +22,8 @@ import { WalletConnectModal } from "app/ui/modal/WalletConnectModal";

export function MintScreen() {
const [equityForSale, setEquityForSale] = useState<string>("1");
const [imageBlob, setImageBlob] = useState<Blob | null>(null);
const [videoBlob, setVideoBlob] = useState<Blob | null>(null);
const [image, setImage] = useState<MediaFileInfo | null>(null);
const [video, setVideo] = useState<MediaFileInfo | null>(null);
const [modalVisible, setModalVisible] = useState<boolean>(false);
const [uri, setUri] = useState<string>("");
const { back } = useRouter();
Expand All @@ -39,6 +39,7 @@ export function MintScreen() {
case "Indexing":
case "Submitting":
case "Sign transaction in your wallet":
case "Checking for copyrights":
return status;
case "IndexError":
return "Retry Indexing";
Expand Down Expand Up @@ -83,8 +84,8 @@ export function MintScreen() {
onSubmit={(values) => {
if (status === "IndexError") {
retryIndex();
} else if (imageBlob && videoBlob) {
mint(values, imageBlob, videoBlob, (newUri) => {
} else if (image && video) {
mint(values, image, video, (newUri) => {
setUri(newUri);
setModalVisible(true);
});
Expand Down Expand Up @@ -185,20 +186,20 @@ export function MintScreen() {
icon={InfoIcon}
label="Artwork"
type="image"
onUploadFinished={setImageBlob}
onUploadFinished={setImage}
validateFile={validateArtwork}
onClear={() => setImageBlob(null)}
success={imageBlob !== null}
onClear={() => setImage(null)}
success={image !== null}
/>
<UploadInputWithIcon
containerClassNames="border-b border-white"
icon={InfoIcon}
label="Media File"
type="other"
onUploadFinished={setVideoBlob}
onUploadFinished={setVideo}
validateFile={validateVideo}
onClear={() => setVideoBlob(null)}
success={videoBlob !== null}
onClear={() => setVideo(null)}
success={video !== null}
/>
<View className="flex flex-row py-5 items-center border-b border-white">
<Text className="mx-4 text-sm">
Expand All @@ -222,8 +223,8 @@ export function MintScreen() {
"IndexError",
])
) ||
!videoBlob ||
!imageBlob
!image ||
!video
}
/>
<Button
Expand Down
85 changes: 85 additions & 0 deletions packages/app/hooks/useAudibleCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useApolloClient } from "@apollo/client";
import { AudibleTokenDocument, AudibleTokenQuery } from "app/api/graphql";
import { useCallback } from "react";
import * as Crypto from "expo-crypto";
import { MediaFileInfo } from "app/types";
import { Platform } from "react-native";

type Return = {
verify: (beatToCheck: MediaFileInfo) => Promise<boolean>;
};

export function useAudibleCheck(): Return {
const { query } = useApolloClient();

const verify = useCallback(async (beatToCheck: MediaFileInfo) => {
return new Promise<boolean>(async (resolve, reject) => {
if (beatToCheck.image) {
reject(Error("Provided file is an image!"));
return;
}
// requests access token
const { data } = await query<AudibleTokenQuery>({
query: AudibleTokenDocument,
fetchPolicy: "network-only",
});
if (!data.getAudibleToken?.token) {
reject(Error("Could not obtain access token"));
return;
}

try {
const id = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
beatToCheck.uri
);
// compose and sends request to audible service
const body = new FormData();
if (Platform.OS === "web") {
const response = await fetch(beatToCheck.uri);
const file = await response.blob();
body.append("beat", file);
} else {
body.append(
"beat",
JSON.parse(
JSON.stringify({
uri: beatToCheck.uri,
type: beatToCheck.mimeType,
name: id,
})
)
);
}
body.append("id", id);

const request = new XMLHttpRequest();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason we are not using fetch or apollo?

// TODO change URL
request.open("POST", "http://142.93.241.220/identify");
request.setRequestHeader(
"Authorization",
`Bearer ${data.getAudibleToken.token}`
);

request.onreadystatechange = () => {
if (request.readyState === 4) {
console.log(request.status, request.responseText);
const { message } = JSON.parse(request.responseText);
if (request.status !== 200) {
reject(
Error(message ?? "Couldn't verify if the file is original")
);
} else {
resolve(true);
}
}
};
request.send(body);
} catch (ex) {
reject(ex);
}
});
}, []);

return { verify };
}
Loading