diff --git a/src/App.native.tsx b/src/App.native.tsx index 39988c5aed..750ea115d7 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -6,17 +6,14 @@ import {GestureHandlerRootView} from 'react-native-gesture-handler' import SplashScreen from 'react-native-splash-screen' import {SafeAreaProvider} from 'react-native-safe-area-context' import {observer} from 'mobx-react-lite' -import { - createClient, - SegmentClient, - AnalyticsProvider, -} from '@segment/analytics-react-native' +import {SegmentClient, AnalyticsProvider} from '@segment/analytics-react-native' import {ThemeProvider} from './view/lib/ThemeContext' import * as view from './view/index' import {RootStoreModel, setupState, RootStoreProvider} from './state' import {MobileShell} from './view/shell/mobile' import {s} from './view/lib/styles' import notifee, {EventType} from '@notifee/react-native' +import {segmentClient} from './lib/segmentClient' const App = observer(() => { const [rootStore, setRootStore] = useState( @@ -27,12 +24,7 @@ const App = observer(() => { // init useEffect(() => { view.setup() - setSegment( - createClient({ - writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI', - trackAppLifecycleEvents: true, - }), - ) + setSegment(segmentClient) setupState().then(store => { setRootStore(store) SplashScreen.hide() @@ -58,7 +50,6 @@ const App = observer(() => { if (!rootStore) { return null } - return ( diff --git a/src/lib/logger.js b/src/lib/logger.js new file mode 100644 index 0000000000..9be96e2408 --- /dev/null +++ b/src/lib/logger.js @@ -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) diff --git a/src/lib/segmentClient.ts b/src/lib/segmentClient.ts new file mode 100644 index 0000000000..4286f74ad1 --- /dev/null +++ b/src/lib/segmentClient.ts @@ -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(), +}) diff --git a/src/state/models/navigation.ts b/src/state/models/navigation.ts index 224ffef0d4..783593bede 100644 --- a/src/state/models/navigation.ts +++ b/src/state/models/navigation.ts @@ -1,5 +1,6 @@ import {makeAutoObservable} from 'mobx' import {TABS_ENABLED} from '../../build-flags' +import {segmentClient} from '../../lib/segmentClient' let __id = 0 function genId() { @@ -96,6 +97,13 @@ export class NavigationTabModel { // = navigate(url: string, title?: string) { + try { + const path = url.split('/')[1] + segmentClient.track('Navigation', { + path, + }) + } catch (error) {} + if (this.current?.url === url) { this.refresh() } else { diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx index 11ca9c0520..1647559cb4 100644 --- a/src/view/com/composer/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -68,7 +68,7 @@ export const ComposePost = observer(function ComposePost({ onPost?: ComposerOpts['onPost'] onClose: () => void }) { - const {track} = useAnalytics() + const {track, screen} = useAnalytics() const pal = usePalette('default') const store = useStores() const textInput = useRef(null) @@ -88,6 +88,10 @@ export const ComposePost = observer(function ComposePost({ ) const [selectedPhotos, setSelectedPhotos] = useState([]) + 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 const autocompleteView = React.useMemo( () => new UserAutocompleteViewModel(store), @@ -183,6 +187,7 @@ export const ComposePost = observer(function ComposePost({ textInput.current?.focus() } const onPressSelectPhotos = () => { + track('ComposePost:SelectPhotos') if (isSelectingPhotos) { setIsSelectingPhotos(false) } else if (selectedPhotos.length < 4) { @@ -190,6 +195,7 @@ export const ComposePost = observer(function ComposePost({ } } const onSelectPhotos = (photos: string[]) => { + track('ComposePost:SelectPhotos:Done') setSelectedPhotos(photos) if (photos.length >= 4) { setIsSelectingPhotos(false) diff --git a/src/view/com/composer/PhotoCarouselPicker.tsx b/src/view/com/composer/PhotoCarouselPicker.tsx index 7057330375..4bc275a522 100644 --- a/src/view/com/composer/PhotoCarouselPicker.tsx +++ b/src/view/com/composer/PhotoCarouselPicker.tsx @@ -19,6 +19,7 @@ import { import {compressIfNeeded, scaleDownDimensions} from '../../../lib/images' import {usePalette} from '../../lib/hooks/usePalette' import {useStores} from '../../../state' +import {useAnalytics} from '@segment/analytics-react-native' const MAX_WIDTH = 2000 const MAX_HEIGHT = 2000 @@ -75,6 +76,7 @@ export const PhotoCarouselPicker = ({ selectedPhotos: string[] onSelectPhotos: (v: string[]) => void }) => { + const {track} = useAnalytics() const pal = usePalette('default') const store = useStores() const [isSetup, setIsSetup] = React.useState(false) @@ -111,6 +113,7 @@ export const PhotoCarouselPicker = ({ const handleSelectPhoto = useCallback( async (item: PhotoIdentifier) => { + track('PhotoCarouselPicker:PhotoSelected') try { const imgPath = await cropPhoto( item.node.image.uri, @@ -123,10 +126,11 @@ export const PhotoCarouselPicker = ({ store.log.warn('Error selecting photo', err) } }, - [store.log, selectedPhotos, onSelectPhotos], + [track, onSelectPhotos, selectedPhotos, store.log], ) const handleOpenGallery = useCallback(async () => { + track('PhotoCarouselPicker:GalleryOpened') if (!(await requestPhotoAccessIfNeeded())) { return } @@ -156,7 +160,7 @@ export const PhotoCarouselPicker = ({ result.push(permanentPath) } onSelectPhotos([...selectedPhotos, ...result]) - }, [selectedPhotos, onSelectPhotos]) + }, [track, selectedPhotos, onSelectPhotos]) return ( void}) => { - const {track} = useAnalytics() + const {track, screen} = useAnalytics() const pal = usePalette('default') const store = useStores() const [isProcessing, setIsProcessing] = useState(false) @@ -46,6 +46,10 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { const [handle, setHandle] = useState('') const [is13, setIs13] = useState(false) + useEffect(() => { + screen('CreateAccount') + }, [screen]) + useEffect(() => { let aborted = false setError('') diff --git a/src/view/com/login/Signin.tsx b/src/view/com/login/Signin.tsx index c72bd31db4..0c1f0620ca 100644 --- a/src/view/com/login/Signin.tsx +++ b/src/view/com/login/Signin.tsx @@ -35,6 +35,7 @@ enum Forms { export const Signin = ({onPressBack}: {onPressBack: () => void}) => { const pal = usePalette('default') const store = useStores() + const {track} = useAnalytics() const [error, setError] = useState('') const [retryDescribeTrigger, setRetryDescribeTrigger] = useState({}) const [serviceUrl, setServiceUrl] = useState(DEFAULT_SERVICE) @@ -88,6 +89,10 @@ export const Signin = ({onPressBack}: {onPressBack: () => void}) => { }, [store.session, store.log, serviceUrl, retryDescribeTrigger]) const onPressRetryConnect = () => setRetryDescribeTrigger({}) + const onPressForgotPassword = () => { + track('Signin:PressedForgotPassword') + setCurrentForm(Forms.ForgotPassword) + } return ( @@ -101,7 +106,7 @@ export const Signin = ({onPressBack}: {onPressBack: () => void}) => { setError={setError} setServiceUrl={setServiceUrl} onPressBack={onPressBack} - onPressForgotPassword={gotoForm(Forms.ForgotPassword)} + onPressForgotPassword={onPressForgotPassword} onPressRetryConnect={onPressRetryConnect} /> ) : undefined} @@ -150,10 +155,14 @@ const ChooseAccountForm = ({ onSelectAccount: (account?: AccountData) => void onPressBack: () => void }) => { - const {track} = useAnalytics() + const {track, screen} = useAnalytics() const pal = usePalette('default') const [isProcessing, setIsProcessing] = React.useState(false) + React.useEffect(() => { + screen('Choose Account') + }, [screen]) + const onTryAccount = async (account: AccountData) => { if (account.accessJwt && account.refreshJwt) { setIsProcessing(true) @@ -267,6 +276,7 @@ const LoginForm = ({ const onPressSelectService = () => { store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl)) Keyboard.dismiss() + track('Signin:PressedSelectService') } const onPressNext = async () => { @@ -458,6 +468,11 @@ const ForgotPasswordForm = ({ const pal = usePalette('default') const [isProcessing, setIsProcessing] = useState(false) const [email, setEmail] = useState('') + const {screen} = useAnalytics() + + useEffect(() => { + screen('Signin:ForgotPassword') + }, [screen]) const onPressSelectService = () => { store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl)) @@ -594,6 +609,12 @@ const SetNewPasswordForm = ({ onPasswordSet: () => void }) => { const pal = usePalette('default') + const {screen} = useAnalytics() + + useEffect(() => { + screen('Signin:SetNewPasswordForm') + }, [screen]) + const [isProcessing, setIsProcessing] = useState(false) const [resetCode, setResetCode] = useState('') const [password, setPassword] = useState('') @@ -716,6 +737,12 @@ const SetNewPasswordForm = ({ } const PasswordUpdatedForm = ({onPressNext}: {onPressNext: () => void}) => { + const {screen} = useAnalytics() + + useEffect(() => { + screen('Signin:PasswordUpdatedForm') + }, [screen]) + const pal = usePalette('default') return ( <> diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx index 3ad0e2871f..15bf7115ea 100644 --- a/src/view/com/modals/EditProfile.tsx +++ b/src/view/com/modals/EditProfile.tsx @@ -24,6 +24,7 @@ import {compressIfNeeded} from '../../../lib/images' import {UserBanner} from '../util/UserBanner' import {UserAvatar} from '../util/UserAvatar' import {usePalette} from '../../lib/hooks/usePalette' +import {useAnalytics} from '@segment/analytics-react-native' export const snapPoints = ['80%'] @@ -37,6 +38,7 @@ export function Component({ const store = useStores() const [error, setError] = useState('') const pal = usePalette('default') + const {track} = useAnalytics() const [isProcessing, setProcessing] = useState(false) const [displayName, setDisplayName] = useState( @@ -57,6 +59,7 @@ export function Component({ store.shell.closeModal() } const onSelectNewAvatar = async (img: PickedImage) => { + track('EditProfile:AvatarSelected') try { const finalImg = await compressIfNeeded(img, 1000000) setNewUserAvatar(finalImg) @@ -66,6 +69,7 @@ export function Component({ } } const onSelectNewBanner = async (img: PickedImage) => { + track('EditProfile:BannerSelected') try { const finalImg = await compressIfNeeded(img, 1000000) setNewUserBanner(finalImg) @@ -75,6 +79,7 @@ export function Component({ } } const onPressSave = async () => { + track('EditProfile:Save') setProcessing(true) if (error) { setError('') diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 1d619fb572..611a539020 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -1,4 +1,4 @@ -import React, {MutableRefObject} from 'react' +import React, {MutableRefObject, useEffect} from 'react' import {observer} from 'mobx-react-lite' import { ActivityIndicator, @@ -16,6 +16,7 @@ import {FeedItem} from './FeedItem' import {PromptButtons} from './PromptButtons' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' import {s} from '../../lib/styles' +import {useAnalytics} from '@segment/analytics-react-native' const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -37,6 +38,12 @@ export const Feed = observer(function Feed({ onScroll?: OnScrollCb testID?: string }) { + const {screen, track} = useAnalytics() + + useEffect(() => { + screen('Feed') + }, [screen]) + // 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 // renderItem function renders components that follow React performance best practices @@ -57,6 +64,7 @@ export const Feed = observer(function Feed({ } } const onRefresh = () => { + track('Feed:onRefresh') feed .refresh() .catch(err => @@ -64,6 +72,7 @@ export const Feed = observer(function Feed({ ) } const onEndReached = () => { + track('Feed:onEndReached') feed .loadMore() .catch(err => feed.rootStore.log.error('Failed to load more posts', err)) diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 21eda1d8eb..4cb600b8cc 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -18,6 +18,7 @@ import {UserAvatar} from '../util/UserAvatar' import {s} from '../../lib/styles' import {useStores} from '../../../state' import {usePalette} from '../../lib/hooks/usePalette' +import {useAnalytics} from '@segment/analytics-react-native' export const FeedItem = observer(function ({ item, @@ -30,6 +31,7 @@ export const FeedItem = observer(function ({ }) { const store = useStores() const pal = usePalette('default') + const {track} = useAnalytics() const [deleted, setDeleted] = useState(false) const record = item.postRecord const itemHref = useMemo(() => { @@ -47,6 +49,7 @@ export const FeedItem = observer(function ({ }, [record?.reply]) const onPressReply = () => { + track('FeedItem:PostReply') store.shell.openComposer({ replyTo: { uri: item.post.uri, @@ -61,11 +64,13 @@ export const FeedItem = observer(function ({ }) } const onPressToggleRepost = () => { + track('FeedItem:PostRepost') return item .toggleRepost() .catch(e => store.log.error('Failed to toggle repost', e)) } const onPressToggleUpvote = () => { + track('FeedItem:PostLike') return item .toggleUpvote() .catch(e => store.log.error('Failed to toggle upvote', e)) @@ -75,6 +80,7 @@ export const FeedItem = observer(function ({ Toast.show('Copied to clipboard') } const onDeletePost = () => { + track('FeedItem:PostDelete') item.delete().then( () => { setDeleted(true) diff --git a/src/view/com/posts/PromptButtons.tsx b/src/view/com/posts/PromptButtons.tsx index 2a8e5808f5..b2bd1980b9 100644 --- a/src/view/com/posts/PromptButtons.tsx +++ b/src/view/com/posts/PromptButtons.tsx @@ -2,6 +2,7 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {Text} from '../util/text/Text' import {usePalette} from '../../lib/hooks/usePalette' +import {useAnalytics} from '@segment/analytics-react-native' export function PromptButtons({ onPressCompose, @@ -9,18 +10,29 @@ export function PromptButtons({ onPressCompose: (imagesOpen?: boolean) => void }) { const pal = usePalette('default') + const {track} = useAnalytics() + + const onPressNewPost = () => { + track('PromptButtons:NewPost') + onPressCompose(false) + } + + const onPressSharePhoto = () => { + track('PromptButtons:SharePhoto') + onPressCompose(true) + } return ( onPressCompose(false)} + onPress={onPressNewPost} style={[styles.btn, {backgroundColor: pal.colors.backgroundLight}]}> New post onPressCompose(true)} + onPress={onPressSharePhoto} style={[styles.btn, {backgroundColor: pal.colors.backgroundLight}]}> Share photo diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 77631397c2..60f7fa4747 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -27,6 +27,7 @@ import {RichText} from '../util/text/RichText' import {UserAvatar} from '../util/UserAvatar' import {UserBanner} from '../util/UserBanner' import {usePalette} from '../../lib/hooks/usePalette' +import {useAnalytics} from '@segment/analytics-react-native' export const ProfileHeader = observer(function ProfileHeader({ view, @@ -37,7 +38,7 @@ export const ProfileHeader = observer(function ProfileHeader({ }) { const pal = usePalette('default') const store = useStores() - + const {track} = useAnalytics() const onPressBack = () => { store.nav.tab.goBack() } @@ -59,18 +60,23 @@ export const ProfileHeader = observer(function ProfileHeader({ ) } const onPressEditProfile = () => { + track('ProfileHeader:EditProfileButtonClicked') store.shell.openModal(new EditProfileModal(view, onRefreshAll)) } const onPressFollowers = () => { + track('ProfileHeader:FollowersButtonClicked') store.nav.navigate(`/profile/${view.handle}/followers`) } const onPressFollows = () => { + track('ProfileHeader:FollowsButtonClicked') store.nav.navigate(`/profile/${view.handle}/follows`) } const onPressShare = () => { + track('ProfileHeader:ShareButtonClicked') Share.share({url: toShareUrl(`/profile/${view.handle}`)}) } const onPressMuteAccount = async () => { + track('ProfileHeader:MuteAccountButtonClicked') try { await view.muteAccount() Toast.show('Account muted') @@ -80,6 +86,7 @@ export const ProfileHeader = observer(function ProfileHeader({ } } const onPressUnmuteAccount = async () => { + track('ProfileHeader:UnmuteAccountButtonClicked') try { await view.unmuteAccount() Toast.show('Account unmuted') @@ -89,6 +96,7 @@ export const ProfileHeader = observer(function ProfileHeader({ } } const onPressReportAccount = () => { + track('ProfileHeader:ReportAccountButtonClicked') store.shell.openModal(new ReportAccountModal(view.did)) } diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index c8b1b2d97e..62745827c7 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -13,6 +13,7 @@ import {MagnifyingGlassIcon} from '../../lib/icons' import {useStores} from '../../../state' import {usePalette} from '../../lib/hooks/usePalette' import {colors} from '../../lib/styles' +import {useAnalytics} from '@segment/analytics-react-native' const HITSLOP = {left: 10, top: 10, right: 10, 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 store = useStores() + const {track} = useAnalytics() const onPressBack = () => { store.nav.tab.goBack() } const onPressMenu = () => { + track('ViewHeader:MenuButtonClicked') store.shell.setMainMenuOpen(true) } const onPressSearch = () => { diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index ff6bd881e3..e549c21142 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -13,6 +13,7 @@ import {ScreenParams} from '../routes' import {s, colors, gradients} from '../lib/styles' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' import {clamp} from 'lodash' +import {useAnalytics} from '@segment/analytics-react-native' const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20} @@ -23,6 +24,7 @@ export const Home = observer(function Home({ }: ScreenParams) { const store = useStores() const onMainScroll = useOnMainScroll(store) + const {track} = useAnalytics() const safeAreaInsets = useSafeAreaInsets() const [wasVisible, setWasVisible] = React.useState(false) const {appState} = useAppState({ @@ -72,6 +74,7 @@ export const Home = observer(function Home({ }, [visible, store, store.me.mainFeed, navIdx, doPoll, wasVisible]) const onPressCompose = (imagesOpen?: boolean) => { + track('Home:ComposeButtonPressed') store.shell.openComposer({imagesOpen}) } const onPressTryAgain = () => { diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx index 136ffb967f..2d204e408b 100644 --- a/src/view/screens/Login.tsx +++ b/src/view/screens/Login.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import React, {useEffect, useState} from 'react' import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' import FastImage, {Source as FISource} from 'react-native-fast-image' import {observer} from 'mobx-react-lite' @@ -9,6 +9,7 @@ import {ErrorBoundary} from '../com/util/ErrorBoundary' import {colors} from '../lib/styles' import {usePalette} from '../lib/hooks/usePalette' import {CLOUD_SPLASH} from '../lib/assets' +import {useAnalytics} from '@segment/analytics-react-native' enum ScreenState { S_SigninOrCreateAccount, @@ -23,6 +24,12 @@ const SigninOrCreateAccount = ({ onPressSignin: () => void onPressCreateAccount: () => void }) => { + const {screen} = useAnalytics() + + useEffect(() => { + screen('Login') + }, [screen]) + const pal = usePalette('default') return ( <> diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx index 9b5dc5970b..eade8f6dc3 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -6,10 +6,16 @@ import {useStores} from '../../state' import {ScreenParams} from '../routes' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' import {s} from '../lib/styles' +import {useAnalytics} from '@segment/analytics-react-native' export const Notifications = ({navIdx, visible}: ScreenParams) => { const store = useStores() const onMainScroll = useOnMainScroll(store) + const {screen} = useAnalytics() + + useEffect(() => { + screen('Notifications') + }, [screen]) useEffect(() => { if (!visible) { diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 9eac521fed..effdf8bc43 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -15,6 +15,7 @@ import {Text} from '../com/util/text/Text' import {FAB} from '../com/util/FAB' import {s, colors} from '../lib/styles' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' +import {useAnalytics} from '@segment/analytics-react-native' const LOADING_ITEM = {_reactKey: '__loading__'} const END_ITEM = {_reactKey: '__end__'} @@ -22,6 +23,12 @@ const EMPTY_ITEM = {_reactKey: '__empty__'} export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { const store = useStores() + const {screen} = useAnalytics() + + useEffect(() => { + screen('Profile') + }, [screen]) + const onMainScroll = useOnMainScroll(store) const [hasSetup, setHasSetup] = useState(false) const uiState = React.useMemo( diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index d874184ba5..7c8488f901 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -18,6 +18,7 @@ import * as Toast from '../com/util/Toast' import {UserAvatar} from '../com/util/UserAvatar' import {usePalette} from '../lib/hooks/usePalette' import {AccountData} from '../../state/models/session' +import {useAnalytics} from '@segment/analytics-react-native' import {DeleteAccountModal} from '../../state/models/shell-ui' export const Settings = observer(function Settings({ @@ -26,8 +27,13 @@ export const Settings = observer(function Settings({ }: ScreenParams) { const pal = usePalette('default') const store = useStores() + const {screen, track} = useAnalytics() const [isSwitching, setIsSwitching] = React.useState(false) + useEffect(() => { + screen('Settings') + }, [screen]) + useEffect(() => { if (!visible) { return @@ -37,6 +43,7 @@ export const Settings = observer(function Settings({ }, [visible, store, navIdx]) const onPressSwitchAccount = async (acct: AccountData) => { + track('Settings:SwitchAccountButtonClicked') setIsSwitching(true) if (await store.session.resumeSession(acct)) { setIsSwitching(false) @@ -50,9 +57,11 @@ export const Settings = observer(function Settings({ store.session.clear() } const onPressAddAccount = () => { + track('Settings:AddAccountButtonClicked') store.session.clear() } const onPressSignout = () => { + track('Settings:SignOutButtonClicked') store.session.logout() } const onPressDeleteAccount = () => { diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx index a7d3e2142c..cdea6dcfa1 100644 --- a/src/view/shell/mobile/Menu.tsx +++ b/src/view/shell/mobile/Menu.tsx @@ -22,15 +22,19 @@ import {UserAvatar} from '../../com/util/UserAvatar' import {Text} from '../../com/util/text/Text' import {ToggleButton} from '../../com/util/forms/ToggleButton' import {usePalette} from '../../lib/hooks/usePalette' +import {useAnalytics} from '@segment/analytics-react-native' export const Menu = observer(({onClose}: {onClose: () => void}) => { const pal = usePalette('default') const store = useStores() + const {track} = useAnalytics() // events // = const onNavigate = (url: string) => { + track('Menu:ItemClicked', {url}) + onClose() if (url === '/notifications') { store.nav.switchTo(1, true) @@ -84,6 +88,11 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => { ) + const onDarkmodePress = () => { + track('Menu:ItemClicked', {url: '/darkmode'}) + store.shell.setDarkMode(!store.shell.darkMode) + } + return ( void}) => { store.shell.setDarkMode(!store.shell.darkMode)} + onPress={onDarkmodePress} /> diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index 95fc826072..97028b029b 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -45,6 +45,7 @@ import { import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' import {useTheme} from '../../lib/ThemeContext' import {usePalette} from '../../lib/hooks/usePalette' +import {useAnalytics} from '@segment/analytics-react-native' const Btn = ({ icon, @@ -133,8 +134,10 @@ export const MobileShell: React.FC = observer(() => { const colorScheme = useColorScheme() const safeAreaInsets = useSafeAreaInsets() const screenRenderDesc = constructScreenRenderDesc(store.nav) + const {track} = useAnalytics() const onPressHome = () => { + track('MobileShell:HomeButtonPressed') if (store.shell.isMainMenuOpen) { store.shell.setMainMenuOpen(false) } @@ -152,6 +155,7 @@ export const MobileShell: React.FC = observer(() => { } } const onPressNotifications = () => { + track('MobileShell:NotificationsButtonPressed') if (store.shell.isMainMenuOpen) { store.shell.setMainMenuOpen(false) }