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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ node_modules/
dist/
web-build/
expo-env.d.ts
ios/
android/
/ios/
/android/

# Native
.kotlin/
Expand Down
7 changes: 4 additions & 3 deletions app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ module.exports = ({ config }) => ({
...(config.expo || {}),
name: "OpenBirding",
slug: "OpenBirding",
version: "1.6.1",
version: "1.6.2",
orientation: "portrait",
icon: "./assets/images/logo.png",
scheme: "openbirding",
userInterfaceStyle: "automatic",
newArchEnabled: true,
ios: {
infoPlist: {
ITSAppUsesNonExemptEncryption: false,
Expand Down Expand Up @@ -57,8 +56,10 @@ module.exports = ({ config }) => ({
],
"expo-sqlite",
"expo-web-browser",
"expo-font",
"expo-image",
],
experiments: { typedRoutes: true },
experiments: { typedRoutes: true, reactCompiler: true },
extra: {
router: {},
eas: { projectId: "2944a151-98b6-4d2a-9104-65facf9def35" },
Expand Down
193 changes: 100 additions & 93 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import tw from "@/lib/tw";
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";
import { QueryClientProvider } from "@tanstack/react-query";
import { useFonts } from "expo-font";
Expand All @@ -12,6 +11,7 @@ import "react-native-reanimated";

import DownloadOverlay from "@/components/DownloadOverlay";
import { useColorScheme } from "@/hooks/useColorScheme";
import { useManagedSplashScreen } from "@/hooks/useManagedSplashScreen";
import { initializeDatabase } from "@/lib/database";
import { queryClient } from "@/lib/queryClient";
import { ensureTaxonomyLoaded } from "@/lib/taxonomy";
Expand Down Expand Up @@ -41,9 +41,18 @@ export default function RootLayout() {

initDatabase();
ensureTaxonomyLoaded();
useLocationPermissionStore.getState().requestPermission();

const permissionTimeout = setTimeout(() => {
void useLocationPermissionStore.getState().requestPermission();
}, 300);

return () => {
clearTimeout(permissionTimeout);
};
}, []);

useManagedSplashScreen(loaded && (dbInitialized || dbError !== null), 500);

if (!loaded) {
return null;
}
Expand All @@ -64,100 +73,98 @@ export default function RootLayout() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<ActionSheetProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="index" options={{ title: "Map", headerShown: false }} />
<Stack.Screen
name="packs"
options={{
title: "Hotspot Packs",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings"
options={{
title: "Settings",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings-map-provider"
options={{
title: "Directions Provider",
headerBackButtonDisplayMode: "minimal",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings-import-life-list"
options={{
title: "Import Life List",
headerBackButtonDisplayMode: "minimal",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings-view-life-list"
options={{
title: "Life List",
headerBackButtonDisplayMode: "minimal",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings-life-list-exclusions"
options={{
title: "Exclusions",
headerBackButtonDisplayMode: "minimal",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen name="+not-found" />
</Stack>
<Toast
config={{
success: ({ text1 }) => (
<View style={toastStyles}>
<View style={tw`mr-1.5`}>
<Ionicons name="checkmark-circle" size={20} color="#16A34A" />
</View>
<Text style={tw`text-gray-800 font-medium text-base`}>{text1}</Text>
<QueryClientProvider client={queryClient}>
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="index" options={{ title: "Map", headerShown: false }} />
<Stack.Screen
name="packs"
options={{
title: "Hotspot Packs",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings"
options={{
title: "Settings",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings-map-provider"
options={{
title: "Directions Provider",
headerBackButtonDisplayMode: "minimal",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings-import-life-list"
options={{
title: "Import Life List",
headerBackButtonDisplayMode: "minimal",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings-view-life-list"
options={{
title: "Life List",
headerBackButtonDisplayMode: "minimal",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="settings-life-list-exclusions"
options={{
title: "Exclusions",
headerBackButtonDisplayMode: "minimal",
headerStyle: { backgroundColor: "#f9fafb" },
headerShadowVisible: false,
}}
/>
<Stack.Screen name="+not-found" />
</Stack>
<Toast
config={{
success: ({ text1 }) => (
<View style={toastStyles}>
<View style={tw`mr-1.5`}>
<Ionicons name="checkmark-circle" size={20} color="#16A34A" />
</View>
),
error: ({ text1 }) => (
<View style={toastStyles}>
<View style={tw`mr-1.5`}>
<Ionicons name="alert-circle" size={20} color="#DC2626" />
</View>
<Text style={tw`text-gray-800 font-medium text-base`}>{text1}</Text>
<Text style={tw`text-gray-800 font-medium text-base`}>{text1}</Text>
</View>
),
error: ({ text1 }) => (
<View style={toastStyles}>
<View style={tw`mr-1.5`}>
<Ionicons name="alert-circle" size={20} color="#DC2626" />
</View>
),
info: ({ text1 }) => (
<View style={toastStyles}>
<View style={tw`mr-1.5`}>
<Ionicons name="information-circle" size={20} color="#2563EB" />
</View>
<Text style={tw`text-gray-800 font-medium text-base`}>{text1}</Text>
<Text style={tw`text-gray-800 font-medium text-base`}>{text1}</Text>
</View>
),
info: ({ text1 }) => (
<View style={toastStyles}>
<View style={tw`mr-1.5`}>
<Ionicons name="information-circle" size={20} color="#2563EB" />
</View>
),
}}
position="top"
topOffset={65}
/>
<DownloadOverlay />
<StatusBar style="auto" />
</ThemeProvider>
</QueryClientProvider>
</ActionSheetProvider>
<Text style={tw`text-gray-800 font-medium text-base`}>{text1}</Text>
</View>
),
}}
position="top"
topOffset={65}
/>
<DownloadOverlay />
<StatusBar style="auto" />
</ThemeProvider>
</QueryClientProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
);
Expand Down
9 changes: 6 additions & 3 deletions app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export default function HomeScreen() {
setCustomPinCoordinates,
isHotspotListOpen,
setIsHotspotListOpen,
isSunDetailsOpen,
setIsSunDetailsOpen,
isMapAttributionOpen,
setIsMapAttributionOpen,
} = useMapStore();
const { data: installedPacks, isLoading: isLoadingInstalledPacks } = useInstalledPacks();
const { hasUpdates } = usePackUpdates();
Expand All @@ -43,6 +47,7 @@ export default function HomeScreen() {
if (hotspotId) setHotspotId(null);
if (placeId) setPlaceId(null);
if (customPinCoordinates) setCustomPinCoordinates(null);
if (isMapAttributionOpen) setIsMapAttributionOpen(false);
};

const handleHotspotSelect = (id: string) => {
Expand Down Expand Up @@ -133,9 +138,7 @@ export default function HomeScreen() {
<PacksNotice variant="banner" />
</View>
) : (
<SunIndicator
style={[tw`absolute`, { top: insets.top > 16 ? insets.top + 4 : insets.top + 16, left: 16 }]}
/>
<SunIndicator style={[tw`absolute`, { top: insets.top > 16 ? insets.top + 4 : insets.top + 16, left: 16 }]} />
)}
<View
style={[
Expand Down
73 changes: 34 additions & 39 deletions app/settings-life-list-exclusions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,12 @@ import SearchInput from "@/components/SearchInput";
import { useTaxonomy, useTaxonomyMap } from "@/hooks/useTaxonomy";
import tw from "@/lib/tw";
import { useSettingsStore } from "@/stores/settingsStore";
import { useActionSheet } from "@expo/react-native-action-sheet";
import { Button, Host, Menu, RNHostView, Section } from "@expo/ui/swift-ui";
import { Ionicons } from "@expo/vector-icons";
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
import { useNavigation } from "expo-router";
import React, { useEffect, useMemo, useRef, useState } from "react";
import {
Alert,
findNodeHandle,
Linking,
Platform,
ScrollView,
Text,
TouchableOpacity,
View,
ViewStyle,
} from "react-native";
import React, { useEffect, useMemo, useState } from "react";
import { Alert, Linking, Platform, ScrollView, Text, TouchableOpacity, View, ViewStyle } from "react-native";

type TaxonomyEntry = {
name: string;
Expand Down Expand Up @@ -74,38 +64,43 @@ function ExclusionItem({
}) {
const borderStyle = isLast ? {} : tw`border-b border-gray-200/50`;
const speciesName = taxonomyMap.get(code) ?? `Unknown (${code})`;
const menuRef = useRef<React.ComponentRef<typeof TouchableOpacity>>(null);
const { showActionSheetWithOptions } = useActionSheet();

const showMenu = () => {
const anchor = menuRef.current ? findNodeHandle(menuRef.current) : undefined;
showActionSheetWithOptions(
{
options: ["View in Merlin", "Remove from Exclusions", "Cancel"],
destructiveButtonIndex: 1,
cancelButtonIndex: 2,
anchor: anchor ?? undefined,
},
(buttonIndex) => {
if (buttonIndex === 0) {
Linking.openURL(`merlinbirdid://species/${code}`).catch(() => {
Alert.alert("Cannot Open Merlin", "Make sure the Merlin Bird ID app is installed.");
});
} else if (buttonIndex === 1) {
onRemove();
}
}
);
};

return (
<View style={[tw`px-4 py-3 flex-row items-center`, borderStyle]}>
<View style={tw`flex-1`}>
<Text style={tw`text-gray-900 text-base font-medium`}>{speciesName}</Text>
</View>
<TouchableOpacity ref={menuRef} onPress={showMenu} style={tw`p-2 -mr-2`}>
<Ionicons name="ellipsis-horizontal" size={18} color={tw.color("gray-400")} />
</TouchableOpacity>
<Host style={tw`p-2 -mr-2`}>
<Menu
label={
<RNHostView matchContents>
<View style={tw`w-8 h-8 items-center justify-center`}>
<Ionicons name="ellipsis-horizontal" size={18} color={tw.color("gray-400")} />
</View>
</RNHostView>
}
>
<Section>
<Button
label="View in Merlin"
systemImage="arrow.up.forward.app"
onPress={() => {
Linking.openURL(`merlinbirdid://species/${code}`).catch(() => {
Alert.alert("Cannot Open Merlin", "Make sure the Merlin Bird ID app is installed.");
});
}}
/>
</Section>
<Section>
<Button
label="Remove"
systemImage="minus.circle"
role="destructive"
onPress={onRemove}
/>
</Section>
</Menu>
</Host>
</View>
);
}
Expand Down
Loading