mirror of
https://github.com/RPCSX/rpcsx-ui.git
synced 2026-01-31 01:05:23 +01:00
explorer: add ps3 games support & localized resource support
This commit is contained in:
@@ -5,6 +5,209 @@
|
||||
|
||||
using namespace rpcsx::ui;
|
||||
|
||||
enum class LanguageCode {
|
||||
ja,
|
||||
en,
|
||||
fr,
|
||||
es,
|
||||
de,
|
||||
it,
|
||||
nl,
|
||||
pt,
|
||||
ru,
|
||||
ko,
|
||||
ch,
|
||||
zh,
|
||||
fi,
|
||||
sv,
|
||||
da,
|
||||
no,
|
||||
pl,
|
||||
br,
|
||||
gb,
|
||||
tr,
|
||||
la,
|
||||
ar,
|
||||
ca,
|
||||
cs,
|
||||
hu,
|
||||
el,
|
||||
ro,
|
||||
th,
|
||||
vi,
|
||||
in,
|
||||
uk,
|
||||
|
||||
_count
|
||||
};
|
||||
|
||||
static std::string languageCodeToString(LanguageCode code) {
|
||||
switch (code) {
|
||||
case LanguageCode::ja:
|
||||
return "ja";
|
||||
case LanguageCode::en:
|
||||
return "en";
|
||||
case LanguageCode::fr:
|
||||
return "fr";
|
||||
case LanguageCode::es:
|
||||
return "es";
|
||||
case LanguageCode::de:
|
||||
return "de";
|
||||
case LanguageCode::it:
|
||||
return "it";
|
||||
case LanguageCode::nl:
|
||||
return "nl";
|
||||
case LanguageCode::pt:
|
||||
return "pt";
|
||||
case LanguageCode::ru:
|
||||
return "ru";
|
||||
case LanguageCode::ko:
|
||||
return "ko";
|
||||
case LanguageCode::ch:
|
||||
return "ch";
|
||||
case LanguageCode::zh:
|
||||
return "zh";
|
||||
case LanguageCode::fi:
|
||||
return "fi";
|
||||
case LanguageCode::sv:
|
||||
return "sv";
|
||||
case LanguageCode::da:
|
||||
return "da";
|
||||
case LanguageCode::no:
|
||||
return "no";
|
||||
case LanguageCode::pl:
|
||||
return "pl";
|
||||
case LanguageCode::br:
|
||||
return "br";
|
||||
case LanguageCode::gb:
|
||||
return "gb";
|
||||
case LanguageCode::tr:
|
||||
return "tr";
|
||||
case LanguageCode::la:
|
||||
return "la";
|
||||
case LanguageCode::ar:
|
||||
return "ar";
|
||||
case LanguageCode::ca:
|
||||
return "ca";
|
||||
case LanguageCode::cs:
|
||||
return "cs";
|
||||
case LanguageCode::hu:
|
||||
return "hu";
|
||||
case LanguageCode::el:
|
||||
return "el";
|
||||
case LanguageCode::ro:
|
||||
return "ro";
|
||||
case LanguageCode::th:
|
||||
return "th";
|
||||
case LanguageCode::vi:
|
||||
return "vi";
|
||||
case LanguageCode::in:
|
||||
return "in";
|
||||
case LanguageCode::uk:
|
||||
return "uk";
|
||||
default:
|
||||
return "en";
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<LocalizedString>
|
||||
fetchLocalizedString(const sfo::registry ®istry, const std::string &key) {
|
||||
if (!registry.contains(key)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<LocalizedString> result;
|
||||
result.push_back({.text = registry.at(key).as_string()});
|
||||
|
||||
for (std::size_t i = 0; i < static_cast<int>(LanguageCode::_count); ++i) {
|
||||
std::string keyWithSuffix = key + (i < 10 ? "_0" : "_");
|
||||
keyWithSuffix += std::to_string(i);
|
||||
|
||||
if (!registry.contains(keyWithSuffix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(
|
||||
{.text = registry.at(keyWithSuffix).as_string(),
|
||||
.lang = languageCodeToString(static_cast<LanguageCode>(i))});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<LocalizedResource>
|
||||
fetchLocalizedResourceFile(const std::filesystem::path &path,
|
||||
const std::string &name, const std::string &ext) {
|
||||
std::vector<LocalizedResource> result;
|
||||
|
||||
if (std::filesystem::is_regular_file(path / (name + ext))) {
|
||||
result.push_back(LocalizedResource{
|
||||
.uri = "file://" + (path / (name + ext)).string(),
|
||||
});
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < static_cast<int>(LanguageCode::_count); ++i) {
|
||||
std::string suffix = (i < 10 ? "_0" : "_");
|
||||
suffix += std::to_string(i);
|
||||
|
||||
if (auto testPath = path / (name + suffix + ext);
|
||||
std::filesystem::is_regular_file(testPath)) {
|
||||
result.push_back(LocalizedResource{
|
||||
.uri = "file://" + testPath.string(),
|
||||
.lang = languageCodeToString(static_cast<LanguageCode>(i)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<LocalizedImage>
|
||||
fetchLocalizedImageFile(const std::filesystem::path &path,
|
||||
const std::string &name, const std::string &ext) {
|
||||
std::vector<LocalizedImage> result;
|
||||
|
||||
if (std::filesystem::is_regular_file(path / (name + ext))) {
|
||||
result.push_back(LocalizedImage{
|
||||
.uri = "file://" + (path / (name + ext)).string(),
|
||||
.resolution = ImageResolution::Normal,
|
||||
});
|
||||
}
|
||||
|
||||
if (std::filesystem::is_regular_file(path / (name + "_4k" + ext))) {
|
||||
result.push_back(LocalizedImage{
|
||||
.uri = "file://" + (path / (name + "_4k" + ext)).string(),
|
||||
.resolution = ImageResolution::High,
|
||||
});
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < static_cast<int>(LanguageCode::_count); ++i) {
|
||||
std::string suffix = (i < 10 ? "_0" : "_");
|
||||
suffix += std::to_string(i);
|
||||
|
||||
if (auto testPath = path / (name + suffix + ext);
|
||||
std::filesystem::is_regular_file(testPath)) {
|
||||
result.push_back(LocalizedImage{
|
||||
.uri = "file://" + testPath.string(),
|
||||
.lang = languageCodeToString(static_cast<LanguageCode>(i)),
|
||||
.resolution = ImageResolution::Normal,
|
||||
});
|
||||
}
|
||||
|
||||
if (auto testPath = path / (name + "_4k" + suffix + ext);
|
||||
std::filesystem::is_regular_file(testPath)) {
|
||||
result.push_back(LocalizedImage{
|
||||
.uri = "file://" + testPath.string(),
|
||||
.lang = languageCodeToString(static_cast<LanguageCode>(i)),
|
||||
.resolution = ImageResolution::High,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
static std::size_t calcDirectorySize(const std::filesystem::path &path) {
|
||||
std::uint64_t result = 0;
|
||||
|
||||
@@ -104,18 +307,12 @@ tryFetchGame(const std::filesystem::directory_entry &entry) {
|
||||
|
||||
ExplorerItem info;
|
||||
info.type = "game";
|
||||
info.name = fetchLocalizedString(data.sfo, "TITLE");
|
||||
|
||||
auto name = sfo::get_string(data.sfo, "TITLE");
|
||||
if (name.empty()) {
|
||||
name = sfo::get_string(data.sfo, "TITLE_ID");
|
||||
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
if (info.name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
info.name = {LocalizedString{.text = std::string(name)}};
|
||||
|
||||
info.titleId = sfo::get_string(data.sfo, "TITLE_ID");
|
||||
info.version = sfo::get_string(data.sfo, "APP_VER");
|
||||
|
||||
@@ -123,21 +320,80 @@ tryFetchGame(const std::filesystem::directory_entry &entry) {
|
||||
info.version = sfo::get_string(data.sfo, "VERSION", "1.0");
|
||||
}
|
||||
|
||||
if (std::filesystem::is_regular_file(sysPath / "icon0.png")) {
|
||||
info.icon = {
|
||||
LocalizedIcon{.uri = "file://" + (sysPath / "icon0.png").string()}};
|
||||
}
|
||||
info.icon = fetchLocalizedImageFile(sysPath, "icon0", ".png");
|
||||
info.iconSound = fetchLocalizedResourceFile(sysPath, "snd0", ".at9");
|
||||
info.background = fetchLocalizedImageFile(sysPath, "pic1", ".png");
|
||||
info.overlayImage = fetchLocalizedImageFile(sysPath, "pic2", ".png");
|
||||
|
||||
info.size = calcDirectorySize(entry.path());
|
||||
info.type = "game";
|
||||
info.launcher = LauncherInfo{
|
||||
.type = "fself-ps4-orbis" // FIXME: self/elf? ps3? ps5?
|
||||
.type = "fself-ps4-orbis" // FIXME: self/elf? ps5?
|
||||
// "fself-ps5-prospero"
|
||||
};
|
||||
info.location = "file://" + entry.path().string();
|
||||
return std::move(info);
|
||||
}
|
||||
|
||||
static std::optional<ExplorerItem>
|
||||
tryFetchPs3Game(const std::filesystem::directory_entry &entry) {
|
||||
auto usrdirPath = entry.path() / "USRDIR";
|
||||
auto paramSfoPath = entry.path() / "PARAM.SFO";
|
||||
auto ebootPath = usrdirPath / "EBOOT.BIN";
|
||||
|
||||
if (!std::filesystem::is_regular_file(ebootPath)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_regular_file(paramSfoPath)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto data = sfo::load(paramSfoPath.string());
|
||||
if (data.errc != sfo::error::ok) {
|
||||
elog("%s: error %d", entry.path().c_str(), static_cast<int>(data.errc));
|
||||
return {};
|
||||
}
|
||||
|
||||
auto titleId = sfo::get_string(data.sfo, "TITLE_ID");
|
||||
auto bootable = sfo::get_integer(data.sfo, "BOOTABLE", 0);
|
||||
auto category = sfo::get_string(data.sfo, "CATEGORY");
|
||||
|
||||
if (!bootable || titleId.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ExplorerItem info;
|
||||
info.type = "game";
|
||||
info.name = fetchLocalizedString(data.sfo, "TITLE");
|
||||
|
||||
if (info.name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
info.version = sfo::get_string(data.sfo, "APP_VER");
|
||||
|
||||
if (info.version->empty()) {
|
||||
info.version = sfo::get_string(data.sfo, "VERSION", "1.0");
|
||||
}
|
||||
|
||||
info.icon = fetchLocalizedImageFile(entry.path(), "ICON0", ".PNG");
|
||||
info.iconSound = fetchLocalizedResourceFile(entry.path(), "SND0", ".AT3");
|
||||
info.iconVideo = fetchLocalizedResourceFile(entry.path(), "ICON1", ".PAM");
|
||||
info.overlayImageWide = fetchLocalizedImageFile(entry.path(), "PIC0", ".PNG");
|
||||
info.background = fetchLocalizedImageFile(entry.path(), "PIC1", ".PNG");
|
||||
info.overlayImage = fetchLocalizedImageFile(entry.path(), "PIC2", ".PNG");
|
||||
|
||||
info.size = calcDirectorySize(entry.path());
|
||||
info.type = "game";
|
||||
info.launcher = LauncherInfo{
|
||||
.type = "self-ps3-cellos" // FIXME: fself/elf?
|
||||
};
|
||||
info.location = "file://" + entry.path().string();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
struct ExplorerExtension : rpcsx::ui::Extension<rpcsx::ui::Explorer> {
|
||||
std::thread explorerThread;
|
||||
std::vector<std::string> locations;
|
||||
@@ -205,6 +461,11 @@ struct ExplorerExtension : rpcsx::ui::Extension<rpcsx::ui::Explorer> {
|
||||
submit(std::move(*fw));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto game = tryFetchPs3Game(entry)) {
|
||||
submit(std::move(*game));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-resolution": {
|
||||
"image-resolution": {
|
||||
"type": "enum",
|
||||
"enumerators": {
|
||||
"normal": 0,
|
||||
"high": 1
|
||||
}
|
||||
},
|
||||
"localized-icon": {
|
||||
"localized-resource": {
|
||||
"type": "object",
|
||||
"params": {
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"localized-image": {
|
||||
"type": "object",
|
||||
"params": {
|
||||
"uri": {
|
||||
@@ -55,7 +67,7 @@
|
||||
"optional": true
|
||||
},
|
||||
"resolution": {
|
||||
"type": "icon-resolution",
|
||||
"type": "image-resolution",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
@@ -177,7 +189,7 @@
|
||||
},
|
||||
"icon": {
|
||||
"type": "array",
|
||||
"item-type": "localized-icon",
|
||||
"item-type": "localized-image",
|
||||
"optional": true
|
||||
},
|
||||
"description": {
|
||||
@@ -218,7 +230,7 @@
|
||||
},
|
||||
"icon": {
|
||||
"type": "array",
|
||||
"item-type": "localized-icon",
|
||||
"item-type": "localized-image",
|
||||
"optional": true
|
||||
},
|
||||
"description": {
|
||||
|
||||
@@ -13,28 +13,21 @@ export function getLocalizedString(string: LocalizedString[], langs: string[] =
|
||||
return string[0].text;
|
||||
}
|
||||
|
||||
export function getLocalizedIcon(icon: LocalizedIcon[], resolution: IconResolution = IconResolution.Normal, langs: string[] = []) {
|
||||
for (let langIndex = 0; langIndex < langs.length; ++langIndex) {
|
||||
const lang = langs[langIndex];
|
||||
|
||||
for (let iconIndex = 0; iconIndex < icon.length; ++iconIndex) {
|
||||
const localizedIcon = icon[iconIndex];
|
||||
if (localizedIcon.lang === lang && localizedIcon.resolution === resolution) {
|
||||
return localizedIcon.uri;
|
||||
}
|
||||
}
|
||||
export function getLocalizedResource(resources: LocalizedResource[], langs: string[] = []) {
|
||||
if (resources.length == 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (let langIndex = 0; langIndex < langs.length; ++langIndex) {
|
||||
const lang = langs[langIndex];
|
||||
|
||||
for (let iconIndex = 0; iconIndex < icon.length; ++iconIndex) {
|
||||
const localizedIcon = icon[iconIndex];
|
||||
if (localizedIcon.lang === lang) {
|
||||
return localizedIcon.uri;
|
||||
for (let resourceIndex = 0; resourceIndex < resources.length; ++resourceIndex) {
|
||||
const localizedResource = resources[resourceIndex];
|
||||
if (localizedResource.lang === lang) {
|
||||
return localizedResource.uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return icon[0].uri;
|
||||
return resources[0].uri;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,32 @@
|
||||
},
|
||||
"icon": {
|
||||
"type": "array",
|
||||
"item-type": "$core/localized-icon",
|
||||
"item-type": "$core/localized-image",
|
||||
"optional": true
|
||||
},
|
||||
"icon-video": {
|
||||
"type": "array",
|
||||
"item-type": "$core/localized-resource",
|
||||
"optional": true
|
||||
},
|
||||
"icon-sound": {
|
||||
"type": "array",
|
||||
"item-type": "$core/localized-resource",
|
||||
"optional": true
|
||||
},
|
||||
"background": {
|
||||
"type": "array",
|
||||
"item-type": "$core/localized-image",
|
||||
"optional": true
|
||||
},
|
||||
"overlay-image": {
|
||||
"type": "array",
|
||||
"item-type": "$core/localized-image",
|
||||
"optional": true
|
||||
},
|
||||
"overlay-image-wide": {
|
||||
"type": "array",
|
||||
"item-type": "$core/localized-image",
|
||||
"optional": true
|
||||
},
|
||||
"publisher": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Region } from '$/Region';
|
||||
import { IconResolution } from '$core/enums';
|
||||
import { getLocalizedString, getLocalizedIcon } from '$core/Localized';
|
||||
import { ImageResolution } from '$core/enums';
|
||||
import { getLocalizedString } from '$core/Localized';
|
||||
|
||||
export function getRegion(contentId?: string) {
|
||||
if (contentId === undefined || contentId.length != 36) {
|
||||
@@ -23,11 +23,41 @@ export function getName(item: ExplorerItem, langs: string[] = []) {
|
||||
return getLocalizedString(item.name, langs);
|
||||
}
|
||||
|
||||
export function getIcon(item: ExplorerItem, resolution: IconResolution = IconResolution.Normal, langs: string[] = []) {
|
||||
export function getIcon(item: ExplorerItem, resolution: ImageResolution = ImageResolution.Normal, langs: string[] = []) {
|
||||
if (!item.icon) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getLocalizedIcon(item.icon, resolution, langs);
|
||||
return getLocalizedImage(item.icon, resolution, langs);
|
||||
}
|
||||
|
||||
export function getLocalizedImage(icon: LocalizedImage[], resolution: ImageResolution = ImageResolution.Normal, langs: string[] = []) {
|
||||
if (icon.length == 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (let langIndex = 0; langIndex < langs.length; ++langIndex) {
|
||||
const lang = langs[langIndex];
|
||||
|
||||
for (let iconIndex = 0; iconIndex < icon.length; ++iconIndex) {
|
||||
const localizedIcon = icon[iconIndex];
|
||||
if (localizedIcon.lang === lang && localizedIcon.resolution === resolution) {
|
||||
return localizedIcon.uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let langIndex = 0; langIndex < langs.length; ++langIndex) {
|
||||
const lang = langs[langIndex];
|
||||
|
||||
for (let iconIndex = 0; iconIndex < icon.length; ++iconIndex) {
|
||||
const localizedIcon = icon[iconIndex];
|
||||
if (localizedIcon.lang === lang) {
|
||||
return localizedIcon.uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return icon[0].uri;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ComponentProps, memo, ReactElement, useEffect, useRef, useState } from 'react';
|
||||
import { Image, Pressable, ScrollView, StyleSheet, View, FlatList, Modal, useWindowDimensions } from 'react-native';
|
||||
import { ComponentProps, memo, useEffect, useRef, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Image, Pressable, ScrollView, StyleSheet, View, FlatList, Modal, useWindowDimensions, ImageBackground } from 'react-native';
|
||||
import { useThemeColor } from '$core/useThemeColor'
|
||||
import ThemedIcon from '$core/ThemedIcon';
|
||||
import { ThemedText } from '$core/ThemedText';
|
||||
import { getIcon, getName } from "$/ExplorerItemUtils"
|
||||
import { getIcon, getName, getLocalizedImage } from "$/ExplorerItemUtils"
|
||||
import { HapticPressable } from '$core/HapticPressable';
|
||||
import { DownShowViewSelector, LeftRightViewSelector } from '$core/ViewSelector';
|
||||
import { getLocalizedString } from '$core/Localized';
|
||||
@@ -242,7 +243,8 @@ const ExplorerItemHeader = memo(function ({ item, active, ...rest }: { item: Exp
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'nowrap',
|
||||
height: "100%",
|
||||
minWidth: 100,
|
||||
minWidth: 250,
|
||||
minHeight: 100
|
||||
},
|
||||
});
|
||||
|
||||
@@ -432,7 +434,7 @@ const ExplorerItemBody = memo(function ({ item }: { item: ExplorerItem }) {
|
||||
)
|
||||
});
|
||||
|
||||
const ExplorerView = function ({ items }: { items: ExplorerItem[] }) {
|
||||
const ExplorerView = function ({ items, setBackground }: { items: ExplorerItem[], setBackground: (uri?: string) => void }) {
|
||||
const styles = StyleSheet.create({
|
||||
topContainer: {
|
||||
width: "100%",
|
||||
@@ -444,7 +446,13 @@ const ExplorerView = function ({ items }: { items: ExplorerItem[] }) {
|
||||
}
|
||||
});
|
||||
|
||||
const [selectedItem, selectItem] = useState(0);
|
||||
const [selectedItem, setSelectedItem] = useState(0);
|
||||
|
||||
const selectItem = (index: number) => {
|
||||
setSelectedItem(index);
|
||||
|
||||
setBackground(items[index].background ? getLocalizedImage(items[index].background) : undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.topContainer}>
|
||||
@@ -523,7 +531,6 @@ const ExplorerStyles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export function Explorer(props?: Props) {
|
||||
const insets = useSafeAreaInsets();
|
||||
const [background, setBackground] = useState<string | undefined>(undefined);
|
||||
@@ -548,12 +555,22 @@ export function Explorer(props?: Props) {
|
||||
self.onExplorerItems(event => {
|
||||
games.push(...event.items.filter(item => item.type == 'game'));
|
||||
setGames(games);
|
||||
setUpdateId(updateId + 1);
|
||||
if (updateId == 0) {
|
||||
setUpdateId(updateId + 1);
|
||||
}
|
||||
console.log("received items", event.items.length, games.length);
|
||||
});
|
||||
|
||||
self.explorerGet({});
|
||||
if (games.length == 0) {
|
||||
self.explorerGet({});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const updateActiveTab = (tab: number) => {
|
||||
setActiveTab(tab);
|
||||
setBackground(undefined);
|
||||
};
|
||||
|
||||
const screens = [
|
||||
{
|
||||
title: "Games",
|
||||
@@ -566,29 +583,31 @@ export function Explorer(props?: Props) {
|
||||
];
|
||||
|
||||
return (
|
||||
<View style={[ExplorerStyles.rootContainer, { backgroundImage: background }]}>
|
||||
<View style={[ExplorerStyles.menuContainer, safeArea.header]}>
|
||||
<View style={ExplorerStyles.containerTabs}>
|
||||
<View style={ExplorerStyles.containerTabItems}>
|
||||
{
|
||||
screens.map((screen, index) =>
|
||||
<ScreenTab key={screen.title} title={screen.title} active={activeTab == index} onPress={() => setActiveTab(index)} />
|
||||
)
|
||||
}
|
||||
<ImageBackground source={{ uri: background }} style={{ width: "100%", height: "100%" }} resizeMode="cover">
|
||||
<View style={[ExplorerStyles.rootContainer]}>
|
||||
<View style={[ExplorerStyles.menuContainer, safeArea.header]}>
|
||||
<View style={ExplorerStyles.containerTabs}>
|
||||
<View style={ExplorerStyles.containerTabItems}>
|
||||
{
|
||||
screens.map((screen, index) =>
|
||||
<ScreenTab key={screen.title} title={screen.title} active={activeTab == index} onPress={() => updateActiveTab(index)} />
|
||||
)
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
<View style={[ExplorerStyles.containerButtons]}>
|
||||
<View style={ExplorerStyles.containerButtonItems}>
|
||||
<HapticPressable><ThemedIcon iconSet="Ionicons" name="search" size={40} /></HapticPressable>
|
||||
<HapticPressable onPress={() => settings.pushSettingsView({})}><ThemedIcon iconSet="Ionicons" name="settings-outline" size={40} /></HapticPressable>
|
||||
<HapticPressable><ThemedIcon iconSet="FontAwesome6" name="user" size={40} /></HapticPressable>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[ExplorerStyles.containerButtons]}>
|
||||
<View style={ExplorerStyles.containerButtonItems}>
|
||||
<HapticPressable><ThemedIcon iconSet="Ionicons" name="search" size={40} /></HapticPressable>
|
||||
<HapticPressable onPress={() => settings.pushSettingsView({})}><ThemedIcon iconSet="Ionicons" name="settings-outline" size={40} /></HapticPressable>
|
||||
<HapticPressable><ThemedIcon iconSet="FontAwesome6" name="user" size={40} /></HapticPressable>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<LeftRightViewSelector key={updateId} list={screens} style={[ExplorerStyles.contentContainer, safeArea.content]} renderItem={item =>
|
||||
<ExplorerView items={item.view} />} selectedItem={activeTab} />
|
||||
</View>
|
||||
<LeftRightViewSelector key={updateId} list={screens} style={[ExplorerStyles.contentContainer, safeArea.content]} renderItem={item =>
|
||||
<ExplorerView items={item.view} setBackground={setBackground} />} selectedItem={activeTab} />
|
||||
</View>
|
||||
</ImageBackground>
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user