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 ? ( + - - - + /> - - {t("sign-in")} + + @{userProfile.handle} - )} - - - )} - - + + ) : ( + + + {({ pressed }) => ( + + + + + + + + {t("sign-in")} + + + + + )} + + + )} + - {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} - - - + <> + + + + Streamplace v{version} + + {runTypeMessage} - - - + + + {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%", }} > -