diff options
-rw-r--r-- | src/state/index.ts | 2 | ||||
-rw-r--r-- | src/state/models/root-store.ts | 2 | ||||
-rw-r--r-- | src/state/models/shell.ts | 28 | ||||
-rw-r--r-- | src/view/com/modals/LinkActions.tsx | 62 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 45 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 5 | ||||
-rw-r--r-- | src/view/shell/mobile/index.tsx | 38 | ||||
-rw-r--r-- | src/view/shell/mobile/location-menu.tsx | 14 |
8 files changed, 173 insertions, 23 deletions
diff --git a/src/state/index.ts b/src/state/index.ts index 9ebf321e4..0749a6cca 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -5,6 +5,8 @@ import * as libapi from './lib/api' import * as storage from './lib/storage' // import * as auth from './auth' TODO +import {ShellModel} from './models/shell' + const ROOT_STATE_STORAGE_KEY = 'root' export async function setupState() { diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index 0dab41675..47213f9d1 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -8,11 +8,13 @@ import {createContext, useContext} from 'react' import {isObj, hasProp} from '../lib/type-guards' import {SessionModel} from './session' import {NavigationModel} from './navigation' +import {ShellModel} from './shell' import {MeModel} from './me' export class RootStoreModel { session = new SessionModel() nav = new NavigationModel() + shell = new ShellModel() me = new MeModel(this) constructor(public api: AdxClient) { diff --git a/src/state/models/shell.ts b/src/state/models/shell.ts new file mode 100644 index 000000000..6755393cd --- /dev/null +++ b/src/state/models/shell.ts @@ -0,0 +1,28 @@ +import {makeAutoObservable} from 'mobx' + +export class LinkActionsModel { + name = 'link-actions' + + constructor(public href: string, public title: string) { + makeAutoObservable(this) + } +} + +export class ShellModel { + isModalActive = false + activeModal: LinkActionsModel | undefined + + constructor() { + makeAutoObservable(this) + } + + openModal(modal: LinkActionsModel) { + this.isModalActive = true + this.activeModal = modal + } + + closeModal() { + this.isModalActive = false + this.activeModal = undefined + } +} diff --git a/src/view/com/modals/LinkActions.tsx b/src/view/com/modals/LinkActions.tsx new file mode 100644 index 000000000..aa185026f --- /dev/null +++ b/src/view/com/modals/LinkActions.tsx @@ -0,0 +1,62 @@ +import React from 'react' +import Toast from '../util/Toast' +import Clipboard from '@react-native-clipboard/clipboard' +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {useStores} from '../../../state' +import {s, colors} from '../../lib/styles' + +export const snapPoints = ['30%'] + +export function Component({title, href}: {title: string; href: string}) { + const store = useStores() + + const onPressOpenNewTab = () => { + store.shell.closeModal() + store.nav.newTab(href) + } + + const onPressCopy = () => { + Clipboard.setString(href) + store.shell.closeModal() + Toast.show('Link copied', { + position: Toast.positions.TOP, + }) + } + + return ( + <View> + <Text style={[s.textCenter, s.bold, s.mb10, s.f16]}>{title || href}</Text> + <View style={s.p10}> + <TouchableOpacity onPress={onPressOpenNewTab} style={styles.btn}> + <FontAwesomeIcon + icon="arrow-up-right-from-square" + style={styles.icon} + /> + <Text style={[s.f16, s.black]}>Open in new tab</Text> + </TouchableOpacity> + <TouchableOpacity onPress={onPressCopy} style={styles.btn}> + <FontAwesomeIcon icon="link" style={styles.icon} /> + <Text style={[s.f16, s.black]}>Copy to clipboard</Text> + </TouchableOpacity> + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + borderColor: colors.gray5, + borderWidth: 1, + borderRadius: 4, + padding: 10, + marginBottom: 10, + }, + icon: { + marginRight: 8, + }, +}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx new file mode 100644 index 000000000..172dd0ad4 --- /dev/null +++ b/src/view/com/modals/Modal.tsx @@ -0,0 +1,45 @@ +import React, {useRef} from 'react' +import {View} from 'react-native' +import {observer} from 'mobx-react-lite' +import BottomSheet from '@gorhom/bottom-sheet' +import {useStores} from '../../../state' +import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' + +import * as LinkActionsModal from './LinkActions' + +export const Modal = observer(function Modal() { + const store = useStores() + const bottomSheetRef = useRef<BottomSheet>(null) + + const onShareBottomSheetChange = (snapPoint: number) => { + if (snapPoint === -1) { + store.shell.closeModal() + } + } + const onClose = () => { + bottomSheetRef.current?.close() + } + + if (!store.shell.isModalActive) { + return <View /> + } + + let snapPoints, element + if (store.shell.activeModal?.name === 'link-actions') { + snapPoints = LinkActionsModal.snapPoints + element = <LinkActionsModal.Component {...store.shell.activeModal} /> + } else { + return <View /> + } + + return ( + <BottomSheet + ref={bottomSheetRef} + snapPoints={snapPoints} + enablePanDownToClose + backdropComponent={createCustomBackdrop(onClose)} + onChange={onShareBottomSheetChange}> + {element} + </BottomSheet> + ) +}) diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 299e9cdea..b29e042bd 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,5 +1,6 @@ import React, {useState, useEffect} from 'react' import {View} from 'react-native' +import {observer} from 'mobx-react-lite' import {Feed} from '../com/feed/Feed' import {FAB} from '../com/util/FloatingActionButton' import {useStores} from '../../state' @@ -7,7 +8,7 @@ import {FeedViewModel} from '../../state/models/feed-view' import {ScreenParams} from '../routes' import {s} from '../lib/styles' -export function Home({visible}: ScreenParams) { +export const Home = observer(function Home({visible}: ScreenParams) { const [hasSetup, setHasSetup] = useState<boolean>(false) const [feedView, setFeedView] = useState<FeedViewModel | undefined>() const store = useStores() @@ -38,4 +39,4 @@ export function Home({visible}: ScreenParams) { <FAB icon="pen-nib" onPress={onComposePress} /> </View> ) -} +}) diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index 8ce2e63d7..35a1b3957 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -15,6 +15,7 @@ import {IconProp} from '@fortawesome/fontawesome-svg-core' import {useStores} from '../../../state' import {NavigationModel} from '../../../state/models/navigation' import {match, MatchResult} from '../../routes' +import {Modal} from '../../com/modals/Modal' import {TabsSelectorModal} from './tabs-selector' import {LocationNavigator} from './location-navigator' import {createBackMenu, createForwardMenu} from './history-menu' @@ -93,10 +94,10 @@ const Btn = ({ } export const MobileShell: React.FC = observer(() => { - const stores = useStores() + const store = useStores() const tabSelectorRef = useRef<{open: () => void}>() const [isLocationMenuActive, setLocationMenuActive] = useState(false) - const screenRenderDesc = constructScreenRenderDesc(stores.nav) + const screenRenderDesc = constructScreenRenderDesc(store.nav) const onPressAvi = () => createAccountsMenu() const onPressLocation = () => setLocationMenuActive(true) @@ -104,22 +105,22 @@ export const MobileShell: React.FC = observer(() => { const onNavigateLocation = (url: string) => { setLocationMenuActive(false) - stores.nav.navigate(url) + store.nav.navigate(url) } const onDismissLocationNavigator = () => setLocationMenuActive(false) - const onPressBack = () => stores.nav.tab.goBack() - const onPressForward = () => stores.nav.tab.goForward() - const onPressHome = () => stores.nav.navigate('/') - const onPressNotifications = () => stores.nav.navigate('/notifications') + const onPressBack = () => store.nav.tab.goBack() + const onPressForward = () => store.nav.tab.goForward() + const onPressHome = () => store.nav.navigate('/') + const onPressNotifications = () => store.nav.navigate('/notifications') const onPressTabs = () => tabSelectorRef.current?.open() - const onLongPressBack = () => createBackMenu(stores.nav.tab) - const onLongPressForward = () => createForwardMenu(stores.nav.tab) + const onLongPressBack = () => createBackMenu(store.nav.tab) + const onLongPressForward = () => createForwardMenu(store.nav.tab) - const onNewTab = () => stores.nav.newTab('/') - const onChangeTab = (tabIndex: number) => stores.nav.setActiveTab(tabIndex) - const onCloseTab = (tabIndex: number) => stores.nav.closeTab(tabIndex) + const onNewTab = () => store.nav.newTab('/') + const onChangeTab = (tabIndex: number) => store.nav.setActiveTab(tabIndex) + const onCloseTab = (tabIndex: number) => store.nav.closeTab(tabIndex) return ( <View style={styles.outerContainer}> @@ -129,7 +130,7 @@ export const MobileShell: React.FC = observer(() => { </TouchableOpacity> <Location icon={screenRenderDesc.icon} - title={stores.nav.tab.current.title} + title={store.nav.tab.current.title} onPress={onPressLocation} /> <TouchableOpacity style={styles.topBarBtn} onPress={onPressEllipsis}> @@ -151,13 +152,13 @@ export const MobileShell: React.FC = observer(() => { <View style={styles.bottomBar}> <Btn icon="angle-left" - inactive={!stores.nav.tab.canGoBack} + inactive={!store.nav.tab.canGoBack} onPress={onPressBack} onLongPress={onLongPressBack} /> <Btn icon="angle-right" - inactive={!stores.nav.tab.canGoForward} + inactive={!store.nav.tab.canGoForward} onPress={onPressForward} onLongPress={onLongPressForward} /> @@ -167,15 +168,16 @@ export const MobileShell: React.FC = observer(() => { </View> <TabsSelectorModal ref={tabSelectorRef} - tabs={stores.nav.tabs} - currentTabIndex={stores.nav.tabIndex} + tabs={store.nav.tabs} + currentTabIndex={store.nav.tabIndex} onNewTab={onNewTab} onChangeTab={onChangeTab} onCloseTab={onCloseTab} /> + <Modal /> {isLocationMenuActive && ( <LocationNavigator - url={stores.nav.tab.current.url} + url={store.nav.tab.current.url} onNavigate={onNavigateLocation} onDismiss={onDismissLocationNavigator} /> diff --git a/src/view/shell/mobile/location-menu.tsx b/src/view/shell/mobile/location-menu.tsx index b5f4d8036..fc851f77a 100644 --- a/src/view/shell/mobile/location-menu.tsx +++ b/src/view/shell/mobile/location-menu.tsx @@ -29,11 +29,17 @@ export function createLocationMenu(): RootSiblings { <Text style={styles.label}>Share</Text> </TouchableOpacity> <TouchableOpacity - style={[styles.menuItem, styles.menuItemBorder]} + style={[styles.menuItem]} onPress={() => onPressItem(0)}> <FontAwesomeIcon style={styles.icon} icon="link" /> <Text style={styles.label}>Copy Link</Text> </TouchableOpacity> + <TouchableOpacity + style={[styles.menuItem, styles.menuItemBorder]} + onPress={() => onPressItem(0)}> + <FontAwesomeIcon style={styles.icon} icon={['far', 'clone']} /> + <Text style={styles.label}>Duplicate Tab</Text> + </TouchableOpacity> </View> </> ), @@ -58,18 +64,20 @@ const styles = StyleSheet.create({ backgroundColor: '#fff', borderRadius: 14, opacity: 1, - paddingVertical: 2, + paddingVertical: 6, }, menuItem: { flexDirection: 'row', alignItems: 'center', - paddingVertical: 8, + paddingVertical: 6, paddingLeft: 10, paddingRight: 30, }, menuItemBorder: { borderTopWidth: 1, borderTopColor: colors.gray1, + marginTop: 4, + paddingTop: 12, }, icon: { marginLeft: 6, |