Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions clients/apps/app/app.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const IS_WIDGET_BUILD = process.env.EXPO_WIDGET_BUILD === '1'

const plugins = [
'expo-router',
[
'expo-splash-screen',
{
image: './assets/images/splash-icon.png',
imageWidth: 120,
resizeMode: 'contain',
backgroundColor: '#0D0E10',
},
],
'expo-secure-store',
'expo-font',
'expo-notifications',
[
'expo-asset',
{
assets: ['./assets/images/login-background.jpg'],
},
],
'expo-web-browser',
]

// Only include Sentry plugin for non-widget builds
// The Sentry plugin fails with @bacons/apple-targets blank template
// because it expects the "Bundle React Native code and images" build phase to exist
if (!IS_WIDGET_BUILD) {
plugins.push([
'@sentry/react-native/expo',
{
url: 'https://sentry.io/',
project: 'polar-app',
organization: 'polar-sh',
},
])
}

plugins.push('@bacons/apple-targets')

module.exports = {
expo: {
name: 'Polar',
slug: 'Polar',
version: '1.1.0',
orientation: 'portrait',
icon: './assets/images/icon.png',
scheme: 'polar',
userInterfaceStyle: 'dark',
newArchEnabled: true,
owner: 'polar-sh',
ios: {
appleTeamId: '55U3YA3QTA',
supportsTablet: false,
bundleIdentifier: 'com.polarsource.Polar',
infoPlist: {
ITSAppUsesNonExemptEncryption: false,
},
icon: './assets/images/ios-dark.png',
entitlements: {
'com.apple.developer.applesignin': ['Default'],
'com.apple.security.application-groups': [
'group.com.polarsource.Polar',
],
},
associatedDomains: ['applinks:polar.godetour.link'],
},
android: {
adaptiveIcon: {
foregroundImage: './assets/images/adaptive-icon.png',
backgroundColor: '#0D0E10',
},
package: 'com.polarsource.Polar',
scheme: 'polar',
googleServicesFile: './google-services.json',
intentFilters: [
{
action: 'VIEW',
autoVerify: true,
data: [
{
scheme: 'https',
host: 'polar.godetour.link',
pathPrefix: '/baSjUTJtg8',
},
],
category: ['BROWSABLE', 'DEFAULT'],
},
],
},
web: {
bundler: 'metro',
output: 'static',
favicon: './assets/images/favicon.png',
},
plugins,
experiments: {
typedRoutes: true,
},
extra: {
router: {
origin: false,
root: './app',
},
eas: {
projectId: '0c79977b-c070-4416-8878-d8b8febe2e25',
},
},
runtimeVersion: {
policy: 'appVersion',
},
updates: {
url: 'https://u.expo.dev/0c79977b-c070-4416-8878-d8b8febe2e25',
},
},
}
101 changes: 0 additions & 101 deletions clients/apps/app/app.json

This file was deleted.

13 changes: 12 additions & 1 deletion clients/apps/app/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { Box } from '@/components/Shared/Box'
import theme from '@/design-system/theme'
import { SessionProvider } from '@/providers/SessionProvider'
import { ExtensionStorage } from '@bacons/apple-targets'
import { useReactNavigationDevTools } from '@dev-plugins/react-navigation'
import { InstrumentSerif_400Regular } from '@expo-google-fonts/instrument-serif/400Regular'
import { InstrumentSerif_400Regular_Italic } from '@expo-google-fonts/instrument-serif/400Regular_Italic'
import { useFonts } from '@expo-google-fonts/instrument-serif/useFonts'
import NetInfo from '@react-native-community/netinfo'
import * as Sentry from '@sentry/react-native'

import { ThemeProvider } from '@shopify/restyle'
import { onlineManager } from '@tanstack/react-query'
import { Slot, useNavigationContainerRef } from 'expo-router'
import * as SplashScreen from 'expo-splash-screen'
import React, { useCallback } from 'react'
import React, { useCallback, useEffect } from 'react'
import { AppState } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { SafeAreaProvider } from 'react-native-safe-area-context'

Expand Down Expand Up @@ -62,6 +65,14 @@ export default Sentry.wrap(function RootLayout() {
InstrumentSerif_400Regular_Italic,
})

useEffect(() => {
AppState.addEventListener('change', (state) => {
if (state === 'background') {
ExtensionStorage.reloadWidget()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the docs:

A widget’s budget applies to a 24-hour period. WidgetKit tunes the 24-hour window to the user’s daily usage pattern, which means the daily budget doesn’t necessarily reset at exactly midnight. For a widget the user frequently views, a daily budget typically includes from 40 to 70 refreshes. This rate roughly translates to widget reloads every 15 to 60 minutes, but it’s common for these intervals to vary due to the many factors involved.

This reloadWidget() call forces the widget to reload as soon as the app enters the background to ensure it's up-to-date.

}
})
}, [])

