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

Video modal #1763

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion ios/App/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def capacitor_pods
pod 'CapacitorLaunchNative', :path => '../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-launch-native'
pod 'CapacitorPluginSafeArea', :path => '../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-plugin-safe-area'
pod 'CapacitorReader', :path => '../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-reader'
pod 'CapacitorStashMedia', :path => '../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-stash-media'
pod 'CapacitorStashMedia', :path => '../../../capacitor-stash-media'
pod 'CapacitorTips', :path => '../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-tips'
end

Expand Down
14 changes: 7 additions & 7 deletions ios/App/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ PODS:
- Capacitor
- CapacitorTips (1.0.0):
- Capacitor
- SDWebImage (5.18.7):
- SDWebImage/Core (= 5.18.7)
- SDWebImage/Core (5.18.7)
- SDWebImage (5.20.0):
- SDWebImage/Core (= 5.20.0)
- SDWebImage/Core (5.20.0)

DEPENDENCIES:
- "Capacitor (from `../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/ios`)"
Expand All @@ -61,7 +61,7 @@ DEPENDENCIES:
- "CapacitorReader (from `../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-reader`)"
- "CapacitorShare (from `../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/share`)"
- "CapacitorSplashScreen (from `../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/splash-screen`)"
- "CapacitorStashMedia (from `../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-stash-media`)"
- CapacitorStashMedia (from `../../../capacitor-stash-media`)
- "CapacitorStatusBar (from `../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/status-bar`)"
- "CapacitorTips (from `../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-tips`)"

Expand Down Expand Up @@ -105,7 +105,7 @@ EXTERNAL SOURCES:
CapacitorSplashScreen:
:path: "../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/splash-screen"
CapacitorStashMedia:
:path: "../../node_modules/.pnpm/[email protected]_@[email protected]/node_modules/capacitor-stash-media"
:path: "../../../capacitor-stash-media"
CapacitorStatusBar:
:path: "../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/status-bar"
CapacitorTips:
Expand All @@ -132,8 +132,8 @@ SPEC CHECKSUMS:
CapacitorStashMedia: 10aa96dc5f874c4c27642528a4c327c46792abf2
CapacitorStatusBar: 3b9ac7d0684770522c532d1158a1434512ab1477
CapacitorTips: 2087733aea06ec041b210085395ca934c8554907
SDWebImage: f9258c58221ed854cfa0e2b80ee4033710b1c6d3
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8

PODFILE CHECKSUM: 810e83271706ad233eac03b47e2284efbdcfc205
PODFILE CHECKSUM: 095fb6bc4cfa620d894c9a6c3321dd14f0a18f85

COCOAPODS: 1.16.2
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"capacitor-launch-native": "^1.0.0",
"capacitor-plugin-safe-area": "^3.0.3",
"capacitor-reader": "^0.2.0",
"capacitor-stash-media": "^2.0.1",
"capacitor-stash-media": "link:../capacitor-stash-media",
"capacitor-tips": "^1.0.0",
"colorjs.io": "^0.5.2",
"compare-versions": "^6.1.1",
Expand Down
13 changes: 2 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/features/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export { default as superscript } from "./svg/superscript.svg";
export { default as subscript } from "./svg/subscript.svg";
export { default as strikethrough } from "./svg/strikethrough.svg";
export { default as trashEllipse } from "./svg/trashEllipse.svg";
export { default as pip } from "./svg/pip.svg";
1 change: 1 addition & 0 deletions src/features/icons/svg/pip.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions src/features/media/InlineMedia.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { CSSProperties } from "react";

import Media, { MediaProps } from "#/features/media/Media";
import { cx } from "#/helpers/css";
import useLatch from "#/helpers/useLatch";
import { useAppDispatch } from "#/store";

