SP state cleanup (#4569)
This commit is contained in:
+136
-58
@@ -4,7 +4,7 @@ import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
|
|||||||
import {
|
import {
|
||||||
createStarterPackGooglePlayUri,
|
createStarterPackGooglePlayUri,
|
||||||
createStarterPackLinkFromAndroidReferrer,
|
createStarterPackLinkFromAndroidReferrer,
|
||||||
parseStarterPackHttpUri,
|
parseStarterPackUri,
|
||||||
} from 'lib/strings/starter-pack'
|
} from 'lib/strings/starter-pack'
|
||||||
import {cleanError} from '../../src/lib/strings/errors'
|
import {cleanError} from '../../src/lib/strings/errors'
|
||||||
import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles'
|
import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles'
|
||||||
@@ -803,62 +803,129 @@ describe('parseEmbedPlayerFromUrl', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('createStarterPackLinkFromAndroidReferrer', () => {
|
describe('createStarterPackLinkFromAndroidReferrer', () => {
|
||||||
const inputs = [
|
const validOutput = 'https://bsky.app/start/haileyok.com/rkey'
|
||||||
'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack-haileyok.com-rkey',
|
|
||||||
'utm_source=bluesky&utm_content=starterpack-haileyok.com-rkey&utm_medium=starterpack',
|
|
||||||
'utm_source=bluesky&utm_content=starterpack-haileyok.com-rkey',
|
|
||||||
'utm_content=starterpack-haileyok.com-rkey',
|
|
||||||
'utm_source=redsea&utm_content=starterpack-haileyok.com-rkey',
|
|
||||||
'utm_source=bluesky&utm_content=starterpack-haileyok.com',
|
|
||||||
'utm_source=bluesky&utm_content=starterpack',
|
|
||||||
'utm_source=bluesky&utm_content=nope',
|
|
||||||
'utm_source=bluesky',
|
|
||||||
'utm_content=starterpack-haileyok.com-rkey',
|
|
||||||
]
|
|
||||||
const outputs = [
|
|
||||||
'https://bsky.app/start/haileyok.com/rkey',
|
|
||||||
'https://bsky.app/start/haileyok.com/rkey',
|
|
||||||
'https://bsky.app/start/haileyok.com/rkey',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
]
|
|
||||||
|
|
||||||
it('returns a starter pack link when input is valid', () => {
|
it('returns a link when input contains utm_source and utm_content', () => {
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
expect(
|
||||||
const result = createStarterPackLinkFromAndroidReferrer(inputs[i])
|
createStarterPackLinkFromAndroidReferrer(
|
||||||
expect(result).toEqual(outputs[i])
|
'utm_source=bluesky&utm_content=starterpack-haileyok.com-rkey',
|
||||||
}
|
),
|
||||||
|
).toEqual(validOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns a link when input contains utm_source and utm_content in different order', () => {
|
||||||
|
expect(
|
||||||
|
createStarterPackLinkFromAndroidReferrer(
|
||||||
|
'utm_content=starterpack-haileyok.com-rkey&utm_source=bluesky',
|
||||||
|
),
|
||||||
|
).toEqual(validOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns a link when input contains other parameters as well', () => {
|
||||||
|
expect(
|
||||||
|
createStarterPackLinkFromAndroidReferrer(
|
||||||
|
'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack-haileyok.com-rkey',
|
||||||
|
),
|
||||||
|
).toEqual(validOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when utm_source is not present', () => {
|
||||||
|
expect(
|
||||||
|
createStarterPackLinkFromAndroidReferrer(
|
||||||
|
'utm_content=starterpack-haileyok.com-rkey',
|
||||||
|
),
|
||||||
|
).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when utm_content is not present', () => {
|
||||||
|
expect(
|
||||||
|
createStarterPackLinkFromAndroidReferrer('utm_source=bluesky'),
|
||||||
|
).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when utm_content is malformed', () => {
|
||||||
|
expect(
|
||||||
|
createStarterPackLinkFromAndroidReferrer(
|
||||||
|
'utm_content=starterpack-haileyok.com',
|
||||||
|
),
|
||||||
|
).toEqual(null)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
createStarterPackLinkFromAndroidReferrer('utm_content=starterpack'),
|
||||||
|
).toEqual(null)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
createStarterPackLinkFromAndroidReferrer(
|
||||||
|
'utm_content=starterpack-haileyok.com-rkey-more',
|
||||||
|
),
|
||||||
|
).toEqual(null)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
createStarterPackLinkFromAndroidReferrer(
|
||||||
|
'utm_content=notastarterpack-haileyok.com-rkey',
|
||||||
|
),
|
||||||
|
).toEqual(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('parseStarterPackHttpUri', () => {
|
describe('parseStarterPackHttpUri', () => {
|
||||||
const inputs = [
|
const baseUri = 'https://bsky.app/start'
|
||||||
'https://bsky.app/start/haileyok.com/rkey',
|
|
||||||
'https://bsky.app/start/haileyok.com/ilovetesting',
|
|
||||||
'https://bsky.app/start/testlover9000.com/rkey',
|
|
||||||
'https://bsky.app/start/testlover9000.com',
|
|
||||||
'https://bsky.app/start/testlover9000.com/rkey/other',
|
|
||||||
'https://bsky.app/start',
|
|
||||||
]
|
|
||||||
const outputs = [
|
|
||||||
{name: 'haileyok.com', rkey: 'rkey'},
|
|
||||||
{name: 'haileyok.com', rkey: 'ilovetesting'},
|
|
||||||
{name: 'testlover9000.com', rkey: 'rkey'},
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
]
|
|
||||||
|
|
||||||
it('returns the correct name and rkey when input is valid', () => {
|
it('returns a valid at uri when http uri is valid', () => {
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
const validHttpUri = `${baseUri}/haileyok.com/rkey`
|
||||||
const result = parseStarterPackHttpUri(inputs[i])
|
expect(parseStarterPackUri(validHttpUri)).toEqual({
|
||||||
expect(result).toEqual(outputs[i])
|
name: 'haileyok.com',
|
||||||
}
|
rkey: 'rkey',
|
||||||
|
})
|
||||||
|
|
||||||
|
const validHttpUri2 = `${baseUri}/haileyok.com/ilovetesting`
|
||||||
|
expect(parseStarterPackUri(validHttpUri2)).toEqual({
|
||||||
|
name: 'haileyok.com',
|
||||||
|
rkey: 'ilovetesting',
|
||||||
|
})
|
||||||
|
|
||||||
|
const validHttpUri3 = `${baseUri}/testlover9000.com/rkey`
|
||||||
|
expect(parseStarterPackUri(validHttpUri3)).toEqual({
|
||||||
|
name: 'testlover9000.com',
|
||||||
|
rkey: 'rkey',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when there is no rkey', () => {
|
||||||
|
const validHttpUri = `${baseUri}/haileyok.com`
|
||||||
|
expect(parseStarterPackUri(validHttpUri)).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when there is an extra path', () => {
|
||||||
|
const validHttpUri = `${baseUri}/haileyok.com/rkey/other`
|
||||||
|
expect(parseStarterPackUri(validHttpUri)).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when there is no handle or rkey', () => {
|
||||||
|
const validHttpUri = `${baseUri}`
|
||||||
|
expect(parseStarterPackUri(validHttpUri)).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the at uri when the input is a valid starterpack at uri', () => {
|
||||||
|
const validAtUri = 'at://did:123/app.bsky.graph.starterpack/rkey'
|
||||||
|
expect(parseStarterPackUri(validAtUri)).toEqual({
|
||||||
|
name: 'did:123',
|
||||||
|
rkey: 'rkey',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when the at uri has no rkey', () => {
|
||||||
|
const validAtUri = 'at://did:123/app.bsky.graph.starterpack'
|
||||||
|
expect(parseStarterPackUri(validAtUri)).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when the collection is not app.bsky.graph.starterpack', () => {
|
||||||
|
const validAtUri = 'at://did:123/app.bsky.graph.list/rkey'
|
||||||
|
expect(parseStarterPackUri(validAtUri)).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when the input is undefined', () => {
|
||||||
|
expect(parseStarterPackUri(undefined)).toEqual(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -866,13 +933,24 @@ describe('createStarterPackGooglePlayUri', () => {
|
|||||||
const base =
|
const base =
|
||||||
'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack-'
|
'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack-'
|
||||||
|
|
||||||
const inputs = [['name', 'rkey'], ['name'], []]
|
|
||||||
const outputs = [base + 'name-rkey', null, null]
|
|
||||||
|
|
||||||
it('returns valid google play uri when input is valid', () => {
|
it('returns valid google play uri when input is valid', () => {
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
expect(createStarterPackGooglePlayUri('name', 'rkey')).toEqual(
|
||||||
const result = createStarterPackGooglePlayUri(inputs[i][0], inputs[i][1])
|
`${base}name-rkey`,
|
||||||
expect(result).toEqual(outputs[i])
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
|
it('returns null when no rkey is supplied', () => {
|
||||||
|
// @ts-expect-error test
|
||||||
|
expect(createStarterPackGooglePlayUri('name', undefined)).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when no name or rkey are supplied', () => {
|
||||||
|
// @ts-expect-error test
|
||||||
|
expect(createStarterPackGooglePlayUri(undefined, undefined)).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when rkey is supplied but no name', () => {
|
||||||
|
// @ts-expect-error test
|
||||||
|
expect(createStarterPackGooglePlayUri(undefined, 'rkey')).toEqual(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,21 +2,31 @@ import React from 'react'
|
|||||||
|
|
||||||
import {createStarterPackLinkFromAndroidReferrer} from 'lib/strings/starter-pack'
|
import {createStarterPackLinkFromAndroidReferrer} from 'lib/strings/starter-pack'
|
||||||
import {isAndroid} from 'platform/detection'
|
import {isAndroid} from 'platform/detection'
|
||||||
import {useSetCurrentStarterPack} from 'state/preferences/starter-pack'
|
import {useHasCheckedForStarterPack} from 'state/preferences/used-starter-packs'
|
||||||
import {useUsedStarterPacks} from 'state/preferences/used-starter-packs'
|
import {useSetActiveStarterPack} from 'state/shell/starter-pack'
|
||||||
import SwissArmyKnife from '../../../modules/expo-bluesky-swiss-army'
|
import SwissArmyKnife from '../../../modules/expo-bluesky-swiss-army'
|
||||||
import GooglePlayReferrer from '../../../modules/expo-google-play-referrer'
|
import GooglePlayReferrer from '../../../modules/expo-google-play-referrer'
|
||||||
|
|
||||||
export function useStarterPackEntry() {
|
export function useStarterPackEntry() {
|
||||||
const [ready, setReady] = React.useState(false)
|
const [ready, setReady] = React.useState(false)
|
||||||
const setCurrentStarterPack = useSetCurrentStarterPack()
|
const setActiveStarterPack = useSetActiveStarterPack()
|
||||||
const usedStarterPacks = useUsedStarterPacks()
|
const hasCheckedForStarterPack = useHasCheckedForStarterPack()
|
||||||
const hasRan = React.useRef(false)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (ready || hasRan.current) return
|
if (ready) return
|
||||||
|
|
||||||
|
// On Android, we cannot clear the referral link. It gets stored for 90 days and all we can do is query for it. So,
|
||||||
|
// let's just ensure we never check again after the first time.
|
||||||
|
if (hasCheckedForStarterPack) {
|
||||||
|
setReady(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety for Android. Very unlike this could happen, but just in case. The response should be nearly immediate
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setReady(true)
|
||||||
|
}, 500)
|
||||||
|
|
||||||
hasRan.current = true
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
let uri: string | null | undefined
|
let uri: string | null | undefined
|
||||||
|
|
||||||
@@ -28,17 +38,22 @@ export function useStarterPackEntry() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uri = await SwissArmyKnife.getStringValueAsync('starterPackUri', true)
|
uri = await SwissArmyKnife.getStringValueAsync('starterPackUri', true)
|
||||||
|
SwissArmyKnife.setStringValueAsync('starterPackUri', null, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri && !usedStarterPacks?.includes(uri)) {
|
if (uri) {
|
||||||
setCurrentStarterPack({
|
setActiveStarterPack({
|
||||||
uri,
|
uri,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setReady(true)
|
setReady(true)
|
||||||
})()
|
})()
|
||||||
}, [ready, setCurrentStarterPack, usedStarterPacks])
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
}, [ready, setActiveStarterPack, hasCheckedForStarterPack])
|
||||||
|
|
||||||
return ready
|
return ready
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {parseStarterPackHttpUri} from 'lib/strings/starter-pack'
|
import {httpStarterPackUriToAtUri} from 'lib/strings/starter-pack'
|
||||||
import {useSetCurrentStarterPack} from 'state/preferences/starter-pack'
|
import {useSetActiveStarterPack} from 'state/shell/starter-pack'
|
||||||
|
|
||||||
export function useStarterPackEntry() {
|
export function useStarterPackEntry() {
|
||||||
const setCurrentStarterPack = useSetCurrentStarterPack()
|
const setActiveStarterPack = useSetActiveStarterPack()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const href = window.location.href
|
const href = window.location.href
|
||||||
const parsed = parseStarterPackHttpUri(href)
|
const atUri = httpStarterPackUriToAtUri(href)
|
||||||
|
|
||||||
if (parsed) {
|
if (atUri) {
|
||||||
const url = new URL(href)
|
const url = new URL(href)
|
||||||
|
// Determines if an App Clip is loading this landing page
|
||||||
const isClip = url.searchParams.get('clip') === 'true'
|
const isClip = url.searchParams.get('clip') === 'true'
|
||||||
|
setActiveStarterPack({
|
||||||
setCurrentStarterPack({
|
uri: atUri,
|
||||||
uri: href,
|
|
||||||
isClip,
|
isClip,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [setCurrentStarterPack])
|
}, [setActiveStarterPack])
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import {AtUri} from '@atproto/api'
|
||||||
|
|
||||||
import {makeStarterPackLink} from 'lib/routes/links'
|
import {makeStarterPackLink} from 'lib/routes/links'
|
||||||
|
|
||||||
export function createStarterPackLinkFromAndroidReferrer(
|
export function createStarterPackLinkFromAndroidReferrer(
|
||||||
@@ -24,21 +26,35 @@ export function createStarterPackLinkFromAndroidReferrer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseStarterPackHttpUri(uri: string): {
|
export function parseStarterPackUri(uri?: string): {
|
||||||
name?: string
|
name: string
|
||||||
rkey?: string
|
rkey: string
|
||||||
} | null {
|
} | null {
|
||||||
try {
|
if (!uri) return null
|
||||||
const url = new URL(uri)
|
|
||||||
const parts = url.pathname.split('/')
|
|
||||||
const name = parts[2]
|
|
||||||
const rkey = parts[3]
|
|
||||||
|
|
||||||
if (parts.length !== 4) return null
|
try {
|
||||||
if (!name || !rkey) return null
|
if (uri.startsWith('at://')) {
|
||||||
return {
|
const atUri = new AtUri(uri)
|
||||||
name,
|
if (atUri.collection !== 'app.bsky.graph.starterpack') return null
|
||||||
rkey,
|
if (atUri.rkey) {
|
||||||
|
return {
|
||||||
|
name: atUri.hostname,
|
||||||
|
rkey: atUri.rkey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
const url = new URL(uri)
|
||||||
|
const parts = url.pathname.split('/')
|
||||||
|
const name = parts[2]
|
||||||
|
const rkey = parts[3]
|
||||||
|
|
||||||
|
if (parts.length !== 4) return null
|
||||||
|
if (!name || !rkey) return null
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
rkey,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null
|
return null
|
||||||
@@ -52,3 +68,14 @@ export function createStarterPackGooglePlayUri(
|
|||||||
if (!name || !rkey) return null
|
if (!name || !rkey) return null
|
||||||
return `https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack-${name}-${rkey}`
|
return `https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack-${name}-${rkey}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function httpStarterPackUriToAtUri(httpUri?: string): string | null {
|
||||||
|
if (!httpUri) return null
|
||||||
|
|
||||||
|
const parsed = parseStarterPackUri(httpUri)
|
||||||
|
if (!parsed) return null
|
||||||
|
|
||||||
|
if (httpUri.startsWith('at://')) return httpUri
|
||||||
|
|
||||||
|
return `at://${parsed.name}/app.bsky.graph.starterpack/${parsed.rkey}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {logger} from '#/logger'
|
|||||||
import {useSessionApi} from '#/state/session'
|
import {useSessionApi} from '#/state/session'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
|
import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
|
||||||
|
import {useSetHasCheckedForStarterPack} from 'state/preferences/used-starter-packs'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {FormError} from '#/components/forms/FormError'
|
import {FormError} from '#/components/forms/FormError'
|
||||||
@@ -69,6 +70,7 @@ export const LoginForm = ({
|
|||||||
const {login} = useSessionApi()
|
const {login} = useSessionApi()
|
||||||
const requestNotificationsPermission = useRequestNotificationsPermission()
|
const requestNotificationsPermission = useRequestNotificationsPermission()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack()
|
||||||
|
|
||||||
const onPressSelectService = React.useCallback(() => {
|
const onPressSelectService = React.useCallback(() => {
|
||||||
Keyboard.dismiss()
|
Keyboard.dismiss()
|
||||||
@@ -116,6 +118,7 @@ export const LoginForm = ({
|
|||||||
'LoginForm',
|
'LoginForm',
|
||||||
)
|
)
|
||||||
setShowLoggedOut(false)
|
setShowLoggedOut(false)
|
||||||
|
setHasCheckedForStarterPack(true)
|
||||||
requestNotificationsPermission('Login')
|
requestNotificationsPermission('Login')
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const errMsg = e.toString()
|
const errMsg = e.toString()
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import {useAgent} from '#/state/session'
|
|||||||
import {useOnboardingDispatch} from '#/state/shell'
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
import {uploadBlob} from 'lib/api'
|
import {uploadBlob} from 'lib/api'
|
||||||
import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
|
import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
|
||||||
import {makeStarterPackLink} from 'lib/routes/links'
|
import {useSetHasCheckedForStarterPack} from 'state/preferences/used-starter-packs'
|
||||||
|
import {useSetSelectedFeed} from 'state/shell/selected-feed'
|
||||||
import {
|
import {
|
||||||
useCurrentStarterPack,
|
useActiveStarterPack,
|
||||||
useSetCurrentStarterPack,
|
useSetActiveStarterPack,
|
||||||
} from 'state/preferences/starter-pack'
|
} from 'state/shell/starter-pack'
|
||||||
import {useAddUsedStarterPack} from 'state/preferences/used-starter-packs'
|
|
||||||
import {
|
import {
|
||||||
DescriptionText,
|
DescriptionText,
|
||||||
OnboardingControls,
|
OnboardingControls,
|
||||||
@@ -48,18 +48,20 @@ export function StepFinished() {
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const requestNotificationsPermission = useRequestNotificationsPermission()
|
const requestNotificationsPermission = useRequestNotificationsPermission()
|
||||||
const currentStarterPack = useCurrentStarterPack()
|
const activeStarterPack = useActiveStarterPack()
|
||||||
const setCurrentStarterPack = useSetCurrentStarterPack()
|
const setActiveStarterPack = useSetActiveStarterPack()
|
||||||
const addUsedStarterPack = useAddUsedStarterPack()
|
const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack()
|
||||||
|
const setSelectedFeed = useSetSelectedFeed()
|
||||||
|
|
||||||
const finishOnboarding = React.useCallback(async () => {
|
const finishOnboarding = React.useCallback(async () => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
let starterPack: AppBskyGraphDefs.StarterPackView | undefined
|
let starterPack: AppBskyGraphDefs.StarterPackView | undefined
|
||||||
let listItems: AppBskyGraphDefs.ListItemView[] | undefined
|
let listItems: AppBskyGraphDefs.ListItemView[] | undefined
|
||||||
if (currentStarterPack) {
|
|
||||||
|
if (activeStarterPack?.uri) {
|
||||||
const spRes = await agent.app.bsky.graph.getStarterPack({
|
const spRes = await agent.app.bsky.graph.getStarterPack({
|
||||||
starterPack: currentStarterPack.uri,
|
starterPack: activeStarterPack.uri,
|
||||||
})
|
})
|
||||||
starterPack = spRes.data.starterPack
|
starterPack = spRes.data.starterPack
|
||||||
|
|
||||||
@@ -92,17 +94,11 @@ export function StepFinished() {
|
|||||||
pinned: true,
|
pinned: true,
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
setCurrentStarterPack({
|
setSelectedFeed(`feedgen|${starterPack.feeds[0].uri}`)
|
||||||
uri: '',
|
|
||||||
initialFeed: starterPack.feeds?.[0].uri,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
setCurrentStarterPack({
|
setSelectedFeed('following')
|
||||||
uri: '',
|
|
||||||
initialFeed: 'following',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
addUsedStarterPack(makeStarterPackLink(starterPack))
|
setActiveStarterPack(undefined)
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -150,7 +146,7 @@ export function StepFinished() {
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
// If there was an error encountered, we need to just clear the starter pack so we don't break things for subsequent
|
// If there was an error encountered, we need to just clear the starter pack so we don't break things for subsequent
|
||||||
// app restarts
|
// app restarts
|
||||||
setCurrentStarterPack(undefined)
|
setActiveStarterPack(undefined)
|
||||||
// don't alert the user, just let them into their account
|
// don't alert the user, just let them into their account
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +164,7 @@ export function StepFinished() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
|
setHasCheckedForStarterPack(true)
|
||||||
dispatch({type: 'finish'})
|
dispatch({type: 'finish'})
|
||||||
onboardDispatch({type: 'finish'})
|
onboardDispatch({type: 'finish'})
|
||||||
track('OnboardingV2:StepFinished:End')
|
track('OnboardingV2:StepFinished:End')
|
||||||
@@ -179,11 +176,12 @@ export function StepFinished() {
|
|||||||
dispatch,
|
dispatch,
|
||||||
onboardDispatch,
|
onboardDispatch,
|
||||||
track,
|
track,
|
||||||
currentStarterPack,
|
activeStarterPack,
|
||||||
state,
|
state,
|
||||||
requestNotificationsPermission,
|
requestNotificationsPermission,
|
||||||
addUsedStarterPack,
|
setActiveStarterPack,
|
||||||
setCurrentStarterPack,
|
setHasCheckedForStarterPack,
|
||||||
|
setSelectedFeed,
|
||||||
])
|
])
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -12,18 +12,14 @@ import {useLingui} from '@lingui/react'
|
|||||||
|
|
||||||
import {isAndroidWeb} from 'lib/browser'
|
import {isAndroidWeb} from 'lib/browser'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {
|
import {createStarterPackGooglePlayUri} from 'lib/strings/starter-pack'
|
||||||
createStarterPackGooglePlayUri,
|
|
||||||
parseStarterPackHttpUri,
|
|
||||||
} from 'lib/strings/starter-pack'
|
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {useModerationOpts} from 'state/preferences/moderation-opts'
|
import {useModerationOpts} from 'state/preferences/moderation-opts'
|
||||||
import {
|
|
||||||
useCurrentStarterPack,
|
|
||||||
useSetCurrentStarterPack,
|
|
||||||
} from 'state/preferences/starter-pack'
|
|
||||||
import {useResolveDidQuery} from 'state/queries/resolve-uri'
|
|
||||||
import {useStarterPackQuery} from 'state/queries/useStarterPackQuery'
|
import {useStarterPackQuery} from 'state/queries/useStarterPackQuery'
|
||||||
|
import {
|
||||||
|
useActiveStarterPack,
|
||||||
|
useSetActiveStarterPack,
|
||||||
|
} from 'state/shell/starter-pack'
|
||||||
import {LoggedOutScreenState} from 'view/com/auth/LoggedOut'
|
import {LoggedOutScreenState} from 'view/com/auth/LoggedOut'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
import {Logo} from 'view/icons/Logo'
|
import {Logo} from 'view/icons/Logo'
|
||||||
@@ -54,43 +50,15 @@ export function LandingScreen({
|
|||||||
setScreenState,
|
setScreenState,
|
||||||
}: {
|
}: {
|
||||||
setScreenState: (state: LoggedOutScreenState) => void
|
setScreenState: (state: LoggedOutScreenState) => void
|
||||||
}) {
|
|
||||||
const currentStarterPack = useCurrentStarterPack()
|
|
||||||
const parsed = parseStarterPackHttpUri(currentStarterPack?.uri || '')
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!parsed) {
|
|
||||||
setScreenState(LoggedOutScreenState.S_LoginOrCreateAccount)
|
|
||||||
}
|
|
||||||
}, [parsed, setScreenState])
|
|
||||||
|
|
||||||
if (!parsed) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return <LandingScreenInner setScreenState={setScreenState} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LandingScreenInner({
|
|
||||||
setScreenState,
|
|
||||||
}: {
|
|
||||||
setScreenState: (state: LoggedOutScreenState) => void
|
|
||||||
}) {
|
}) {
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const currentStarterPack = useCurrentStarterPack()
|
const activeStarterPack = useActiveStarterPack()
|
||||||
const {name, rkey} =
|
|
||||||
parseStarterPackHttpUri(currentStarterPack?.uri || '') ?? {}
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: did,
|
|
||||||
isLoading: isLoadingDid,
|
|
||||||
isError: isErrorDid,
|
|
||||||
} = useResolveDidQuery(name)
|
|
||||||
const {
|
const {
|
||||||
data: starterPack,
|
data: starterPack,
|
||||||
isLoading: isLoadingStarterPack,
|
isLoading: isLoadingStarterPack,
|
||||||
isError: isErrorStarterPack,
|
isError: isErrorStarterPack,
|
||||||
} = useStarterPackQuery({did, rkey})
|
} = useStarterPackQuery({uri: activeStarterPack?.uri})
|
||||||
|
|
||||||
const isValid =
|
const isValid =
|
||||||
starterPack &&
|
starterPack &&
|
||||||
@@ -98,17 +66,15 @@ export function LandingScreenInner({
|
|||||||
AppBskyGraphStarterpack.validateRecord(starterPack.record)
|
AppBskyGraphStarterpack.validateRecord(starterPack.record)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isErrorDid || isErrorStarterPack || (starterPack && !isValid)) {
|
if (isErrorStarterPack || (starterPack && !isValid)) {
|
||||||
setScreenState(LoggedOutScreenState.S_LoginOrCreateAccount)
|
setScreenState(LoggedOutScreenState.S_LoginOrCreateAccount)
|
||||||
}
|
}
|
||||||
}, [isErrorDid, isErrorStarterPack, setScreenState, isValid, starterPack])
|
}, [isErrorStarterPack, setScreenState, isValid, starterPack])
|
||||||
|
|
||||||
if (!did || !starterPack || !isValid || !moderationOpts) {
|
if (!starterPack || !isValid || !moderationOpts) {
|
||||||
return (
|
return (
|
||||||
<ListMaybePlaceholder
|
<ListMaybePlaceholder
|
||||||
isLoading={
|
isLoading={isLoadingStarterPack || !isValid || !moderationOpts}
|
||||||
isLoadingDid || isLoadingStarterPack || !isValid || !moderationOpts
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -136,8 +102,8 @@ function LandingScreenLoaded({
|
|||||||
const {record, creator, listItemsSample, feeds, joinedWeekCount} = starterPack
|
const {record, creator, listItemsSample, feeds, joinedWeekCount} = starterPack
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const currentStarterPack = useCurrentStarterPack()
|
const activeStarterPack = useActiveStarterPack()
|
||||||
const setCurrentStarterPack = useSetCurrentStarterPack()
|
const setActiveStarterPack = useSetActiveStarterPack()
|
||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||||
const androidDialogControl = useDialogControl()
|
const androidDialogControl = useDialogControl()
|
||||||
|
|
||||||
@@ -147,14 +113,14 @@ function LandingScreenLoaded({
|
|||||||
const listItemsCount = starterPack.list?.listItemCount ?? 0
|
const listItemsCount = starterPack.list?.listItemCount ?? 0
|
||||||
|
|
||||||
const onContinue = () => {
|
const onContinue = () => {
|
||||||
setCurrentStarterPack({
|
setActiveStarterPack({
|
||||||
uri: starterPack.uri,
|
uri: starterPack.uri,
|
||||||
})
|
})
|
||||||
setScreenState(LoggedOutScreenState.S_CreateAccount)
|
setScreenState(LoggedOutScreenState.S_CreateAccount)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onJoinPress = () => {
|
const onJoinPress = () => {
|
||||||
if (currentStarterPack?.isClip) {
|
if (activeStarterPack?.isClip) {
|
||||||
setAppClipOverlayVisible(true)
|
setAppClipOverlayVisible(true)
|
||||||
postAppClipMessage({
|
postAppClipMessage({
|
||||||
action: 'present',
|
action: 'present',
|
||||||
@@ -185,7 +151,7 @@ function LandingScreenLoaded({
|
|||||||
borderBottomLeftRadius: 10,
|
borderBottomLeftRadius: 10,
|
||||||
borderBottomRightRadius: 10,
|
borderBottomRightRadius: 10,
|
||||||
},
|
},
|
||||||
currentStarterPack?.isClip && {
|
activeStarterPack?.isClip && {
|
||||||
paddingTop: 100,
|
paddingTop: 100,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
@@ -312,7 +278,7 @@ function LandingScreenLoaded({
|
|||||||
size="medium"
|
size="medium"
|
||||||
style={[a.mt_2xl]}
|
style={[a.mt_2xl]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setCurrentStarterPack(undefined)
|
setActiveStarterPack(undefined)
|
||||||
setScreenState(LoggedOutScreenState.S_CreateAccount)
|
setScreenState(LoggedOutScreenState.S_CreateAccount)
|
||||||
}}>
|
}}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
|
|||||||
@@ -113,12 +113,10 @@ function reducer(state: State, action: Action): State {
|
|||||||
export function Provider({
|
export function Provider({
|
||||||
starterPack,
|
starterPack,
|
||||||
listItems,
|
listItems,
|
||||||
profile,
|
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
starterPack?: AppBskyGraphDefs.StarterPackView
|
starterPack?: AppBskyGraphDefs.StarterPackView
|
||||||
listItems?: AppBskyGraphDefs.ListItemView[]
|
listItems?: AppBskyGraphDefs.ListItemView[]
|
||||||
profile: AppBskyActorDefs.ProfileView
|
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const createInitialState = (): State => {
|
const createInitialState = (): State => {
|
||||||
@@ -138,7 +136,7 @@ export function Provider({
|
|||||||
return {
|
return {
|
||||||
canNext: true,
|
canNext: true,
|
||||||
currentStep: 'Details',
|
currentStep: 'Details',
|
||||||
profiles: [profile],
|
profiles: [],
|
||||||
feeds: [],
|
feeds: [],
|
||||||
processing: false,
|
processing: false,
|
||||||
transitionDirection: 'Forward',
|
transitionDirection: 'Forward',
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export function Wizard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider starterPack={starterPack} listItems={listItems} profile={profile}>
|
<Provider starterPack={starterPack} listItems={listItems}>
|
||||||
<WizardInner
|
<WizardInner
|
||||||
did={did}
|
did={did}
|
||||||
rkey={rkey}
|
rkey={rkey}
|
||||||
@@ -138,6 +138,7 @@ export function Wizard({
|
|||||||
}
|
}
|
||||||
listItems={listItems}
|
listItems={listItems}
|
||||||
listUri={listUri}
|
listUri={listUri}
|
||||||
|
profile={profile}
|
||||||
moderationOpts={moderationOpts}
|
moderationOpts={moderationOpts}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
@@ -150,6 +151,7 @@ function WizardInner({
|
|||||||
createdAt: initialCreatedAt,
|
createdAt: initialCreatedAt,
|
||||||
listUri: initialListUri,
|
listUri: initialListUri,
|
||||||
listItems: initialListItems,
|
listItems: initialListItems,
|
||||||
|
profile,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
}: {
|
}: {
|
||||||
did?: string
|
did?: string
|
||||||
@@ -157,6 +159,7 @@ function WizardInner({
|
|||||||
createdAt?: string
|
createdAt?: string
|
||||||
listUri?: string
|
listUri?: string
|
||||||
listItems?: AppBskyGraphDefs.ListItemView[]
|
listItems?: AppBskyGraphDefs.ListItemView[]
|
||||||
|
profile: AppBskyActorDefs.ProfileViewBasic
|
||||||
moderationOpts: ModerationOpts
|
moderationOpts: ModerationOpts
|
||||||
}) {
|
}) {
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
@@ -470,6 +473,7 @@ function WizardInner({
|
|||||||
onNext={onNext}
|
onNext={onNext}
|
||||||
nextBtnText={currUiStrings.nextBtn}
|
nextBtnText={currUiStrings.nextBtn}
|
||||||
moderationOpts={moderationOpts}
|
moderationOpts={moderationOpts}
|
||||||
|
profile={profile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
@@ -552,10 +556,12 @@ function Footer({
|
|||||||
onNext,
|
onNext,
|
||||||
nextBtnText,
|
nextBtnText,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
|
profile,
|
||||||
}: {
|
}: {
|
||||||
onNext: () => void
|
onNext: () => void
|
||||||
nextBtnText: string
|
nextBtnText: string
|
||||||
moderationOpts: ModerationOpts
|
moderationOpts: ModerationOpts
|
||||||
|
profile: AppBskyActorDefs.ProfileViewBasic
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -563,7 +569,10 @@ function Footer({
|
|||||||
const editDialogControl = useDialogControl()
|
const editDialogControl = useDialogControl()
|
||||||
const {bottom: bottomInset} = useSafeAreaInsets()
|
const {bottom: bottomInset} = useSafeAreaInsets()
|
||||||
|
|
||||||
const items = state.currentStep === 'Profiles' ? state.profiles : state.feeds
|
const items =
|
||||||
|
state.currentStep === 'Profiles'
|
||||||
|
? [profile, ...state.profiles]
|
||||||
|
: state.feeds
|
||||||
const initialNamesIndex = state.currentStep === 'Profiles' ? 1 : 0
|
const initialNamesIndex = state.currentStep === 'Profiles' ? 1 : 0
|
||||||
|
|
||||||
const isEditEnabled =
|
const isEditEnabled =
|
||||||
|
|||||||
@@ -87,14 +87,7 @@ export const schema = z.object({
|
|||||||
disableHaptics: z.boolean().optional(),
|
disableHaptics: z.boolean().optional(),
|
||||||
disableAutoplay: z.boolean().optional(),
|
disableAutoplay: z.boolean().optional(),
|
||||||
kawaii: z.boolean().optional(),
|
kawaii: z.boolean().optional(),
|
||||||
currentStarterPack: z
|
hasCheckedForStarterPack: z.boolean().optional(),
|
||||||
.object({
|
|
||||||
uri: z.string(),
|
|
||||||
initialFeed: z.string().optional(),
|
|
||||||
isClip: z.boolean().optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
usedStarterPacks: z.array(z.string()).optional(),
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
mutedThreads: z.array(z.string()),
|
mutedThreads: z.array(z.string()),
|
||||||
})
|
})
|
||||||
@@ -135,6 +128,5 @@ export const defaults: Schema = {
|
|||||||
disableHaptics: false,
|
disableHaptics: false,
|
||||||
disableAutoplay: prefersReducedMotion,
|
disableAutoplay: prefersReducedMotion,
|
||||||
kawaii: false,
|
kawaii: false,
|
||||||
currentStarterPack: undefined,
|
hasCheckedForStarterPack: false,
|
||||||
usedStarterPacks: [],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import {Provider as StarterPackProvider} from '../shell/starter-pack'
|
||||||
import {Provider as AltTextRequiredProvider} from './alt-text-required'
|
import {Provider as AltTextRequiredProvider} from './alt-text-required'
|
||||||
import {Provider as AutoplayProvider} from './autoplay'
|
import {Provider as AutoplayProvider} from './autoplay'
|
||||||
import {Provider as DisableHapticsProvider} from './disable-haptics'
|
import {Provider as DisableHapticsProvider} from './disable-haptics'
|
||||||
@@ -8,7 +9,6 @@ import {Provider as HiddenPostsProvider} from './hidden-posts'
|
|||||||
import {Provider as InAppBrowserProvider} from './in-app-browser'
|
import {Provider as InAppBrowserProvider} from './in-app-browser'
|
||||||
import {Provider as KawaiiProvider} from './kawaii'
|
import {Provider as KawaiiProvider} from './kawaii'
|
||||||
import {Provider as LanguagesProvider} from './languages'
|
import {Provider as LanguagesProvider} from './languages'
|
||||||
import {Provider as StarterPackProvider} from './starter-pack'
|
|
||||||
import {Provider as UsedStarterPacksProvider} from './used-starter-packs'
|
import {Provider as UsedStarterPacksProvider} from './used-starter-packs'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
import * as persisted from '#/state/persisted'
|
|
||||||
|
|
||||||
type StateContext =
|
|
||||||
| {
|
|
||||||
uri: string
|
|
||||||
initialFeed?: string
|
|
||||||
isClip?: boolean
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
type SetContext = (v: StateContext) => void
|
|
||||||
|
|
||||||
const stateContext = React.createContext<StateContext>(undefined)
|
|
||||||
const setContext = React.createContext<SetContext>((_: StateContext) => {})
|
|
||||||
|
|
||||||
export function Provider({children}: {children: React.ReactNode}) {
|
|
||||||
const [state, setState] = React.useState<StateContext>(() =>
|
|
||||||
persisted.get('currentStarterPack'),
|
|
||||||
)
|
|
||||||
|
|
||||||
const setStateWrapped = (v: StateContext) => {
|
|
||||||
setState(v)
|
|
||||||
persisted.write('currentStarterPack', v)
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
return persisted.onUpdate(() => {
|
|
||||||
setState(persisted.get('currentStarterPack'))
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<stateContext.Provider value={state}>
|
|
||||||
<setContext.Provider value={setStateWrapped}>
|
|
||||||
{children}
|
|
||||||
</setContext.Provider>
|
|
||||||
</stateContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCurrentStarterPack = () => React.useContext(stateContext)
|
|
||||||
export const useSetCurrentStarterPack = () => React.useContext(setContext)
|
|
||||||
@@ -2,25 +2,25 @@ import React from 'react'
|
|||||||
|
|
||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
|
|
||||||
type StateContext = string[] | undefined
|
type StateContext = boolean | undefined
|
||||||
type SetContext = (v: string) => void
|
type SetContext = (v: boolean) => void
|
||||||
|
|
||||||
const stateContext = React.createContext<StateContext>([])
|
const stateContext = React.createContext<StateContext>(false)
|
||||||
const setContext = React.createContext<SetContext>((_: string) => {})
|
const setContext = React.createContext<SetContext>((_: boolean) => {})
|
||||||
|
|
||||||
export function Provider({children}: {children: React.ReactNode}) {
|
export function Provider({children}: {children: React.ReactNode}) {
|
||||||
const [state, setState] = React.useState<StateContext>(() =>
|
const [state, setState] = React.useState<StateContext>(() =>
|
||||||
persisted.get('usedStarterPacks'),
|
persisted.get('hasCheckedForStarterPack'),
|
||||||
)
|
)
|
||||||
|
|
||||||
const setStateWrapped = (v: string) => {
|
const setStateWrapped = (v: boolean) => {
|
||||||
persisted.write('usedStarterPacks', [...(state ? state : []), v])
|
setState(v)
|
||||||
setState(prev => [...(prev ? prev : []), v])
|
persisted.write('hasCheckedForStarterPack', v)
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
return persisted.onUpdate(() => {
|
return persisted.onUpdate(() => {
|
||||||
setState(persisted.get('usedStarterPacks'))
|
setState(persisted.get('hasCheckedForStarterPack'))
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -33,5 +33,5 @@ export function Provider({children}: {children: React.ReactNode}) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUsedStarterPacks = () => React.useContext(stateContext)
|
export const useHasCheckedForStarterPack = () => React.useContext(stateContext)
|
||||||
export const useAddUsedStarterPack = () => React.useContext(setContext)
|
export const useSetHasCheckedForStarterPack = () => React.useContext(setContext)
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
import {StarterPackView} from '@atproto/api/dist/client/types/app/bsky/graph/defs'
|
import {StarterPackView} from '@atproto/api/dist/client/types/app/bsky/graph/defs'
|
||||||
import {QueryClient, useQuery} from '@tanstack/react-query'
|
import {QueryClient, useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {httpStarterPackUriToAtUri} from 'lib/strings/starter-pack'
|
||||||
import {useAgent} from 'state/session'
|
import {useAgent} from 'state/session'
|
||||||
|
|
||||||
const RQKEY_ROOT = 'starter-pack'
|
const RQKEY_ROOT = 'starter-pack'
|
||||||
const RQKEY = (did?: string, rkey?: string) => [RQKEY_ROOT, did, rkey]
|
const RQKEY = (did?: string, rkey?: string) => [RQKEY_ROOT, did, rkey]
|
||||||
|
|
||||||
export function useStarterPackQuery({
|
export function useStarterPackQuery({
|
||||||
|
uri,
|
||||||
did,
|
did,
|
||||||
rkey,
|
rkey,
|
||||||
}: {
|
}: {
|
||||||
|
uri?: string
|
||||||
did?: string
|
did?: string
|
||||||
rkey?: string
|
rkey?: string
|
||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const uri = `at://${did}/app.bsky.graph.starterpack/${rkey}`
|
|
||||||
|
|
||||||
return useQuery<StarterPackView>({
|
return useQuery<StarterPackView>({
|
||||||
queryKey: RQKEY(did, rkey),
|
queryKey: RQKEY(did, rkey),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
if (!uri) {
|
||||||
|
uri = `at://${did}/app.bsky.graph.starterpack/${rkey}`
|
||||||
|
} else if (uri && !uri.startsWith('at://')) {
|
||||||
|
// TODO remove this assertion
|
||||||
|
uri = httpStarterPackUriToAtUri(uri) as string
|
||||||
|
}
|
||||||
const res = await agent.app.bsky.graph.getStarterPack({
|
const res = await agent.app.bsky.graph.getStarterPack({
|
||||||
starterPack: uri,
|
starterPack: uri,
|
||||||
})
|
})
|
||||||
return res.data.starterPack
|
return res.data.starterPack
|
||||||
},
|
},
|
||||||
enabled: Boolean(did) && Boolean(rkey),
|
enabled: Boolean(uri) || Boolean(did && rkey),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type StateContext =
|
||||||
|
| {
|
||||||
|
uri: string
|
||||||
|
isClip?: boolean
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
type SetContext = (v: StateContext) => void
|
||||||
|
|
||||||
|
const stateContext = React.createContext<StateContext>(undefined)
|
||||||
|
const setContext = React.createContext<SetContext>((_: StateContext) => {})
|
||||||
|
|
||||||
|
export function Provider({children}: {children: React.ReactNode}) {
|
||||||
|
const [state, setState] = React.useState<StateContext>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<stateContext.Provider value={state}>
|
||||||
|
<setContext.Provider value={setState}>{children}</setContext.Provider>
|
||||||
|
</stateContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useActiveStarterPack = () => React.useContext(stateContext)
|
||||||
|
export const useSetActiveStarterPack = () => React.useContext(setContext)
|
||||||
+28
-42
@@ -22,12 +22,13 @@ import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
|
|||||||
import {useOTAUpdates} from 'lib/hooks/useOTAUpdates'
|
import {useOTAUpdates} from 'lib/hooks/useOTAUpdates'
|
||||||
import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
|
import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
|
||||||
import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||||
import {
|
import {parseStarterPackUri} from 'lib/strings/starter-pack'
|
||||||
useCurrentStarterPack,
|
import {isWeb} from 'platform/detection'
|
||||||
useSetCurrentStarterPack,
|
|
||||||
} from 'state/preferences/starter-pack'
|
|
||||||
import {useUsedStarterPacks} from 'state/preferences/used-starter-packs'
|
|
||||||
import {useLoggedOutViewControls} from 'state/shell/logged-out'
|
import {useLoggedOutViewControls} from 'state/shell/logged-out'
|
||||||
|
import {
|
||||||
|
useActiveStarterPack,
|
||||||
|
useSetActiveStarterPack,
|
||||||
|
} from 'state/shell/starter-pack'
|
||||||
import {FeedPage} from 'view/com/feeds/FeedPage'
|
import {FeedPage} from 'view/com/feeds/FeedPage'
|
||||||
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||||
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
|
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
|
||||||
@@ -38,33 +39,37 @@ import {HomeHeader} from '../com/home/HomeHeader'
|
|||||||
|
|
||||||
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
||||||
export function HomeScreen(props: Props) {
|
export function HomeScreen(props: Props) {
|
||||||
|
const {navigation} = props
|
||||||
|
const {hasSession} = useSession()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
|
const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
|
||||||
usePinnedFeedsInfos()
|
usePinnedFeedsInfos()
|
||||||
const currentStarterPack = useCurrentStarterPack()
|
const activeStarterPack = useActiveStarterPack()
|
||||||
const usedStarterPacks = useUsedStarterPacks()
|
const setActiveStarterPack = useSetActiveStarterPack()
|
||||||
const {setShowLoggedOut, requestSwitchToAccount} = useLoggedOutViewControls()
|
const {setShowLoggedOut, requestSwitchToAccount} = useLoggedOutViewControls()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (currentStarterPack && !currentStarterPack?.initialFeed) {
|
// This will be true if the app was launched with a starter pack referral.
|
||||||
// In test environments, the URL won't start with `https://bsky.app`, so we want to find it by the route
|
if (activeStarterPack?.uri) {
|
||||||
try {
|
if (hasSession) {
|
||||||
const route = new URL(currentStarterPack.uri).pathname
|
const parsed = parseStarterPackUri(activeStarterPack.uri)
|
||||||
const foundIndex = usedStarterPacks?.findIndex(p => p.includes(route))
|
if (!parsed) return
|
||||||
if (foundIndex === -1) {
|
setActiveStarterPack(undefined)
|
||||||
setShowLoggedOut(true)
|
navigation.navigate('StarterPack', parsed)
|
||||||
requestSwitchToAccount({requestedAccount: 'starterpack'})
|
} else {
|
||||||
}
|
setShowLoggedOut(true)
|
||||||
} catch {
|
requestSwitchToAccount({
|
||||||
// Don't need to handle anything here, just put the user on the home screen
|
requestedAccount: isWeb ? 'starterpack' : 'new',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
hasSession,
|
||||||
setShowLoggedOut,
|
setShowLoggedOut,
|
||||||
requestSwitchToAccount,
|
requestSwitchToAccount,
|
||||||
currentStarterPack?.initialFeed,
|
activeStarterPack,
|
||||||
currentStarterPack,
|
setActiveStarterPack,
|
||||||
usedStarterPacks,
|
navigation,
|
||||||
])
|
])
|
||||||
|
|
||||||
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
|
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
|
||||||
@@ -96,23 +101,9 @@ function HomeScreenReady({
|
|||||||
[pinnedFeedInfos],
|
[pinnedFeedInfos],
|
||||||
)
|
)
|
||||||
|
|
||||||
const currentStarterPack = useCurrentStarterPack()
|
|
||||||
const setCurrentStarterPack = useSetCurrentStarterPack()
|
|
||||||
|
|
||||||
const starterPackInitialFeed = currentStarterPack?.initialFeed
|
|
||||||
? allFeeds.find(f => {
|
|
||||||
if (currentStarterPack.initialFeed === 'following') {
|
|
||||||
return f === 'following'
|
|
||||||
} else {
|
|
||||||
return f === `feedgen|${currentStarterPack.initialFeed}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: undefined
|
|
||||||
const rawSelectedFeed = useSelectedFeed() ?? allFeeds[0]
|
const rawSelectedFeed = useSelectedFeed() ?? allFeeds[0]
|
||||||
const setSelectedFeed = useSetSelectedFeed()
|
const setSelectedFeed = useSetSelectedFeed()
|
||||||
const maybeFoundIndex = allFeeds.indexOf(
|
const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed)
|
||||||
starterPackInitialFeed ?? rawSelectedFeed,
|
|
||||||
)
|
|
||||||
const selectedIndex = Math.max(0, maybeFoundIndex)
|
const selectedIndex = Math.max(0, maybeFoundIndex)
|
||||||
const selectedFeed = allFeeds[selectedIndex]
|
const selectedFeed = allFeeds[selectedIndex]
|
||||||
const requestNotificationsPermission = useRequestNotificationsPermission()
|
const requestNotificationsPermission = useRequestNotificationsPermission()
|
||||||
@@ -138,12 +129,7 @@ function HomeScreenReady({
|
|||||||
lastPagerReportedIndexRef.current = selectedIndex
|
lastPagerReportedIndexRef.current = selectedIndex
|
||||||
pagerRef.current?.setPage(selectedIndex, 'desktop-sidebar-click')
|
pagerRef.current?.setPage(selectedIndex, 'desktop-sidebar-click')
|
||||||
}
|
}
|
||||||
}, [
|
}, [selectedIndex, allFeeds])
|
||||||
selectedIndex,
|
|
||||||
allFeeds,
|
|
||||||
setCurrentStarterPack,
|
|
||||||
currentStarterPack?.initialFeed,
|
|
||||||
])
|
|
||||||
|
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
|
|||||||
Reference in New Issue
Block a user