const onLayoutRootView = useCallback(() => {
if (fontsLoaded) {
// This tells the splash screen to hide immediately! If we call this after
Expand Down
8 changes: 8 additions & 0 deletions clients/apps/app/hooks/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useOAuthConfig } from '@/hooks/oauth'
import { useNotifications } from '@/providers/NotificationsProvider'
import { useSession } from '@/providers/SessionProvider'
import { ExtensionStorage } from '@bacons/apple-targets'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useQueryClient } from '@tanstack/react-query'
import { revokeAsync } from 'expo-auth-session'
Expand All @@ -13,6 +14,8 @@ import {
useGetNotificationRecipient,
} from './polar/notifications'

const widgetStorage = new ExtensionStorage('group.com.polarsource.Polar')

export const useLogout = () => {
const { session, setSession } = useSession()
const { expoPushToken } = useNotifications()
Expand Down Expand Up @@ -44,6 +47,11 @@ export const useLogout = () => {
WebBrowser.coolDownAsync().catch(() => {})
queryClient.clear()
await AsyncStorage.clear()

widgetStorage.set('widget_api_token', '')
widgetStorage.set('widget_organization_id', '')
widgetStorage.set('widget_organization_name', '')

setSession(null)
router.replace('/')
} catch (error) {
Expand Down
5 changes: 4 additions & 1 deletion clients/apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
"lint": "pnpm format:check && expo lint",
"typecheck": "tsc --noEmit",
"push:test": "node tooling/push-notifications/send-push.js",
"postinstall": "pnpm --filter @polar-sh/client build"
"postinstall": "pnpm --filter @polar-sh/client build && patch-package",
"prewidget": "EXPO_NO_GIT_STATUS=1 EXPO_WIDGET_BUILD=1 npx expo prebuild --template ./node_modules/@bacons/apple-targets/prebuild-blank.tgz --clean"
},
"jest": {
"preset": "jest-expo"
},
"dependencies": {
"@bacons/apple-targets": "^3.0.6",
"@dev-plugins/react-navigation": "^0.3.1",
"@dev-plugins/react-query": "^0.1.0",
"@expo-google-fonts/instrument-serif": "^0.4.0",
Expand Down Expand Up @@ -67,6 +69,7 @@
"expo-updates": "~29.0.12",
"expo-web-browser": "~15.0.9",
"nativewind": "^4.1.23",
"patch-package": "^8.0.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-error-boundary": "^6.0.0",
Expand Down
10 changes: 10 additions & 0 deletions clients/apps/app/providers/OrganizationProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Box } from '@/components/Shared/Box'
import { useOrganizations } from '@/hooks/polar/organizations'
import { useStorageState } from '@/hooks/storage'
import { ExtensionStorage } from '@bacons/apple-targets'
import { schemas } from '@polar-sh/client'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { Redirect, usePathname } from 'expo-router'
import { createContext, PropsWithChildren, useEffect, useMemo } from 'react'
import { ActivityIndicator } from 'react-native'
import { useSession } from './SessionProvider'

const storage = new ExtensionStorage('group.com.polarsource.Polar')

export interface OrganizationContextValue {
isLoading: boolean
organization: schemas['Organization'] | undefined
Expand Down Expand Up @@ -66,6 +69,13 @@ export function PolarOrganizationProvider({ children }: PropsWithChildren) {
)
}, [organizationData, organizationId])

useEffect(() => {
if (organization) {
storage.set('widget_organization_id', organization.id)
storage.set('widget_organization_name', organization.name)
}
}, [organization])

const isLoading = isStorageLoading || isLoadingOrganizations

const organizations = organizationData?.items ?? []
Expand Down
16 changes: 15 additions & 1 deletion clients/apps/app/providers/SessionProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { useStorageState } from '@/hooks/storage'
import { createContext, useContext, type PropsWithChildren } from 'react'
import { ExtensionStorage } from '@bacons/apple-targets'
import {
createContext,
useContext,
useEffect,
type PropsWithChildren,
} from 'react'

const storage = new ExtensionStorage('group.com.polarsource.Polar')

const AuthContext = createContext<{
setSession: (session: string | null) => void
Expand All @@ -26,6 +34,12 @@ export function useSession() {
export function SessionProvider({ children }: PropsWithChildren) {
const [[isLoading, session], setSession] = useStorageState('session')

useEffect(() => {
if (session) {
storage.set('widget_api_token', session)
}
}, [session])

return (
<AuthContext.Provider
value={{
Expand Down
Loading
Loading