Look & feel updates: replace the "FAB" with a footer menu item, update the side menu (#263)
* Remove old tab controls from the mobile shell * Add 'compose' and 'profile' to the footer; remove the FAB * Fix lint * Tune the footer icons * Tune the 'current' state of footer icons * Add 2xl text styles * Tune the footer icons a bit more * Fix lint * More footer tuning
This commit is contained in:
parent
159615990d
commit
eeac64cc88
11 changed files with 500 additions and 640 deletions
|
@ -28,6 +28,11 @@ export type ShapeName = 'button' | 'bigButton' | 'smallButton'
|
||||||
export type Shapes = Record<ShapeName, ViewStyle>
|
export type Shapes = Record<ShapeName, ViewStyle>
|
||||||
|
|
||||||
export type TypographyVariant =
|
export type TypographyVariant =
|
||||||
|
| '2xl-thin'
|
||||||
|
| '2xl'
|
||||||
|
| '2xl-medium'
|
||||||
|
| '2xl-bold'
|
||||||
|
| '2xl-heavy'
|
||||||
| 'xl-thin'
|
| 'xl-thin'
|
||||||
| 'xl'
|
| 'xl'
|
||||||
| 'xl-medium'
|
| 'xl-medium'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleProp, TextStyle, ViewStyle} from 'react-native'
|
import {StyleProp, TextStyle, ViewStyle} from 'react-native'
|
||||||
import Svg, {Path, Rect} from 'react-native-svg'
|
import Svg, {Path, Rect, Line, Ellipse} from 'react-native-svg'
|
||||||
|
|
||||||
export function GridIcon({
|
export function GridIcon({
|
||||||
style,
|
style,
|
||||||
|
@ -72,9 +72,13 @@ export function HomeIcon({
|
||||||
export function HomeIconSolid({
|
export function HomeIconSolid({
|
||||||
style,
|
style,
|
||||||
size,
|
size,
|
||||||
|
strokeWidth = 4,
|
||||||
|
fillOpacity = 1,
|
||||||
}: {
|
}: {
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
size?: string | number
|
size?: string | number
|
||||||
|
strokeWidth?: number
|
||||||
|
fillOpacity?: number
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Svg
|
<Svg
|
||||||
|
@ -84,8 +88,13 @@ export function HomeIconSolid({
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
style={style}>
|
style={style}>
|
||||||
<Path
|
<Path
|
||||||
strokeWidth={2}
|
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
|
stroke="none"
|
||||||
|
opacity={fillOpacity}
|
||||||
|
d="M 23.951 2 C 23.631 2.011 23.323 2.124 23.072 2.322 L 8.859 13.52 C 7.055 14.941 6 17.114 6 19.41 L 6 38.5 C 6 39.864 7.136 41 8.5 41 L 18.5 41 C 19.864 41 21 39.864 21 38.5 L 21 28.5 C 21 28.205 21.205 28 21.5 28 L 26.5 28 C 26.795 28 27 28.205 27 28.5 L 27 38.5 C 27 39.864 28.136 41 29.5 41 L 39.5 41 C 40.864 41 42 39.864 42 38.5 L 42 19.41 C 42 17.114 40.945 14.941 39.141 13.52 L 24.928 2.322 C 24.65 2.103 24.304 1.989 23.951 2 Z"
|
||||||
|
/>
|
||||||
|
<Path
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
d="M 23.951 2 C 23.631 2.011 23.323 2.124 23.072 2.322 L 8.859 13.52 C 7.055 14.941 6 17.114 6 19.41 L 6 38.5 C 6 39.864 7.136 41 8.5 41 L 18.5 41 C 19.864 41 21 39.864 21 38.5 L 21 28.5 C 21 28.205 21.205 28 21.5 28 L 26.5 28 C 26.795 28 27 28.205 27 28.5 L 27 38.5 C 27 39.864 28.136 41 29.5 41 L 39.5 41 C 40.864 41 42 39.864 42 38.5 L 42 19.41 C 42 17.114 40.945 14.941 39.141 13.52 L 24.928 2.322 C 24.65 2.103 24.304 1.989 23.951 2 Z"
|
d="M 23.951 2 C 23.631 2.011 23.323 2.124 23.072 2.322 L 8.859 13.52 C 7.055 14.941 6 17.114 6 19.41 L 6 38.5 C 6 39.864 7.136 41 8.5 41 L 18.5 41 C 19.864 41 21 39.864 21 38.5 L 21 28.5 C 21 28.205 21.205 28 21.5 28 L 26.5 28 C 26.795 28 27 28.205 27 28.5 L 27 38.5 C 27 39.864 28.136 41 29.5 41 L 39.5 41 C 40.864 41 42 39.864 42 38.5 L 42 19.41 C 42 17.114 40.945 14.941 39.141 13.52 L 24.928 2.322 C 24.65 2.103 24.304 1.989 23.951 2 Z"
|
||||||
/>
|
/>
|
||||||
</Svg>
|
</Svg>
|
||||||
|
@ -121,13 +130,74 @@ export function MagnifyingGlassIcon({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function MagnifyingGlassIcon2({
|
||||||
|
style,
|
||||||
|
size,
|
||||||
|
strokeWidth = 2,
|
||||||
|
}: {
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
size?: string | number
|
||||||
|
strokeWidth?: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Svg
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
stroke="currentColor"
|
||||||
|
width={size || 24}
|
||||||
|
height={size || 24}
|
||||||
|
style={style}>
|
||||||
|
<Ellipse cx="12" cy="11" rx="9" ry="9" />
|
||||||
|
<Line x1="19" y1="17.3" x2="23.5" y2="21" strokeLinecap="round" />
|
||||||
|
</Svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MagnifyingGlassIcon2Solid({
|
||||||
|
style,
|
||||||
|
size,
|
||||||
|
strokeWidth = 2,
|
||||||
|
fillOpacity = 1,
|
||||||
|
}: {
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
size?: string | number
|
||||||
|
strokeWidth?: number
|
||||||
|
fillOpacity?: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Svg
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
stroke="currentColor"
|
||||||
|
width={size || 24}
|
||||||
|
height={size || 24}
|
||||||
|
style={style}>
|
||||||
|
<Ellipse
|
||||||
|
cx="12"
|
||||||
|
cy="11"
|
||||||
|
rx="7"
|
||||||
|
ry="7"
|
||||||
|
stroke="none"
|
||||||
|
fill="currentColor"
|
||||||
|
opacity={fillOpacity}
|
||||||
|
/>
|
||||||
|
<Ellipse cx="12" cy="11" rx="9" ry="9" />
|
||||||
|
<Line x1="19" y1="17.3" x2="23.5" y2="21" strokeLinecap="round" />
|
||||||
|
</Svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/Remix-Design/RemixIcon/blob/master/License
|
// https://github.com/Remix-Design/RemixIcon/blob/master/License
|
||||||
export function BellIcon({
|
export function BellIcon({
|
||||||
style,
|
style,
|
||||||
size,
|
size,
|
||||||
|
strokeWidth = 1.5,
|
||||||
}: {
|
}: {
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
size?: string | number
|
size?: string | number
|
||||||
|
strokeWidth?: number
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Svg
|
<Svg
|
||||||
|
@ -135,12 +205,11 @@ export function BellIcon({
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
width={size || 24}
|
width={size || 24}
|
||||||
height={size || 24}
|
height={size || 24}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
stroke="currentColor"
|
||||||
style={style}>
|
style={style}>
|
||||||
<Path fill="none" d="M0 0h24v24H0z" />
|
<Path d="M 11.642 2 H 12.442 A 8.6 8.55 0 0 1 21.042 10.55 V 18.1 A 1 1 0 0 1 20.042 19.1 H 4.042 A 1 1 0 0 1 3.042 18.1 V 10.55 A 8.6 8.55 0 0 1 11.642 2 Z" />
|
||||||
<Path
|
<Line x1="9" y1="22" x2="15" y2="22" />
|
||||||
fill="currentColor"
|
|
||||||
d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0v7zm-2 0v-7a6 6 0 1 0-12 0v7h12zm-9 4h6v2H9v-2z"
|
|
||||||
/>
|
|
||||||
</Svg>
|
</Svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -149,22 +218,30 @@ export function BellIcon({
|
||||||
export function BellIconSolid({
|
export function BellIconSolid({
|
||||||
style,
|
style,
|
||||||
size,
|
size,
|
||||||
|
strokeWidth = 1.5,
|
||||||
|
fillOpacity = 1,
|
||||||
}: {
|
}: {
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
size?: string | number
|
size?: string | number
|
||||||
|
strokeWidth?: number
|
||||||
|
fillOpacity?: number
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Svg
|
<Svg
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
width={size || 24}
|
width={size || 24}
|
||||||
height={size || 24}
|
height={size || 24}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
stroke="currentColor"
|
||||||
style={style}>
|
style={style}>
|
||||||
<Path fill="none" d="M0 0h24v24H0z" />
|
|
||||||
<Path
|
<Path
|
||||||
|
d="M 11.642 2 H 12.442 A 8.6 8.55 0 0 1 21.042 10.55 V 18.1 A 1 1 0 0 1 20.042 19.1 H 4.042 A 1 1 0 0 1 3.042 18.1 V 10.55 A 8.6 8.55 0 0 1 11.642 2 Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M 20 17 L 22 17 L 22 19 L 2 19 L 2 17 L 4 17 L 4 10 C 4 3.842 10.667 -0.007 16 3.072 C 18.475 4.501 20 7.142 20 10 L 20 17 Z M 9 21 L 15 21 L 15 23 L 9 23 L 9 21 Z"
|
stroke="none"
|
||||||
|
opacity={fillOpacity}
|
||||||
/>
|
/>
|
||||||
|
<Path d="M 11.642 2 H 12.442 A 8.6 8.55 0 0 1 21.042 10.55 V 18.1 A 1 1 0 0 1 20.042 19.1 H 4.042 A 1 1 0 0 1 3.042 18.1 V 10.55 A 8.6 8.55 0 0 1 11.642 2 Z" />
|
||||||
|
<Line x1="9" y1="22" x2="15" y2="22" />
|
||||||
</Svg>
|
</Svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -527,6 +604,7 @@ export function RectTallIcon({
|
||||||
</Svg>
|
</Svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ComposeIcon({
|
export function ComposeIcon({
|
||||||
style,
|
style,
|
||||||
size,
|
size,
|
||||||
|
@ -553,3 +631,107 @@ export function ComposeIcon({
|
||||||
</Svg>
|
</Svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ComposeIcon2({
|
||||||
|
style,
|
||||||
|
size,
|
||||||
|
strokeWidth = 1.5,
|
||||||
|
backgroundColor,
|
||||||
|
}: {
|
||||||
|
style?: StyleProp<TextStyle>
|
||||||
|
size?: string | number
|
||||||
|
strokeWidth?: number
|
||||||
|
backgroundColor: string
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
stroke="currentColor"
|
||||||
|
width={size || 24}
|
||||||
|
height={size || 24}
|
||||||
|
style={style}>
|
||||||
|
<Rect
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
x="4"
|
||||||
|
y="4"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
rx="4"
|
||||||
|
ry="4"
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
x1="10"
|
||||||
|
y1="14"
|
||||||
|
x2="22"
|
||||||
|
y2="2"
|
||||||
|
strokeWidth={strokeWidth * 4}
|
||||||
|
stroke={backgroundColor}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
strokeLinecap="round"
|
||||||
|
x1="10"
|
||||||
|
y1="14"
|
||||||
|
x2="18.5"
|
||||||
|
y2="5.5"
|
||||||
|
strokeWidth={strokeWidth * 1.5}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
strokeLinecap="round"
|
||||||
|
x1="20.5"
|
||||||
|
y1="3.5"
|
||||||
|
x2="21"
|
||||||
|
y2="3"
|
||||||
|
strokeWidth={strokeWidth * 1.5}
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SquarePlusIcon({
|
||||||
|
style,
|
||||||
|
size,
|
||||||
|
strokeWidth = 1.5,
|
||||||
|
}: {
|
||||||
|
style?: StyleProp<TextStyle>
|
||||||
|
size?: string | number
|
||||||
|
strokeWidth?: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
stroke="currentColor"
|
||||||
|
width={size || 24}
|
||||||
|
height={size || 24}
|
||||||
|
style={style}>
|
||||||
|
<Line
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
x1="12"
|
||||||
|
y1="5.5"
|
||||||
|
x2="12"
|
||||||
|
y2="18.5"
|
||||||
|
strokeWidth={strokeWidth * 1.5}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
x1="5.5"
|
||||||
|
y1="12"
|
||||||
|
x2="18.5"
|
||||||
|
y2="12"
|
||||||
|
strokeWidth={strokeWidth * 1.5}
|
||||||
|
/>
|
||||||
|
<Rect
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
x="4"
|
||||||
|
y="4"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
rx="4"
|
||||||
|
ry="4"
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ export const colors = {
|
||||||
blue3: '#0085ff',
|
blue3: '#0085ff',
|
||||||
blue4: '#0062bd',
|
blue4: '#0062bd',
|
||||||
blue5: '#034581',
|
blue5: '#034581',
|
||||||
|
blue6: '#012561',
|
||||||
|
|
||||||
red1: '#ffe6f2',
|
red1: '#ffe6f2',
|
||||||
red2: '#fba2ce',
|
red2: '#fba2ce',
|
||||||
|
|
|
@ -82,6 +82,31 @@ export const defaultTheme: Theme = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
|
'2xl-thin': {
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
fontWeight: '300',
|
||||||
|
},
|
||||||
|
'2xl': {
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
fontWeight: '400',
|
||||||
|
},
|
||||||
|
'2xl-medium': {
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
'2xl-bold': {
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
fontWeight: '700',
|
||||||
|
},
|
||||||
|
'2xl-heavy': {
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
fontWeight: '800',
|
||||||
|
},
|
||||||
'xl-thin': {
|
'xl-thin': {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: 0.25,
|
||||||
|
|
|
@ -56,6 +56,7 @@ import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
|
||||||
import {faShare} from '@fortawesome/free-solid-svg-icons/faShare'
|
import {faShare} from '@fortawesome/free-solid-svg-icons/faShare'
|
||||||
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
|
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
|
||||||
import {faShield} from '@fortawesome/free-solid-svg-icons/faShield'
|
import {faShield} from '@fortawesome/free-solid-svg-icons/faShield'
|
||||||
|
import {faSquarePlus} from '@fortawesome/free-regular-svg-icons/faSquarePlus'
|
||||||
import {faSignal} from '@fortawesome/free-solid-svg-icons/faSignal'
|
import {faSignal} from '@fortawesome/free-solid-svg-icons/faSignal'
|
||||||
import {faReply} from '@fortawesome/free-solid-svg-icons/faReply'
|
import {faReply} from '@fortawesome/free-solid-svg-icons/faReply'
|
||||||
import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet'
|
import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet'
|
||||||
|
@ -131,6 +132,7 @@ export function setup() {
|
||||||
faShare,
|
faShare,
|
||||||
faShareFromSquare,
|
faShareFromSquare,
|
||||||
faShield,
|
faShield,
|
||||||
|
faSquarePlus,
|
||||||
faSignal,
|
faSignal,
|
||||||
faUser,
|
faUser,
|
||||||
faUsers,
|
faUsers,
|
||||||
|
|
|
@ -207,6 +207,21 @@ function TypographyView() {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
return (
|
return (
|
||||||
<View style={[pal.view]}>
|
<View style={[pal.view]}>
|
||||||
|
<Text type="2xl-thin" style={[pal.text]}>
|
||||||
|
'2xl-thin' lorem ipsum dolor
|
||||||
|
</Text>
|
||||||
|
<Text type="2xl" style={[pal.text]}>
|
||||||
|
'2xl' lorem ipsum dolor
|
||||||
|
</Text>
|
||||||
|
<Text type="2xl-medium" style={[pal.text]}>
|
||||||
|
'2xl-medium' lorem ipsum dolor
|
||||||
|
</Text>
|
||||||
|
<Text type="2xl-bold" style={[pal.text]}>
|
||||||
|
'2xl-bold' lorem ipsum dolor
|
||||||
|
</Text>
|
||||||
|
<Text type="2xl-heavy" style={[pal.text]}>
|
||||||
|
'2xl-heavy' lorem ipsum dolor
|
||||||
|
</Text>
|
||||||
<Text type="xl-thin" style={[pal.text]}>
|
<Text type="xl-thin" style={[pal.text]}>
|
||||||
'xl-thin' lorem ipsum dolor
|
'xl-thin' lorem ipsum dolor
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {observer} from 'mobx-react-lite'
|
||||||
import useAppState from 'react-native-appstate-hook'
|
import useAppState from 'react-native-appstate-hook'
|
||||||
import {ViewHeader} from '../com/util/ViewHeader'
|
import {ViewHeader} from '../com/util/ViewHeader'
|
||||||
import {Feed} from '../com/posts/Feed'
|
import {Feed} from '../com/posts/Feed'
|
||||||
import {FAB} from '../com/util/FAB'
|
|
||||||
import {LoadLatestBtn} from '../com/util/LoadLatestBtn'
|
import {LoadLatestBtn} from '../com/util/LoadLatestBtn'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {ScreenParams} from '../routes'
|
import {ScreenParams} from '../routes'
|
||||||
|
@ -17,7 +16,7 @@ const HEADER_HEIGHT = 42
|
||||||
export const Home = observer(function Home({navIdx, visible}: ScreenParams) {
|
export const Home = observer(function Home({navIdx, visible}: ScreenParams) {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const onMainScroll = useOnMainScroll(store)
|
const onMainScroll = useOnMainScroll(store)
|
||||||
const {screen, track} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
const scrollElRef = React.useRef<FlatList>(null)
|
const scrollElRef = React.useRef<FlatList>(null)
|
||||||
const [wasVisible, setWasVisible] = React.useState<boolean>(false)
|
const [wasVisible, setWasVisible] = React.useState<boolean>(false)
|
||||||
const {appState} = useAppState({
|
const {appState} = useAppState({
|
||||||
|
@ -75,10 +74,6 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) {
|
||||||
return cleanup
|
return cleanup
|
||||||
}, [visible, store, store.me.mainFeed, navIdx, doPoll, wasVisible, scrollToTop, screen])
|
}, [visible, store, store.me.mainFeed, navIdx, doPoll, wasVisible, scrollToTop, screen])
|
||||||
|
|
||||||
const onPressCompose = (imagesOpen?: boolean) => {
|
|
||||||
track('Home:ComposeButtonPressed')
|
|
||||||
store.shell.openComposer({imagesOpen})
|
|
||||||
}
|
|
||||||
const onPressTryAgain = () => {
|
const onPressTryAgain = () => {
|
||||||
store.me.mainFeed.refresh()
|
store.me.mainFeed.refresh()
|
||||||
}
|
}
|
||||||
|
@ -105,11 +100,6 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) {
|
||||||
{store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing && (
|
{store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing && (
|
||||||
<LoadLatestBtn onPress={onPressLoadLatest} />
|
<LoadLatestBtn onPress={onPressLoadLatest} />
|
||||||
)}
|
)}
|
||||||
<FAB
|
|
||||||
testID="composeFAB"
|
|
||||||
icon="plus"
|
|
||||||
onPress={() => onPressCompose(false)}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||||
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
||||||
import {EmptyState} from '../com/util/EmptyState'
|
import {EmptyState} from '../com/util/EmptyState'
|
||||||
import {Text} from '../com/util/text/Text'
|
import {Text} from '../com/util/text/Text'
|
||||||
import {FAB} from '../com/util/FAB'
|
|
||||||
import {s, colors} from 'lib/styles'
|
import {s, colors} from 'lib/styles'
|
||||||
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
||||||
import {useAnalytics} from 'lib/analytics'
|
import {useAnalytics} from 'lib/analytics'
|
||||||
|
@ -87,10 +86,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
||||||
uiState.setup()
|
uiState.setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPressCompose = () => {
|
|
||||||
store.shell.openComposer({})
|
|
||||||
}
|
|
||||||
|
|
||||||
// rendering
|
// rendering
|
||||||
// =
|
// =
|
||||||
|
|
||||||
|
@ -191,7 +186,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
||||||
) : (
|
) : (
|
||||||
<CenteredView>{renderHeader()}</CenteredView>
|
<CenteredView>{renderHeader()}</CenteredView>
|
||||||
)}
|
)}
|
||||||
<FAB icon="plus" onPress={onPressCompose} />
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,19 +17,23 @@ import {FEEDBACK_FORM_URL} from 'lib/constants'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {
|
import {
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
|
HomeIconSolid,
|
||||||
BellIcon,
|
BellIcon,
|
||||||
|
BellIconSolid,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
CogIcon,
|
CogIcon,
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon2,
|
||||||
|
MagnifyingGlassIcon2Solid,
|
||||||
} from 'lib/icons'
|
} from 'lib/icons'
|
||||||
import {TabPurpose, TabPurposeMainPath} from 'state/models/navigation'
|
import {TabPurpose, TabPurposeMainPath} from 'state/models/navigation'
|
||||||
import {UserAvatar} from '../../com/util/UserAvatar'
|
import {UserAvatar} from '../../com/util/UserAvatar'
|
||||||
import {Text} from '../../com/util/text/Text'
|
import {Text} from '../../com/util/text/Text'
|
||||||
import {ToggleButton} from '../../com/util/forms/ToggleButton'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useAnalytics} from 'lib/analytics'
|
import {useAnalytics} from 'lib/analytics'
|
||||||
|
|
||||||
export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
||||||
|
const theme = useTheme()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
|
@ -89,11 +93,8 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text
|
||||||
type="title"
|
type={bold ? '2xl-bold' : '2xl'}
|
||||||
style={[
|
style={[pal.text, s.flex1]}
|
||||||
pal.text,
|
|
||||||
bold ? styles.menuItemLabelBold : styles.menuItemLabel,
|
|
||||||
]}
|
|
||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -105,68 +106,114 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
||||||
store.shell.setDarkMode(!store.shell.darkMode)
|
store.shell.setDarkMode(!store.shell.darkMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isAtHome =
|
||||||
|
store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Default]
|
||||||
|
const isAtSearch =
|
||||||
|
store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Search]
|
||||||
|
const isAtNotifications =
|
||||||
|
store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Notifs]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
testID="menuView"
|
testID="menuView"
|
||||||
style={[
|
style={[
|
||||||
styles.view,
|
styles.view,
|
||||||
pal.view,
|
theme.colorScheme === 'light' ? pal.view : styles.viewDarkMode,
|
||||||
store.shell.minimalShellMode && styles.viewMinimalShell,
|
store.shell.minimalShellMode && styles.viewMinimalShell,
|
||||||
]}>
|
]}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
testID="profileCardButton"
|
testID="profileCardButton"
|
||||||
onPress={() => onNavigate(`/profile/${store.me.handle}`)}
|
onPress={() => onNavigate(`/profile/${store.me.handle}`)}>
|
||||||
style={styles.profileCard}>
|
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
size={60}
|
size={80}
|
||||||
displayName={store.me.displayName}
|
displayName={store.me.displayName}
|
||||||
handle={store.me.handle}
|
handle={store.me.handle}
|
||||||
avatar={store.me.avatar}
|
avatar={store.me.avatar}
|
||||||
/>
|
/>
|
||||||
<View style={s.flex1}>
|
<Text
|
||||||
<Text
|
type="title-lg"
|
||||||
type="title-lg"
|
style={[pal.text, s.bold, styles.profileCardDisplayName]}
|
||||||
style={[pal.text, styles.profileCardDisplayName]}
|
numberOfLines={1}>
|
||||||
numberOfLines={1}>
|
{store.me.displayName || store.me.handle}
|
||||||
{store.me.displayName || store.me.handle}
|
</Text>
|
||||||
</Text>
|
<Text
|
||||||
<Text
|
type="2xl"
|
||||||
style={[pal.textLight, styles.profileCardHandle]}
|
style={[pal.textLight, styles.profileCardHandle]}
|
||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
@{store.me.handle}
|
@{store.me.handle}
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="searchBtn"
|
|
||||||
style={[styles.searchBtn, pal.btn]}
|
|
||||||
onPress={() => onNavigate('/search')}>
|
|
||||||
<MagnifyingGlassIcon
|
|
||||||
style={pal.text as StyleProp<ViewStyle>}
|
|
||||||
size={25}
|
|
||||||
/>
|
|
||||||
<Text type="title" style={[pal.text, styles.searchBtnLabel]}>
|
|
||||||
Search
|
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={[styles.section, pal.border, s.pt5]}>
|
<View style={s.flex1} />
|
||||||
|
<View>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<HomeIcon style={pal.text as StyleProp<ViewStyle>} size="26" />}
|
icon={
|
||||||
label="Home"
|
isAtSearch ? (
|
||||||
url="/"
|
<MagnifyingGlassIcon2Solid
|
||||||
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
|
size={24}
|
||||||
|
strokeWidth={1.7}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MagnifyingGlassIcon2
|
||||||
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
|
size={24}
|
||||||
|
strokeWidth={1.7}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
label="Search"
|
||||||
|
url="/search"
|
||||||
|
bold={isAtSearch}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<BellIcon style={pal.text as StyleProp<ViewStyle>} size="28" />}
|
icon={
|
||||||
|
isAtHome ? (
|
||||||
|
<HomeIconSolid
|
||||||
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
|
size="24"
|
||||||
|
strokeWidth={3.25}
|
||||||
|
fillOpacity={1}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<HomeIcon
|
||||||
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
|
size="24"
|
||||||
|
strokeWidth={3.25}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
label="Home"
|
||||||
|
url="/"
|
||||||
|
bold={isAtHome}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
icon={
|
||||||
|
isAtNotifications ? (
|
||||||
|
<BellIconSolid
|
||||||
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
|
size="24"
|
||||||
|
strokeWidth={1.7}
|
||||||
|
fillOpacity={1}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BellIcon
|
||||||
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
|
size="24"
|
||||||
|
strokeWidth={1.7}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
label="Notifications"
|
label="Notifications"
|
||||||
url="/notifications"
|
url="/notifications"
|
||||||
count={store.me.notifications.unreadCount}
|
count={store.me.notifications.unreadCount}
|
||||||
|
bold={isAtNotifications}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={
|
icon={
|
||||||
<UserIcon
|
<UserIcon
|
||||||
style={pal.text as StyleProp<ViewStyle>}
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
size="30"
|
size="26"
|
||||||
strokeWidth={2}
|
strokeWidth={1.5}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Profile"
|
label="Profile"
|
||||||
|
@ -176,34 +223,46 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
||||||
icon={
|
icon={
|
||||||
<CogIcon
|
<CogIcon
|
||||||
style={pal.text as StyleProp<ViewStyle>}
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
size="30"
|
size="26"
|
||||||
strokeWidth={2}
|
strokeWidth={1.75}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Settings"
|
label="Settings"
|
||||||
url="/settings"
|
url="/settings"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.section, pal.border]}>
|
|
||||||
<ToggleButton
|
|
||||||
label="Dark mode"
|
|
||||||
isSelected={store.shell.darkMode}
|
|
||||||
onPress={onDarkmodePress}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={s.flex1} />
|
<View style={s.flex1} />
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<MenuItem
|
<TouchableOpacity
|
||||||
icon={
|
onPress={onDarkmodePress}
|
||||||
<FontAwesomeIcon
|
style={[
|
||||||
style={pal.text as FontAwesomeIconStyle}
|
styles.footerBtn,
|
||||||
size={24}
|
theme.colorScheme === 'light' ? pal.btn : styles.footerBtnDarkMode,
|
||||||
icon={['far', 'message']}
|
]}>
|
||||||
/>
|
<CogIcon
|
||||||
}
|
style={pal.text as StyleProp<ViewStyle>}
|
||||||
label="Feedback"
|
size="26"
|
||||||
|
strokeWidth={1.75}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
onPress={onPressFeedback}
|
onPress={onPressFeedback}
|
||||||
/>
|
style={[
|
||||||
|
styles.footerBtn,
|
||||||
|
styles.footerBtnFeedback,
|
||||||
|
theme.colorScheme === 'light'
|
||||||
|
? styles.footerBtnFeedbackLight
|
||||||
|
: styles.footerBtnFeedbackDark,
|
||||||
|
]}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
style={pal.link as FontAwesomeIconStyle}
|
||||||
|
size={19}
|
||||||
|
icon={['far', 'message']}
|
||||||
|
/>
|
||||||
|
<Text type="2xl-medium" style={[pal.link, s.pl10]}>
|
||||||
|
Feedback
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -212,70 +271,37 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
view: {
|
view: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
paddingTop: 10,
|
||||||
paddingBottom: 90,
|
paddingBottom: 90,
|
||||||
|
paddingLeft: 30,
|
||||||
|
},
|
||||||
|
viewDarkMode: {
|
||||||
|
backgroundColor: colors.gray8,
|
||||||
},
|
},
|
||||||
viewMinimalShell: {
|
viewMinimalShell: {
|
||||||
paddingBottom: 50,
|
paddingBottom: 50,
|
||||||
},
|
},
|
||||||
section: {
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingBottom: 10,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
},
|
|
||||||
heading: {
|
|
||||||
paddingVertical: 8,
|
|
||||||
paddingHorizontal: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
profileCard: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
margin: 10,
|
|
||||||
marginBottom: 6,
|
|
||||||
},
|
|
||||||
profileCardDisplayName: {
|
profileCardDisplayName: {
|
||||||
marginLeft: 12,
|
marginTop: 20,
|
||||||
},
|
},
|
||||||
profileCardHandle: {
|
profileCardHandle: {
|
||||||
marginLeft: 12,
|
marginTop: 4,
|
||||||
},
|
|
||||||
|
|
||||||
searchBtn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
borderRadius: 8,
|
|
||||||
margin: 10,
|
|
||||||
marginBottom: 0,
|
|
||||||
paddingVertical: 10,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
},
|
|
||||||
searchBtnLabel: {
|
|
||||||
marginLeft: 14,
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
menuItem: {
|
menuItem: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingVertical: 6,
|
paddingVertical: 16,
|
||||||
paddingLeft: 6,
|
|
||||||
paddingRight: 10,
|
paddingRight: 10,
|
||||||
},
|
},
|
||||||
menuItemIconWrapper: {
|
menuItemIconWrapper: {
|
||||||
width: 36,
|
width: 24,
|
||||||
height: 36,
|
height: 24,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
menuItemLabel: {
|
|
||||||
flex: 1,
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
menuItemLabelBold: {
|
|
||||||
flex: 1,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
menuItemCount: {
|
menuItemCount: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: -6,
|
right: -6,
|
||||||
|
@ -292,6 +318,27 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
|
|
||||||
footer: {
|
footer: {
|
||||||
paddingHorizontal: 10,
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingRight: 30,
|
||||||
|
paddingTop: 20,
|
||||||
|
},
|
||||||
|
footerBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 10,
|
||||||
|
borderRadius: 25,
|
||||||
|
},
|
||||||
|
footerBtnDarkMode: {
|
||||||
|
backgroundColor: colors.black,
|
||||||
|
},
|
||||||
|
footerBtnFeedback: {
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
},
|
||||||
|
footerBtnFeedbackLight: {
|
||||||
|
backgroundColor: '#DDEFFF',
|
||||||
|
},
|
||||||
|
footerBtnFeedbackDark: {
|
||||||
|
backgroundColor: colors.blue6,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,327 +0,0 @@
|
||||||
import React, {createRef, useRef, useMemo, useState} from 'react'
|
|
||||||
import {observer} from 'mobx-react-lite'
|
|
||||||
import {
|
|
||||||
Animated,
|
|
||||||
ScrollView,
|
|
||||||
Share,
|
|
||||||
StyleSheet,
|
|
||||||
TouchableWithoutFeedback,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {Text} from '../../com/util/text/Text'
|
|
||||||
import Swipeable from 'react-native-gesture-handler/Swipeable'
|
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {s, colors} from 'lib/styles'
|
|
||||||
import {toShareUrl} from 'lib/strings/url-helpers'
|
|
||||||
import {match} from '../../routes'
|
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
|
||||||
|
|
||||||
const TAB_HEIGHT = 42
|
|
||||||
|
|
||||||
export const TabsSelector = observer(
|
|
||||||
({
|
|
||||||
active,
|
|
||||||
tabMenuInterp,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
active: boolean
|
|
||||||
tabMenuInterp: Animated.Value
|
|
||||||
onClose: () => void
|
|
||||||
}) => {
|
|
||||||
const store = useStores()
|
|
||||||
const insets = useSafeAreaInsets()
|
|
||||||
const [closingTabIndex, setClosingTabIndex] = useState<number | undefined>(
|
|
||||||
undefined,
|
|
||||||
)
|
|
||||||
const closeInterp = useAnimatedValue(0)
|
|
||||||
const tabsContainerRef = useRef<View>(null)
|
|
||||||
const tabsRef = useRef<ScrollView>(null)
|
|
||||||
const tabRefs = useMemo(
|
|
||||||
() =>
|
|
||||||
Array.from({length: store.nav.tabs.length}).map(() =>
|
|
||||||
createRef<View>(),
|
|
||||||
),
|
|
||||||
[store.nav.tabs.length],
|
|
||||||
)
|
|
||||||
|
|
||||||
const wrapperAnimStyle = {
|
|
||||||
transform: [
|
|
||||||
{
|
|
||||||
translateY: tabMenuInterp.interpolate({
|
|
||||||
inputRange: [0, 1.0],
|
|
||||||
outputRange: [320, 0],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
// events
|
|
||||||
// =
|
|
||||||
|
|
||||||
const onPressNewTab = () => {
|
|
||||||
store.nav.newTab('/')
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
const onPressCloneTab = () => {
|
|
||||||
store.nav.newTab(store.nav.tab.current.url)
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
const onPressShareTab = () => {
|
|
||||||
onClose()
|
|
||||||
Share.share({url: toShareUrl(store.nav.tab.current.url)})
|
|
||||||
}
|
|
||||||
const onPressChangeTab = (tabIndex: number) => {
|
|
||||||
store.nav.setActiveTab(tabIndex)
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
const onCloseTab = (tabIndex: number) => {
|
|
||||||
setClosingTabIndex(tabIndex)
|
|
||||||
closeInterp.setValue(0)
|
|
||||||
Animated.timing(closeInterp, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 300,
|
|
||||||
useNativeDriver: false,
|
|
||||||
}).start(() => {
|
|
||||||
setClosingTabIndex(undefined)
|
|
||||||
store.nav.closeTab(tabIndex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const onLayout = () => {
|
|
||||||
// focus the current tab
|
|
||||||
const targetTab = tabRefs[store.nav.tabIndex]
|
|
||||||
if (tabsContainerRef.current && tabsRef.current && targetTab.current) {
|
|
||||||
targetTab.current.measureLayout?.(
|
|
||||||
tabsContainerRef.current,
|
|
||||||
(_left: number, top: number) => {
|
|
||||||
tabsRef.current?.scrollTo({y: top, animated: false})
|
|
||||||
},
|
|
||||||
() => {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rendering
|
|
||||||
// =
|
|
||||||
|
|
||||||
const renderSwipeActions = () => {
|
|
||||||
return <View style={[s.p2]} />
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTabIndex = store.nav.tabIndex
|
|
||||||
const closingTabAnimStyle = {
|
|
||||||
height: Animated.multiply(TAB_HEIGHT, Animated.subtract(1, closeInterp)),
|
|
||||||
opacity: Animated.subtract(1, closeInterp),
|
|
||||||
marginBottom: Animated.multiply(4, Animated.subtract(1, closeInterp)),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!active) {
|
|
||||||
return <View testID="emptyView" />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Animated.View
|
|
||||||
testID="tabsSelectorView"
|
|
||||||
style={[
|
|
||||||
styles.wrapper,
|
|
||||||
{bottom: insets.bottom + 55},
|
|
||||||
wrapperAnimStyle,
|
|
||||||
]}>
|
|
||||||
<View onLayout={onLayout}>
|
|
||||||
<View style={[s.p10, styles.section]}>
|
|
||||||
<View style={styles.btns}>
|
|
||||||
<TouchableWithoutFeedback
|
|
||||||
testID="shareButton"
|
|
||||||
onPress={onPressShareTab}>
|
|
||||||
<View style={[styles.btn]}>
|
|
||||||
<View style={styles.btnIcon}>
|
|
||||||
<FontAwesomeIcon size={16} icon="share" />
|
|
||||||
</View>
|
|
||||||
<Text style={styles.btnText}>Share</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
<TouchableWithoutFeedback
|
|
||||||
testID="cloneButton"
|
|
||||||
onPress={onPressCloneTab}>
|
|
||||||
<View style={[styles.btn]}>
|
|
||||||
<View style={styles.btnIcon}>
|
|
||||||
<FontAwesomeIcon size={16} icon={['far', 'clone']} />
|
|
||||||
</View>
|
|
||||||
<Text style={styles.btnText}>Clone tab</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
<TouchableWithoutFeedback
|
|
||||||
testID="newTabButton"
|
|
||||||
onPress={onPressNewTab}>
|
|
||||||
<View style={[styles.btn]}>
|
|
||||||
<View style={styles.btnIcon}>
|
|
||||||
<FontAwesomeIcon size={16} icon="plus" />
|
|
||||||
</View>
|
|
||||||
<Text style={styles.btnText}>New tab</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
ref={tabsContainerRef}
|
|
||||||
style={[s.p10, styles.section, styles.sectionGrayBg]}>
|
|
||||||
<ScrollView ref={tabsRef} style={styles.tabs}>
|
|
||||||
{store.nav.tabs.map((tab, tabIndex) => {
|
|
||||||
const {icon} = match(tab.current.url)
|
|
||||||
const isActive = tabIndex === currentTabIndex
|
|
||||||
const isClosing = closingTabIndex === tabIndex
|
|
||||||
return (
|
|
||||||
<Swipeable
|
|
||||||
key={tab.id}
|
|
||||||
testID="tabsSwipable"
|
|
||||||
renderLeftActions={renderSwipeActions}
|
|
||||||
renderRightActions={renderSwipeActions}
|
|
||||||
leftThreshold={100}
|
|
||||||
rightThreshold={100}
|
|
||||||
onSwipeableWillOpen={() => onCloseTab(tabIndex)}>
|
|
||||||
<Animated.View
|
|
||||||
style={[
|
|
||||||
styles.tabOuter,
|
|
||||||
isClosing ? closingTabAnimStyle : undefined,
|
|
||||||
]}>
|
|
||||||
<Animated.View
|
|
||||||
// HOTFIX
|
|
||||||
// TabsSelector.test.tsx snapshot fails if the
|
|
||||||
// ref was set like this: ref={tabRefs[tabIndex]}
|
|
||||||
ref={(ref: any) => (tabRefs[tabIndex] = ref)}
|
|
||||||
style={[
|
|
||||||
styles.tab,
|
|
||||||
styles.existing,
|
|
||||||
isActive && styles.active,
|
|
||||||
]}>
|
|
||||||
<TouchableWithoutFeedback
|
|
||||||
testID="changeTabButton"
|
|
||||||
onPress={() => onPressChangeTab(tabIndex)}>
|
|
||||||
<View style={styles.tabInner}>
|
|
||||||
<View style={styles.tabIcon}>
|
|
||||||
<FontAwesomeIcon size={20} icon={icon} />
|
|
||||||
</View>
|
|
||||||
<Text
|
|
||||||
ellipsizeMode="tail"
|
|
||||||
numberOfLines={1}
|
|
||||||
suppressHighlighting={true}
|
|
||||||
style={[
|
|
||||||
styles.tabText,
|
|
||||||
isActive && styles.tabTextActive,
|
|
||||||
]}>
|
|
||||||
{tab.current.title || tab.current.url}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
<TouchableWithoutFeedback
|
|
||||||
testID="closeTabButton"
|
|
||||||
onPress={() => onCloseTab(tabIndex)}>
|
|
||||||
<View style={styles.tabClose}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
size={14}
|
|
||||||
icon="x"
|
|
||||||
style={styles.tabCloseIcon}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
</Animated.View>
|
|
||||||
</Animated.View>
|
|
||||||
</Swipeable>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Animated.View>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
wrapper: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: 320,
|
|
||||||
borderTopColor: colors.gray2,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
section: {
|
|
||||||
borderBottomColor: colors.gray2,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
},
|
|
||||||
sectionGrayBg: {
|
|
||||||
backgroundColor: colors.gray1,
|
|
||||||
},
|
|
||||||
tabs: {
|
|
||||||
height: 240,
|
|
||||||
},
|
|
||||||
tabOuter: {
|
|
||||||
height: TAB_HEIGHT + 4,
|
|
||||||
overflow: 'hidden',
|
|
||||||
},
|
|
||||||
tab: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
height: TAB_HEIGHT,
|
|
||||||
backgroundColor: colors.gray1,
|
|
||||||
alignItems: 'center',
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
tabInner: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingLeft: 12,
|
|
||||||
paddingVertical: 12,
|
|
||||||
},
|
|
||||||
existing: {
|
|
||||||
borderColor: colors.gray4,
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
active: {
|
|
||||||
backgroundColor: colors.white,
|
|
||||||
borderColor: colors.black,
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
tabIcon: {},
|
|
||||||
tabText: {
|
|
||||||
flex: 1,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
tabTextActive: {
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
tabClose: {
|
|
||||||
paddingVertical: 16,
|
|
||||||
paddingRight: 16,
|
|
||||||
},
|
|
||||||
tabCloseIcon: {
|
|
||||||
color: '#655',
|
|
||||||
},
|
|
||||||
btns: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
paddingTop: 2,
|
|
||||||
},
|
|
||||||
btn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
backgroundColor: colors.gray1,
|
|
||||||
borderRadius: 4,
|
|
||||||
marginRight: 5,
|
|
||||||
paddingLeft: 12,
|
|
||||||
paddingRight: 16,
|
|
||||||
paddingVertical: 10,
|
|
||||||
},
|
|
||||||
btnIcon: {
|
|
||||||
marginRight: 8,
|
|
||||||
},
|
|
||||||
btnText: {
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -2,21 +2,17 @@ import React, {useState, useEffect} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {
|
import {
|
||||||
Animated,
|
Animated,
|
||||||
Easing,
|
|
||||||
GestureResponderEvent,
|
GestureResponderEvent,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
TouchableWithoutFeedback,
|
TouchableWithoutFeedback,
|
||||||
useColorScheme,
|
|
||||||
useWindowDimensions,
|
useWindowDimensions,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {ScreenContainer, Screen} from 'react-native-screens'
|
import {ScreenContainer, Screen} from 'react-native-screens'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||||
import {TABS_ENABLED} from 'lib/build-flags'
|
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {
|
import {
|
||||||
NavigationModel,
|
NavigationModel,
|
||||||
|
@ -31,18 +27,18 @@ import {ModalsContainer} from '../../com/modals/Modal'
|
||||||
import {Lightbox} from '../../com/lightbox/Lightbox'
|
import {Lightbox} from '../../com/lightbox/Lightbox'
|
||||||
import {Text} from '../../com/util/text/Text'
|
import {Text} from '../../com/util/text/Text'
|
||||||
import {ErrorBoundary} from '../../com/util/ErrorBoundary'
|
import {ErrorBoundary} from '../../com/util/ErrorBoundary'
|
||||||
import {TabsSelector} from './TabsSelector'
|
|
||||||
import {Composer} from './Composer'
|
import {Composer} from './Composer'
|
||||||
import {s, colors} from 'lib/styles'
|
import {s, colors} from 'lib/styles'
|
||||||
import {clamp} from 'lib/numbers'
|
import {clamp} from 'lib/numbers'
|
||||||
import {
|
import {
|
||||||
GridIcon,
|
|
||||||
GridIconSolid,
|
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
HomeIconSolid,
|
HomeIconSolid,
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon2,
|
||||||
|
MagnifyingGlassIcon2Solid,
|
||||||
|
ComposeIcon2,
|
||||||
BellIcon,
|
BellIcon,
|
||||||
BellIconSolid,
|
BellIconSolid,
|
||||||
|
UserIcon,
|
||||||
} from 'lib/icons'
|
} from 'lib/icons'
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
|
@ -52,74 +48,14 @@ import {useAnalytics} from 'lib/analytics'
|
||||||
const Btn = ({
|
const Btn = ({
|
||||||
icon,
|
icon,
|
||||||
notificationCount,
|
notificationCount,
|
||||||
tabCount,
|
|
||||||
onPress,
|
onPress,
|
||||||
onLongPress,
|
onLongPress,
|
||||||
}: {
|
}: {
|
||||||
icon:
|
icon: JSX.Element
|
||||||
| IconProp
|
|
||||||
| 'menu'
|
|
||||||
| 'menu-solid'
|
|
||||||
| 'home'
|
|
||||||
| 'home-solid'
|
|
||||||
| 'search'
|
|
||||||
| 'search-solid'
|
|
||||||
| 'bell'
|
|
||||||
| 'bell-solid'
|
|
||||||
notificationCount?: number
|
notificationCount?: number
|
||||||
tabCount?: number
|
|
||||||
onPress?: (event: GestureResponderEvent) => void
|
onPress?: (event: GestureResponderEvent) => void
|
||||||
onLongPress?: (event: GestureResponderEvent) => void
|
onLongPress?: (event: GestureResponderEvent) => void
|
||||||
}) => {
|
}) => {
|
||||||
const pal = usePalette('default')
|
|
||||||
let iconEl
|
|
||||||
if (icon === 'menu') {
|
|
||||||
iconEl = <GridIcon style={[styles.ctrlIcon, pal.text]} />
|
|
||||||
} else if (icon === 'menu-solid') {
|
|
||||||
iconEl = <GridIconSolid style={[styles.ctrlIcon, pal.text]} />
|
|
||||||
} else if (icon === 'home') {
|
|
||||||
iconEl = <HomeIcon size={27} style={[styles.ctrlIcon, pal.text]} />
|
|
||||||
} else if (icon === 'home-solid') {
|
|
||||||
iconEl = <HomeIconSolid size={27} style={[styles.ctrlIcon, pal.text]} />
|
|
||||||
} else if (icon === 'search') {
|
|
||||||
iconEl = (
|
|
||||||
<MagnifyingGlassIcon
|
|
||||||
size={28}
|
|
||||||
style={[styles.ctrlIcon, pal.text, styles.bumpUpOnePixel]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (icon === 'search-solid') {
|
|
||||||
iconEl = (
|
|
||||||
<MagnifyingGlassIcon
|
|
||||||
size={28}
|
|
||||||
strokeWidth={3}
|
|
||||||
style={[styles.ctrlIcon, pal.text, styles.bumpUpOnePixel]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (icon === 'bell') {
|
|
||||||
iconEl = (
|
|
||||||
<BellIcon
|
|
||||||
size={27}
|
|
||||||
style={[styles.ctrlIcon, pal.text, styles.bumpUpOnePixel]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (icon === 'bell-solid') {
|
|
||||||
iconEl = (
|
|
||||||
<BellIconSolid
|
|
||||||
size={27}
|
|
||||||
style={[styles.ctrlIcon, pal.text, styles.bumpUpOnePixel]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
iconEl = (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={icon}
|
|
||||||
size={24}
|
|
||||||
style={[styles.ctrlIcon, pal.text]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.ctrl}
|
style={styles.ctrl}
|
||||||
|
@ -131,12 +67,7 @@ const Btn = ({
|
||||||
<Text style={styles.notificationCountLabel}>{notificationCount}</Text>
|
<Text style={styles.notificationCountLabel}>{notificationCount}</Text>
|
||||||
</View>
|
</View>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{tabCount && tabCount > 1 ? (
|
{icon}
|
||||||
<View style={styles.tabCount}>
|
|
||||||
<Text style={styles.tabCountLabel}>{tabCount}</Text>
|
|
||||||
</View>
|
|
||||||
) : undefined}
|
|
||||||
{iconEl}
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -145,15 +76,10 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const [isTabsSelectorActive, setTabsSelectorActive] = useState(false)
|
|
||||||
const winDim = useWindowDimensions()
|
const winDim = useWindowDimensions()
|
||||||
const [menuSwipingDirection, setMenuSwipingDirection] = useState(0)
|
const [menuSwipingDirection, setMenuSwipingDirection] = useState(0)
|
||||||
const swipeGestureInterp = useAnimatedValue(0)
|
const swipeGestureInterp = useAnimatedValue(0)
|
||||||
const minimalShellInterp = useAnimatedValue(0)
|
const minimalShellInterp = useAnimatedValue(0)
|
||||||
const tabMenuInterp = useAnimatedValue(0)
|
|
||||||
const newTabInterp = useAnimatedValue(0)
|
|
||||||
const [isRunningNewTabAnim, setIsRunningNewTabAnim] = useState(false)
|
|
||||||
const colorScheme = useColorScheme()
|
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const screenRenderDesc = constructScreenRenderDesc(store.nav)
|
const screenRenderDesc = constructScreenRenderDesc(store.nav)
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
|
@ -188,6 +114,10 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const onPressCompose = () => {
|
||||||
|
track('MobileShell:ComposeButtonPressed')
|
||||||
|
store.shell.openComposer({})
|
||||||
|
}
|
||||||
const onPressNotifications = () => {
|
const onPressNotifications = () => {
|
||||||
track('MobileShell:NotificationsButtonPressed')
|
track('MobileShell:NotificationsButtonPressed')
|
||||||
if (store.nav.tab.fixedTabPurpose === TabPurpose.Notifs) {
|
if (store.nav.tab.fixedTabPurpose === TabPurpose.Notifs) {
|
||||||
|
@ -203,8 +133,10 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onPressTabs = () => toggleTabsMenu(!isTabsSelectorActive)
|
const onPressProfile = () => {
|
||||||
const doNewTab = (url: string) => () => store.nav.newTab(url)
|
track('MobileShell:ProfileButtonPressed')
|
||||||
|
store.nav.navigate(`/profile/${store.me.handle}`)
|
||||||
|
}
|
||||||
|
|
||||||
// minimal shell animation
|
// minimal shell animation
|
||||||
// =
|
// =
|
||||||
|
@ -229,60 +161,6 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
transform: [{translateY: Animated.multiply(minimalShellInterp, 100)}],
|
transform: [{translateY: Animated.multiply(minimalShellInterp, 100)}],
|
||||||
}
|
}
|
||||||
|
|
||||||
// tab selector animation
|
|
||||||
// =
|
|
||||||
const toggleTabsMenu = (active: boolean) => {
|
|
||||||
if (active) {
|
|
||||||
// will trigger the animation below
|
|
||||||
setTabsSelectorActive(true)
|
|
||||||
} else {
|
|
||||||
Animated.timing(tabMenuInterp, {
|
|
||||||
toValue: 0,
|
|
||||||
duration: 100,
|
|
||||||
useNativeDriver: false,
|
|
||||||
}).start(() => {
|
|
||||||
// hide once the animation has finished
|
|
||||||
setTabsSelectorActive(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
if (isTabsSelectorActive) {
|
|
||||||
// trigger the animation once the tabs selector is rendering
|
|
||||||
Animated.timing(tabMenuInterp, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 100,
|
|
||||||
useNativeDriver: false,
|
|
||||||
}).start()
|
|
||||||
}
|
|
||||||
}, [tabMenuInterp, isTabsSelectorActive])
|
|
||||||
|
|
||||||
// new tab animation
|
|
||||||
// =
|
|
||||||
useEffect(() => {
|
|
||||||
if (screenRenderDesc.hasNewTab && !isRunningNewTabAnim) {
|
|
||||||
setIsRunningNewTabAnim(true)
|
|
||||||
}
|
|
||||||
}, [isRunningNewTabAnim, screenRenderDesc.hasNewTab])
|
|
||||||
useEffect(() => {
|
|
||||||
if (isRunningNewTabAnim) {
|
|
||||||
const reset = () => {
|
|
||||||
store.nav.tab.setIsNewTab(false)
|
|
||||||
setIsRunningNewTabAnim(false)
|
|
||||||
}
|
|
||||||
Animated.timing(newTabInterp, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 250,
|
|
||||||
easing: Easing.out(Easing.exp),
|
|
||||||
useNativeDriver: false,
|
|
||||||
}).start(() => {
|
|
||||||
reset()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
newTabInterp.setValue(0)
|
|
||||||
}
|
|
||||||
}, [newTabInterp, store.nav.tab, isRunningNewTabAnim])
|
|
||||||
|
|
||||||
// navigation swipes
|
// navigation swipes
|
||||||
// =
|
// =
|
||||||
const isMenuActive = store.shell.isMainMenuOpen
|
const isMenuActive = store.shell.isMainMenuOpen
|
||||||
|
@ -495,20 +373,6 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
)}
|
)}
|
||||||
</HorzSwipe>
|
</HorzSwipe>
|
||||||
</View>
|
</View>
|
||||||
{isTabsSelectorActive ? (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.topBarProtector,
|
|
||||||
colorScheme === 'dark' ? styles.topBarProtectorDark : undefined,
|
|
||||||
{height: safeAreaInsets.top},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
<TabsSelector
|
|
||||||
active={isTabsSelectorActive}
|
|
||||||
tabMenuInterp={tabMenuInterp}
|
|
||||||
onClose={() => toggleTabsMenu(false)}
|
|
||||||
/>
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
styles.bottomBar,
|
styles.bottomBar,
|
||||||
|
@ -518,28 +382,85 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
footerMinimalShellTransform,
|
footerMinimalShellTransform,
|
||||||
]}>
|
]}>
|
||||||
<Btn
|
<Btn
|
||||||
icon={isAtHome ? 'home-solid' : 'home'}
|
icon={
|
||||||
|
isAtHome ? (
|
||||||
|
<HomeIconSolid
|
||||||
|
strokeWidth={4}
|
||||||
|
size={24}
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<HomeIcon
|
||||||
|
strokeWidth={4}
|
||||||
|
size={24}
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
onPress={onPressHome}
|
onPress={onPressHome}
|
||||||
onLongPress={TABS_ENABLED ? doNewTab('/') : undefined}
|
|
||||||
/>
|
/>
|
||||||
<Btn
|
<Btn
|
||||||
icon={isAtSearch ? 'search-solid' : 'search'}
|
icon={
|
||||||
|
isAtSearch ? (
|
||||||
|
<MagnifyingGlassIcon2Solid
|
||||||
|
size={25}
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
|
||||||
|
strokeWidth={1.8}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MagnifyingGlassIcon2
|
||||||
|
size={25}
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
|
||||||
|
strokeWidth={1.8}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
onPress={onPressSearch}
|
onPress={onPressSearch}
|
||||||
onLongPress={TABS_ENABLED ? doNewTab('/') : undefined}
|
|
||||||
/>
|
/>
|
||||||
{TABS_ENABLED ? (
|
|
||||||
<Btn
|
|
||||||
icon={isTabsSelectorActive ? 'clone' : ['far', 'clone']}
|
|
||||||
onPress={onPressTabs}
|
|
||||||
tabCount={store.nav.tabCount}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
<Btn
|
<Btn
|
||||||
icon={isAtNotifications ? 'bell-solid' : 'bell'}
|
icon={
|
||||||
|
<View style={styles.ctrlIconSizingWrapper}>
|
||||||
|
<ComposeIcon2
|
||||||
|
strokeWidth={1.5}
|
||||||
|
size={29}
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.composeIcon]}
|
||||||
|
backgroundColor={pal.colors.background}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
onPress={onPressCompose}
|
||||||
|
/>
|
||||||
|
<Btn
|
||||||
|
icon={
|
||||||
|
isAtNotifications ? (
|
||||||
|
<BellIconSolid
|
||||||
|
size={24}
|
||||||
|
strokeWidth={1.9}
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BellIcon
|
||||||
|
size={24}
|
||||||
|
strokeWidth={1.9}
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
onPress={onPressNotifications}
|
onPress={onPressNotifications}
|
||||||
onLongPress={TABS_ENABLED ? doNewTab('/notifications') : undefined}
|
|
||||||
notificationCount={store.me.notifications.unreadCount}
|
notificationCount={store.me.notifications.unreadCount}
|
||||||
/>
|
/>
|
||||||
|
<Btn
|
||||||
|
icon={
|
||||||
|
<View style={styles.ctrlIconSizingWrapper}>
|
||||||
|
<UserIcon
|
||||||
|
size={28}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
onPress={onPressProfile}
|
||||||
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
<ModalsContainer />
|
<ModalsContainer />
|
||||||
<Lightbox />
|
<Lightbox />
|
||||||
|
@ -650,46 +571,51 @@ const styles = StyleSheet.create({
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
paddingLeft: 5,
|
paddingLeft: 5,
|
||||||
paddingRight: 25,
|
paddingRight: 10,
|
||||||
},
|
},
|
||||||
ctrl: {
|
ctrl: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingTop: 12,
|
paddingTop: 13,
|
||||||
paddingBottom: 5,
|
paddingBottom: 4,
|
||||||
},
|
},
|
||||||
notificationCount: {
|
notificationCount: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '60%',
|
left: '56%',
|
||||||
top: 10,
|
top: 10,
|
||||||
backgroundColor: colors.red3,
|
backgroundColor: colors.blue3,
|
||||||
paddingHorizontal: 4,
|
paddingHorizontal: 4,
|
||||||
paddingBottom: 1,
|
paddingBottom: 1,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
|
zIndex: 1,
|
||||||
},
|
},
|
||||||
notificationCountLabel: {
|
notificationCountLabel: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: colors.white,
|
color: colors.white,
|
||||||
},
|
},
|
||||||
tabCount: {
|
|
||||||
position: 'absolute',
|
|
||||||
left: 46,
|
|
||||||
top: 30,
|
|
||||||
},
|
|
||||||
tabCountLabel: {
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: colors.black,
|
|
||||||
},
|
|
||||||
ctrlIcon: {
|
ctrlIcon: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
},
|
},
|
||||||
|
ctrlIconSizingWrapper: {
|
||||||
|
height: 27,
|
||||||
|
},
|
||||||
inactive: {
|
inactive: {
|
||||||
color: colors.gray3,
|
color: colors.gray3,
|
||||||
},
|
},
|
||||||
bumpUpOnePixel: {
|
homeIcon: {
|
||||||
position: 'relative',
|
top: 0,
|
||||||
top: -1,
|
},
|
||||||
|
searchIcon: {
|
||||||
|
top: -2,
|
||||||
|
},
|
||||||
|
bellIcon: {
|
||||||
|
top: -2.5,
|
||||||
|
},
|
||||||
|
composeIcon: {
|
||||||
|
top: -4.5,
|
||||||
|
},
|
||||||
|
profileIcon: {
|
||||||
|
top: -4,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue