started app section

This commit is contained in:
PhilReact 2024-10-17 06:09:20 +03:00
parent e9860ae7be
commit bd170d8481
13 changed files with 451 additions and 4 deletions

View File

@ -46,6 +46,6 @@
],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src 'self' https://api.qortal.org https://api2.qortal.org https://appnode.qortal.org https://apinode.qortalnodes.live https://apinode1.qortalnodes.live https://apinode2.qortalnodes.live https://apinode3.qortalnodes.live https://apinode4.qortalnodes.live https://ext-node.qortal.link wss://appnode.qortal.org wss://ext-node.qortal.link ws://127.0.0.1:12391 http://127.0.0.1:12391 https://ext-node.qortal.link; "
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src 'self' https://*:* http://*:* wss://*:* ws://*:*;"
}
}

View File

@ -141,7 +141,7 @@ const defaultValues: MyContextInterface = {
message: "",
},
};
export let isMobile = false;
export let isMobile = true;
const isMobileDevice = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;

View File

@ -0,0 +1,6 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8.5" cy="8.5" r="8.5" fill="white"/>
<circle cx="8.5" cy="8.50003" r="6.61111" fill="#434343"/>
<path d="M5.66675 5.66669L11.3334 11.3334" stroke="white" stroke-width="2"/>
<path d="M11.3333 5.66675L5.66658 11.3334" stroke="white" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 365 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 821 KiB

View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.08728 0.00158245C2.72507 0.00158245 0 2.7262 0 6.08784C0 9.44948 2.72507 12.1741 6.08728 12.1741C7.62099 12.1741 9.02317 11.6043 10.0947 10.6668L13.3088 13.8803C13.3881 13.9596 13.4911 14 13.595 14C13.6988 14 13.8018 13.9596 13.8811 13.8803C14.0396 13.7218 14.0396 13.4643 13.8811 13.3066L10.667 10.093C11.6047 9.02162 12.1746 7.62202 12.1746 6.08626C12.1746 2.72461 9.44951 0 6.0873 0L6.08728 0.00158245ZM6.08728 11.3626C3.17756 11.3626 0.811637 8.99707 0.811637 6.08784C0.811637 3.17861 3.17756 0.813083 6.08728 0.813083C8.997 0.813083 11.3629 3.17861 11.3629 6.08784C11.3629 8.99707 8.997 11.3626 6.08728 11.3626Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@ -0,0 +1,22 @@
import React, { useEffect, useMemo, useState } from "react";
import {
AppsLibraryContainer,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
import { Add } from "@mui/icons-material";
import { getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Spacer } from "../../common/Spacer";
export const AppInfo = ({ app }) => {
return (
<AppsLibraryContainer>
</AppsLibraryContainer>
);
};

View File

@ -0,0 +1,108 @@
import {
AppBar,
Button,
Toolbar,
Typography,
Box,
TextField,
InputLabel,
} from "@mui/material";
import { styled } from "@mui/system";
export const AppsParent = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
alignItems: "center",
overflow: 'auto',
// For WebKit-based browsers (Chrome, Safari, etc.)
"::-webkit-scrollbar": {
width: "0px", // Set the width to 0 to hide the scrollbar
height: "0px", // Set the height to 0 for horizontal scrollbar
},
// For Firefox
scrollbarWidth: "none", // Hides the scrollbar in Firefox
// Optional for better cross-browser consistency
"-ms-overflow-style": "none" // Hides scrollbar in IE and Edge
}));
export const AppsContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'space-evenly',
gap: '24px',
flexWrap: 'wrap',
alignItems: 'flex-start',
}));
export const AppsLibraryContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'flex-start',
}));
export const AppsSearchContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#434343',
borderRadius: '8px',
padding: '0px 10px',
height: '36px'
}));
export const AppsSearchLeft = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'flex-start',
alignItems: 'center',
gap: '10px'
}));
export const AppsSearchRight = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
justifyContent: 'flex-end',
alignItems: 'center',
}));
export const AppCircleContainer = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
gap: '5px',
alignItems: 'center',
width: '100%'
}));
export const Add = styled(Typography)(({ theme }) => ({
fontSize: '36px',
fontWeight: 500,
lineHeight: '43.57px',
textAlign: 'left'
}));
export const AppCircleLabel = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 500,
lineHeight: 1.2,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: '100%'
}));
export const AppLibrarySubTitle = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
}));
export const AppCircle = styled(Box)(({ theme }) => ({
display: "flex",
width: "60px",
flexDirection: "column",
height: "60px",
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%',
backgroundColor: "var(--apps-circle)",
border: '1px solid #FFFFFF'
}));

