diff options
| -rw-r--r-- | App.tsx | 29 | ||||
| -rw-r--r-- | android/app/src/main/AndroidManifest.xml | 2 | ||||
| -rwxr-xr-x | bun.lockb | bin | 398223 -> 398665 bytes | |||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | src/View.tsx | 29 | ||||
| -rw-r--r-- | src/components/AppMenu.tsx | 79 | ||||
| -rw-r--r-- | src/components/Clock.tsx | 4 | ||||
| -rw-r--r-- | src/components/Favorites.tsx | 49 | ||||
| -rw-r--r-- | src/hooks/useFavorites.ts | 14 | ||||
| -rw-r--r-- | src/screens/AppList.tsx | 22 | ||||
| -rw-r--r-- | src/screens/Home.tsx | 7 |
11 files changed, 173 insertions, 63 deletions
@@ -1,34 +1,13 @@ -import React, {useState} from 'react'; +import React from 'react'; import {SafeAreaView} from 'react-native'; -import {InstalledApps} from 'react-native-launcher-kit'; -import Swiper from 'react-native-swiper'; -import {AppList} from './src/screens/AppList'; -import {Home} from './src/screens/Home'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; +import {View} from './src/View'; 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 - horizontal - autoplay={false} - loop={false} - dot={<></>} - activeDot={<></>} - onIndexChanged={setScreenIndex}> - <Home apps={apps} /> - <AppList apps={apps} isActive={screenIndex === 1} /> - </Swiper> + <SafeAreaView className="flex flex-1 bg-black"> + <View /> </SafeAreaView> </GestureHandlerRootView> ); diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4122f36..2a2071e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" /> <application android:name=".MainApplication" Binary files differdiff --git a/package.json b/package.json index 6006dab..ba53403 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "test": "jest" }, "dependencies": { + "@angelkrak/react-native-intent-launcher": "^1.0.2", "@react-native-async-storage/async-storage": "^1.23.1", "match-sorter": "^6.3.4", "nativewind": "^2.0.11", diff --git a/src/View.tsx b/src/View.tsx new file mode 100644 index 0000000..c5084f2 --- /dev/null +++ b/src/View.tsx @@ -0,0 +1,29 @@ +import React, {useState} from 'react'; +import {InstalledApps} from 'react-native-launcher-kit'; +import Swiper from 'react-native-swiper'; +import {AppList} from './screens/AppList'; +import {Home} from './screens/Home'; + +export const View: React.FC = () => { + const [apps, _setApps] = useState(() => InstalledApps.getSortedApps()); + const [screenIndex, setScreenIndex] = useState(0); + + // const refreshApps = () => setApps(InstalledApps.getSortedApps()); + + // useEffect(() => { + // refreshApps(); + // }, [screenIndex]); + + return ( + <Swiper + horizontal + autoplay={false} + loop={false} + dot={<></>} + activeDot={<></>} + onIndexChanged={setScreenIndex}> + <Home apps={apps} active={screenIndex === 0} /> + <AppList apps={apps} active={screenIndex === 1} /> + </Swiper> + ); +}; diff --git a/src/components/AppMenu.tsx b/src/components/AppMenu.tsx new file mode 100644 index 0000000..ec0d684 --- /dev/null +++ b/src/components/AppMenu.tsx @@ -0,0 +1,79 @@ +import {Text, View, Modal, TouchableOpacity, Pressable} from 'react-native'; +import {AppDetail} from 'react-native-launcher-kit/typescript/Interfaces/InstalledApps'; +import {useFavorites} from '../hooks/useFavorites'; +import React, {useState} from 'react'; +import IntentLauncher from '@angelkrak/react-native-intent-launcher'; + +export const AppMenu: React.FC<React.PropsWithChildren<{app: AppDetail}>> = ({ + app, + children, +}) => { + const [isOpen, setIsOpen] = useState(false); + const {addToFavorites, isFavorite, removeFromFavorites} = useFavorites(); + + const menuPress = (fn: () => Promise<void> | void) => async () => { + await fn(); + setIsOpen(false); + }; + + const menuItems = [ + isFavorite(app.packageName) + ? { + label: 'Remove from favorites', + onPress: menuPress(() => removeFromFavorites(app.packageName)), + } + : { + label: 'Add to favorites', + onPress: menuPress(() => addToFavorites(app.packageName)), + }, + { + label: 'App info', + onPress: menuPress(() => + IntentLauncher.startActivity({ + action: 'android.settings.APPLICATION_DETAILS_SETTINGS', + data: 'package:' + app.packageName, + }), + ), + }, + { + label: 'Uninstall', + onPress: menuPress(() => + IntentLauncher.startActivity({ + action: 'android.intent.action.DELETE', + data: 'package:' + app.packageName, + }), + ), + }, + ]; + + return ( + <> + <TouchableOpacity onLongPress={() => setIsOpen(true)}> + {children} + </TouchableOpacity> + + <Modal + visible={isOpen} + transparent={true} + animationType="none" + onRequestClose={() => setIsOpen(false)}> + <TouchableOpacity className="flex-1" onPress={() => setIsOpen(false)}> + <View className="flex justify-center items-center h-full"> + <View className="shadow-lg bg-gray-900 w-2/3"> + {menuItems.map((menuItem) => ( + <Pressable + key={menuItem.label} + onPress={menuItem.onPress} + className="py-3 px-4 border-b border-gray-800 last:border-b-0"> + <Text className="text-lg text-gray-300"> + {menuItem.label} + </Text> + </Pressable> + ))} + </View> + </View> + </TouchableOpacity> + </Modal> + </> + ); +}; diff --git a/src/components/Clock.tsx b/src/components/Clock.tsx index 113201f..619a31b 100644 --- a/src/components/Clock.tsx +++ b/src/components/Clock.tsx @@ -14,7 +14,7 @@ export const Clock: React.FC = () => { const formatter = new Intl.DateTimeFormat('en', { hour: 'numeric', minute: 'numeric', - second: 'numeric', + // second: 'numeric', }); return formatter.format(date); }, [date]); @@ -30,7 +30,7 @@ export const Clock: React.FC = () => { return ( <View className="p-6"> - <Text className="text-3xl font-bold">{timeText}</Text> + <Text className="text-4xl font-bold">{timeText}</Text> <Text className="text-lg">{dateText}</Text> </View> ); diff --git a/src/components/Favorites.tsx b/src/components/Favorites.tsx index 85ab3c2..c2adae7 100644 --- a/src/components/Favorites.tsx +++ b/src/components/Favorites.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {Text, TouchableOpacity, View} from 'react-native'; import {AppDetail} from 'react-native-launcher-kit/typescript/Interfaces/InstalledApps'; import {useFavorites} from '../hooks/useFavorites'; @@ -8,9 +8,13 @@ import DraggableFlatList, { ShadowDecorator, } from 'react-native-draggable-flatlist'; import Icon from 'react-native-vector-icons/MaterialIcons'; +import {AppMenu} from './AppMenu'; -export const Favorites: React.FC<{apps: AppDetail[]}> = ({apps}) => { - const {favoriteAppNames, setFavorites} = useFavorites(); +export const Favorites: React.FC<{apps: AppDetail[]; active: boolean}> = ({ + apps, + active, +}) => { + const {favoriteAppNames, setFavorites, refreshFavorites} = useFavorites(); const appByName = useMemo( () => Object.fromEntries(apps.map((app) => [app.packageName, app])), @@ -29,8 +33,13 @@ export const Favorites: React.FC<{apps: AppDetail[]}> = ({apps}) => { [setFavorites], ); + useEffect(() => { + if (!active) return; + refreshFavorites(); + }, [active, refreshFavorites]); + return ( - <View className="px-4"> + <View className="px-4 py-1"> <DraggableFlatList data={favoriteApps} onDragEnd={onDragEnd} @@ -38,24 +47,26 @@ export const Favorites: React.FC<{apps: AppDetail[]}> = ({apps}) => { renderItem={({item: app, drag, isActive}) => ( <ShadowDecorator> <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> + <AppMenu app={app}> + <View + className={`py-3 px-2 flex-row w-full ${ + isActive ? 'bg-gray-950 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="#444" /> + </View> + </TouchableOpacity> + </View> + </AppMenu> </OpacityDecorator> </ShadowDecorator> )} - renderPlaceholder={() => <View className="flex-1 bg-gray-200" />} + renderPlaceholder={() => <View className="flex-1 bg-gray-950" />} /> </View> ); diff --git a/src/hooks/useFavorites.ts b/src/hooks/useFavorites.ts index 619817c..46b6e77 100644 --- a/src/hooks/useFavorites.ts +++ b/src/hooks/useFavorites.ts @@ -24,24 +24,34 @@ export const useFavorites = () => { const setFavorites = useStableCallback(async (names: string[]) => { const newNames = [...new Set(names)]; - setFavoriteAppNames(newNames); await favoritesStorage.setItem(JSON.stringify(newNames)); + refreshFavorites(); }); const addToFavorites = useStableCallback(async (packageName: string) => { const names = await getFavoriteAppNames(); setFavorites([...names, packageName]); }); + const removeFromFavorites = useStableCallback(async (packageName: string) => { + const names = await getFavoriteAppNames(); + setFavorites(names.filter((name) => name !== packageName)); + }); + + const isFavorite = useStableCallback((packageName: string) => + favoriteAppNames.includes(packageName), + ); useEffect(() => { refreshFavorites(); }, [refreshFavorites]); return { + addToFavorites, favoriteAppNames, getFavoriteAppNames, - addToFavorites, + isFavorite, refreshFavorites, + removeFromFavorites, setFavorites, }; }; diff --git a/src/screens/AppList.tsx b/src/screens/AppList.tsx index 81c8f4f..e6b3e6e 100644 --- a/src/screens/AppList.tsx +++ b/src/screens/AppList.tsx @@ -2,16 +2,13 @@ import {matchSorter} from 'match-sorter'; 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'; +import {AppMenu} from '../components/AppMenu'; -export const AppList: React.FC<{apps: AppDetail[]; isActive: boolean}> = ({ +export const AppList: React.FC<{apps: AppDetail[]; active: boolean}> = ({ apps, - isActive, + active, }) => { - const {addToFavorites} = useFavorites(); - const textInputRef = useRef<TextInput>(null); const [searchText, setSearchText] = useState(''); @@ -24,8 +21,8 @@ export const AppList: React.FC<{apps: AppDetail[]; isActive: boolean}> = ({ // Autofocus useEffect(() => { if (!textInputRef.current) return; - if (isActive) TextInput.State.focusTextInput(textInputRef.current); - }, [isActive]); + if (active) TextInput.State.focusTextInput(textInputRef.current); + }, [active]); return ( <View> @@ -44,15 +41,14 @@ export const AppList: React.FC<{apps: AppDetail[]; isActive: boolean}> = ({ <ScrollView contentInsetAdjustmentBehavior="automatic"> <View className="flex-1"> {filteredApps.map((app, index) => ( - <View className="mt-4 px-4" key={app.packageName + index}> - <TouchableOpacity - onLongPress={() => addToFavorites(app.packageName)}> + <AppMenu app={app} key={app.packageName + index}> + <View className="mt-4 px-4"> <Text className="text-md font-bold dark:text-white"> {app.label} </Text> <Text className="text-xs">{app.packageName}</Text> - </TouchableOpacity> - </View> + </View> + </AppMenu> ))} </View> </ScrollView> diff --git a/src/screens/Home.tsx b/src/screens/Home.tsx index 00a4b1f..7bd1a3f 100644 --- a/src/screens/Home.tsx +++ b/src/screens/Home.tsx @@ -4,11 +4,14 @@ import {Clock} from '../components/Clock'; import {AppDetail} from 'react-native-launcher-kit/typescript/Interfaces/InstalledApps'; import {Favorites} from '../components/Favorites'; -export const Home: React.FC<{apps: AppDetail[]}> = ({apps}) => { +export const Home: React.FC<{apps: AppDetail[]; active: boolean}> = ({ + apps, + active, +}) => { return ( <View> <Clock /> - <Favorites apps={apps} /> + <Favorites apps={apps} active={active} /> </View> ); }; |
