diff --git a/packages/components/src/components/cards/EmptyCards.tsx b/packages/components/src/components/cards/EmptyCards.tsx
index fbdcd4f4..8b6c3f2f 100644
--- a/packages/components/src/components/cards/EmptyCards.tsx
+++ b/packages/components/src/components/cards/EmptyCards.tsx
@@ -14,6 +14,10 @@ import {
import { SpringAnimatedActivityIndicator } from '../animated/spring/SpringAnimatedActivityIndicator'
import { SpringAnimatedText } from '../animated/spring/SpringAnimatedText'
import { Button, defaultButtonSize } from '../common/Button'
+import { fabSize } from '../common/FAB'
+import { Spacer } from '../common/Spacer'
+import { useAppLayout } from '../context/LayoutContext'
+import { fabSpacing, shouldRenderFAB } from '../layout/FABRenderer'
import { GenericMessageWithButtonView } from './GenericMessageWithButtonView'
const clearMessages = [
@@ -65,6 +69,7 @@ export const EmptyCards = React.memo((props: EmptyCardsProps) => {
refresh,
} = props
+ const { sizename } = useAppLayout()
const springAnimatedTheme = useCSSVariablesOrSpringAnimatedTheme()
const setColumnClearedAtFilter = useReduxAction(
actions.setColumnClearedAtFilter,
@@ -180,6 +185,10 @@ export const EmptyCards = React.memo((props: EmptyCardsProps) => {
) : null}
+
+ {shouldRenderFAB(sizename) && (
+
+ )}
)
})
diff --git a/packages/components/src/components/cards/EventCards.tsx b/packages/components/src/components/cards/EventCards.tsx
index e727f5c5..1fa33eee 100644
--- a/packages/components/src/components/cards/EventCards.tsx
+++ b/packages/components/src/components/cards/EventCards.tsx
@@ -17,8 +17,12 @@ import { bugsnag, ErrorBoundary } from '../../libs/bugsnag'
import * as actions from '../../redux/actions'
import { contentPadding } from '../../styles/variables'
import { Button } from '../common/Button'
+import { fabSize } from '../common/FAB'
import { RefreshControl } from '../common/RefreshControl'
+import { Spacer } from '../common/Spacer'
import { useFocusedColumn } from '../context/ColumnFocusContext'
+import { useAppLayout } from '../context/LayoutContext'
+import { fabSpacing, shouldRenderFAB } from '../layout/FABRenderer'
import { EmptyCards, EmptyCardsProps } from './EmptyCards'
import { EventCard } from './EventCard'
import { CardItemSeparator } from './partials/CardItemSeparator'
@@ -167,6 +171,8 @@ export const EventCards = React.memo((props: EventCardsProps) => {
])
const renderFooter = useCallback(() => {
+ const { sizename } = useAppLayout()
+
return (
<>
@@ -201,6 +207,10 @@ export const EventCards = React.memo((props: EventCardsProps) => {
/>
) : null}
+
+ {shouldRenderFAB(sizename) && (
+
+ )}
>
)
}, [
diff --git a/packages/components/src/components/cards/NotificationCards.tsx b/packages/components/src/components/cards/NotificationCards.tsx
index 3bc91e2a..c503dd31 100644
--- a/packages/components/src/components/cards/NotificationCards.tsx
+++ b/packages/components/src/components/cards/NotificationCards.tsx
@@ -17,8 +17,12 @@ import { bugsnag, ErrorBoundary } from '../../libs/bugsnag'
import * as actions from '../../redux/actions'
import { contentPadding } from '../../styles/variables'
import { Button } from '../common/Button'
+import { fabSize } from '../common/FAB'
import { RefreshControl } from '../common/RefreshControl'
+import { Spacer } from '../common/Spacer'
import { useFocusedColumn } from '../context/ColumnFocusContext'
+import { useAppLayout } from '../context/LayoutContext'
+import { fabSpacing, shouldRenderFAB } from '../layout/FABRenderer'
import { EmptyCards, EmptyCardsProps } from './EmptyCards'
import { NotificationCard } from './NotificationCard'
import { CardItemSeparator } from './partials/CardItemSeparator'
@@ -170,6 +174,8 @@ export const NotificationCards = React.memo((props: NotificationCardsProps) => {
])
const renderFooter = useCallback(() => {
+ const { sizename } = useAppLayout()
+
return (
<>
@@ -204,6 +210,10 @@ export const NotificationCards = React.memo((props: NotificationCardsProps) => {
/>
) : null}
+
+ {shouldRenderFAB(sizename) && (
+
+ )}
>
)
}, [
diff --git a/packages/components/src/components/columns/ColumnSwitcher.tsx b/packages/components/src/components/columns/ColumnSwitcher.tsx
new file mode 100644
index 00000000..acca95af
--- /dev/null
+++ b/packages/components/src/components/columns/ColumnSwitcher.tsx
@@ -0,0 +1,139 @@
+import React from 'react'
+import { StyleSheet } from 'react-native'
+
+import { useAppViewMode } from '../../hooks/use-app-view-mode'
+import { useCSSVariablesOrSpringAnimatedTheme } from '../../hooks/use-css-variables-or-spring--animated-theme'
+import { useReduxState } from '../../hooks/use-redux-state'
+import { emitter } from '../../libs/emitter'
+import * as selectors from '../../redux/selectors'
+import { contentPadding } from '../../styles/variables'
+import { SpringAnimatedIcon } from '../animated/spring/SpringAnimatedIcon'
+import { SpringAnimatedTouchableOpacity } from '../animated/spring/SpringAnimatedTouchableOpacity'
+import { SpringAnimatedView } from '../animated/spring/SpringAnimatedView'
+import { fabSize } from '../common/FAB'
+import { useFocusedColumn } from '../context/ColumnFocusContext'
+import { useAppLayout } from '../context/LayoutContext'
+import { fabSpacing, shouldRenderFAB } from '../layout/FABRenderer'
+
+const spacing = fabSpacing
+
+const styles = StyleSheet.create({
+ container: {
+ position: 'absolute',
+ bottom: spacing,
+ left: contentPadding,
+ flexDirection: 'row',
+ borderRadius: fabSize / 2,
+ shadowColor: '#000000',
+ shadowOffset: {
+ width: 0,
+ height: 3,
+ },
+ shadowOpacity: 0.2,
+ shadowRadius: 6,
+ overflow: 'hidden',
+ zIndex: 1000,
+ },
+})
+
+export function ColumnSwitcher() {
+ const columnIds = useReduxState(selectors.columnIdsSelector)
+ const currentOpenedModal = useReduxState(selectors.currentOpenedModal)
+ const focusedColumnId = useFocusedColumn() || columnIds[0]
+ const springAnimatedTheme = useCSSVariablesOrSpringAnimatedTheme()
+ const { appViewMode } = useAppViewMode()
+ const { sizename } = useAppLayout()
+
+ if (!(appViewMode === 'single-column' && shouldRenderFAB(sizename)))
+ return null
+ if (currentOpenedModal) return null
+
+ const isFirst = focusedColumnId === columnIds[0]
+ const isLast = focusedColumnId === columnIds.slice(-1)[0]
+
+ return (
+
+ {
+ emitter.emit('FOCUS_ON_PREVIOUS_COLUMN', {
+ highlight: isFirst,
+ })
+ }}
+ style={[
+ {
+ alignItems: 'center',
+ alignContent: 'center',
+ justifyContent: 'center',
+ width: fabSize,
+ height: fabSize,
+ borderRadius: 0,
+ borderTopLeftRadius: fabSize / 2,
+ borderBottomLeftRadius: fabSize / 2,
+ backgroundColor: springAnimatedTheme.backgroundColor,
+ },
+ isFirst && { opacity: 0.5 },
+ ]}
+ >
+
+
+ {
+ emitter.emit('FOCUS_ON_NEXT_COLUMN', {
+ highlight: isLast,
+ })
+ }}
+ style={[
+ {
+ alignItems: 'center',
+ alignContent: 'center',
+ justifyContent: 'center',
+ width: fabSize,
+ height: fabSize,
+ borderRadius: 0,
+ borderTopRightRadius: fabSize / 2,
+ borderBottomRightRadius: fabSize / 2,
+ backgroundColor: springAnimatedTheme.backgroundColor,
+ },
+ isLast && { opacity: 0.5 },
+ ]}
+ >
+
+
+
+ )
+}
diff --git a/packages/components/src/components/common/FAB.tsx b/packages/components/src/components/common/FAB.tsx
index 82758efe..e19a4635 100644
--- a/packages/components/src/components/common/FAB.tsx
+++ b/packages/components/src/components/common/FAB.tsx
@@ -13,7 +13,7 @@ import {
} from '../animated/spring/SpringAnimatedTouchableOpacity'
import { SpringAnimatedView } from '../animated/spring/SpringAnimatedView'
-export const fabSize = 50
+export const fabSize = 44
export interface FABProps extends SpringAnimatedTouchableOpacityProps {
children?: string | React.ReactElement
@@ -98,11 +98,11 @@ export function FAB(props: FABProps) {
name={iconName}
style={[
{
- width: 24,
- height: 24,
- lineHeight: 24,
+ width: fabSize / 2,
+ height: fabSize / 2,
+ lineHeight: fabSize / 2,
marginTop: 1,
- fontSize: 24,
+ fontSize: fabSize / 2,
textAlign: 'center',
color: useBrandColor
? springAnimatedTheme.primaryForegroundColor
diff --git a/packages/components/src/components/context/ColumnFocusContext.tsx b/packages/components/src/components/context/ColumnFocusContext.tsx
index 1c1e5a00..e4b84fd2 100644
--- a/packages/components/src/components/context/ColumnFocusContext.tsx
+++ b/packages/components/src/components/context/ColumnFocusContext.tsx
@@ -1,6 +1,9 @@
import React, { useContext, useState } from 'react'
import { useEmitter } from '../../hooks/use-emitter'
+import { emitter } from '../../libs/emitter'
+import { useReduxStore } from '../../redux/context/ReduxStoreContext'
+import * as selectors from '../../redux/selectors'
export interface ColumnFocusProviderProps {
children?: React.ReactNode
@@ -13,6 +16,7 @@ export const ColumnFocusContext = React.createContext(
)
export function ColumnFocusProvider(props: ColumnFocusProviderProps) {
+ const store = useReduxStore()
const [columnId, setColumnId] = useState(null)
useEmitter(
@@ -23,6 +27,52 @@ export function ColumnFocusProvider(props: ColumnFocusProviderProps) {
[],
)
+ useEmitter(
+ 'FOCUS_ON_PREVIOUS_COLUMN',
+ payload => {
+ const state = store.getState()
+ const columnIds = selectors.columnIdsSelector(state)
+ const focusedColumnIndex = columnIds
+ ? columnIds.findIndex(id => id === (columnId || columnIds[0]))
+ : -1
+
+ const previousColumnIndex = Math.max(
+ 0,
+ Math.min(focusedColumnIndex - 1, columnIds.length - 1),
+ )
+
+ emitter.emit('FOCUS_ON_COLUMN', {
+ ...payload,
+ columnId: columnIds[previousColumnIndex],
+ columnIndex: previousColumnIndex,
+ })
+ },
+ [columnId],
+ )
+
+ useEmitter(
+ 'FOCUS_ON_NEXT_COLUMN',
+ payload => {
+ const state = store.getState()
+ const columnIds = selectors.columnIdsSelector(state)
+ const focusedColumnIndex = columnIds
+ ? columnIds.findIndex(id => id === (columnId || columnIds[0]))
+ : -1
+
+ const nextColumnIndex = Math.max(
+ 0,
+ Math.min(focusedColumnIndex + 1, columnIds.length - 1),
+ )
+
+ emitter.emit('FOCUS_ON_COLUMN', {
+ ...payload,
+ columnId: columnIds[nextColumnIndex],
+ columnIndex: nextColumnIndex,
+ })
+ },
+ [columnId],
+ )
+
useEmitter(
'SCROLL_DOWN_COLUMN',
payload => {
diff --git a/packages/components/src/components/layout/FABRenderer.tsx b/packages/components/src/components/layout/FABRenderer.tsx
index c2b0e519..3d26a76f 100644
--- a/packages/components/src/components/layout/FABRenderer.tsx
+++ b/packages/components/src/components/layout/FABRenderer.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { View, ViewStyle } from 'react-native'
+import { StyleSheet, View } from 'react-native'
import { useKeyboardVisibility } from '../../hooks/use-keyboard-visibility'
import { useReduxAction } from '../../hooks/use-redux-action'
@@ -7,18 +7,30 @@ import { useReduxState } from '../../hooks/use-redux-state'
import * as actions from '../../redux/actions'
import * as selectors from '../../redux/selectors'
import { contentPadding } from '../../styles/variables'
-import { defaultButtonSize } from '../common/Button'
-import { FAB, fabSize } from '../common/FAB'
-import { useAppLayout } from '../context/LayoutContext'
+import { FAB } from '../common/FAB'
+import { AppLayoutProviderState, useAppLayout } from '../context/LayoutContext'
-export const fabSpacing =
- contentPadding / 2 + Math.max(0, (fabSize - defaultButtonSize) / 2) - 2
+export const fabSpacing = contentPadding / 2 // + Math.max(0, (fabSize - defaultButtonSize) / 2) - 2
-const fabPositionStyle: ViewStyle = {
- position: 'absolute',
- bottom: fabSpacing,
- right: contentPadding,
- zIndex: 1000,
+const styles = StyleSheet.create({
+ container: {
+ position: 'absolute',
+ bottom: fabSpacing,
+ right: contentPadding,
+ zIndex: 1000,
+ },
+})
+
+export function shouldRenderFAB(
+ sizename: AppLayoutProviderState['sizename'],
+ keyboardVisibility?: ReturnType,
+) {
+ if (!(sizename <= '3-large')) return false
+
+ if (keyboardVisibility === 'appearing' || keyboardVisibility === 'visible')
+ return false
+
+ return true
}
export function FABRenderer() {
@@ -32,9 +44,7 @@ export function FABRenderer() {
const closeAllModals = useReduxAction(actions.closeAllModals)
const replaceModal = useReduxAction(actions.replaceModal)
- if (!(sizename < '3-large')) return null
- if (keyboardVisibility === 'appearing' || keyboardVisibility === 'visible')
- return null
+ if (!shouldRenderFAB(sizename, keyboardVisibility)) return null
if (!currentOpenedModal) {
/*
@@ -57,7 +67,7 @@ export function FABRenderer() {
const iconStyle = undefined
return (
-
+
+
{
{
<>
{/* */}
- {!!large && (
+ {!!large && !shouldRenderFAB(sizename) && (
<>
{
const scrollLeft = useCallback(() => {
if (currentOpenedModal) return
- const previousColumnIndex = Math.max(
- 0,
- Math.min(focusedColumnIndex - 1, columnIds.length - 1),
- )
-
- emitter.emit('FOCUS_ON_COLUMN', {
+ emitter.emit('FOCUS_ON_PREVIOUS_COLUMN', {
animated: true,
- columnId: columnIds[previousColumnIndex],
- columnIndex: previousColumnIndex,
focusOnVisibleItem: true,
highlight: false,
scrollTo: true,
})
- }, [currentOpenedModal, focusedColumnIndex, columnIds])
+ }, [currentOpenedModal])
useKeyPressCallback('ArrowLeft', scrollLeft)
useKeyPressCallback('h', scrollLeft)
@@ -209,20 +203,13 @@ export const MainScreen = React.memo(() => {
const scrollRight = useCallback(() => {
if (currentOpenedModal) return
- const nextColumnIndex = Math.max(
- 0,
- Math.min(focusedColumnIndex + 1, columnIds.length - 1),
- )
-
- emitter.emit('FOCUS_ON_COLUMN', {
+ emitter.emit('FOCUS_ON_NEXT_COLUMN', {
animated: true,
- columnId: columnIds[nextColumnIndex],
- columnIndex: nextColumnIndex,
focusOnVisibleItem: true,
highlight: false,
scrollTo: true,
})
- }, [currentOpenedModal, focusedColumnIndex, columnIds])
+ }, [currentOpenedModal])
useKeyPressCallback('ArrowRight', scrollRight)
useKeyPressCallback('l', scrollRight)
@@ -367,6 +354,7 @@ export const MainScreen = React.memo(() => {
/>
+