Skip to content

Commit

Permalink
ux: Fade in the header on load (#2179)
Browse files Browse the repository at this point in the history
  • Loading branch information
NigelBreslaw authored Jul 27, 2024
1 parent 4dead16 commit 09a0f93
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 63 deletions.
88 changes: 88 additions & 0 deletions native/app/UI/CharacterHeaderButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { TouchableOpacity, View } from "react-native";
import { Image } from "expo-image";

import { useGGStore } from "@/app/store/GGStore.ts";
import Animated, {
Extrapolation,
interpolate,
ReduceMotion,
useAnimatedStyle,
useSharedValue,
withSpring,
} from "react-native-reanimated";
import { useEffect } from "react";

const scale = 0.4;
const originalHeight = 96;
const borderRadius = 7;
const transferHeight = originalHeight * scale;

export default function CharacterHeaderButtons() {
"use memo";
const ggCharacters = useGGStore((state) => state.ggCharacters);
const currentListIndex = useGGStore((state) => state.currentListIndex);

const opacity = useSharedValue(0);
const viewAnimationStyle = useAnimatedStyle(() => ({
opacity: interpolate(opacity.value, [0, 1], [0, 1], Extrapolation.CLAMP),
}));

// biome-ignore lint/correctness/useExhaustiveDependencies: <This subscribes on mount>
useEffect(() => {
const unsubscribe = useGGStore.subscribe(
(state) => state.initialPageLoaded,
(initialPageLoaded, _previous) => {
if (initialPageLoaded) {
opacity.value = withSpring(1, {
duration: 1250,
reduceMotion: ReduceMotion.System,
});
}
},
);
return unsubscribe;
}, []);

return (
<Animated.View style={[viewAnimationStyle, { flexDirection: "row", gap: 10 }]}>
{ggCharacters.map((ggCharacter, index) => {
return (
<TouchableOpacity
onPress={() => useGGStore.getState().setJumpToIndex({ index, animate: true })}
key={ggCharacter.characterId}
>
<View
style={{
width: transferHeight,
height: transferHeight,
borderRadius,
overflow: "hidden",
}}
>
<View
style={{
transformOrigin: "top left",
transform: [{ scale: scale }],
}}
>
<Image source={ggCharacter.emblemPath} style={{ width: 96, height: 96 }} />
</View>
<View
style={{
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
borderRadius,
borderWidth: 1,
borderColor: index === currentListIndex ? "white" : "#5B5B5B",
}}
/>
</View>
</TouchableOpacity>
);
})}
</Animated.View>
);
}
54 changes: 1 addition & 53 deletions native/app/UI/MainDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Spinner from "@/app/UI/Spinner.tsx";
import SearchView from "@/app/inventory/pages/SearchView.tsx";
import Settings from "@/app/screens/Settings.tsx";
import Ellipses from "@/images/svg/ellipses-horizontal.svg";
import CharacterHeaderButtons from "@/app/UI/CharacterHeaderButtons.tsx";

function MenuButton() {
"use memo";
Expand All @@ -33,59 +34,6 @@ function MenuButton() {
);
}

function CharacterHeaderButtons() {
"use memo";
const ggCharacters = useGGStore((state) => state.ggCharacters);
const currentListIndex = useGGStore((state) => state.currentListIndex);
const scale = 0.4;
const originalHeight = 96;
const borderRadius = 7;
const transferHeight = originalHeight * scale;

return (
<View style={{ flexDirection: "row", gap: 10 }}>
{ggCharacters.map((ggCharacter, index) => {
return (
<TouchableOpacity
onPress={() => useGGStore.getState().setJumpToIndex({ index, animate: true })}
key={ggCharacter.characterId}
>
<View
style={{
width: transferHeight,
height: transferHeight,
borderRadius,
overflow: "hidden",
}}
>
<View
style={{
transformOrigin: "top left",
transform: [{ scale: scale }],
}}
>
<Image source={ggCharacter.emblemPath} style={{ width: 96, height: 96 }} />
</View>
<View
style={{
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
borderRadius,
borderWidth: 1,
borderColor: index === currentListIndex ? "white" : "#5B5B5B",
}}
/>
</View>
</TouchableOpacity>
);
})}
</View>
);
}

export type GGDrawerType = {
Inventory: undefined;
Search: undefined;
Expand Down
41 changes: 33 additions & 8 deletions native/app/inventory/pages/InventoryHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
import { View } from "react-native";
import { Image } from "expo-image";
import { useEffect } from "react";
import Animated, {
Extrapolation,
interpolate,
ReduceMotion,
useAnimatedStyle,
useSharedValue,
withSpring,
} from "react-native-reanimated";

import { useGGStore } from "@/app/store/GGStore.ts";

export default function InventoryHeader() {
"use memo";
const totalCharacters = useGGStore((state) => state.ggCharacters.length);
const currentListIndex = useGGStore((state) => state.currentListIndex);
const characterBackgroundEmblem = useGGStore((state) => state.ggCharacters[currentListIndex]?.secondarySpecial);
const initialPageLoaded = useGGStore((state) => state.initialPageLoaded);

if (totalCharacters === 0) {
return null;
}
const opacity = useSharedValue(0);
const viewAnimationStyle = useAnimatedStyle(() => ({
opacity: interpolate(opacity.value, [0, 1], [0, 1], Extrapolation.CLAMP),
}));

// biome-ignore lint/correctness/useExhaustiveDependencies: <This subscribes on mount>
useEffect(() => {
const unsubscribe = useGGStore.subscribe(
(state) => state.initialPageLoaded,
(initialPageLoaded, _previous) => {
if (initialPageLoaded) {
opacity.value = withSpring(1, {
duration: 1250,
reduceMotion: ReduceMotion.System,
});
}
},
);
return unsubscribe;
}, []);

return (
<View style={{ flex: 1 }}>
<Image style={{ flex: 1 }} transition={150} source={characterBackgroundEmblem} />
</View>
<Animated.View style={[viewAnimationStyle, { flex: 1 }]}>
<Image style={{ flex: 1 }} transition={initialPageLoaded ? 150 : 0} source={characterBackgroundEmblem} />
</Animated.View>
);
}
5 changes: 3 additions & 2 deletions native/app/inventory/pages/InventoryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function InventoryPage({ inventoryPageEnum, pageEstimatedFlashLis
const pullRefreshing = useGGStore((state) => state.pullRefreshing);
const [pageReady, setPageReady] = useState(false);
const opacity = useSharedValue(0);
const transferButtonStyle = useAnimatedStyle(() => ({
const viewAnimationStyle = useAnimatedStyle(() => ({
opacity: interpolate(opacity.value, [0, 1], [0, 1], Extrapolation.CLAMP),
}));

Expand Down Expand Up @@ -116,7 +116,7 @@ export default function InventoryPage({ inventoryPageEnum, pageEstimatedFlashLis
const debouncedMove = debounce(listMovedRef.current, 40);
const debounceListIndex = debounce(calcCurrentListIndex, 40);
return (
<Animated.View style={[transferButtonStyle, { flex: 1, width: "100%", height: "100%" }]}>
<Animated.View style={[viewAnimationStyle, { flex: 1, width: "100%", height: "100%" }]}>
<ScrollView
horizontal
pagingEnabled
Expand Down Expand Up @@ -145,6 +145,7 @@ export default function InventoryPage({ inventoryPageEnum, pageEstimatedFlashLis
}
onLoad={() => {
if (index === useGGStore.getState().currentListIndex) {
useGGStore.getState().setInitialPageLoaded();
setPageReady(true);
jumpToCharacterRef.current();
opacity.value = withSpring(1, {
Expand Down
6 changes: 6 additions & 0 deletions native/app/store/Account/AccountSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export type AccountSliceGetter = Parameters<StateCreator<IStore, [], [], Account
export interface AccountSlice {
stateHydrated: boolean;
appReady: boolean;
initialPageLoaded: boolean;

appStartupTime: number;
refreshing: boolean;
Expand Down Expand Up @@ -136,11 +137,13 @@ export interface AccountSlice {
setWeaponsSubmenuOpen: (weaponsSubmenuOpen: boolean) => void;
setArmorSubmenuOpen: (armorSubmenuOpen: boolean) => void;
setCurrentInventoryPage: (currentInventoryPage: InventoryPageEnums) => void;
setInitialPageLoaded: () => void;
}

export const createAccountSlice: StateCreator<IStore, [], [], AccountSlice> = (set, get) => ({
stateHydrated: false,
appReady: false,
initialPageLoaded: false,

appStartupTime: 0,
refreshing: false,
Expand Down Expand Up @@ -359,6 +362,9 @@ export const createAccountSlice: StateCreator<IStore, [], [], AccountSlice> = (s
set({ currentInventoryPage });
}
},
setInitialPageLoaded: () => {
set({ initialPageLoaded: true });
},
});

function updateProfile(get: AccountSliceGetter, set: AccountSliceSetter, profile: ProfileData) {
Expand Down

0 comments on commit 09a0f93

Please sign in to comment.