View File

@ -0,0 +1,62 @@
import React, { useEffect, useState } from 'react'
import { AppsHome } from './AppsHome'
import { Spacer } from '../../common/Spacer'
import { getBaseApiReact } from '../../App'
import { AppsLibrary } from './AppsLibrary'
export const Apps = () => {
const [mode, setMode] = useState('home')
const [availableQapps, setAvailableQapps] = useState([])
const [downloadedQapps, setDownloadedQapps] = useState([])
const getQapps = React.useCallback(
async () => {
try {
let apps = []
let websites = []
// dispatch(setIsLoadingGlobal(true))
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&includestatus=true&limit=0&includemetadata=true`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if(!response?.ok) return
const responseData = await response.json();
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&includestatus=true&limit=0&includemetadata=true`;
const responseWebsites = await fetch(urlWebsites, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if(!responseWebsites?.ok) return
const responseDataWebsites = await responseWebsites.json();
apps = responseData
websites = responseDataWebsites
const combine = [...apps, ...websites]
setAvailableQapps(combine)
setDownloadedQapps(combine.filter((qapp)=> qapp?.status?.status === 'READY'))
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
}
},
[]
);
useEffect(()=> {
getQapps()
}, [getQapps])
return (
<>
<Spacer height="30px" />
{mode === 'home' && <AppsHome downloadedQapps={downloadedQapps} setMode={setMode} />}
{mode === 'library' && <AppsLibrary downloadedQapps={downloadedQapps} availableQapps={availableQapps} />}
</>
)
}

View File

@ -0,0 +1,56 @@
import React, { useMemo, useState } from 'react'
import { AppCircle, AppCircleContainer, AppCircleLabel, AppsContainer, AppsParent } from './Apps-styles'
import { Avatar, ButtonBase } from '@mui/material'
import { Add } from '@mui/icons-material'
import { getBaseApiReact } from '../../App'
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
export const AppsHome = ({downloadedQapps, setMode}) => {
return (
<AppsParent>
<AppsContainer>
<ButtonBase onClick={()=> {
setMode('library')
}}>
<AppCircleContainer>
<AppCircle>
<Add>+</Add>
</AppCircle>
<AppCircleLabel>Add</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
{downloadedQapps?.map((qapp)=> {
return (
<ButtonBase sx={{
height: '80px',
width: '60px'
}}>
<AppCircleContainer>
<AppCircle sx={{
border: 'none'
}}>
<Avatar
sx={{
height: "31px",
width: "31px",
}}
alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${qapp?.name}/qortal_avatar?async=true`}
>
<img style={{
width: '31px',
height: 'auto'
}} src={LogoSelected} alt="center-icon" />
</Avatar>
</AppCircle>
<AppCircleLabel>{qapp?.metadata?.title || qapp?.name}</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
)
})}
</AppsContainer>
</AppsParent>
)
}

View File

@ -0,0 +1,163 @@
import React, { useEffect, useMemo, useState } from "react";
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppLibrarySubTitle,
AppsContainer,
AppsLibraryContainer,
AppsParent,
AppsSearchContainer,
AppsSearchLeft,
AppsSearchRight,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
import { Add } from "@mui/icons-material";
import { getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import IconSearch from "../../assets/svgs/Search.svg";
import IconClearInput from "../../assets/svgs/ClearInput.svg";
import { Spacer } from "../../common/Spacer";
const officialAppList = [
"q-tube",
"q-blog",
"q-share",
"q-support",
"q-mail",
"qombo",
"q-fund",
"q-shop",
];
export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
const [searchValue, setSearchValue] = useState('')
const officialApps = useMemo(() => {
return availableQapps.filter((app) => app.service === 'APP' &&
officialAppList.includes(app?.name?.toLowerCase())
);
}, [availableQapps]);
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
// Debounce logic
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(searchValue); // Update debounced value after delay
}, 500); // 500ms debounce time (adjustable)
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
clearTimeout(handler);
};
}, [searchValue]); // Runs effect when searchValue changes
// Example: Perform search or other actions based on debouncedValue
const searchedList = useMemo(()=> {
if(!debouncedValue) return []
return availableQapps.filter((app)=> app.name.toLowerCase().includes(debouncedValue.toLowerCase()))
}, [debouncedValue])
console.log('officialApps', searchedList)
return (
<AppsParent>
<AppsLibraryContainer>
<Box sx={{
display: 'flex',
width: '100%',
justifyContent: 'center'
}}>
<AppsSearchContainer>
<AppsSearchLeft>
<img src={IconSearch} />
<InputBase
value={searchValue}
onChange={(e)=> setSearchValue(e.target.value)}
sx={{ ml: 1, flex: 1 }}
placeholder="Search for apps"
inputProps={{ 'aria-label': 'Search for apps', fontSize: '16px', fontWeight: 400 }}
/>
</AppsSearchLeft>
<AppsSearchRight>
{searchValue && (
<ButtonBase onClick={()=> {
setSearchValue('')
}}>
<img src={IconClearInput} />
</ButtonBase>
)}
</AppsSearchRight>
</AppsSearchContainer>
</Box>
<Spacer height="25px" />
{searchedList?.length > 0 ? (
<>
{searchedList.map((app)=> {
return (
<AppInfo app={app} />
)
})}
</>
) : (
<>
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
<Spacer height="18px" />
<AppsContainer>
{officialApps?.map((qapp) => {
return (
<ButtonBase
sx={{
height: "80px",
width: "60px",
}}
>
<AppCircleContainer>
<AppCircle
sx={{
border: "none",
}}
>
<Avatar
sx={{
height: "31px",
width: "31px",
}}
alt={qapp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
qapp?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "31px",
height: "auto",
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
<AppCircleLabel>
{qapp?.metadata?.title || qapp?.name}
</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
);
})}
</AppsContainer>
<Spacer height="18px" />
<AppLibrarySubTitle>Featured</AppLibrarySubTitle>
<Spacer height="18px" />
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
</>
)}
</AppsLibraryContainer>
</AppsParent>
);
};

View File

@ -88,6 +88,7 @@ import { ExitIcon } from "../../assets/Icons/ExitIcon";
import { HomeDesktop } from "./HomeDesktop";
import { DesktopFooter } from "../Desktop/DesktopFooter";
import { DesktopHeader } from "../Desktop/DesktopHeader";
import { Apps } from "../Apps/Apps";
// let touchStartY = 0;
// let disablePullToRefresh = false;
@ -2733,6 +2734,9 @@ export const Group = ({
setMobileViewMode={setMobileViewMode}
/>
)}
{isMobile && mobileViewMode === "apps" && (
<Apps />
)}
{
!isMobile && !selectedGroup &&
groupSection === "home" && (
@ -2958,7 +2962,7 @@ export const Group = ({
/>
</div>
{isMobile && mobileViewMode === "home" && !mobileViewModeKeepOpen && (
{(isMobile && mobileViewMode === "home" || isMobile && mobileViewMode === "apps") && !mobileViewModeKeepOpen && (
<>
<div
style={{

View File

@ -2,11 +2,14 @@ import * as React from "react";
import {
BottomNavigation,
BottomNavigationAction,
ButtonBase,
Typography,
} from "@mui/material";
import { Home, Groups, Message, ShowChart } from "@mui/icons-material";
import Box from "@mui/material/Box";
import BottomLogo from "../../assets/svgs/BottomLogo5.svg";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { CustomSvg } from "../../common/CustomSvg";
import { WalletIcon } from "../../assets/Icons/WalletIcon";
import { HubsIcon } from "../../assets/Icons/HubsIcon";
@ -132,6 +135,15 @@ export const MobileFooter = ({
zIndex: 3,
}}
>
<ButtonBase onClick={()=> {
if(mobileViewMode === 'home'){
setMobileViewMode('apps')
} else {
setMobileViewMode('home')
}
}}>
<Box
sx={{
width: "49px", // Slightly smaller inner circle
@ -144,8 +156,9 @@ export const MobileFooter = ({
}}
>
{/* Custom Center Icon */}
<img src={BottomLogo} alt="center-icon" />
<img src={mobileViewMode === 'apps' ? LogoSelected : BottomLogo} alt="center-icon" />
</Box>
</ButtonBase>
</Box>
<BottomNavigation

View File

@ -35,6 +35,7 @@
--bg-2: #27282c;
--bg-3: rgba(0, 0, 0, 0.1);
--unread: rgba(255, 0, 0, 1);
--apps-circle: #1F2023
}
body {