diff --git a/js/app/components/settings/about-category-settings.tsx b/js/app/components/settings/about-category-settings.tsx
index 0d47d164..9b7d6851 100644
--- a/js/app/components/settings/about-category-settings.tsx
+++ b/js/app/components/settings/about-category-settings.tsx
@@ -1,6 +1,18 @@
-import { Text, View, zero } from "@streamplace/components";
+import {
+ MenuContainer,
+ MenuGroup,
+ MenuSeparator,
+ Text,
+ useTheme,
+ View,
+ zero,
+} from "@streamplace/components";
import { useTranslation } from "react-i18next";
import { ScrollView } from "react-native";
+import {
+ SettingsExternalItem,
+ SettingsRowItem,
+} from "./components/settings-navigation-item";
import { Updates } from "./updates";
let buildInfo: {
@@ -18,8 +30,20 @@ try {
// build-info.json doesn't exist in dev mode
}
+const VERSION_REGEX = /^v?(\d+\.\d+\.\d+)(-.+)?$/;
+function cutVersionPrefix(version: string) {
+ if (VERSION_REGEX.test(version)) {
+ const match = VERSION_REGEX.exec(version);
+ if (match && match[1]) {
+ return match[1];
+ }
+ }
+ return version;
+}
+
export function AboutCategorySettings() {
const { t } = useTranslation("settings");
+ const theme = useTheme();
const getBuildStatus = () => {
if (!buildInfo) {
@@ -35,35 +59,37 @@ export function AboutCategorySettings() {
return (
-
-
+
-
- This version is
+
-
+
-
-
- Build
-
-
-
- {buildLabel} ({buildStatus})
-
-
-
-
+
+
+
+ Build
+
+
+
+ {buildLabel} ({buildStatus})
+
+
+
+
+
+
+
+
+
);
diff --git a/js/app/components/settings/advanced-category-settings.tsx b/js/app/components/settings/advanced-category-settings.tsx
index 5e14c890..7f61321a 100644
--- a/js/app/components/settings/advanced-category-settings.tsx
+++ b/js/app/components/settings/advanced-category-settings.tsx
@@ -1,4 +1,11 @@
-import { Button, Input, Text, View, zero } from "@streamplace/components";
+import {
+ Button,
+ Input,
+ MenuGroup,
+ Text,
+ View,
+ zero,
+} from "@streamplace/components";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ScrollView } from "react-native";
@@ -35,80 +42,61 @@ export function AdvancedCategorySettings() {
return (
-
-
-
+
+
+
+
+
+
+ {overrideEnabled && (
-
-
- {overrideEnabled && (
-
+
-
-
-
-
-
- )}
+ variant="default"
+ numberOfLines={1}
+ multiline={false}
+ placeholder={t("enter-custom-node-url")}
+ placeholderTextColor="#999"
+ onChangeText={setNewUrl}
+ onSubmitEditing={onSubmitUrl}
+ textContentType="URL"
+ autoCapitalize="none"
+ autoCorrect={false}
+ keyboardType="url"
+ />
+
+
-
+ )}
diff --git a/js/app/components/settings/components/setting-toggle.tsx b/js/app/components/settings/components/setting-toggle.tsx
index 1ab7a36b..029aa8f7 100644
--- a/js/app/components/settings/components/setting-toggle.tsx
+++ b/js/app/components/settings/components/setting-toggle.tsx
@@ -1,5 +1,4 @@
-import { Text, View, zero } from "@streamplace/components";
-import { mergeStyles } from "@streamplace/components/src/ui";
+import { MenuItem, Text, View } from "@streamplace/components";
import { Switch, ViewStyle } from "react-native";
export interface SettingToggleProps {
@@ -18,24 +17,16 @@ export function SettingToggle({
style,
}: SettingToggleProps) {
return (
-
-
+
+ {title}
+ {description && (
+
+ {description}
+
)}
- >
-
- {title}
-
-
- {description && (
-
- {description}
-
- )}
-
+
+
);
}
diff --git a/js/app/components/settings/components/settings-navigation-item.tsx b/js/app/components/settings/components/settings-navigation-item.tsx
index 458257da..f5445e42 100644
--- a/js/app/components/settings/components/settings-navigation-item.tsx
+++ b/js/app/components/settings/components/settings-navigation-item.tsx
@@ -1,7 +1,7 @@
import { useNavigation } from "@react-navigation/native";
-import { Text, View } from "@streamplace/components";
-import { ChevronRight, LucideIcon } from "lucide-react-native";
-import { Pressable } from "react-native";
+import { Text, View, zero } from "@streamplace/components";
+import { ChevronRight, ExternalLink, LucideIcon } from "lucide-react-native";
+import { Linking, Pressable } from "react-native";
interface SettingsNavigationItemProps {
title: string;
@@ -21,12 +21,13 @@ export function SettingsNavigationItem({
{({ pressed }) => (
);
}
+
+interface SettingsRowItemProps {
+ children?: React.ReactNode;
+}
+
+export function SettingsRowItem({ children }: SettingsRowItemProps) {
+ const navigation = useNavigation();
+
+ return (
+
+ {children}
+
+ );
+}
+
+interface SettingsExternalItemProps {
+ title: string;
+ link: string;
+}
+
+export function SettingsExternalItem({
+ title,
+ link,
+}: SettingsExternalItemProps) {
+ return (
+ Linking.openURL(link)}>
+ {({ pressed }) => (
+
+
+ {title}
+
+
+
+ )}
+
+ );
+}
diff --git a/js/app/components/settings/danmu-category-settings.tsx b/js/app/components/settings/danmu-category-settings.tsx
index 05fd00e3..4fd1d339 100644
--- a/js/app/components/settings/danmu-category-settings.tsx
+++ b/js/app/components/settings/danmu-category-settings.tsx
@@ -31,14 +31,9 @@ export function DanmuCategorySettings() {
return (
-
-
-
+
+
+
{/* Enable/Disable Danmu */}
-
-
-
- {t("language-selection")}
-
- {t("language-selection-description")}
-
-
-
-
-
-
- {manifest.languages[
- i18n.language as keyof typeof manifest.languages
- ]?.flag || "🌍"}{" "}
- {manifest.languages[
- i18n.language as keyof typeof manifest.languages
- ]?.nativeName || i18n.language}
-
-
-
-
-
+
+
+
+
+
+ {t("language-selection")}
+
+
+ {t("language-selection-description")}
+
+
+
+
+
+
+
+ {manifest.languages[
+ i18n.language as keyof typeof manifest.languages
+ ]?.flag || "🌍"}{" "}
+ {manifest.languages[
+ i18n.language as keyof typeof manifest.languages
+ ]?.nativeName || i18n.language}
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
- {filteredLanguages.map(([code, info], i) => (
-
- {
- i18n.changeLanguage(code);
- setLanguageSearchQuery("");
- }}
- >
-
-
+ {filteredLanguages.map(([code, info], i) => (
+
+ {
+ i18n.changeLanguage(code);
+ setLanguageSearchQuery("");
}}
>
- {info.flag}
-
- {info.nativeName}
-
- {info.name !== info.nativeName && (
+ {info.flag}
+
+
+ {info.nativeName}
+
+ {info.name !== info.nativeName && (
+
+ {info.name}
+
+ )}
+
+
+ {i18n.language === code && (
- {info.name}
+
)}
-
- {i18n.language === code && (
-
-
-
+
+ {i < filteredLanguages.length - 1 && (
+
)}
+
+ ))}
+
+ {filteredLanguages.length === 0 && (
+
+
+ {t("no-languages-found")}
+
-
- {i < filteredLanguages.length - 1 && (
-
)}
-
- ))}
-
- {filteredLanguages.length === 0 && (
-
-
- {t("no-languages-found")}
-
-
- )}
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
- {t("currently-translating")}
+
+
+
+
+ {t("currently-translating")}
+
+
+ {t("currently-translating-description")}
+
-
- {t("currently-translating-description")}
-
-
-
-
-
- {t("help-translate")}
+
+
+
+ {t("help-translate")}
+
+
+ {t("help-translate-description")}
+
-
- {t("help-translate-description")}
-
diff --git a/js/app/components/settings/privacy-category-settings.tsx b/js/app/components/settings/privacy-category-settings.tsx
index db8be14b..862b6c12 100644
--- a/js/app/components/settings/privacy-category-settings.tsx
+++ b/js/app/components/settings/privacy-category-settings.tsx
@@ -1,4 +1,4 @@
-import { View, zero } from "@streamplace/components";
+import { MenuGroup, View, zero } from "@streamplace/components";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { ScrollView } from "react-native";
@@ -29,25 +29,22 @@ export function PrivacyCategorySettings() {
return (
-
-
- {
- if (value === true) {
- createServerSettingsRecord(true);
- } else {
- createServerSettingsRecord(false);
- }
- }}
- />
+
+
+
+ {
+ if (value === true) {
+ createServerSettingsRecord(true);
+ } else {
+ createServerSettingsRecord(false);
+ }
+ }}
+ />
+
diff --git a/js/app/components/settings/settings.tsx b/js/app/components/settings/settings.tsx
index 3ea7767d..602c367d 100644
--- a/js/app/components/settings/settings.tsx
+++ b/js/app/components/settings/settings.tsx
@@ -1,4 +1,9 @@
import {
+ MenuContainer,
+ MenuGroup,
+ MenuInfo,
+ MenuItem,
+ MenuSeparator,
Text,
useDanmuUnlocked,
useTranslation,
@@ -10,26 +15,12 @@ import { SettingsNavigationItem } from "components/settings/components/settings-
import { Globe, Info, Lock, LogIn, Shield, Video } from "lucide-react-native";
import { ImageBackground, Pressable, ScrollView } from "react-native";
-import { ml, mt } from "@streamplace/components/src/ui";
import Mu from "components/mobile/desktop-ui/mu";
import { useStore } from "store";
import { useUserProfile } from "store/hooks";
import pkg from "../../package.json";
-export function HorizontalBar() {
- return (
-
- );
-}
-
export function Settings() {
- // are we logged in?
const loggedIn = useStore((state) => state.authStatus === "loggedIn");
const userProfile = useUserProfile();
const danmuUnlocked = useDanmuUnlocked();
@@ -38,117 +29,120 @@ export function Settings() {
return (
-
- {loggedIn && userProfile ? (
-
-
-
-
- @{userProfile.handle}
-
-
-
- ) : (
-
-
- {({ pressed }) => (
+
+
+
+ {loggedIn && userProfile ? (
+
-
- )}
-
-
+
+ ) : (
+
+
+ {({ pressed }) => (
+
+ )}
+
+
+ )}
+
- {loggedIn && (
- <>
+ {loggedIn && (
+
+
+
+
+
+ )}
+ {danmuUnlocked && (
+
+
+
+ )}
+
-
+
-
- >
- )}
- {danmuUnlocked && (
- <>
+
-
- >
- )}
-
-
-
-
-
-
- {t("app-version", { version: pkg.version })}
-
+
+
+
diff --git a/js/app/components/settings/streaming-category-settings.tsx b/js/app/components/settings/streaming-category-settings.tsx
index a1466603..d3f461e7 100644
--- a/js/app/components/settings/streaming-category-settings.tsx
+++ b/js/app/components/settings/streaming-category-settings.tsx
@@ -1,27 +1,28 @@
-import { View, zero } from "@streamplace/components";
+import { MenuGroup, MenuSeparator, View, zero } from "@streamplace/components";
import { Key, Webhook } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { ScrollView } from "react-native";
import { SettingsNavigationItem } from "./components/settings-navigation-item";
-import { HorizontalBar } from "./settings";
export function StreamingCategorySettings() {
const { t } = useTranslation("settings");
return (
-
-
-
-
+
+
+
+
+
+
diff --git a/js/app/components/settings/updates.native.tsx b/js/app/components/settings/updates.native.tsx
index 8f5e532d..4cfe7e36 100644
--- a/js/app/components/settings/updates.native.tsx
+++ b/js/app/components/settings/updates.native.tsx
@@ -1,16 +1,18 @@
import {
Button,
+ MenuItem,
Text,
useDanmuUnlocked,
useSetDanmuUnlocked,
useTheme,
useToast,
+ View,
zero,
} from "@streamplace/components";
import * as ExpoUpdates from "expo-updates";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { Platform, Pressable, TouchableOpacity, View } from "react-native";
+import { Platform, TouchableOpacity } from "react-native";
import pkg from "../../package.json";
const UNLOCK_TAP_COUNT = 5;
@@ -80,27 +82,18 @@ export function Updates() {
}
return (
-
-
-
- Streamplace v{version}
-
-
-
+ <>
+
-
-
+
+
+
{isUpdatePending && (
-
+
{t("button-reload-app-on-update")}
)}
-
+ >
);
}
diff --git a/js/app/components/settings/updates.tsx b/js/app/components/settings/updates.tsx
index ee633f6c..8074aa10 100644
--- a/js/app/components/settings/updates.tsx
+++ b/js/app/components/settings/updates.tsx
@@ -6,8 +6,9 @@ import {
} from "@streamplace/components";
import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { Pressable } from "react-native-gesture-handler";
+import { Pressable } from "react-native";
import pkg from "../../package.json";
+import { SettingsRowItem } from "./components/settings-navigation-item";
const UNLOCK_TAP_COUNT = 5;
@@ -52,7 +53,9 @@ export function Updates() {
return (
- {t("app-version", { version: pkg.version })}
+
+ {t("app-version", { version: pkg.version })}
+
);
}
diff --git a/js/components/src/components/ui/index.ts b/js/components/src/components/ui/index.ts
index bcc04fca..cb00263f 100644
--- a/js/components/src/components/ui/index.ts
+++ b/js/components/src/components/ui/index.ts
@@ -14,6 +14,7 @@ export * from "./info-box";
export * from "./info-row";
export * from "./input";
export * from "./loader";
+export * from "./menu";
export * from "./resizeable";
export * from "./slider";
export * from "./text";
diff --git a/js/components/src/components/ui/menu.tsx b/js/components/src/components/ui/menu.tsx
new file mode 100644
index 00000000..18050eee
--- /dev/null
+++ b/js/components/src/components/ui/menu.tsx
@@ -0,0 +1,171 @@
+import { forwardRef, ReactNode } from "react";
+import { Platform, View, ViewStyle } from "react-native";
+import {
+ a,
+ borderRadius,
+ fontSize,
+ gap,
+ mt,
+ mx,
+ p,
+ pb,
+ pl,
+ pr,
+ pt,
+ px,
+ py,
+} from "../../lib/theme/atoms";
+import { mergeStyles, useTheme } from "../../ui";
+import { Text } from "./text";
+
+export interface MenuContainerProps {
+ children: ReactNode;
+ style?: ViewStyle;
+}
+
+export const MenuContainer = forwardRef(
+ ({ children, style }, ref) => {
+ const { theme } = useTheme();
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+export interface MenuGroupProps {
+ children: ReactNode;
+ style?: ViewStyle;
+}
+
+export const MenuGroup = forwardRef(
+ ({ children, style }, ref) => {
+ const { theme } = useTheme();
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+export interface MenuItemProps {
+ children: ReactNode;
+ disabled?: boolean;
+ style?: ViewStyle;
+ onPress?: () => void;
+}
+
+export const MenuItem = forwardRef(
+ ({ children, disabled, style }, ref) => {
+ const { theme } = useTheme();
+ return (
+
+ {typeof children === "string" ? (
+
+ {children}
+
+ ) : (
+ children
+ )}
+
+ );
+ },
+);
+
+export interface MenuLabelProps {
+ children: ReactNode;
+ style?: ViewStyle;
+}
+
+export const MenuLabel = forwardRef(
+ ({ children, style }, ref) => {
+ const { theme } = useTheme();
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+export interface MenuSeparatorProps {
+ style?: ViewStyle;
+}
+
+export const MenuSeparator = forwardRef(
+ ({ style }, ref) => {
+ const { theme } = useTheme();
+ return (
+
+ );
+ },
+);
+
+export interface MenuInfoProps {
+ description: string;
+ style?: ViewStyle;
+}
+
+export const MenuInfo = forwardRef(
+ ({ description, style }, ref) => {
+ const { theme } = useTheme();
+ return (
+
+ {description}
+
+ );
+ },
+);
diff --git a/js/components/src/components/ui/toast.tsx b/js/components/src/components/ui/toast.tsx
index 94259657..d1e3440d 100644
--- a/js/components/src/components/ui/toast.tsx
+++ b/js/components/src/components/ui/toast.tsx
@@ -880,13 +880,18 @@ export function Toast({
width: "100%",
}}
>
-