diff options
| author | Akshay Nair <phenax5@gmail.com> | 2024-09-27 23:58:15 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2024-09-29 16:16:39 +0530 |
| commit | 5d456899ea444301870e1d3c721d139c9251c531 (patch) | |
| tree | bfb9d7a2d013be8d43a90bd4714147258ee3ddd6 | |
| parent | dda041a9aebc23f7a0576fc4030d06c71fa370d2 (diff) | |
| download | daft-launcher-5d456899ea444301870e1d3c721d139c9251c531.tar.gz daft-launcher-5d456899ea444301870e1d3c721d139c9251c531.zip | |
Fix too many updates issue with useFavorites + ui changes
| -rw-r--r-- | .prettierrc.js | 2 | ||||
| -rw-r--r-- | App.tsx | 20 | ||||
| -rw-r--r-- | android/app/build.gradle | 2 | ||||
| -rwxr-xr-x | bun.lockb | bin | 397425 -> 398223 bytes | |||
| -rw-r--r-- | flake.lock | 6 | ||||
| -rw-r--r-- | justfile | 6 | ||||
| -rw-r--r-- | package.json | 3 | ||||
| -rw-r--r-- | src/components/Favorites.tsx | 53 | ||||
| -rw-r--r-- | src/hooks/useFavorites.ts | 35 | ||||
| -rw-r--r-- | src/hooks/useStableCallback.ts | 9 | ||||
| -rw-r--r-- | src/screens/AppList.tsx | 37 |
11 files changed, 98 insertions, 75 deletions
diff --git a/.prettierrc.js b/.prettierrc.js index 2b54074..64b0b0f 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,5 +1,5 @@ module.exports = { - arrowParens: 'avoid', + arrowParens: 'always', bracketSameLine: true, bracketSpacing: false, singleQuote: true, @@ -6,30 +6,32 @@ import {AppList} from './src/screens/AppList'; import {Home} from './src/screens/Home'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; -function App(): React.JSX.Element { - const [apps, setApps] = useState(() => InstalledApps.getSortedApps()); - const refreshApps = () => setApps(InstalledApps.getSortedApps()); +const App: React.FC = () => { + const [apps, _setApps] = useState(() => InstalledApps.getSortedApps()); const [screenIndex, setScreenIndex] = useState(0); + // const refreshApps = () => setApps(InstalledApps.getSortedApps()); + + // useEffect(() => { + // refreshApps(); + // }, [screenIndex]); + return ( <GestureHandlerRootView> <SafeAreaView className="flex flex-1 bg-slate-100 dark:bg-black"> <Swiper - autoplay={false} horizontal + autoplay={false} loop={false} - loadMinimal dot={<></>} activeDot={<></>} - index={screenIndex} onIndexChanged={setScreenIndex}> <Home apps={apps} /> - - <AppList apps={apps} screenIndex={screenIndex} /> + <AppList apps={apps} isActive={screenIndex === 1} /> </Swiper> </SafeAreaView> </GestureHandlerRootView> ); -} +}; export default App; diff --git a/android/app/build.gradle b/android/app/build.gradle index 378d7b8..57743c6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -56,7 +56,7 @@ react { /** * Set this to true to Run Proguard on Release builds to minify the Java bytecode. */ -def enableProguardInReleaseBuilds = false +def enableProguardInReleaseBuilds = true /** * The preferred build flavor of JavaScriptCore (JSC) Binary files differ@@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1715346633, - "narHash": "sha256-A9vSieOHR7B41QoWZcb7fEY7r29E4Vq3liXE0h0edf0=", + "lastModified": 1726871744, + "narHash": "sha256-V5LpfdHyQkUF7RfOaDPrZDP+oqz88lTJrMT1+stXNwo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d42c1c8d447a388e1f2776d22c77f5642d703da6", + "rev": "a1d92660c6b3b7c26fb883500a80ea9d33321be2", "type": "github" }, "original": { @@ -1,9 +1,11 @@ android-setup: - #!/usr/bin/env sh sdkmanager "$MY_ANDROID_AVD_PKG" && \ avdmanager create avd -n channels -k "$MY_ANDROID_AVD_PKG" --device 26 --force; emu: emulator -gpu swiftshader_indirect @channels - # emulator -gpu mode @channels + +build: + bun run react-native build-android --mode release + diff --git a/package.json b/package.json index 35f548c..6006dab 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "react-native-launcher-kit": "^1.0.4", "react-native-reanimated": "^3.11.0", "react-native-swiper": "^1.6.0", - "react-native-vector-icons": "^10.1.0", + "react-native-vector-icons": "^10.2.0", "tailwindcss": "3.3.2" }, "devDependencies": { @@ -32,6 +32,7 @@ "@react-native/metro-config": "0.74.83", "@react-native/typescript-config": "0.74.83", "@types/react": "^18.2.6", + "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.6.3", "eslint": "^8.19.0", diff --git a/src/components/Favorites.tsx b/src/components/Favorites.tsx index 712a07f..85ab3c2 100644 --- a/src/components/Favorites.tsx +++ b/src/components/Favorites.tsx @@ -1,10 +1,10 @@ import React, {useCallback, useMemo} from 'react'; -import {ScrollView, Text, TouchableOpacity, View} from 'react-native'; +import {Text, TouchableOpacity, View} from 'react-native'; import {AppDetail} from 'react-native-launcher-kit/typescript/Interfaces/InstalledApps'; import {useFavorites} from '../hooks/useFavorites'; import DraggableFlatList, { + DragEndParams, OpacityDecorator, - ScaleDecorator, ShadowDecorator, } from 'react-native-draggable-flatlist'; import Icon from 'react-native-vector-icons/MaterialIcons'; @@ -13,52 +13,49 @@ export const Favorites: React.FC<{apps: AppDetail[]}> = ({apps}) => { const {favoriteAppNames, setFavorites} = useFavorites(); const appByName = useMemo( - () => Object.fromEntries(apps.map(app => [app.packageName, app])), + () => Object.fromEntries(apps.map((app) => [app.packageName, app])), [apps], ); const favoriteApps = useMemo( - () => favoriteAppNames.map(name => appByName[name]).filter(Boolean), + () => favoriteAppNames.map((name) => appByName[name]).filter(Boolean), [appByName, favoriteAppNames], ); const onDragEnd = useCallback( - ({data}: {data: AppDetail[]}) => { - setFavorites(data.map(app => app.packageName)); + ({data}: DragEndParams<AppDetail>) => { + setFavorites(data.map((app) => app.packageName)); }, [setFavorites], ); return ( - <View> + <View className="px-4"> <DraggableFlatList data={favoriteApps} onDragEnd={onDragEnd} - keyExtractor={item => item.packageName} + keyExtractor={(item) => item.packageName} renderItem={({item: app, drag, isActive}) => ( <ShadowDecorator> - <ScaleDecorator> - <OpacityDecorator> - <View - className={`py-4 px-2 flex-row ${ - isActive ? 'bg-gray-700 opacity-60' : '' - }`}> - <TouchableOpacity - onLongPress={drag} - disabled={isActive} - delayLongPress={500}> - <View className={'py-1 pl-3 pr-3'}> - <Icon name="drag-indicator" size={21} color="#666" /> - </View> - </TouchableOpacity> - - <Text className="text-lg">{app.label}</Text> - </View> - </OpacityDecorator> - </ScaleDecorator> + <OpacityDecorator> + <View + className={`py-2 px-2 flex-row w-full ${ + isActive ? 'bg-gray-200 opacity-60' : '' + }`}> + <Text className="text-lg flex-1">{app.label}</Text> + <TouchableOpacity + onLongPress={drag} + disabled={isActive} + delayLongPress={500}> + <View className={'py-1'}> + <Icon name="drag-indicator" size={21} color="#aaa" /> + </View> + </TouchableOpacity> + </View> + </OpacityDecorator> </ShadowDecorator> )} - renderPlaceholder={() => <View className="flex-1 bg-gray-700" />} + renderPlaceholder={() => <View className="flex-1 bg-gray-200" />} /> </View> ); diff --git a/src/hooks/useFavorites.ts b/src/hooks/useFavorites.ts index 54d77fd..619817c 100644 --- a/src/hooks/useFavorites.ts +++ b/src/hooks/useFavorites.ts @@ -1,11 +1,12 @@ -import {useCallback, useEffect, useState} from 'react'; +import {useEffect, useState} from 'react'; import {useAsyncStorage} from '@react-native-async-storage/async-storage'; +import {useStableCallback} from './useStableCallback'; export const useFavorites = () => { const favoritesStorage = useAsyncStorage('favorites'); const [favoriteAppNames, setFavoriteAppNames] = useState<string[]>([]); - const getFavoriteAppNames = useCallback(async (): Promise<string[]> => { + const getFavoriteAppNames = useStableCallback(async (): Promise<string[]> => { try { const result = await favoritesStorage.getItem(); const apps = result ? JSON.parse(result) : []; @@ -15,28 +16,22 @@ export const useFavorites = () => { console.error(err); return []; } - }, [favoritesStorage]); + }); - const refreshFavorites = useCallback(async () => { + const refreshFavorites = useStableCallback(async () => { getFavoriteAppNames().then(setFavoriteAppNames); - }, [getFavoriteAppNames]); + }); - const setFavorites = useCallback( - async (names: string[]) => { - const newNames = [...new Set(names)]; - setFavoriteAppNames(newNames); - await favoritesStorage.setItem(JSON.stringify(newNames)); - }, - [favoritesStorage], - ); + const setFavorites = useStableCallback(async (names: string[]) => { + const newNames = [...new Set(names)]; + setFavoriteAppNames(newNames); + await favoritesStorage.setItem(JSON.stringify(newNames)); + }); - const addToFavorites = useCallback( - async (packageName: string) => { - const names = await getFavoriteAppNames(); - setFavorites([...names, packageName]); - }, - [getFavoriteAppNames, setFavorites], - ); + const addToFavorites = useStableCallback(async (packageName: string) => { + const names = await getFavoriteAppNames(); + setFavorites([...names, packageName]); + }); useEffect(() => { refreshFavorites(); diff --git a/src/hooks/useStableCallback.ts b/src/hooks/useStableCallback.ts new file mode 100644 index 0000000..98e6c0d --- /dev/null +++ b/src/hooks/useStableCallback.ts @@ -0,0 +1,9 @@ +import {useCallback, useRef} from 'react'; + +export const useStableCallback = <Args extends any[], Ret>( + callback: (...a: Args) => Ret, +): ((...a: Args) => Ret) => { + const cb = useRef(callback); + cb.current = callback; + return useCallback((...args: Args) => cb.current(...args), [cb]); +}; diff --git a/src/screens/AppList.tsx b/src/screens/AppList.tsx index ec83124..81c8f4f 100644 --- a/src/screens/AppList.tsx +++ b/src/screens/AppList.tsx @@ -1,12 +1,18 @@ import {matchSorter} from 'match-sorter'; -import React, {useMemo, useState} from 'react'; +import React, {useEffect, useMemo, useRef, useState} from 'react'; import {ScrollView, Text, TextInput, View} from 'react-native'; import {AppDetail} from 'react-native-launcher-kit/typescript/Interfaces/InstalledApps'; +import {useFavorites} from '../hooks/useFavorites'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import Icon from 'react-native-vector-icons/MaterialIcons'; -export const AppList: React.FC<{apps: AppDetail[]; screenIndex: number}> = ({ +export const AppList: React.FC<{apps: AppDetail[]; isActive: boolean}> = ({ apps, - screenIndex, + isActive, }) => { + const {addToFavorites} = useFavorites(); + + const textInputRef = useRef<TextInput>(null); const [searchText, setSearchText] = useState(''); const filteredApps = useMemo(() => { @@ -15,14 +21,22 @@ export const AppList: React.FC<{apps: AppDetail[]; screenIndex: number}> = ({ return matchSorter(apps, searchText, {keys: ['label', 'packageName']}); }, [apps, searchText]); + // Autofocus + useEffect(() => { + if (!textInputRef.current) return; + if (isActive) TextInput.State.focusTextInput(textInputRef.current); + }, [isActive]); + return ( <View> - <View> + <View className="flex justify-between flex-row items-center gap-2"> + <Icon name="search" size={21} color="#aaa" /> <TextInput - key={screenIndex} - autoFocus + ref={textInputRef} + autoFocus={false} autoCorrect={false} value={searchText} + className="flex-1" onChangeText={setSearchText} /> </View> @@ -31,10 +45,13 @@ export const AppList: React.FC<{apps: AppDetail[]; screenIndex: number}> = ({ <View className="flex-1"> {filteredApps.map((app, index) => ( <View className="mt-4 px-4" key={app.packageName + index}> - <Text className="text-md font-bold dark:text-white"> - {app.label} - </Text> - <Text className="text-xs">{app.packageName}</Text> + <TouchableOpacity + onLongPress={() => addToFavorites(app.packageName)}> + <Text className="text-md font-bold dark:text-white"> + {app.label} + </Text> + <Text className="text-xs">{app.packageName}</Text> + </TouchableOpacity> </View> ))} </View> |
