Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
32d9dcc
feat: update @expo/ui to canary version and add FAB for adding tokens
Luc1412 Apr 8, 2026
ec84b88
Merge branch 'main' into feature/android-ui
Luc1412 Apr 17, 2026
1c8fd66
Update Fab text and change font to bold
Luc1412 Apr 22, 2026
a537f4e
update dependencies
Luc1412 Apr 23, 2026
9734428
Merge branch 'main' into feature/android-ui
Luc1412 Apr 23, 2026
ab597b0
Merge branch 'main' into feature/android-ui
Luc1412 May 4, 2026
7ab29a8
Merge branch 'main' into feat/android-ui
Luc1412 May 7, 2026
f5bbaa1
feat: replace add icon with SymbolView component and remove XML asset
Luc1412 May 7, 2026
41dff2b
feat: enhance Android UI with dynamic theming for colors across compo…
Luc1412 May 8, 2026
2dbe23e
Merge branch 'main' into feat/android-ui
Luc1412 May 16, 2026
fdac219
Add missing Link import
Luc1412 May 16, 2026
20f2526
Merge branch 'main' into feat/android-ui
Luc1412 May 20, 2026
b57d378
Merge branch 'main' into feat/android-ui
Luc1412 May 21, 2026
fd3e64d
Merge branch 'main' into feat/android-ui
Luc1412 May 21, 2026
05ab53f
feat: replace SymbolView with expo/ui Icon for FAB
Luc1412 May 21, 2026
3e54daf
feat: enhance Android UI theme colors for better dynamic support
Luc1412 May 21, 2026
d329a6d
feat: implement token action menu for android
Luc1412 May 22, 2026
27e44b4
Merge branch 'main' into feat/android-ui
Luc1412 May 22, 2026
8600e7f
Merge branch 'main' into feat/android-ui
Luc1412 May 27, 2026
64d36b3
fix: token deletion not working
Luc1412 May 28, 2026
167de68
chore: update dependencies
Luc1412 May 28, 2026
35a2579
Merge branch 'main' into feat/android-ui
Luc1412 May 28, 2026
d643f46
add missing import
Luc1412 May 28, 2026
00fbea7
fix: update fallback background color for TokenImage component
Luc1412 May 28, 2026
3b8609f
Merge branch 'main' into feat/android-ui
Luc1412 Jun 9, 2026
bb94beb
chore: update translations
Luc1412 Jun 9, 2026
61da644
revert: bun.lock to main
Luc1412 Jun 9, 2026
1bedc00
refactor: rename TokenDetails with TokenListItem
Luc1412 Jun 9, 2026
5ff09ee
Merge branch 'main' into feat/android-ui
Luc1412 Jun 9, 2026
de11f9a
Merge branch 'main' into feat/android-ui
Luc1412 Jun 12, 2026
3f51fd4
fix imports
Luc1412 Jun 12, 2026
a187a4f
Merge branch 'main' into feat/android-ui
Luc1412 Jun 17, 2026
089c67a
renable edit shortcut
Luc1412 Jun 17, 2026
c2e61cd
remove ios long press action
Luc1412 Jun 17, 2026
6c785d4
Implement Status bar styling based on theme
Luc1412 Jun 17, 2026
4d737b0
Implement theme switching support
Luc1412 Jun 17, 2026
87c956e
only show add cta button on empty screen on ios, since android got it…
Luc1412 Jun 17, 2026
6e9f5b0
Merge branch 'main' into feat/android-ui
Luc1412 Jun 24, 2026
f385ac8
Merge branch 'main' into feat/android-ui
Luc1412 Jun 29, 2026
1e11d3e
Change menu trigger to long press instead of icon button
Luc1412 Jun 30, 2026
4d17f7b
fix refresh control colors for android
Luc1412 Jun 30, 2026
78a579f
fix ios issues
Luc1412 Jun 30, 2026
4d35480
update translations
Luc1412 Jun 30, 2026
3f2e122
Align token actions menu with the rest of the project
Luc1412 Jun 30, 2026
7e50a3d
Align token actions menu with the rest of the project
Luc1412 Jul 1, 2026
f4eb682
Merge branch 'main' into feat/android-ui
Luc1412 Jul 1, 2026
0e243c4
format imports
Luc1412 Jul 1, 2026
1706740
Update icon imports
Luc1412 Jul 1, 2026
134d588
simplify TokenListItem component
Luc1412 Jul 1, 2026
aed817a
update translations
Luc1412 Jul 1, 2026
7ca6d03
chore(deps): update expo monorepo
Luc1412 Jul 6, 2026
280a884
chore(deps): update and deduplicate lockfile
Luc1412 Jul 6, 2026
a64af65
Merge branch 'main' into feat/android-ui
Luc1412 Jul 6, 2026
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
4 changes: 4 additions & 0 deletions src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
AppState,
AppStateStatus,
Platform,
StatusBar,
useColorScheme,
} from "react-native";

