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 android
zio/stable
Paul Frazee 2023-04-24 16:36:05 -05:00 committed by GitHub
parent 9d8600c213
commit da8af38dcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 54 additions and 36 deletions

View File

@ -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",

View File

@ -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) {

View File

@ -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,
}, },
}) })

View File

@ -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>
) )

View File

@ -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}`
} }

View File

@ -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()
})
} }

View File

@ -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,
}, },

View File

@ -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 (

View File

@ -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,

View File

@ -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
})} })}

View File

@ -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}>

View File

@ -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,
}, },
}) })

View File

@ -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'} />

View File

@ -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"