Skip to content

Commit

Permalink
Fix: update keyboard avoidance for android 15 and improve modal behav…
Browse files Browse the repository at this point in the history
…ior (#167)

* fix: resolve keyboard avoiding issue for ChatInput

* fix: resolve floating keyboard issue for chat view

* fix: resolve search bar keyboard avoiding behaviour

* fix: resolve scroll and TouchableOpacity conflict in search view

* feat: add load more for hf search

* feat: add warnings to hf model details card

* chore: bump targetSdkVersion to 35
  • Loading branch information
a-ghorbani authored Jan 11, 2025
1 parent 8111bca commit 818787c
Show file tree
Hide file tree
Showing 29 changed files with 711 additions and 397 deletions.
105 changes: 54 additions & 51 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
gestureHandlerRootHOC,
GestureHandlerRootView,
} from 'react-native-gesture-handler';
import {KeyboardProvider} from 'react-native-keyboard-controller';

import {useTheme} from './src/hooks';
import {Theme} from './src/utils/types';
Expand Down Expand Up @@ -46,58 +47,60 @@ const App = observer(() => {
return (
<GestureHandlerRootView style={styles.root}>
<SafeAreaProvider>
<BottomSheetModalProvider>
<PaperProvider theme={theme}>
<NavigationContainer>
<Drawer.Navigator
useLegacyImplementation={false}
screenOptions={{
drawerStyle: {
width: screenWidth * 0.8,
},
headerStyle: {
backgroundColor: theme.colors.background,
},
headerTintColor: theme.colors.onBackground,
}}
drawerContent={props => <SidebarContent {...props} />}>
<Drawer.Screen
name="Chat"
component={gestureHandlerRootHOC(ChatScreen)}
options={{
title: chatTitle,
headerRight: () => <HeaderRight />,
headerStyle: chatSessionStore.shouldShowHeaderDivider
? styles.headerWithDivider
: styles.headerWithoutDivider,
<KeyboardProvider statusBarTranslucent navigationBarTranslucent>
<BottomSheetModalProvider>
<PaperProvider theme={theme}>
<NavigationContainer>
<Drawer.Navigator
useLegacyImplementation={false}
screenOptions={{
drawerStyle: {
width: screenWidth * 0.8,
},
headerStyle: {
backgroundColor: theme.colors.background,
},
headerTintColor: theme.colors.onBackground,
}}
/>
<Drawer.Screen
name="Models"
component={gestureHandlerRootHOC(ModelsScreen)}
options={{
headerRight: () => <ModelsHeaderRight />,
headerStyle: styles.headerWithoutDivider,
}}
/>
<Drawer.Screen
name="Settings"
component={gestureHandlerRootHOC(SettingsScreen)}
options={{
headerStyle: styles.headerWithoutDivider,
}}
/>
<Drawer.Screen
name="Benchmark"
component={gestureHandlerRootHOC(BenchmarkScreen)}
options={{
headerStyle: styles.headerWithoutDivider,
}}
/>
</Drawer.Navigator>
</NavigationContainer>
</PaperProvider>
</BottomSheetModalProvider>
drawerContent={props => <SidebarContent {...props} />}>
<Drawer.Screen
name="Chat"
component={gestureHandlerRootHOC(ChatScreen)}
options={{
title: chatTitle,
headerRight: () => <HeaderRight />,
headerStyle: chatSessionStore.shouldShowHeaderDivider
? styles.headerWithDivider
: styles.headerWithoutDivider,
}}
/>
<Drawer.Screen
name="Models"
component={gestureHandlerRootHOC(ModelsScreen)}
options={{
headerRight: () => <ModelsHeaderRight />,
headerStyle: styles.headerWithoutDivider,
}}
/>
<Drawer.Screen
name="Settings"
component={gestureHandlerRootHOC(SettingsScreen)}
options={{
headerStyle: styles.headerWithoutDivider,
}}
/>
<Drawer.Screen
name="Benchmark"
component={gestureHandlerRootHOC(BenchmarkScreen)}
options={{
headerStyle: styles.headerWithoutDivider,
}}
/>
</Drawer.Navigator>
</NavigationContainer>
</PaperProvider>
</BottomSheetModalProvider>
</KeyboardProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
);
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<uses-permission android:name="android.permission.INTERNET" />

<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="34" />
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="35" />

<application
android:name=".MainApplication"
Expand Down
39 changes: 32 additions & 7 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ PODS:
- hermes-engine (0.76.3):
- hermes-engine/Pre-built (= 0.76.3)
- hermes-engine/Pre-built (0.76.3)
- llama-rn (0.4.7-2):
- llama-rn (0.4.7-3):
- React-Core
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
Expand Down Expand Up @@ -1431,9 +1431,30 @@ PODS:
- Yoga
- react-native-get-random-values (1.11.0):
- React-Core
- react-native-keyboard-controller (1.15.2):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-render-html (6.3.4):
- React-Core
- react-native-safe-area-context (4.14.0):
- react-native-safe-area-context (4.14.1):
- React-Core
- react-native-slider (4.5.5):
- DoubleConversion
Expand Down Expand Up @@ -1888,7 +1909,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (4.3.0):
- RNScreens (4.4.0):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1985,6 +2006,7 @@ DEPENDENCIES:
- react-native-config (from `../node_modules/react-native-config`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- react-native-keyboard-controller (from `../node_modules/react-native-keyboard-controller`)
- react-native-render-html (from `../node_modules/react-native-render-html`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
Expand Down Expand Up @@ -2132,6 +2154,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-picker"
react-native-get-random-values:
:path: "../node_modules/react-native-get-random-values"
react-native-keyboard-controller:
:path: "../node_modules/react-native-keyboard-controller"
react-native-render-html:
:path: "../node_modules/react-native-render-html"
react-native-safe-area-context:
Expand Down Expand Up @@ -2240,7 +2264,7 @@ SPEC CHECKSUMS:
GoogleAppMeasurement: ee5c2d2242816773fbf79e5b0563f5355ef1c315
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
hermes-engine: 0555a84ea495e8e3b4bde71b597cd87fbb382888
llama-rn: 07deb682d25cca8050d69323c05c284d2de1e88a
llama-rn: 3672635ffd08fee27e8646aae92640f660977dce
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RCT-Folly: 84578c8756030547307e4572ab1947de1685c599
Expand Down Expand Up @@ -2276,8 +2300,9 @@ SPEC CHECKSUMS:
react-native-config: ea75335a7cca1d3326de1da384227e580a7c082e
react-native-document-picker: 530879d9e89b490f0954bcc4ab697c5b5e35d659
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
react-native-keyboard-controller: b2535550c830b58d647b21a4e9ec6f10552d4ae5
react-native-render-html: 5afc4751f1a98621b3009432ef84c47019dcb2bd
react-native-safe-area-context: b13be9714d9771fbde0120bc519c963484de3a71
react-native-safe-area-context: 758e894ca5a9bd1868d2a9cfbca7326a2b6bf9dc
react-native-slider: fc7f35c082abec47e341dfe43657a1c26f38db2f
React-nativeconfig: aeed6e2a8ac02b2df54476afcc7c663416c12bf7
React-NativeModulesApple: c0783e5e21c71aa2764ac33120abc96208466fa6
Expand Down Expand Up @@ -2316,12 +2341,12 @@ SPEC CHECKSUMS:
RNGestureHandler: 5b24d10761754ad271b714e536c457fd89b17c54
RNReactNativeHapticFeedback: 00ba111b82aa266bb3ee1aa576831c2ea9a9dfad
RNReanimated: 929c26a706dfe1af8feee9f2cf78004394e4dd04
RNScreens: e21c8d32fe97737ecc30f1f21e7b6f69f341a1f5
RNScreens: 4a5ab20b324ed1e3fe3862796b8f8e0a6208c415
RNSVG: 6a529f4faed8be4ebfb00f1a29e25cb046d95e61
RNVectorIcons: 182892e7d1a2f27b52d3c627eca5d2665a22ee28
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 3deb2471faa9916c8a82dda2a22d3fba2620ad37

PODFILE CHECKSUM: 32a4153d1beca6e668aaf0a6895592c98472b30c
PODFILE CHECKSUM: 3a849f0fd1fa4567c5aecd29d88c9ab4cc38b8d2

COCOAPODS: 1.16.2
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFiles: ['./jest/setup.ts'],
transformIgnorePatterns: [
'node_modules/(?!(@flyerhq|@react-native|react-native|uuid|react-native-reanimated|react-native-gesture-handler|react-native-vector-icons|react-native-image-viewing|react-native-parsed-text|@react-navigation/.*|@react-native-masked-view/masked-view|react-native-linear-gradient|react-native-picker-select|react-native-paper)/)',
'node_modules/(?!(@flyerhq|@react-native|react-native|uuid|react-native-reanimated|react-native-gesture-handler|react-native-vector-icons|react-native-image-viewing|react-native-parsed-text|@react-navigation/.*|@react-native-masked-view/masked-view|react-native-linear-gradient|react-native-picker-select|react-native-paper|react-native-keyboard-controller)/)',
],
testMatch: [
'**/__tests__/**/*.test.[jt]s?(x)',
Expand Down
10 changes: 10 additions & 0 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import 'react-native-gesture-handler/jestSetup';

jest.mock('react-native-haptic-feedback');

jest.mock('react-native-keyboard-controller', () => {
const KeyboardControllerMock = require('react-native-keyboard-controller/jest');
return KeyboardControllerMock;
});

// Mock react-native-reanimated
//require('react-native-reanimated').setUpTests();
jest.mock('react-native-reanimated', () => {
Expand All @@ -28,6 +33,11 @@ jest.mock('react-native-reanimated', () => {
return Reanimated;
});

jest.mock('@react-navigation/elements', () => ({
...jest.requireActual('@react-navigation/elements'),
useHeaderHeight: jest.fn().mockReturnValue(56), // Provide a mock return value
}));

import {mockUiStore} from '../__mocks__/stores/uiStore';
import {mockHFStore} from '../__mocks__/stores/hfStore';
import {mockModelStore} from '../__mocks__/stores/modelStore';
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,17 @@
"react-native-get-random-values": "^1.11.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-image-viewing": "^0.2.2",
"react-native-keyboard-controller": "^1.15.2",
"react-native-linear-gradient": "^2.8.3",
"react-native-marked": "^6.0.4",
"react-native-modal": "^13.0.1",
"react-native-paper": "^5.12.3",
"react-native-paper": "^5.12.5",
"react-native-parsed-text": "^0.0.22",
"react-native-picker-select": "^9.1.3",
"react-native-reanimated": "^3.16.3",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^4.14.0",
"react-native-screens": "^4.3.0",
"react-native-screens": "^4.4.0",
"react-native-svg": "^15.9.0",
"react-native-vector-icons": "^10.1.0",
"uuid": "^10.0.0"
Expand Down
2 changes: 1 addition & 1 deletion src/api/__tests__/hf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('fetchModels', () => {
it('should fetch models with basic parameters', async () => {
const mockResponse = {
data: [{id: 'model1'}],
headers: {link: 'next-page-link'},
headers: {link: '<next-page-link>'},
};
mockedAxios.get.mockResolvedValueOnce(mockResponse);

Expand Down
18 changes: 15 additions & 3 deletions src/api/hf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function fetchModels({
limit,
full,
config,
nextPageUrl,
}: {
search?: string;
author?: string;
Expand All @@ -42,9 +43,10 @@ export async function fetchModels({
limit?: number;
full?: boolean;
config?: boolean;
nextPageUrl?: string;
}): Promise<HuggingFaceModelsResponse> {
try {
const response = await axios.get(urls.modelsList(), {
const response = await axios.get(nextPageUrl || urls.modelsList(), {
params: {
search,
author,
Expand All @@ -56,10 +58,20 @@ export async function fetchModels({
config,
},
});
// console.log('response.data: ', response.data);

const linkHeader = response.headers.link;
let nextLink = null;

if (linkHeader) {
const match = linkHeader.match(/<([^>]*)>/);
if (match) {
nextLink = match[1];
}
}

return {
models: response.data as HuggingFaceModel[],
nextLink: response.headers.link || null, // null if no pagination link is provided
nextLink,
};
} catch (error) {
console.error('Error fetching models:', error);
Expand Down
10 changes: 6 additions & 4 deletions src/components/ChatInput/__tests__/ChatInput.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {fireEvent, render} from '@testing-library/react-native';
import {fireEvent, render, waitFor} from '@testing-library/react-native';
import * as React from 'react';
import {ScrollView} from 'react-native';

Expand Down Expand Up @@ -116,7 +116,7 @@ describe('input', () => {
expect(textInput.props).toHaveProperty('value', '');
});

it('sends a text message if value is provided', () => {
it('sends a text message if value is provided', async () => {
expect.assertions(2);
const onSendPress = jest.fn();
const value = 'value';
Expand All @@ -133,9 +133,11 @@ describe('input', () => {
</UserContext.Provider>,
);
const textInput = getByPlaceholderText(l10n.en.inputPlaceholder);
fireEvent.changeText(textInput, 'text');
await waitFor(() => fireEvent.changeText(textInput, 'text')); // Wait for the input to update

const button = getByLabelText(l10n.en.sendButtonAccessibilityLabel);
fireEvent.press(button);
await waitFor(() => fireEvent.press(button)); // Wait for the press event to be processed

expect(onSendPress).toHaveBeenCalledWith({text: value, type: 'text'});
expect(textInput.props).toHaveProperty('value', value);
});
Expand Down
Loading

0 comments on commit 818787c

Please sign in to comment.