Adds more tracking all around the app (#142)

* Adds more tracking all around the app

* more events

* lint

* using better analytics naming

* missed file

* more fixes
This commit is contained in:
Aryan Goharzad
2023-02-02 19:48:36 -05:00
committed by GitHub
parent 8b310d91f0
commit cede0c772c
21 changed files with 196 additions and 24 deletions
+3 -12
View File
@@ -6,17 +6,14 @@ import {GestureHandlerRootView} from 'react-native-gesture-handler'
import SplashScreen from 'react-native-splash-screen' import SplashScreen from 'react-native-splash-screen'
import {SafeAreaProvider} from 'react-native-safe-area-context' import {SafeAreaProvider} from 'react-native-safe-area-context'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import { import {SegmentClient, AnalyticsProvider} from '@segment/analytics-react-native'
createClient,
SegmentClient,
AnalyticsProvider,
} from '@segment/analytics-react-native'
import {ThemeProvider} from './view/lib/ThemeContext' import {ThemeProvider} from './view/lib/ThemeContext'
import * as view from './view/index' import * as view from './view/index'
import {RootStoreModel, setupState, RootStoreProvider} from './state' import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {MobileShell} from './view/shell/mobile' import {MobileShell} from './view/shell/mobile'
import {s} from './view/lib/styles' import {s} from './view/lib/styles'
import notifee, {EventType} from '@notifee/react-native' import notifee, {EventType} from '@notifee/react-native'
import {segmentClient} from './lib/segmentClient'
const App = observer(() => { const App = observer(() => {
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>( const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
@@ -27,12 +24,7 @@ const App = observer(() => {
// init // init
useEffect(() => { useEffect(() => {
view.setup() view.setup()
setSegment( setSegment(segmentClient)
createClient({
writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI',
trackAppLifecycleEvents: true,
}),
)
setupState().then(store => { setupState().then(store => {
setRootStore(store) setRootStore(store)
SplashScreen.hide() SplashScreen.hide()
@@ -58,7 +50,6 @@ const App = observer(() => {
if (!rootStore) { if (!rootStore) {
return null return null
} }
return ( return (
<GestureHandlerRootView style={s.h100pct}> <GestureHandlerRootView style={s.h100pct}>
<ThemeProvider theme={rootStore.shell.darkMode ? 'dark' : 'light'}> <ThemeProvider theme={rootStore.shell.darkMode ? 'dark' : 'light'}>
+35
View File
@@ -0,0 +1,35 @@
export class Logger {
isDisabled: boolean
constructor(isDisabled: boolean = process.env.NODE_ENV === 'production') {
this.isDisabled = isDisabled
}
enable() {
this.isDisabled = false
}
disable() {
this.isDisabled = true
}
info(message?: any, ...optionalParams: any[]): void {
if (!this.isDisabled) {
console.info(message, ...optionalParams)
}
}
warn(message?: any, ...optionalParams: any[]): void {
if (!this.isDisabled) {
console.warn(message, ...optionalParams)
}
}
error(message?: any, ...optionalParams: any[]): void {
if (!this.isDisabled) {
console.error(message, ...optionalParams)
}
}
}
export const createLogger = (isDisabled?: boolean) => new Logger(isDisabled)
+9
View File
@@ -0,0 +1,9 @@
import {createClient} from '@segment/analytics-react-native'
// import {createLogger} from './logger'
export const segmentClient = createClient({
writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI',
trackAppLifecycleEvents: true,
// Uncomment to debug:
// logger: createLogger(),
})
+8
View File
@@ -1,5 +1,6 @@
import {makeAutoObservable} from 'mobx' import {makeAutoObservable} from 'mobx'
import {TABS_ENABLED} from '../../build-flags' import {TABS_ENABLED} from '../../build-flags'
import {segmentClient} from '../../lib/segmentClient'
let __id = 0 let __id = 0
function genId() { function genId() {
@@ -96,6 +97,13 @@ export class NavigationTabModel {
// = // =
navigate(url: string, title?: string) { navigate(url: string, title?: string) {
try {
const path = url.split('/')[1]
segmentClient.track('Navigation', {
path,
})
} catch (error) {}
if (this.current?.url === url) { if (this.current?.url === url) {
this.refresh() this.refresh()
} else { } else {
+7 -1
View File
@@ -68,7 +68,7 @@ export const ComposePost = observer(function ComposePost({
onPost?: ComposerOpts['onPost'] onPost?: ComposerOpts['onPost']
onClose: () => void onClose: () => void
}) { }) {
const {track} = useAnalytics() const {track, screen} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const textInput = useRef<PasteInputRef>(null) const textInput = useRef<PasteInputRef>(null)
@@ -88,6 +88,10 @@ export const ComposePost = observer(function ComposePost({
) )
const [selectedPhotos, setSelectedPhotos] = useState<string[]>([]) const [selectedPhotos, setSelectedPhotos] = useState<string[]>([])
useEffect(() => {
screen('ComposePost')
}, [screen])
// Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment // Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment
const autocompleteView = React.useMemo<UserAutocompleteViewModel>( const autocompleteView = React.useMemo<UserAutocompleteViewModel>(
() => new UserAutocompleteViewModel(store), () => new UserAutocompleteViewModel(store),
@@ -183,6 +187,7 @@ export const ComposePost = observer(function ComposePost({
textInput.current?.focus() textInput.current?.focus()
} }
const onPressSelectPhotos = () => { const onPressSelectPhotos = () => {
track('ComposePost:SelectPhotos')
if (isSelectingPhotos) { if (isSelectingPhotos) {
setIsSelectingPhotos(false) setIsSelectingPhotos(false)
} else if (selectedPhotos.length < 4) { } else if (selectedPhotos.length < 4) {
@@ -190,6 +195,7 @@ export const ComposePost = observer(function ComposePost({
} }
} }
const onSelectPhotos = (photos: string[]) => { const onSelectPhotos = (photos: string[]) => {
track('ComposePost:SelectPhotos:Done')
setSelectedPhotos(photos) setSelectedPhotos(photos)
if (photos.length >= 4) { if (photos.length >= 4) {
setIsSelectingPhotos(false) setIsSelectingPhotos(false)
@@ -19,6 +19,7 @@ import {
import {compressIfNeeded, scaleDownDimensions} from '../../../lib/images' import {compressIfNeeded, scaleDownDimensions} from '../../../lib/images'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {useAnalytics} from '@segment/analytics-react-native'
const MAX_WIDTH = 2000 const MAX_WIDTH = 2000
const MAX_HEIGHT = 2000 const MAX_HEIGHT = 2000
@@ -75,6 +76,7 @@ export const PhotoCarouselPicker = ({
selectedPhotos: string[] selectedPhotos: string[]
onSelectPhotos: (v: string[]) => void onSelectPhotos: (v: string[]) => void
}) => { }) => {
const {track} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const [isSetup, setIsSetup] = React.useState<boolean>(false) const [isSetup, setIsSetup] = React.useState<boolean>(false)
@@ -111,6 +113,7 @@ export const PhotoCarouselPicker = ({
const handleSelectPhoto = useCallback( const handleSelectPhoto = useCallback(
async (item: PhotoIdentifier) => { async (item: PhotoIdentifier) => {
track('PhotoCarouselPicker:PhotoSelected')
try { try {
const imgPath = await cropPhoto( const imgPath = await cropPhoto(
item.node.image.uri, item.node.image.uri,
@@ -123,10 +126,11 @@ export const PhotoCarouselPicker = ({
store.log.warn('Error selecting photo', err) store.log.warn('Error selecting photo', err)
} }
}, },
[store.log, selectedPhotos, onSelectPhotos], [track, onSelectPhotos, selectedPhotos, store.log],
) )
const handleOpenGallery = useCallback(async () => { const handleOpenGallery = useCallback(async () => {
track('PhotoCarouselPicker:GalleryOpened')
if (!(await requestPhotoAccessIfNeeded())) { if (!(await requestPhotoAccessIfNeeded())) {
return return
} }
@@ -156,7 +160,7 @@ export const PhotoCarouselPicker = ({
result.push(permanentPath) result.push(permanentPath)
} }
onSelectPhotos([...selectedPhotos, ...result]) onSelectPhotos([...selectedPhotos, ...result])
}, [selectedPhotos, onSelectPhotos]) }, [track, selectedPhotos, onSelectPhotos])
return ( return (
<ScrollView <ScrollView
+5 -1
View File
@@ -29,7 +29,7 @@ import {ServerInputModal} from '../../../state/models/shell-ui'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
const {track} = useAnalytics() const {track, screen} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
@@ -46,6 +46,10 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
const [handle, setHandle] = useState<string>('') const [handle, setHandle] = useState<string>('')
const [is13, setIs13] = useState<boolean>(false) const [is13, setIs13] = useState<boolean>(false)
useEffect(() => {
screen('CreateAccount')
}, [screen])
useEffect(() => { useEffect(() => {
let aborted = false let aborted = false
setError('') setError('')
+29 -2
View File
@@ -35,6 +35,7 @@ enum Forms {
export const Signin = ({onPressBack}: {onPressBack: () => void}) => { export const Signin = ({onPressBack}: {onPressBack: () => void}) => {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {track} = useAnalytics()
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const [retryDescribeTrigger, setRetryDescribeTrigger] = useState<any>({}) const [retryDescribeTrigger, setRetryDescribeTrigger] = useState<any>({})
const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE) const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE)
@@ -88,6 +89,10 @@ export const Signin = ({onPressBack}: {onPressBack: () => void}) => {
}, [store.session, store.log, serviceUrl, retryDescribeTrigger]) }, [store.session, store.log, serviceUrl, retryDescribeTrigger])
const onPressRetryConnect = () => setRetryDescribeTrigger({}) const onPressRetryConnect = () => setRetryDescribeTrigger({})
const onPressForgotPassword = () => {
track('Signin:PressedForgotPassword')
setCurrentForm(Forms.ForgotPassword)
}
return ( return (
<KeyboardAvoidingView testID="signIn" behavior="padding" style={[pal.view]}> <KeyboardAvoidingView testID="signIn" behavior="padding" style={[pal.view]}>
@@ -101,7 +106,7 @@ export const Signin = ({onPressBack}: {onPressBack: () => void}) => {
setError={setError} setError={setError}
setServiceUrl={setServiceUrl} setServiceUrl={setServiceUrl}
onPressBack={onPressBack} onPressBack={onPressBack}
onPressForgotPassword={gotoForm(Forms.ForgotPassword)} onPressForgotPassword={onPressForgotPassword}
onPressRetryConnect={onPressRetryConnect} onPressRetryConnect={onPressRetryConnect}
/> />
) : undefined} ) : undefined}
@@ -150,10 +155,14 @@ const ChooseAccountForm = ({
onSelectAccount: (account?: AccountData) => void onSelectAccount: (account?: AccountData) => void
onPressBack: () => void onPressBack: () => void
}) => { }) => {
const {track} = useAnalytics() const {track, screen} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
const [isProcessing, setIsProcessing] = React.useState(false) const [isProcessing, setIsProcessing] = React.useState(false)
React.useEffect(() => {
screen('Choose Account')
}, [screen])
const onTryAccount = async (account: AccountData) => { const onTryAccount = async (account: AccountData) => {
if (account.accessJwt && account.refreshJwt) { if (account.accessJwt && account.refreshJwt) {
setIsProcessing(true) setIsProcessing(true)
@@ -267,6 +276,7 @@ const LoginForm = ({
const onPressSelectService = () => { const onPressSelectService = () => {
store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl)) store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl))
Keyboard.dismiss() Keyboard.dismiss()
track('Signin:PressedSelectService')
} }
const onPressNext = async () => { const onPressNext = async () => {
@@ -458,6 +468,11 @@ const ForgotPasswordForm = ({
const pal = usePalette('default') const pal = usePalette('default')
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [email, setEmail] = useState<string>('') const [email, setEmail] = useState<string>('')
const {screen} = useAnalytics()
useEffect(() => {
screen('Signin:ForgotPassword')
}, [screen])
const onPressSelectService = () => { const onPressSelectService = () => {
store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl)) store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl))
@@ -594,6 +609,12 @@ const SetNewPasswordForm = ({
onPasswordSet: () => void onPasswordSet: () => void
}) => { }) => {
const pal = usePalette('default') const pal = usePalette('default')
const {screen} = useAnalytics()
useEffect(() => {
screen('Signin:SetNewPasswordForm')
}, [screen])
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [resetCode, setResetCode] = useState<string>('') const [resetCode, setResetCode] = useState<string>('')
const [password, setPassword] = useState<string>('') const [password, setPassword] = useState<string>('')
@@ -716,6 +737,12 @@ const SetNewPasswordForm = ({
} }
const PasswordUpdatedForm = ({onPressNext}: {onPressNext: () => void}) => { const PasswordUpdatedForm = ({onPressNext}: {onPressNext: () => void}) => {
const {screen} = useAnalytics()
useEffect(() => {
screen('Signin:PasswordUpdatedForm')
}, [screen])
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
<> <>
+5
View File
@@ -24,6 +24,7 @@ import {compressIfNeeded} from '../../../lib/images'
import {UserBanner} from '../util/UserBanner' import {UserBanner} from '../util/UserBanner'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
import {useAnalytics} from '@segment/analytics-react-native'
export const snapPoints = ['80%'] export const snapPoints = ['80%']
@@ -37,6 +38,7 @@ export function Component({
const store = useStores() const store = useStores()
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const pal = usePalette('default') const pal = usePalette('default')
const {track} = useAnalytics()
const [isProcessing, setProcessing] = useState<boolean>(false) const [isProcessing, setProcessing] = useState<boolean>(false)
const [displayName, setDisplayName] = useState<string>( const [displayName, setDisplayName] = useState<string>(
@@ -57,6 +59,7 @@ export function Component({
store.shell.closeModal() store.shell.closeModal()
} }
const onSelectNewAvatar = async (img: PickedImage) => { const onSelectNewAvatar = async (img: PickedImage) => {
track('EditProfile:AvatarSelected')
try { try {
const finalImg = await compressIfNeeded(img, 1000000) const finalImg = await compressIfNeeded(img, 1000000)
setNewUserAvatar(finalImg) setNewUserAvatar(finalImg)
@@ -66,6 +69,7 @@ export function Component({
} }
} }
const onSelectNewBanner = async (img: PickedImage) => { const onSelectNewBanner = async (img: PickedImage) => {
track('EditProfile:BannerSelected')
try { try {
const finalImg = await compressIfNeeded(img, 1000000) const finalImg = await compressIfNeeded(img, 1000000)
setNewUserBanner(finalImg) setNewUserBanner(finalImg)
@@ -75,6 +79,7 @@ export function Component({
} }
} }
const onPressSave = async () => { const onPressSave = async () => {
track('EditProfile:Save')
setProcessing(true) setProcessing(true)
if (error) { if (error) {
setError('') setError('')
+10 -1
View File
@@ -1,4 +1,4 @@
import React, {MutableRefObject} from 'react' import React, {MutableRefObject, useEffect} from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import { import {
ActivityIndicator, ActivityIndicator,
@@ -16,6 +16,7 @@ import {FeedItem} from './FeedItem'
import {PromptButtons} from './PromptButtons' import {PromptButtons} from './PromptButtons'
import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll'
import {s} from '../../lib/styles' import {s} from '../../lib/styles'
import {useAnalytics} from '@segment/analytics-react-native'
const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'} const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'}
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
@@ -37,6 +38,12 @@ export const Feed = observer(function Feed({
onScroll?: OnScrollCb onScroll?: OnScrollCb
testID?: string testID?: string
}) { }) {
const {screen, track} = useAnalytics()
useEffect(() => {
screen('Feed')
}, [screen])
// TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
// VirtualizedList: You have a large list that is slow to update - make sure your // VirtualizedList: You have a large list that is slow to update - make sure your
// renderItem function renders components that follow React performance best practices // renderItem function renders components that follow React performance best practices
@@ -57,6 +64,7 @@ export const Feed = observer(function Feed({
} }
} }
const onRefresh = () => { const onRefresh = () => {
track('Feed:onRefresh')
feed feed
.refresh() .refresh()
.catch(err => .catch(err =>
@@ -64,6 +72,7 @@ export const Feed = observer(function Feed({
) )
} }
const onEndReached = () => { const onEndReached = () => {
track('Feed:onEndReached')
feed feed
.loadMore() .loadMore()
.catch(err => feed.rootStore.log.error('Failed to load more posts', err)) .catch(err => feed.rootStore.log.error('Failed to load more posts', err))
+6
View File
@@ -18,6 +18,7 @@ import {UserAvatar} from '../util/UserAvatar'
import {s} from '../../lib/styles' import {s} from '../../lib/styles'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
import {useAnalytics} from '@segment/analytics-react-native'
export const FeedItem = observer(function ({ export const FeedItem = observer(function ({
item, item,
@@ -30,6 +31,7 @@ export const FeedItem = observer(function ({
}) { }) {
const store = useStores() const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const {track} = useAnalytics()
const [deleted, setDeleted] = useState(false) const [deleted, setDeleted] = useState(false)
const record = item.postRecord const record = item.postRecord
const itemHref = useMemo(() => { const itemHref = useMemo(() => {
@@ -47,6 +49,7 @@ export const FeedItem = observer(function ({
}, [record?.reply]) }, [record?.reply])
const onPressReply = () => { const onPressReply = () => {
track('FeedItem:PostReply')
store.shell.openComposer({ store.shell.openComposer({
replyTo: { replyTo: {
uri: item.post.uri, uri: item.post.uri,
@@ -61,11 +64,13 @@ export const FeedItem = observer(function ({
}) })
} }
const onPressToggleRepost = () => { const onPressToggleRepost = () => {
track('FeedItem:PostRepost')
return item return item
.toggleRepost() .toggleRepost()
.catch(e => store.log.error('Failed to toggle repost', e)) .catch(e => store.log.error('Failed to toggle repost', e))
} }
const onPressToggleUpvote = () => { const onPressToggleUpvote = () => {
track('FeedItem:PostLike')
return item return item
.toggleUpvote() .toggleUpvote()
.catch(e => store.log.error('Failed to toggle upvote', e)) .catch(e => store.log.error('Failed to toggle upvote', e))
@@ -75,6 +80,7 @@ export const FeedItem = observer(function ({
Toast.show('Copied to clipboard') Toast.show('Copied to clipboard')
} }
const onDeletePost = () => { const onDeletePost = () => {
track('FeedItem:PostDelete')
item.delete().then( item.delete().then(
() => { () => {
setDeleted(true) setDeleted(true)
+14 -2
View File
@@ -2,6 +2,7 @@ import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
import {useAnalytics} from '@segment/analytics-react-native'
export function PromptButtons({ export function PromptButtons({
onPressCompose, onPressCompose,
@@ -9,18 +10,29 @@ export function PromptButtons({
onPressCompose: (imagesOpen?: boolean) => void onPressCompose: (imagesOpen?: boolean) => void
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const {track} = useAnalytics()
const onPressNewPost = () => {
track('PromptButtons:NewPost')
onPressCompose(false)
}
const onPressSharePhoto = () => {
track('PromptButtons:SharePhoto')
onPressCompose(true)
}
return ( return (
<View style={[pal.view, pal.border, styles.container]}> <View style={[pal.view, pal.border, styles.container]}>
<TouchableOpacity <TouchableOpacity
testID="composePromptButton" testID="composePromptButton"
onPress={() => onPressCompose(false)} onPress={onPressNewPost}
style={[styles.btn, {backgroundColor: pal.colors.backgroundLight}]}> style={[styles.btn, {backgroundColor: pal.colors.backgroundLight}]}>
<Text type="button" style={pal.text}> <Text type="button" style={pal.text}>
New post New post
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
onPress={() => onPressCompose(true)} onPress={onPressSharePhoto}
style={[styles.btn, {backgroundColor: pal.colors.backgroundLight}]}> style={[styles.btn, {backgroundColor: pal.colors.backgroundLight}]}>
<Text type="button" style={pal.text}> <Text type="button" style={pal.text}>
Share photo Share photo
+9 -1
View File
@@ -27,6 +27,7 @@ import {RichText} from '../util/text/RichText'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {UserBanner} from '../util/UserBanner' import {UserBanner} from '../util/UserBanner'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
import {useAnalytics} from '@segment/analytics-react-native'
export const ProfileHeader = observer(function ProfileHeader({ export const ProfileHeader = observer(function ProfileHeader({
view, view,
@@ -37,7 +38,7 @@ export const ProfileHeader = observer(function ProfileHeader({
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {track} = useAnalytics()
const onPressBack = () => { const onPressBack = () => {
store.nav.tab.goBack() store.nav.tab.goBack()
} }
@@ -59,18 +60,23 @@ export const ProfileHeader = observer(function ProfileHeader({
) )
} }
const onPressEditProfile = () => { const onPressEditProfile = () => {
track('ProfileHeader:EditProfileButtonClicked')
store.shell.openModal(new EditProfileModal(view, onRefreshAll)) store.shell.openModal(new EditProfileModal(view, onRefreshAll))
} }
const onPressFollowers = () => { const onPressFollowers = () => {
track('ProfileHeader:FollowersButtonClicked')
store.nav.navigate(`/profile/${view.handle}/followers`) store.nav.navigate(`/profile/${view.handle}/followers`)
} }
const onPressFollows = () => { const onPressFollows = () => {
track('ProfileHeader:FollowsButtonClicked')
store.nav.navigate(`/profile/${view.handle}/follows`) store.nav.navigate(`/profile/${view.handle}/follows`)
} }
const onPressShare = () => { const onPressShare = () => {
track('ProfileHeader:ShareButtonClicked')
Share.share({url: toShareUrl(`/profile/${view.handle}`)}) Share.share({url: toShareUrl(`/profile/${view.handle}`)})
} }
const onPressMuteAccount = async () => { const onPressMuteAccount = async () => {
track('ProfileHeader:MuteAccountButtonClicked')
try { try {
await view.muteAccount() await view.muteAccount()
Toast.show('Account muted') Toast.show('Account muted')
@@ -80,6 +86,7 @@ export const ProfileHeader = observer(function ProfileHeader({
} }
} }
const onPressUnmuteAccount = async () => { const onPressUnmuteAccount = async () => {
track('ProfileHeader:UnmuteAccountButtonClicked')
try { try {
await view.unmuteAccount() await view.unmuteAccount()
Toast.show('Account unmuted') Toast.show('Account unmuted')
@@ -89,6 +96,7 @@ export const ProfileHeader = observer(function ProfileHeader({
} }
} }
const onPressReportAccount = () => { const onPressReportAccount = () => {
track('ProfileHeader:ReportAccountButtonClicked')
store.shell.openModal(new ReportAccountModal(view.did)) store.shell.openModal(new ReportAccountModal(view.did))
} }
+3
View File
@@ -13,6 +13,7 @@ import {MagnifyingGlassIcon} from '../../lib/icons'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
import {colors} from '../../lib/styles' import {colors} from '../../lib/styles'
import {useAnalytics} from '@segment/analytics-react-native'
const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
const BACK_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} const BACK_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10}
@@ -28,10 +29,12 @@ export const ViewHeader = observer(function ViewHeader({
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {track} = useAnalytics()
const onPressBack = () => { const onPressBack = () => {
store.nav.tab.goBack() store.nav.tab.goBack()
} }
const onPressMenu = () => { const onPressMenu = () => {
track('ViewHeader:MenuButtonClicked')
store.shell.setMainMenuOpen(true) store.shell.setMainMenuOpen(true)
} }
const onPressSearch = () => { const onPressSearch = () => {
+3
View File
@@ -13,6 +13,7 @@ import {ScreenParams} from '../routes'
import {s, colors, gradients} from '../lib/styles' import {s, colors, gradients} from '../lib/styles'
import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll'
import {clamp} from 'lodash' import {clamp} from 'lodash'
import {useAnalytics} from '@segment/analytics-react-native'
const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20} const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20}
@@ -23,6 +24,7 @@ export const Home = observer(function Home({
}: ScreenParams) { }: ScreenParams) {
const store = useStores() const store = useStores()
const onMainScroll = useOnMainScroll(store) const onMainScroll = useOnMainScroll(store)
const {track} = useAnalytics()
const safeAreaInsets = useSafeAreaInsets() const safeAreaInsets = useSafeAreaInsets()
const [wasVisible, setWasVisible] = React.useState<boolean>(false) const [wasVisible, setWasVisible] = React.useState<boolean>(false)
const {appState} = useAppState({ const {appState} = useAppState({
@@ -72,6 +74,7 @@ export const Home = observer(function Home({
}, [visible, store, store.me.mainFeed, navIdx, doPoll, wasVisible]) }, [visible, store, store.me.mainFeed, navIdx, doPoll, wasVisible])
const onPressCompose = (imagesOpen?: boolean) => { const onPressCompose = (imagesOpen?: boolean) => {
track('Home:ComposeButtonPressed')
store.shell.openComposer({imagesOpen}) store.shell.openComposer({imagesOpen})
} }
const onPressTryAgain = () => { const onPressTryAgain = () => {
+8 -1
View File
@@ -1,4 +1,4 @@
import React, {useState} from 'react' import React, {useEffect, useState} from 'react'
import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native'
import FastImage, {Source as FISource} from 'react-native-fast-image' import FastImage, {Source as FISource} from 'react-native-fast-image'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
@@ -9,6 +9,7 @@ import {ErrorBoundary} from '../com/util/ErrorBoundary'
import {colors} from '../lib/styles' import {colors} from '../lib/styles'
import {usePalette} from '../lib/hooks/usePalette' import {usePalette} from '../lib/hooks/usePalette'
import {CLOUD_SPLASH} from '../lib/assets' import {CLOUD_SPLASH} from '../lib/assets'
import {useAnalytics} from '@segment/analytics-react-native'
enum ScreenState { enum ScreenState {
S_SigninOrCreateAccount, S_SigninOrCreateAccount,
@@ -23,6 +24,12 @@ const SigninOrCreateAccount = ({
onPressSignin: () => void onPressSignin: () => void
onPressCreateAccount: () => void onPressCreateAccount: () => void
}) => { }) => {
const {screen} = useAnalytics()
useEffect(() => {
screen('Login')
}, [screen])
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
<> <>
+6
View File
@@ -6,10 +6,16 @@ import {useStores} from '../../state'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll'
import {s} from '../lib/styles' import {s} from '../lib/styles'
import {useAnalytics} from '@segment/analytics-react-native'
export const Notifications = ({navIdx, visible}: ScreenParams) => { export const Notifications = ({navIdx, visible}: ScreenParams) => {
const store = useStores() const store = useStores()
const onMainScroll = useOnMainScroll(store) const onMainScroll = useOnMainScroll(store)
const {screen} = useAnalytics()
useEffect(() => {
screen('Notifications')
}, [screen])
useEffect(() => { useEffect(() => {
if (!visible) { if (!visible) {
+7
View File
@@ -15,6 +15,7 @@ import {Text} from '../com/util/text/Text'
import {FAB} from '../com/util/FAB' 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 '@segment/analytics-react-native'
const LOADING_ITEM = {_reactKey: '__loading__'} const LOADING_ITEM = {_reactKey: '__loading__'}
const END_ITEM = {_reactKey: '__end__'} const END_ITEM = {_reactKey: '__end__'}
@@ -22,6 +23,12 @@ const EMPTY_ITEM = {_reactKey: '__empty__'}
export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
const store = useStores() const store = useStores()
const {screen} = useAnalytics()
useEffect(() => {
screen('Profile')
}, [screen])
const onMainScroll = useOnMainScroll(store) const onMainScroll = useOnMainScroll(store)
const [hasSetup, setHasSetup] = useState<boolean>(false) const [hasSetup, setHasSetup] = useState<boolean>(false)
const uiState = React.useMemo( const uiState = React.useMemo(
+9
View File
@@ -18,6 +18,7 @@ import * as Toast from '../com/util/Toast'
import {UserAvatar} from '../com/util/UserAvatar' import {UserAvatar} from '../com/util/UserAvatar'
import {usePalette} from '../lib/hooks/usePalette' import {usePalette} from '../lib/hooks/usePalette'
import {AccountData} from '../../state/models/session' import {AccountData} from '../../state/models/session'
import {useAnalytics} from '@segment/analytics-react-native'
import {DeleteAccountModal} from '../../state/models/shell-ui' import {DeleteAccountModal} from '../../state/models/shell-ui'
export const Settings = observer(function Settings({ export const Settings = observer(function Settings({
@@ -26,8 +27,13 @@ export const Settings = observer(function Settings({
}: ScreenParams) { }: ScreenParams) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {screen, track} = useAnalytics()
const [isSwitching, setIsSwitching] = React.useState(false) const [isSwitching, setIsSwitching] = React.useState(false)
useEffect(() => {
screen('Settings')
}, [screen])
useEffect(() => { useEffect(() => {
if (!visible) { if (!visible) {
return return
@@ -37,6 +43,7 @@ export const Settings = observer(function Settings({
}, [visible, store, navIdx]) }, [visible, store, navIdx])
const onPressSwitchAccount = async (acct: AccountData) => { const onPressSwitchAccount = async (acct: AccountData) => {
track('Settings:SwitchAccountButtonClicked')
setIsSwitching(true) setIsSwitching(true)
if (await store.session.resumeSession(acct)) { if (await store.session.resumeSession(acct)) {
setIsSwitching(false) setIsSwitching(false)
@@ -50,9 +57,11 @@ export const Settings = observer(function Settings({
store.session.clear() store.session.clear()
} }
const onPressAddAccount = () => { const onPressAddAccount = () => {
track('Settings:AddAccountButtonClicked')
store.session.clear() store.session.clear()
} }
const onPressSignout = () => { const onPressSignout = () => {
track('Settings:SignOutButtonClicked')
store.session.logout() store.session.logout()
} }
const onPressDeleteAccount = () => { const onPressDeleteAccount = () => {
+10 -1
View File
@@ -22,15 +22,19 @@ 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 {ToggleButton} from '../../com/util/forms/ToggleButton'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
import {useAnalytics} from '@segment/analytics-react-native'
export const Menu = observer(({onClose}: {onClose: () => void}) => { export const Menu = observer(({onClose}: {onClose: () => void}) => {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {track} = useAnalytics()
// events // events
// = // =
const onNavigate = (url: string) => { const onNavigate = (url: string) => {
track('Menu:ItemClicked', {url})
onClose() onClose()
if (url === '/notifications') { if (url === '/notifications') {
store.nav.switchTo(1, true) store.nav.switchTo(1, true)
@@ -84,6 +88,11 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
</TouchableOpacity> </TouchableOpacity>
) )
const onDarkmodePress = () => {
track('Menu:ItemClicked', {url: '/darkmode'})
store.shell.setDarkMode(!store.shell.darkMode)
}
return ( return (
<ScrollView testID="menuView" style={[styles.view, pal.view]}> <ScrollView testID="menuView" style={[styles.view, pal.view]}>
<TouchableOpacity <TouchableOpacity
@@ -161,7 +170,7 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
<ToggleButton <ToggleButton
label="Dark mode" label="Dark mode"
isSelected={store.shell.darkMode} isSelected={store.shell.darkMode}
onPress={() => store.shell.setDarkMode(!store.shell.darkMode)} onPress={onDarkmodePress}
/> />
</View> </View>
<View style={styles.footer}> <View style={styles.footer}>
+4
View File
@@ -45,6 +45,7 @@ import {
import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
import {useTheme} from '../../lib/ThemeContext' import {useTheme} from '../../lib/ThemeContext'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
import {useAnalytics} from '@segment/analytics-react-native'
const Btn = ({ const Btn = ({
icon, icon,
@@ -133,8 +134,10 @@ export const MobileShell: React.FC = observer(() => {
const colorScheme = useColorScheme() const colorScheme = useColorScheme()
const safeAreaInsets = useSafeAreaInsets() const safeAreaInsets = useSafeAreaInsets()
const screenRenderDesc = constructScreenRenderDesc(store.nav) const screenRenderDesc = constructScreenRenderDesc(store.nav)
const {track} = useAnalytics()
const onPressHome = () => { const onPressHome = () => {
track('MobileShell:HomeButtonPressed')
if (store.shell.isMainMenuOpen) { if (store.shell.isMainMenuOpen) {
store.shell.setMainMenuOpen(false) store.shell.setMainMenuOpen(false)
} }
@@ -152,6 +155,7 @@ export const MobileShell: React.FC = observer(() => {
} }
} }
const onPressNotifications = () => { const onPressNotifications = () => {
track('MobileShell:NotificationsButtonPressed')
if (store.shell.isMainMenuOpen) { if (store.shell.isMainMenuOpen) {
store.shell.setMainMenuOpen(false) store.shell.setMainMenuOpen(false)
} }