import GalleryMedia, { GalleryMediaProps } from "./gallery/GalleryMedia";
import { IMAGE_FAILED, imageFailed, imageLoaded } from "./imageSlice";
import MediaPlaceholder from "./MediaPlaceholder";
import { isLoadedAspectRatio } from "./useAspectRatio";
Expand All @@ -14,7 +14,7 @@ import useMediaLoadObserver, {

import mediaPlaceholderStyles from "./MediaPlaceholder.module.css";

export type InlineMediaProps = Omit<MediaProps, "ref"> & {
export type InlineMediaProps = Omit<GalleryMediaProps, "ref"> & {
defaultAspectRatio?: number;
mediaClassName?: string;
};
Expand Down Expand Up @@ -58,12 +58,12 @@ export default function InlineMedia({
state={buildPlaceholderState()}
defaultAspectRatio={defaultAspectRatio}
>
<Media
<GalleryMedia
{...props}
src={src}
className={cx(mediaPlaceholderStyles.media, mediaClassName)}
style={buildStyle()}
ref={mediaRef}
ref={mediaRef as React.Ref<HTMLImageElement>}
onError={() => {
if (src) dispatch(imageFailed(src));
}}
Expand Down
49 changes: 0 additions & 49 deletions src/features/media/Media.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.enableWarningContainer {
display: flex;
flex-direction: column;
padding: 16px;
text-align: center;
}
73 changes: 73 additions & 0 deletions src/features/media/external/loops/LargeFeedLoopsMedia.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { IonButton, IonText } from "@ionic/react";
import { ComponentProps, useEffect } from "react";

import MediaPlaceholder from "#/features/media/MediaPlaceholder";
import LargeFeedMedia from "#/features/post/inFeed/large/media/LargeFeedMedia";
import { setEmbedExternalMedia } from "#/features/settings/settingsSlice";
import { stopIonicTapClick } from "#/helpers/ionic";
import { useAppDispatch, useAppSelector } from "#/store";

import { enable, getVideoSrc } from "./loopsSlice";

import styles from "./LargeFeedLoopsMedia.module.css";

interface LoopsProps
extends Omit<ComponentProps<typeof LargeFeedMedia>, "src"> {
url: string;
}

export default function LargeFeedLoopsMedia({ url, ...rest }: LoopsProps) {
const dispatch = useAppDispatch();
const enabled = useAppSelector((state) => state.loops.enabled);
const src = useAppSelector((state) => {
const video = state.loops.videoByUrl[url];
if (typeof video === "object") return video.src;
});

useEffect(() => {
dispatch(getVideoSrc(url));
}, [enabled, url, dispatch]);

if (!enabled)
return (
<MediaPlaceholder state="custom" className={rest.className}>
<div className={styles.enableWarningContainer}>
<IonText className="ion-margin-bottom">
Embed videos from <strong>loops.video</strong>?
</IonText>
<IonButton
onClick={(e) => {
e.stopPropagation();
dispatch(enable());
}}
onTouchStart={() => stopIonicTapClick()}
size="default"
>
Ok
</IonButton>
<IonButton
onClick={(e) => {
e.stopPropagation();
dispatch(setEmbedExternalMedia(false));
}}
onTouchStart={() => stopIonicTapClick()}
size="default"
fill="clear"
color="dark"
>
Never embed external media
</IonButton>
</div>
</MediaPlaceholder>
);

if (!src)
return (
<MediaPlaceholder
state="loading"
onTouchStart={() => stopIonicTapClick()}
/>
);

return <LargeFeedMedia {...rest} src={src} />;
}
15 changes: 15 additions & 0 deletions src/features/media/external/loops/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { canBypassCors } from "#/helpers/device";

// https://loops.video/v/4sMCq0wx00
// allow dash and uppercase
export const loopsUrlRegex = /^https?:\/\/loops.video\/v\/([a-zA-Z0-9-_]+)/;

export function platformSupportsLoops() {
return canBypassCors();
}

export function isLoops(url: string): boolean {
if (!platformSupportsLoops()) return false;

return loopsUrlRegex.test(url);
}
96 changes: 96 additions & 0 deletions src/features/media/external/loops/loopsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

import { db } from "#/services/db";
import { getVideo } from "#/services/loops";
import { RootState } from "#/store";

import { loopsUrlRegex } from "./helpers";

interface Video {
src: string;
}

interface LoopsState {
enabled: boolean;
videoByUrl: Record<string, Video | "pending">;
}

const initialState: LoopsState = {
enabled: false,
videoByUrl: {},
};

export const loopsSlice = createSlice({
name: "loops",
initialState,
reducers: {
resetLoops: () => {
db.resetProviders(); // this should be abstracted when other providers added
return initialState;
},
},
extraReducers: (builder) => {
builder

.addCase(enable.pending, (state) => {
state.enabled = true;
})
.addCase(enable.rejected, (state) => {
state.enabled = false;
})
.addCase(enable.fulfilled, (state) => {
state.enabled = true;
})
.addCase(getVideoSrc.fulfilled, (state, action) => {
state.videoByUrl[action.meta.arg] = {
src: action.payload,
};
})
.addCase(getVideoSrc.pending, (state, action) => {
state.videoByUrl[action.meta.arg] = "pending";
})
.addCase(getVideoSrc.rejected, (state, action) => {
delete state.videoByUrl[action.meta.arg];
});
},
});

export const { resetLoops } = loopsSlice.actions;

export const getVideoSrc = createAsyncThunk(
"loops/getVideoSrc",
async (url: string, { getState }) => {
const enabled = (getState() as RootState).loops.enabled;

if (!enabled) throw new Error("Provider data not ready");

const match = url.match(loopsUrlRegex);
if (!match || !match[1])
throw new Error(`Expected loops URL, instead received ${url}`);

return await getVideo(match[1]);
},
{
condition: (url, { getState }) => {
const state = getState() as RootState;

const potentialVideo = state.loops.videoByUrl[url];

if (potentialVideo === undefined) return true;

return false;
},
},
);

export const enable = createAsyncThunk("loops/enable", async () => {
const newProvider = {
name: "loops",
data: {},
} as const;

await db.setProvider(newProvider);
return newProvider;
});

export default loopsSlice.reducer;
Loading
Loading