diff --git a/js/app/components/chat/chat-box.tsx b/js/app/components/chat/chat-box.tsx
index 2a837986..3f79ac6e 100644
--- a/js/app/components/chat/chat-box.tsx
+++ b/js/app/components/chat/chat-box.tsx
@@ -1,15 +1,15 @@
+import { useNavigation } from "@react-navigation/native";
import { Send } from "@tamagui/lucide-icons";
-import { useRef, useState } from "react";
-import { Button, Form, isWeb, View, Input, TextArea } from "tamagui";
-import { Keyboard } from "react-native";
-import { usePlayerLivestream } from "features/player/playerSlice";
-import { useAppDispatch, useAppSelector } from "store/hooks";
import {
chatPost,
selectIsReady,
selectUserProfile,
} from "features/bluesky/blueskySlice";
-import { useNavigation } from "@react-navigation/native";
+import { usePlayerLivestream } from "features/player/playerSlice";
+import { useRef, useState } from "react";
+import { Keyboard } from "react-native";
+import { useAppDispatch, useAppSelector } from "store/hooks";
+import { Button, Form, Input, isWeb, TextArea, View } from "tamagui";
export default function ChatBox() {
const [message, setMessage] = useState("");
@@ -79,6 +79,7 @@ export default function ChatBox() {
submit();
}
}}
+ onSubmitEditing={submit}
/>
) {
return (
@@ -34,7 +35,24 @@ export function LivestreamInner(props: Partial) {
const video = player.segment?.video?.[0];
const [videoWidth, setVideoWidth] = useState(0);
const [videoHeight, setVideoHeight] = useState(0);
- const { isKeyboardVisible } = useKeyboard();
+ const { isKeyboardVisible, keyboardHeight } = useKeyboard();
+ const { isIOS } = usePlatform();
+
+ const [outerHeight, setOuterHeight] = useState(0);
+ const [innerHeight, setInnerHeight] = useState(0);
+
+ // this would all be really easy if i had library that would give me the
+ // safe area view height and width but i don't. so let's measure
+ const onInnerLayout = useCallback((event: LayoutChangeEvent) => {
+ const { width, height } = event.nativeEvent.layout;
+ setInnerHeight(height);
+ }, []);
+
+ const onOuterLayout = useCallback((event: LayoutChangeEvent) => {
+ const { width, height } = event.nativeEvent.layout;
+ setOuterHeight(height);
+ }, []);
+
useEffect(() => {
if (video) {
const ratio = video.width / width;
@@ -43,128 +61,161 @@ export function LivestreamInner(props: Partial) {
}
}, [video, width, height]);
+ let slideKeyboard = 0;
+ if (isIOS && keyboardHeight > 0) {
+ slideKeyboard = -keyboardHeight + (outerHeight - innerHeight);
+ }
+
return (
-
- {videoWidth === 0 && (
-
-
-
- )}
- {telemetry === null && (
- {
- dispatch(telemetryOpt(false));
- }}
- containerProps={{
- bottom: "$8",
- zIndex: 1000,
- }}
- bubbleProps={{
- cursor: "pointer",
- backgroundColor: "$accentBackground",
- gap: "$3",
- maxWidth: 400,
- }}
+
+
+
- Player Telemetry
-
- Streamplace is beta software and it helps us out to have the player
- report back on how playback is working. Would you like to opt in to
- optional player telemetry?
-
-
- {
- dispatch(telemetryOpt(true));
- }}
+ {videoWidth === 0 && (
+
- Opt in
-
- {
+
+
+ )}
+ {telemetry === null && (
+ {
dispatch(telemetryOpt(false));
}}
+ containerProps={{
+ bottom: "$8",
+ zIndex: 1000,
+ }}
+ bubbleProps={{
+ cursor: "pointer",
+ backgroundColor: "$accentBackground",
+ gap: "$3",
+ maxWidth: 400,
+ }}
>
- Opt out
-
-
-
- )}
-
-
-
+ Player Telemetry
+
+ Streamplace is beta software and it helps us out to have the
+ player report back on how playback is working. Would you like to
+ opt in to optional player telemetry?
+
+
+ {
+ dispatch(telemetryOpt(true));
+ }}
+ >
+ Opt in
+
+ {
+ dispatch(telemetryOpt(false));
+ }}
+ >
+ Opt out
+
+
+
+ )}
- {player.livestream?.record.title}
-
-
+
+
+
+ {player.livestream?.record.title}
+
+
+
+
+
+
+
+
+
+
+ {player.livestream?.record.title}
+
+
+
+
+
+
+
+
+
+
-
-
-
-
- {player.livestream?.record.title}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
);
}
diff --git a/js/app/components/player/provider.tsx b/js/app/components/player/provider.tsx
index 5fdecabd..32b972ae 100644
--- a/js/app/components/player/provider.tsx
+++ b/js/app/components/player/provider.tsx
@@ -5,12 +5,11 @@ import {
PlayerContext,
usePlayerActions,
} from "features/player/playerSlice";
-import { useState, useEffect, useContext, useRef } from "react";
+import { selectUrl } from "features/streamplace/streamplaceSlice";
+import { useContext, useEffect, useRef, useState } from "react";
+import useWebSocket, { ReadyState } from "react-use-websocket";
import { useAppDispatch, useAppSelector } from "store/hooks";
import { PlayerProps } from "./props";
-import { selectUrl } from "features/streamplace/streamplaceSlice";
-import useWebSocket from "react-use-websocket";
-import { ReadyState } from "react-use-websocket";
const POLL_INTERVAL = 3000;
// PlayerInner starts doing player stuff
@@ -76,7 +75,6 @@ export function PlayerDataContext(
wsUrl = wsUrl.replace(/^https\:/, "wss:");
const ref = useRef([]);
- const last = useRef(0);
const handle = useRef(null);
const { readyState } = useWebSocket(`${wsUrl}/api/websocket/${props.src}`, {
@@ -84,10 +82,15 @@ export function PlayerDataContext(
shouldReconnect: () => true,
onOpen: () => {
- console.log("onOpen");
ref.current = [];
},
+ onError: (e) => {
+ console.log("onError", e);
+ },
+
+ // spamming the redux store with messages causes a zillion re-renders,
+ // so we batch them up a bit
onMessage: (msg) => {
try {
const data = JSON.parse(msg.data);
@@ -95,18 +98,13 @@ export function PlayerDataContext(
if (handle.current) {
return;
}
- let scheduleUpdate = Date.now() - last.current;
- if (scheduleUpdate < 0) {
- scheduleUpdate = 0;
- }
handle.current = setTimeout(() => {
dispatch(handleWebSocketMessages(ref.current));
ref.current = [];
- last.current = Date.now();
handle.current = null;
- }, scheduleUpdate);
+ }, 250);
} catch (e) {
- last.current = Date.now();
+ console.log("onMessage parse error", e);
}
},
});
diff --git a/js/app/hooks/useKeyboard.tsx b/js/app/hooks/useKeyboard.tsx
index f94c48ce..7fd41f7e 100644
--- a/js/app/hooks/useKeyboard.tsx
+++ b/js/app/hooks/useKeyboard.tsx
@@ -1,21 +1,41 @@
-import { useState, useEffect } from "react";
+import { useEffect, useState } from "react";
import { Keyboard } from "react-native";
export function useKeyboard() {
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
-
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
useEffect(() => {
- const showSubscription = Keyboard.addListener("keyboardDidShow", () => {
+ const willShowSubscription = Keyboard.addListener(
+ "keyboardWillShow",
+ (e) => {
+ // setIsKeyboardVisible(true);
+ setKeyboardHeight(e.endCoordinates.height);
+ console.log("keyboardWillShow", e.endCoordinates.height);
+ },
+ );
+ const willHideSubscription = Keyboard.addListener(
+ "keyboardWillHide",
+ (e) => {
+ // setIsKeyboardVisible(false);
+ setKeyboardHeight(0);
+ console.log("keyboardWillHide", e.endCoordinates.height);
+ },
+ );
+ const showSubscription = Keyboard.addListener("keyboardDidShow", (e) => {
setIsKeyboardVisible(true);
+ setKeyboardHeight(e.endCoordinates.height);
+ console.log("keyboardDidShow", e.endCoordinates.height);
});
const hideSubscription = Keyboard.addListener("keyboardDidHide", () => {
setIsKeyboardVisible(false);
+ setKeyboardHeight(0);
});
return () => {
showSubscription.remove();
hideSubscription.remove();
+ willShowSubscription.remove();
};
}, []);
- return { isKeyboardVisible };
+ return { isKeyboardVisible, keyboardHeight };
}