wip bsky client for the web & android
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 194 lines 4.1 kB view raw
1<script setup lang="ts"> 2import { ref, onMounted, watch } from 'vue' 3import { App, type URLOpenListenerEvent } from '@capacitor/app' 4 5import { useNavigationStore } from '@/stores/navigation' 6import { useEnvironmentStore } from '@/stores/environment' 7import { useThemeStore } from '@/stores/theme' 8import { useAuthStore } from '@/stores/auth' 9import { useModalStore } from '@/stores/modal' 10 11import OAuthCallback from '@/views/Auth/OAuthCallback.vue' 12import OnboardingFlow from '@/views/Onboarding/OnboardingFlow.vue' 13 14import AppShell from '@/components/Layout/AppShell.vue' 15import SplashScreen from '@/components/Layout/SplashScreen.vue' 16import ModalStack from '@/components/UI/ModalStack.vue' 17import PronounsModal from '@/components/Modals/PronounsModal.vue' 18 19import KEYS from './utils/keys' 20 21type AppPhase = 'loading' | 'callback' | 'intro' | 'shell' 22const currentPhase = ref<AppPhase>('loading') 23 24const nav = useNavigationStore() 25const env = useEnvironmentStore() 26const theme = useThemeStore() 27const auth = useAuthStore() 28const modals = useModalStore() 29 30// init stuff 31// ======================================================== 32async function initializeApp() { 33 theme.init() 34 env.init() 35 auth.init() 36 37 const path = window.location.pathname 38 if (path.includes('/oauth/callback')) { 39 currentPhase.value = 'callback' 40 return 41 } 42 43 const wait = () => new Promise((resolve) => setTimeout(resolve, 1500)) 44 45 // waiting for auth 46 // we then either determine the Next Phase - either the onboarding flow or to the shell 47 if (auth.isLoading) { 48 const unwatch = watch( 49 () => auth.isLoading, 50 async (loading) => { 51 if (!loading) { 52 await wait() 53 unwatch() 54 determineNextPhase() 55 } 56 }, 57 ) 58 } else { 59 await wait() 60 determineNextPhase() 61 } 62} 63 64function determineNextPhase() { 65 const hasSeenIntro = localStorage.getItem(KEYS.STATE.INTRO_COMPLETE) === 'true' 66 67 // onboarding flow! 68 if (!hasSeenIntro) { 69 currentPhase.value = 'intro' 70 } 71 // shell/main app! 72 else { 73 finishStartup() 74 } 75} 76 77function finishStartup() { 78 nav.init() 79 currentPhase.value = 'shell' 80 81 const profile = auth.profile 82 if (!profile) return 83 84 if (!profile?.pronouns) { 85 setTimeout(() => { 86 const dismissed = localStorage.getItem(KEYS.STATE.WOKE_DISMISSED) 87 if (!dismissed) { 88 modals.open(PronounsModal) 89 } 90 }, 750) 91 } 92} 93 94// event handlers 95// ======================================================== 96const onAuthCallbackComplete = () => { 97 window.history.replaceState(null, '', '/') 98 theme.init() 99 env.init() 100 finishStartup() 101} 102 103const onIntroComplete = (action: 'stay' | 'login') => { 104 localStorage.setItem(KEYS.STATE.INTRO_COMPLETE, 'true') 105 finishStartup() 106 107 if (action === 'login') { 108 setTimeout(() => { 109 nav.push('login') 110 }, 100) 111 } 112} 113 114// ======================================================== 115onMounted(() => { 116 initializeApp() 117 118 App.addListener('appUrlOpen', function (event: URLOpenListenerEvent) { 119 const url = new URL(event.url) 120 const path = url.pathname 121 const hash = url.hash 122 123 if (!path) return 124 125 if (path.startsWith('/oauth/callback')) { 126 auth._hash = hash 127 currentPhase.value = 'callback' 128 } else { 129 nav.navigateToUrl(path) 130 } 131 }) 132}) 133</script> 134 135<template> 136 <div class="app-root"> 137 <ModalStack /> 138 139 <Transition name="fade" mode="out-in"> 140 <SplashScreen v-if="currentPhase === 'loading'" key="loading" /> 141 142 <OAuthCallback 143 v-else-if="currentPhase === 'callback'" 144 key="callback" 145 class="view-layer" 146 @complete="onAuthCallbackComplete" 147 /> 148 149 <OnboardingFlow 150 v-else-if="currentPhase === 'intro'" 151 key="intro" 152 @complete="onIntroComplete('stay')" 153 /> 154 155 <div 156 v-else-if="currentPhase === 'shell'" 157 key="shell" 158 class="shell-wrapper" 159 :inert="modals.stack.length > 0" 160 > 161 <AppShell /> 162 </div> 163 </Transition> 164 </div> 165</template> 166 167<style scoped> 168.app-root { 169 width: 100%; 170 height: 100%; 171} 172 173.view-layer { 174 position: absolute; 175 inset: 0; 176 width: 100vw; 177 height: 100vh; 178} 179 180.shell-wrapper { 181 height: 100%; 182 width: 100%; 183} 184 185.fade-enter-active, 186.fade-leave-active { 187 transition: opacity 0.3s ease; 188} 189 190.fade-enter-from, 191.fade-leave-to { 192 opacity: 0; 193} 194</style>