Android & visual fixes: color themes, repost icon, navigation, back handler, etc (#519)
* Switch android to use slide left/right animations on navigation * Bump the repost icon down by a pixel * Tune theme colors for contrast and darker bg on darkmode * Move back handler to a point in the init flow that leads to more consistent capture of events * Fix image share flow on android * Fix lint * Add todo about sharing not available * Drop the android slide animation because it's too slow * Fix 'flashes of white' in dark mode androidzio/stable
parent
9d8600c213
commit
da8af38dcc
|
@ -68,6 +68,7 @@
|
||||||
"expo-image-picker": "~14.1.1",
|
"expo-image-picker": "~14.1.1",
|
||||||
"expo-localization": "~14.1.1",
|
"expo-localization": "~14.1.1",
|
||||||
"expo-media-library": "~15.2.3",
|
"expo-media-library": "~15.2.3",
|
||||||
|
"expo-sharing": "~11.2.2",
|
||||||
"expo-splash-screen": "~0.18.1",
|
"expo-splash-screen": "~0.18.1",
|
||||||
"expo-status-bar": "~1.4.4",
|
"expo-status-bar": "~1.4.4",
|
||||||
"expo-system-ui": "~2.2.1",
|
"expo-system-ui": "~2.2.1",
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {RootStoreModel, setupState, RootStoreProvider} from './state'
|
||||||
import {Shell} from './view/shell'
|
import {Shell} from './view/shell'
|
||||||
import * as notifee from 'lib/notifee'
|
import * as notifee from 'lib/notifee'
|
||||||
import * as analytics from 'lib/analytics'
|
import * as analytics from 'lib/analytics'
|
||||||
import * as backHandler from 'lib/routes/back-handler'
|
|
||||||
import * as Toast from './view/com/util/Toast'
|
import * as Toast from './view/com/util/Toast'
|
||||||
import {handleLink} from './Navigation'
|
import {handleLink} from './Navigation'
|
||||||
|
|
||||||
|
@ -29,7 +28,6 @@ const App = observer(() => {
|
||||||
setRootStore(store)
|
setRootStore(store)
|
||||||
analytics.init(store)
|
analytics.init(store)
|
||||||
notifee.init(store)
|
notifee.init(store)
|
||||||
backHandler.init(store)
|
|
||||||
SplashScreen.hideAsync()
|
SplashScreen.hideAsync()
|
||||||
Linking.getInitialURL().then((url: string | null) => {
|
Linking.getInitialURL().then((url: string | null) => {
|
||||||
if (url) {
|
if (url) {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import {
|
||||||
createNavigationContainerRef,
|
createNavigationContainerRef,
|
||||||
CommonActions,
|
CommonActions,
|
||||||
StackActions,
|
StackActions,
|
||||||
|
DefaultTheme,
|
||||||
|
DarkTheme,
|
||||||
} from '@react-navigation/native'
|
} from '@react-navigation/native'
|
||||||
import {createNativeStackNavigator} from '@react-navigation/native-stack'
|
import {createNativeStackNavigator} from '@react-navigation/native-stack'
|
||||||
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'
|
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'
|
||||||
|
@ -256,8 +258,9 @@ const LINKING = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const theme = useColorSchemeStyle(DefaultTheme, DarkTheme)
|
||||||
return (
|
return (
|
||||||
<NavigationContainer ref={navigationRef} linking={LINKING}>
|
<NavigationContainer ref={navigationRef} linking={LINKING} theme={theme}>
|
||||||
{children}
|
{children}
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
)
|
)
|
||||||
|
@ -334,7 +337,7 @@ const styles = StyleSheet.create({
|
||||||
backgroundColor: colors.black,
|
backgroundColor: colors.black,
|
||||||
},
|
},
|
||||||
bgLight: {
|
bgLight: {
|
||||||
backgroundColor: colors.gray1,
|
backgroundColor: colors.white,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -431,7 +431,7 @@ export function RepostIcon({
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
fill="none"
|
fill="none"
|
||||||
d="M 14.437 17.081 L 5.475 17.095 C 4.7 17.095 4.072 16.467 4.072 15.692 L 4.082 5.65 L 1.22 9.854 M 4.082 5.65 L 7.006 9.854 M 9.859 5.65 L 18.625 5.654 C 19.4 5.654 20.028 6.282 20.028 7.057 L 20.031 17.081 L 17.167 12.646 M 20.031 17.081 L 22.866 12.646"
|
d="M 14.437 18.081 L 5.475 18.095 C 4.7 18.095 4.072 17.467 4.072 16.692 L 4.082 6.65 L 1.22 10.854 M 4.082 6.65 L 7.006 10.854 M 9.859 6.65 L 18.625 6.654 C 19.4 6.654 20.028 7.282 20.028 8.057 L 20.031 18.081 L 17.167 13.646 M 20.031 18.081 L 22.866 13.646"
|
||||||
/>
|
/>
|
||||||
</Svg>
|
</Svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import RNFetchBlob from 'rn-fetch-blob'
|
import RNFetchBlob from 'rn-fetch-blob'
|
||||||
import ImageResizer from '@bam.tech/react-native-image-resizer'
|
import ImageResizer from '@bam.tech/react-native-image-resizer'
|
||||||
import {Image as RNImage, Share} from 'react-native'
|
import {Image as RNImage} from 'react-native'
|
||||||
import {Image} from 'react-native-image-crop-picker'
|
import {Image} from 'react-native-image-crop-picker'
|
||||||
import RNFS from 'react-native-fs'
|
import RNFS from 'react-native-fs'
|
||||||
import uuid from 'react-native-uuid'
|
import uuid from 'react-native-uuid'
|
||||||
import * as Toast from 'view/com/util/Toast'
|
import * as Sharing from 'expo-sharing'
|
||||||
import {Dimensions} from './types'
|
import {Dimensions} from './types'
|
||||||
import {POST_IMG_MAX} from 'lib/constants'
|
import {POST_IMG_MAX} from 'lib/constants'
|
||||||
import {isAndroid} from 'platform/detection'
|
import {isAndroid} from 'platform/detection'
|
||||||
|
@ -120,20 +120,19 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveImageModal({uri}: {uri: string}) {
|
export async function saveImageModal({uri}: {uri: string}) {
|
||||||
|
if (!(await Sharing.isAvailableAsync())) {
|
||||||
|
// TODO might need to give an error to the user in this case -prf
|
||||||
|
return
|
||||||
|
}
|
||||||
const downloadResponse = await RNFetchBlob.config({
|
const downloadResponse = await RNFetchBlob.config({
|
||||||
fileCache: true,
|
fileCache: true,
|
||||||
}).fetch('GET', uri)
|
}).fetch('GET', uri)
|
||||||
|
|
||||||
const imagePath = downloadResponse.path()
|
let imagePath = downloadResponse.path()
|
||||||
const base64Data = await downloadResponse.readFile('base64')
|
await Sharing.shareAsync(normalizePath(imagePath, true), {
|
||||||
const result = await Share.share({
|
mimeType: 'image/png',
|
||||||
url: 'data:image/png;base64,' + base64Data,
|
UTI: 'public.png',
|
||||||
})
|
})
|
||||||
if (result.action === Share.sharedAction) {
|
|
||||||
Toast.show('Image saved to gallery')
|
|
||||||
} else if (result.action === Share.dismissedAction) {
|
|
||||||
// dismissed
|
|
||||||
}
|
|
||||||
RNFS.unlink(imagePath)
|
RNFS.unlink(imagePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,8 +200,8 @@ async function moveToPermanentPath(path: string): Promise<string> {
|
||||||
return normalizePath(destinationPath)
|
return normalizePath(destinationPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePath(str: string): string {
|
function normalizePath(str: string, allPlatforms = false): string {
|
||||||
if (isAndroid) {
|
if (isAndroid || allPlatforms) {
|
||||||
if (!str.startsWith('file://')) {
|
if (!str.startsWith('file://')) {
|
||||||
return `file://${str}`
|
return `file://${str}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import {BackHandler} from 'react-native'
|
import {BackHandler} from 'react-native'
|
||||||
import {RootStoreModel} from 'state/index'
|
import {RootStoreModel} from 'state/index'
|
||||||
|
|
||||||
export function onBack(cb: () => boolean): () => void {
|
|
||||||
const subscription = BackHandler.addEventListener('hardwareBackPress', cb)
|
|
||||||
return () => subscription.remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function init(store: RootStoreModel) {
|
export function init(store: RootStoreModel) {
|
||||||
onBack(() => store.shell.closeAnyActiveElement())
|
BackHandler.addEventListener('hardwareBackPress', () => {
|
||||||
|
return store.shell.closeAnyActiveElement()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,8 +291,8 @@ export const darkTheme: Theme = {
|
||||||
palette: {
|
palette: {
|
||||||
...defaultTheme.palette,
|
...defaultTheme.palette,
|
||||||
default: {
|
default: {
|
||||||
background: colors.gray8,
|
background: colors.black,
|
||||||
backgroundLight: colors.gray6,
|
backgroundLight: colors.gray7,
|
||||||
text: colors.white,
|
text: colors.white,
|
||||||
textLight: colors.gray3,
|
textLight: colors.gray3,
|
||||||
textInverted: colors.black,
|
textInverted: colors.black,
|
||||||
|
@ -307,7 +307,7 @@ export const darkTheme: Theme = {
|
||||||
replyLineDot: colors.gray6,
|
replyLineDot: colors.gray6,
|
||||||
unreadNotifBg: colors.blue7,
|
unreadNotifBg: colors.blue7,
|
||||||
unreadNotifBorder: colors.blue6,
|
unreadNotifBorder: colors.blue6,
|
||||||
postCtrl: '#61657A',
|
postCtrl: '#707489',
|
||||||
brandText: '#0085ff',
|
brandText: '#0085ff',
|
||||||
emptyStateIcon: colors.gray4,
|
emptyStateIcon: colors.gray4,
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,7 +33,7 @@ import {OpenCameraBtn} from './photos/OpenCameraBtn'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import QuoteEmbed from '../util/post-embeds/QuoteEmbed'
|
import QuoteEmbed from '../util/post-embeds/QuoteEmbed'
|
||||||
import {useExternalLinkFetch} from './useExternalLinkFetch'
|
import {useExternalLinkFetch} from './useExternalLinkFetch'
|
||||||
import {isDesktopWeb} from 'platform/detection'
|
import {isDesktopWeb, isAndroid} from 'platform/detection'
|
||||||
import {GalleryModel} from 'state/models/media/gallery'
|
import {GalleryModel} from 'state/models/media/gallery'
|
||||||
import {Gallery} from './photos/Gallery'
|
import {Gallery} from './photos/Gallery'
|
||||||
|
|
||||||
|
@ -195,8 +195,8 @@ export const ComposePost = observer(function ComposePost({
|
||||||
|
|
||||||
const canSelectImages = gallery.size <= 4
|
const canSelectImages = gallery.size <= 4
|
||||||
const viewStyles = {
|
const viewStyles = {
|
||||||
paddingBottom: Platform.OS === 'android' ? insets.bottom : 0,
|
paddingBottom: isAndroid ? insets.bottom : 0,
|
||||||
paddingTop: Platform.OS === 'android' ? insets.top : 15,
|
paddingTop: isAndroid ? insets.top : isDesktopWeb ? 0 : 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -97,6 +97,7 @@ function Modal({modal}: {modal: ModalIface}) {
|
||||||
styles.container,
|
styles.container,
|
||||||
isMobileWeb && styles.containerMobile,
|
isMobileWeb && styles.containerMobile,
|
||||||
pal.view,
|
pal.view,
|
||||||
|
pal.border,
|
||||||
]}>
|
]}>
|
||||||
{element}
|
{element}
|
||||||
</View>
|
</View>
|
||||||
|
@ -124,6 +125,7 @@ const styles = StyleSheet.create({
|
||||||
paddingVertical: 20,
|
paddingVertical: 20,
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 24,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
containerMobile: {
|
containerMobile: {
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
|
|
|
@ -291,6 +291,8 @@ const DropdownItems = ({
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const dropDownBackgroundColor =
|
const dropDownBackgroundColor =
|
||||||
theme.colorScheme === 'dark' ? pal.btn : pal.view
|
theme.colorScheme === 'dark' ? pal.btn : pal.view
|
||||||
|
const separatorColor =
|
||||||
|
theme.colorScheme === 'dark' ? pal.borderDark : pal.border
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -322,7 +324,9 @@ const DropdownItems = ({
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
} else if (isSep(item)) {
|
} else if (isSep(item)) {
|
||||||
return <View key={index} style={[styles.separator, pal.border]} />
|
return (
|
||||||
|
<View key={index} style={[styles.separator, separatorColor]} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import {AccountData} from 'state/models/session'
|
||||||
import {useAnalytics} from 'lib/analytics'
|
import {useAnalytics} from 'lib/analytics'
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
import {pluralize} from 'lib/strings/helpers'
|
import {pluralize} from 'lib/strings/helpers'
|
||||||
|
import {isDesktopWeb} from 'platform/detection'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
|
||||||
export const SettingsScreen = withAuthRequired(
|
export const SettingsScreen = withAuthRequired(
|
||||||
|
@ -139,9 +140,12 @@ export const SettingsScreen = withAuthRequired(
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[s.hContentRegion]} testID="settingsScreen">
|
<View style={s.hContentRegion} testID="settingsScreen">
|
||||||
<ViewHeader title="Settings" showOnDesktop />
|
<ViewHeader title="Settings" />
|
||||||
<ScrollView style={s.hContentRegion} scrollIndicatorInsets={{right: 1}}>
|
<ScrollView
|
||||||
|
style={s.hContentRegion}
|
||||||
|
contentContainerStyle={!isDesktopWeb && pal.viewLight}
|
||||||
|
scrollIndicatorInsets={{right: 1}}>
|
||||||
<View style={styles.spacer20} />
|
<View style={styles.spacer20} />
|
||||||
<View style={[s.flexRow, styles.heading]}>
|
<View style={[s.flexRow, styles.heading]}>
|
||||||
<Text type="xl-bold" style={pal.text}>
|
<Text type="xl-bold" style={pal.text}>
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const Composer = observer(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.mask}>
|
<View style={styles.mask}>
|
||||||
<View style={[styles.container, pal.view]}>
|
<View style={[styles.container, pal.view, pal.border]}>
|
||||||
<ComposePost
|
<ComposePost
|
||||||
replyTo={replyTo}
|
replyTo={replyTo}
|
||||||
quote={quote}
|
quote={quote}
|
||||||
|
@ -63,5 +63,6 @@ const styles = StyleSheet.create({
|
||||||
paddingHorizontal: 2,
|
paddingHorizontal: 2,
|
||||||
borderRadius: isMobileWeb ? 0 : 8,
|
borderRadius: isMobileWeb ? 0 : 8,
|
||||||
marginBottom: '10vh',
|
marginBottom: '10vh',
|
||||||
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {DrawerContent} from './Drawer'
|
||||||
import {Composer} from './Composer'
|
import {Composer} from './Composer'
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import * as backHandler from 'lib/routes/back-handler'
|
||||||
import {RoutesContainer, TabsNavigator} from '../../Navigation'
|
import {RoutesContainer, TabsNavigator} from '../../Navigation'
|
||||||
import {isStateAtTabRoot} from 'lib/routes/helpers'
|
import {isStateAtTabRoot} from 'lib/routes/helpers'
|
||||||
|
|
||||||
|
@ -34,6 +35,9 @@ const ShellInner = observer(() => {
|
||||||
[store],
|
[store],
|
||||||
)
|
)
|
||||||
const canGoBack = useNavigationState(state => !isStateAtTabRoot(state))
|
const canGoBack = useNavigationState(state => !isStateAtTabRoot(state))
|
||||||
|
React.useEffect(() => {
|
||||||
|
backHandler.init(store)
|
||||||
|
}, [store])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -69,8 +73,8 @@ const ShellInner = observer(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Shell: React.FC = observer(() => {
|
export const Shell: React.FC = observer(() => {
|
||||||
const theme = useTheme()
|
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
const theme = useTheme()
|
||||||
return (
|
return (
|
||||||
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
|
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
|
||||||
<StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
|
<StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
|
||||||
|
|
|
@ -8427,6 +8427,11 @@ expo-pwa@0.0.124:
|
||||||
commander "2.20.0"
|
commander "2.20.0"
|
||||||
update-check "1.5.3"
|
update-check "1.5.3"
|
||||||
|
|
||||||
|
expo-sharing@~11.2.2:
|
||||||
|
version "11.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-11.2.2.tgz#7d9e387f1a902e6dd6838c22d9599dae9e7432cf"
|
||||||
|
integrity sha512-4Lhm1eS/CFIzX+JPuxMUTWBt9rv/WdvJvpQ9y+71bL/9w9dhvsdt9tv0SsNZATz4hk0tbrYD8ZEUsgiHiT1KkQ==
|
||||||
|
|
||||||
expo-splash-screen@~0.18.1:
|
expo-splash-screen@~0.18.1:
|
||||||
version "0.18.1"
|
version "0.18.1"
|
||||||
resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.18.1.tgz#e090b045a7f8c5d9597b7a96910caa4eae1fcf3b"
|
resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.18.1.tgz#e090b045a7f8c5d9597b7a96910caa4eae1fcf3b"
|
||||||
|
|
Loading…
Reference in New Issue