Expand Down Expand Up @@ -65,6 +66,8 @@ function RootLayoutContent() {

const theme = useTheme();
const tabBarBackgroundColor = theme.background;
const statusBarStyle =
colorScheme === "dark" ? "light-content" : "dark-content";

// Initialize notifications once at app startup, then start pending rollouts and poll for challenges
useEffect(() => {
Expand Down Expand Up @@ -150,6 +153,7 @@ function RootLayoutContent() {

return (
<>
<StatusBar backgroundColor={theme.background} barStyle={statusBarStyle} />
<Stack>
<Stack.Screen
name="index"
Expand Down
211 changes: 118 additions & 93 deletions src/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,27 @@ import { useTheme } from "@/hooks/use-theme";
import { useToken } from "@/hooks/use-token";
import type { PushToken } from "@/types/token";
import { PushTokenRolloutState } from "@/types/token";
import type { TokenAction } from "@/types/token-actions";
import AddSymbol from "@expo/material-symbols/add.xml";
import CodeSymbol from "@expo/material-symbols/code.xml";
import { Button, Text as ExpoText, Host, Icon, Row } from "@expo/ui";
import {
Host as AndroidHost,
Icon as AndroidIcon,
ExtendedFloatingActionButton,
Text,
} from "@expo/ui/jetpack-compose";
import { buttonStyle, controlSize } from "@expo/ui/swift-ui/modifiers";
import { Trans, useLingui } from "@lingui/react/macro";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import { Link, Stack, useLocalSearchParams, useRouter } from "expo-router";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { SymbolView } from "expo-symbols";
import { useCallback, useMemo } from "react";
import {
Keyboard,
Platform,
Pressable,
RefreshControl,
StyleSheet,
useColorScheme,
useWindowDimensions,
View,
} from "react-native";
Expand All @@ -49,15 +54,16 @@ export default function Tokens() {
} = useNotificationStatus();
const { height, width } = useWindowDimensions();
const { bottom, top } = useSafeAreaInsets();
const colorScheme = useColorScheme();
const theme = useTheme();
const backgroundColor = theme.background;
const { t } = useLingui();
const tabBarTintColor = theme.text;
const transparentColor = theme.transparent;
const tabBarBackgroundColor = theme.background;
const refreshControlTintColor =
colorScheme === "dark" ? StaticColors.white : StaticColors.black;
const refreshControlColor =
Platform.OS === "android" ? theme.branding : theme.text;
const refreshControlProgressBackgroundColor =
Platform.OS === "android" ? theme.backgroundSecondary : undefined;

const params = useLocalSearchParams<{ q?: string }>();

Expand Down Expand Up @@ -98,64 +104,52 @@ export default function Tokens() {

const renderItem = useCallback(
({ item }: { item: PushToken }) => {
const primaryTokenAction: TokenAction = PushTokenRolloutState.isFailed(
item.rolloutState,
)
? {
iosIcon: "arrow.clockwise",
key: "refresh",
label: t`Retry Rollout`,
onPress: () => rolloutToken(item.id),
}
: {
iosIcon: "square.and.pencil",
key: "edit",
label: t`Edit`,
onPress: () => {
router.push({
pathname: "/token/[tokenId]",
params: { edit: "1", tokenId: item.id },
});
},
};
const tokenActions: TokenAction[] = [
primaryTokenAction,
{
destructive: true,
iosIcon: "trash",
key: "delete",
label: t`Delete`,
onPress: () => confirmDeleteToken(item.id),
},
];
const isRolloutFinished = PushTokenRolloutState.isFinished(
item.rolloutState,
);

return (
<Animated.View
key={item.id}
entering={FadeIn}
exiting={FadeOut}
style={styles.tokenWrapper}
>
<Link
push
key={item.id}
href={{
pathname: "/token/[tokenId]",
params: { tokenId: item.id },
}}
asChild
>
<Link.Trigger>
<Pressable
onLongPress={() => {}}
style={styles.tokenCard}
disabled={!PushTokenRolloutState.isFinished(item.rolloutState)}
>
<TokenListItem token={item} key={item.id} />
</Pressable>
</Link.Trigger>
<Link.Menu>
{!PushTokenRolloutState.isFailed(item.rolloutState) && (
<Link.MenuAction
icon="square.and.pencil"
onPress={() => {
router.push({
pathname: "/token/[tokenId]",
params: { edit: "1", tokenId: item.id },
});
}}
>
{t`Edit`}
</Link.MenuAction>
)}
{PushTokenRolloutState.isFailed(item.rolloutState) && (
<Link.MenuAction
icon="arrow.clockwise"
onPress={() => {
rolloutToken(item.id);
}}
>
{t`Retry Rollout`}
</Link.MenuAction>
)}
<Link.MenuAction
icon="trash"
destructive
onPress={() => confirmDeleteToken(item.id)}
>
{t`Delete`}
</Link.MenuAction>
</Link.Menu>
</Link>
<TokenListItem
actions={tokenActions}
isRolloutFinished={isRolloutFinished}
token={item}
/>
</Animated.View>
);
},
Expand Down Expand Up @@ -246,6 +240,28 @@ export default function Tokens() {
</Stack.Toolbar>
) : null;

const androidAddFab =
Platform.OS === "android" ? (
<AndroidHost
matchContents
style={[styles.fabHost, { bottom: bottom + Spacing.lg }]}
>
<ExtendedFloatingActionButton
expanded={tokens.length === 0}
onClick={() => {
router.navigate("/token/add");
}}
>
<ExtendedFloatingActionButton.Icon>
<AndroidIcon source={AddSymbol} />
</ExtendedFloatingActionButton.Icon>
<ExtendedFloatingActionButton.Text>
<Text style={styles.fabText}>{t`Add token`}</Text>
</ExtendedFloatingActionButton.Text>
</ExtendedFloatingActionButton>
</AndroidHost>
) : null;

if (!tokens.length) {
return (
<>
Expand Down Expand Up @@ -276,37 +292,40 @@ export default function Tokens() {
device.
</Trans>
</ThemedText>
<Host
matchContents={{ vertical: true }}
style={[styles.noTokenButton, { width: emptyStateButtonWidth }]}
>
<Button
modifiers={[
controlSize("large"),
buttonStyle(
isLiquidGlassAvailable()
? "glassProminent"
: "borderedProminent",
),
]}
onPress={() => {
router.navigate("/token/add");
}}
style={{ width: emptyStateButtonWidth }}
{Platform.OS === "ios" && (
<Host
matchContents={{ vertical: true }}
style={[styles.noTokenButton, { width: emptyStateButtonWidth }]}
>
<Row alignment="center" spacing={6}>
<Icon
name={Icon.select({
ios: "plus",
android: AddSymbol,
})}
accessibilityLabel={t`Add token`}
/>
<ExpoText numberOfLines={1}>{t`Add token`}</ExpoText>
</Row>
</Button>
</Host>
<Button
modifiers={[
controlSize("large"),
buttonStyle(
isLiquidGlassAvailable()
? "glassProminent"
: "borderedProminent",
),
]}
onPress={() => {
router.navigate("/token/add");
}}
style={{ width: emptyStateButtonWidth }}
>
<Row alignment="center" spacing={6}>
<Icon
name={Icon.select({
ios: "plus",
android: AddSymbol,
})}
accessibilityLabel={t`Add token`}
/>
<ExpoText numberOfLines={1}>{t`Add token`}</ExpoText>
</Row>
</Button>
</Host>
)}
</ThemedView>
{androidAddFab}
{footer}
</>
);
Expand Down Expand Up @@ -361,12 +380,14 @@ export default function Tokens() {
refreshing={isPolling}
onRefresh={onRefresh}
title={t`Refreshing...`}
tintColor={refreshControlTintColor}
titleColor={refreshControlTintColor}
colors={[refreshControlTintColor]}
tintColor={refreshControlColor}
titleColor={refreshControlColor}
colors={[refreshControlColor]}
progressBackgroundColor={refreshControlProgressBackgroundColor}
/>
}
/>
{androidAddFab}
{footer}
</>
);
Expand All @@ -376,6 +397,14 @@ export const styles = StyleSheet.create({
contentContainer: {
paddingHorizontal: Spacing.lg,
},
fabHost: {
position: "absolute",
right: Spacing.lg,
zIndex: 10,
},
fabText: {
fontWeight: "bold",
},
noResultsContainer: {
padding: Spacing.xl,
},
Expand Down Expand Up @@ -409,10 +438,6 @@ export const styles = StyleSheet.create({
notificationNotice: {
marginVertical: Spacing.sm,
},
tokenCard: {
borderRadius: Radii.xl,
overflow: "hidden",
},
tokenWrapper: {
marginVertical: Spacing.sm,
},
Expand Down
15 changes: 9 additions & 6 deletions src/components/push-request-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ function ActionButton({
const theme = useTheme();
const brandingColor = theme.branding;
const backgroundColor = theme.fill;
const primaryButtonTextColor = theme.textOnBranding;
const secondaryButtonTextColor = theme.branding;
const pressScale = useSharedValue(1);
const primaryButtonStyle = useMemo(
() => ({ backgroundColor: brandingColor }),
Expand All @@ -281,9 +283,13 @@ function ActionButton({
() => ({ backgroundColor, borderColor: brandingColor }),
[backgroundColor, brandingColor],
);
const primaryButtonTextStyle = useMemo(
() => ({ color: primaryButtonTextColor }),
[primaryButtonTextColor],
);
const secondaryButtonTextStyle = useMemo(
() => ({ color: brandingColor }),
[brandingColor],
() => ({ color: secondaryButtonTextColor }),
[secondaryButtonTextColor],
);

const animatedStyle = useAnimatedStyle(() => ({
Expand Down Expand Up @@ -320,7 +326,7 @@ function ActionButton({
fontWeight="semiBold"
style={[
styles.buttonText,
isPrimary ? styles.buttonPrimaryText : secondaryButtonTextStyle,
isPrimary ? primaryButtonTextStyle : secondaryButtonTextStyle,
]}
>
{label}
Expand All @@ -346,9 +352,6 @@ const styles = StyleSheet.create({
buttonDisabled: {
opacity: 0.6,
},
buttonPrimaryText: {
color: StaticColors.white,
},
buttonSecondary: {
borderWidth: 1,
},
Expand Down
Loading