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
10 changes: 9 additions & 1 deletion example/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,15 @@ export default (config: ExpoConfig): ExpoConfig => ({
},
plugins: [
"expo-router",
["react-native-bootsplash", { assetsDir: "./assets/bootsplash" }],
[
"react-native-bootsplash",
{
logo: "./assets/icons/logo/logo.png",
background: "#FAFAFA",
logoWidth: 110,
assetsOutput: "./assets/bootsplash",
},
],
[
"expo-build-properties",
{
Expand Down
Binary file modified example/assets/bootsplash/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/assets/bootsplash/logo@1,5x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/assets/bootsplash/logo@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/assets/bootsplash/logo@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/assets/bootsplash/logo@4x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions example/assets/bootsplash/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"background": "#f5fcff",
"background": "#fafafa",
"logo": {
"width": 100,
"height": 61
"width": 110,
"height": 67
}
}
294 changes: 81 additions & 213 deletions example/bun.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Profile Scroll-to-Top Transition Design

## Goal

Make the Profile tab's floating scroll-to-top arrow follow scrolling smoothly without changing the existing Motionify behavior elsewhere on the screen.

## Design

- Render the Profile list with Reanimated's `Animated.ScrollView`.
- Track its native UI-thread offset with `useAnimatedRef` and `useScrollOffset`.
- Pass that shared offset explicitly to the arrow's `MotionifyViewWithInterpolation`.
- Clamp a combined transition over roughly 80–260 px of scroll:
- opacity: hidden to visible;
- scale: 0.7 to 1;
- translateY: 12 to 0.
- Keep the existing `useMotionify` scroll handler attached so the floating header, status cards, and bottom-tab behavior remain unchanged.
- Keep the arrow press behavior: animate the same scroll view back to offset zero.

## Scope

Only `src/screens/core/Profile/index.tsx` should require behavior changes. No navigation, library, or shared animation API changes are planned.

## Verification

- TypeScript type-check passes.
- On iOS Simulator, entering Profile and scrolling through the reveal range produces a continuous arrow transition with no visible jump.
- Scrolling back upward reverses the interpolation smoothly.
- Pressing the visible arrow still animates to the top.
- Existing Profile header and tab-bar reactions continue working.
14 changes: 13 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@
"name": "motionify-example",
"main": "index",
"version": "1.0.0",
"trustedDependencies": [
"@shopify/react-native-skia"
],
"expo": {
"install": {
"exclude": [
"react-native-bootsplash"
]
}
},
"scripts": {
"postinstall": "patch-package",
"start": "expo start --dev-client",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"typecheck": "tsc --noEmit",
"lint": "expo lint",
"install-check": "npx expo install --check",
"xc": "cd ios && open *.xcworkspace && cd ..",
Expand All @@ -24,6 +35,7 @@
"@react-navigation/elements": "^2.7.0",
"@react-navigation/native": "^7.1.18",
"@reduxjs/toolkit": "^2.9.2",
"@shopify/react-native-skia": "2.6.2",
"axios": "^1.12.2",
"eas-local-cache": "^1.0.3",
"expo": "~57.0.11",
Expand All @@ -49,7 +61,7 @@
"react-fast-compare": "^3.2.2",
"react-i18next": "^17.0.11",
"react-native": "0.86.2",
"react-native-bootsplash": "^6.3.10",
"react-native-bootsplash": "^7.3.2",
"react-native-gesture-handler": "~2.32.0",
"react-native-indicators": "^0.17.0",
"react-native-localize": "^3.5.4",
Expand Down
8 changes: 5 additions & 3 deletions example/src/components/BottomTabBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
import { BottomTabBarProps } from "expo-router/js-tabs";
import React from "react";
import { Pressable, StyleSheet, View } from "react-native";
import Animated, {
Expand Down Expand Up @@ -150,7 +150,9 @@ const MotionifyBottomTabBar = (props: MotionifyBottomTabBarProps) => {
key={"bottom-tab-" + route.key}
accessibilityRole="button"
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
accessibilityLabel={
options.tabBarAccessibilityLabel ?? String(label)
}
onPress={onPress}
onLongPress={onLongPress}
style={[
Expand Down Expand Up @@ -195,7 +197,7 @@ const styles = StyleSheet.create({
position: "absolute",
},
tabContainer: {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
},
tabItem: {
borderTopLeftRadius: 44,
Expand Down
9 changes: 7 additions & 2 deletions example/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { StyleProp, TouchableOpacity, ViewStyle } from 'react-native';
import {
StyleProp,
TextStyle,
TouchableOpacity,
ViewStyle,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { COLORS } from '@/theme';
import React from 'react';
Expand All @@ -11,7 +16,7 @@ type Props = {
onPress?: () => void;
outline?: boolean;
isOriginal?: boolean;
textButtonStyle?: StyleProp<ViewStyle>;
textButtonStyle?: StyleProp<TextStyle>;
style?: StyleProp<ViewStyle>;
};

Expand Down
2 changes: 1 addition & 1 deletion example/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Icons } from "@/theme";
import { isAndroid } from "@/utils/device";
import { moderateScale, moderateVerticalScale } from "@/utils/scale";
import { useNavigation } from "@react-navigation/native";
import { useNavigation } from "expo-router/react-navigation";
import { ImageProps } from "expo-image";
import React, { FC } from "react";
import {
Expand Down
2 changes: 1 addition & 1 deletion example/src/components/Indicator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Indicator = memo((props: IndicatorProps) => {
if (visible) {
return (
<ActivityIndicator
style={[StyleSheet.absoluteFillObject, AppStyles.columnCenter]}
style={[StyleSheet.absoluteFill, AppStyles.columnCenter]}
color={COLORS.primary}
size="small"
/>
Expand Down
2 changes: 1 addition & 1 deletion example/src/components/Input/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface InputProps {
containerStyle?: StyleProp<ViewStyle>;
style?: StyleProp<ViewStyle>;
titleStyle?: StyleProp<TextStyle>;
inputStyle?: StyleProp<ViewStyle>;
inputStyle?: StyleProp<TextStyle>;
defaultValue?: string;
isRequired?: boolean;
isPickerImage?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion example/src/hooks/useCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function useAsyncState<T>(
(newValue: SetStateAction<T>, callback?: (newState: T) => void) => void,
] {
const [state, setState] = useState(initialValue);
const _callback = useRef<(newState: T) => void>();
const _callback = useRef<((newState: T) => void) | undefined>(undefined);

const _setState = (
newValue: SetStateAction<T>,
Expand Down
2 changes: 1 addition & 1 deletion example/src/libs/reactive/ReactiveView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export const ReactiveView: React.FC<ReactiveViewProps> = ({
directionShared,
]);

const combinedStyle: StyleProp<ViewStyle> = [style, animatedStyle];
const combinedStyle = [style, animatedStyle];

return (
<Animated.View
Expand Down
47 changes: 17 additions & 30 deletions example/src/navigators/InitializeApp.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,27 @@
import React, { useEffect, useRef, useState } from "react";
import { AppState, StatusBar } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import React, { useCallback, useState } from "react";
import { StatusBar } from "react-native";

import { AppLoader } from "@/components";
import { Splash } from "@/screens/others";
import { useAppDispatch } from "@/stores/types";
import { getAppState, getTaskState } from "@/stores/slices";
import { useAppSelector } from "@/stores/types";
import { appLoaderHolder } from "@/utils/holder";
import { enableScreens } from "react-native-screens";

type InitializeAppType = {};
type InitializeAppProps = {
navigationReady: boolean;
};

const InitializeApp = (props: InitializeAppType) => {
const dispatch = useAppDispatch();
const insets = useSafeAreaInsets();
const InitializeApp = ({ navigationReady }: InitializeAppProps) => {
const { isFirstTimeLaunch } = useAppSelector(getAppState);
const { initialLoadSettled } = useAppSelector(getTaskState);
const [visibleBootSplash, setVisibleBootSplash] = useState(true);

const appState = useRef(AppState.currentState);

enableScreens();
useEffect(() => {
const subscription = AppState.addEventListener("change", (nextAppState) => {
if (
appState.current.match(/inactive|background/) &&
nextAppState === "active"
) {
onAppState();
}
appState.current = nextAppState;
});

return () => subscription.remove();
}, []);

const onAppState = () => {};
const appReady =
navigationReady && (isFirstTimeLaunch || initialLoadSettled);

const onBootSplashCompleted = async () => {
const onBootSplashCompleted = useCallback(() => {
setVisibleBootSplash(false);
};
}, []);

return (
<>
Expand All @@ -48,7 +33,9 @@ const InitializeApp = (props: InitializeAppType) => {
showHideTransition={"fade"}
/>

{visibleBootSplash && <Splash onAnimationEnd={onBootSplashCompleted} />}
{visibleBootSplash && (
<Splash ready={appReady} onAnimationEnd={onBootSplashCompleted} />
)}

<AppLoader ref={appLoaderHolder} />
</>
Expand Down
13 changes: 9 additions & 4 deletions example/src/navigators/RootNavigator.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import React from "react";
import React, { useState } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { MenuProvider } from "react-native-popup-menu";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import { NavigationContainer } from "@react-navigation/native";
import { NavigationContainer } from "expo-router/react-navigation";

import { AppStyles } from "@/styles";
import { navigationRef } from "@/utils/holder";
import InitializeApp from "./InitializeApp";
import { RootStack } from "./stack";

const RootNavigator = () => {
const [navigationReady, setNavigationReady] = useState(false);

return (
<GestureHandlerRootView style={[AppStyles.fill]}>
<MenuProvider>
<BottomSheetModalProvider>
<NavigationContainer ref={navigationRef}>
<NavigationContainer
ref={navigationRef}
onReady={() => setNavigationReady(true)}
>
<RootStack />
<InitializeApp />
</NavigationContainer>
<InitializeApp navigationReady={navigationReady} />
</BottomSheetModalProvider>
</MenuProvider>
</GestureHandlerRootView>
Expand Down
4 changes: 1 addition & 3 deletions example/src/navigators/global/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { NativeStackScreenProps } from '@/navigators/native-stack';
import { RootStackParamList } from '../stack/root';

declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}

type RootStackNavigationProps = NativeStackScreenProps<RootStackParamList>;

type RootStackScreenProps<T extends keyof RootStackParamList> =
Expand Down
5 changes: 5 additions & 0 deletions example/src/navigators/native-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Expo Router SDK 56+ owns the React Navigation runtime used by the app.
// Keep this compatibility import centralized because native-stack has no
// public codemod target while the app still uses an explicit NavigationContainer.
export { createNativeStackNavigator } from "expo-router/build/react-navigation/native-stack";
export type { NativeStackScreenProps } from "expo-router";
2 changes: 1 addition & 1 deletion example/src/navigators/stack/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createNativeStackNavigator } from '@/navigators/native-stack';
import { RoutesAuthStack } from '../routes';
import { ForgotPassword, SignIn, SignUp } from '@/screens';

Expand Down
13 changes: 2 additions & 11 deletions example/src/navigators/stack/bottom-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { BlurView } from "@react-native-community/blur";
import {
BottomTabScreenProps,
createBottomTabNavigator,
} from "@react-navigation/bottom-tabs";
import { CompositeScreenProps } from "@react-navigation/native";
import { NativeStackScreenProps } from "@react-navigation/native-stack";
} from "expo-router/js-tabs";
import React from "react";
import { StyleSheet } from "react-native";

Expand All @@ -20,12 +17,6 @@ export type BottomTabStackParamsList = {
[RoutesBottomTabStack.COMPLETED]: undefined;
};

export type RootTabScreenProps<Screen extends keyof BottomTabStackParamsList> =
CompositeScreenProps<
BottomTabScreenProps<BottomTabStackParamsList, Screen>,
NativeStackScreenProps<BottomTabStackParamsList>
>;

const Tab = createBottomTabNavigator<BottomTabStackParamsList>();

const BottomTabStack = () => {
Expand All @@ -39,7 +30,7 @@ const BottomTabStack = () => {
<BlurView
style={[
{
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
backgroundColor: "transparent",
},
]}
Expand Down
2 changes: 1 addition & 1 deletion example/src/navigators/stack/main.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createNativeStackNavigator } from '@/navigators/native-stack';
import { RoutesMainStack } from '../routes';
import { EnterCategory, EnterTaskify, Taskify } from '@/screens';
import { CategoryResponseModel, TaskResponseModel } from '@/api/types';
Expand Down
2 changes: 1 addition & 1 deletion example/src/navigators/stack/other.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createNativeStackNavigator } from '@/navigators/native-stack';
import { RoutesOtherStack } from '../routes';
import { Onboard, Welcome } from '@/screens';

Expand Down
6 changes: 3 additions & 3 deletions example/src/navigators/stack/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NavigatorScreenParams } from '@react-navigation/native';
import { createNativeStackNavigator } from '@/navigators/native-stack';
import { NavigatorScreenParams } from 'expo-router/react-navigation';

import { getAppState } from '@/stores/slices';
import { useAppSelector } from '@/stores/types';
Expand All @@ -24,7 +24,7 @@ export type RootStackParamList = {
| undefined;
};

const Root = createNativeStackNavigator<ReactNavigation.RootParamList>();
const Root = createNativeStackNavigator<RootStackParamList>();

const RootStack = () => {
const { isFirstTimeLaunch } = useAppSelector(getAppState);
Expand Down
2 changes: 1 addition & 1 deletion example/src/screens/auth/ForgotPassword/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useRef } from 'react';
import { ScrollView, TouchableOpacity, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useNavigation } from "expo-router/react-navigation";

import { Button, Header, Input } from '@/components';
import { InputRef } from '@/components/Input/type';
Expand Down
2 changes: 1 addition & 1 deletion example/src/screens/auth/SignIn/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useRef } from 'react';
import { ScrollView, TouchableOpacity, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { useNavigation } from "expo-router/react-navigation";

import { Button, Image, Input } from '@/components';
import { InputRef } from '@/components/Input/type';
Expand Down
Loading