@@ -17,11 +17,16 @@ import {
1717} from 'react-native' ;
1818import { SafeAreaView } from 'react-native-safe-area-context' ;
1919import Icon from 'react-native-vector-icons/FontAwesome' ;
20+ import { useNavigation } from '@react-navigation/native' ;
21+ import type { BottomTabNavigationProp } from '@react-navigation/bottom-tabs' ;
22+ import type { RootStackParamList } from '../types/navigation' ;
2023
2124// 3. サービスとフックをインポート
2225import photoService from '../services/photoService' ;
2326import { useImagePicker } from '../hooks/useImagePicker' ;
2427
28+ type NavigationProp = BottomTabNavigationProp < RootStackParamList , 'Gallery' > ;
29+
2530const { width } = Dimensions . get ( 'window' ) ;
2631const ITEM_SIZE = ( width - 20 ) / 2 ; // アイテムサイズを計算
2732
@@ -32,6 +37,8 @@ interface Photo {
3237 source ?: ReturnType < typeof require > ; // require() の戻り値の型(ローカル画像の場合)
3338 uri ?: string ; // API画像のURL
3439 createdAt : number ; // ソート用の仮のタイムスタンプ (追加日時)
40+ lat ?: number ; // 緯度
41+ lng ?: number ; // 経度
3542}
3643
3744// 型エイリアス(後方互換性のため)
@@ -51,6 +58,9 @@ const SORT_OPTIONS: { key: SortOrder; label: string }[] = [
5158] ;
5259
5360const PhotoGalleryScreen = ( ) => {
61+ // Navigation hookを使用
62+ const navigation = useNavigation < NavigationProp > ( ) ;
63+
5464 // 状態変数: 表示する写真リスト、ソート順序、ローディング状態
5565 const [ photos , setPhotos ] = useState < Photo [ ] > ( [ ] ) ;
5666 const [ sortOrder , setSortOrder ] = useState < SortOrder > ( 'added_desc' ) ;
@@ -77,6 +87,8 @@ const PhotoGalleryScreen = () => {
7787 name : photo . title || '無題' ,
7888 uri : photo . s3_key , // S3のURLを直接使用
7989 createdAt : new Date ( photo . created_at ) . getTime ( ) ,
90+ lat : photo . lat ,
91+ lng : photo . lng ,
8092 } ;
8193 } ) ;
8294 } catch ( error ) {
@@ -172,13 +184,63 @@ const PhotoGalleryScreen = () => {
172184 }
173185 } ;
174186
187+ // 写真をクリックしてMap画面に遷移する関数
188+ const handlePhotoPress = ( photo : Photo ) => {
189+ if ( photo . lat !== undefined && photo . lng !== undefined ) {
190+ navigation . navigate ( 'Map' , {
191+ latitude : photo . lat ,
192+ longitude : photo . lng ,
193+ photoId : photo . id ,
194+ photoTitle : photo . name ,
195+ } ) ;
196+ } else {
197+ Alert . alert ( '位置情報なし' , 'この写真には位置情報が含まれていません' ) ;
198+ }
199+ } ;
200+
201+ // 写真を削除する関数
202+ const handleDeletePhoto = async ( photoId : string , photoName : string ) => {
203+ Alert . alert (
204+ '写真を削除' ,
205+ `「${ photoName } 」を削除しますか?` ,
206+ [
207+ {
208+ text : 'キャンセル' ,
209+ style : 'cancel' ,
210+ } ,
211+ {
212+ text : '削除' ,
213+ style : 'destructive' ,
214+ onPress : async ( ) => {
215+ try {
216+ await photoService . deletePhoto ( photoId ) ;
217+ // 写真を再読み込み
218+ await fetchAndSortLocalPhotos ( sortOrder ) ;
219+ Alert . alert ( '成功' , '写真を削除しました' ) ;
220+ } catch ( error ) {
221+ console . error ( '削除エラー:' , error ) ;
222+ const errorMessage = error instanceof Error ? error . message : '写真の削除に失敗しました' ;
223+ Alert . alert ( 'エラー' , errorMessage ) ;
224+ }
225+ } ,
226+ } ,
227+ ] ,
228+ { cancelable : true }
229+ ) ;
230+ } ;
231+
175232 // --- FlatList の各写真アイテムをレンダリングする関数 ---
176233 const renderPhotoItem = ( { item } : { item : Photo } ) => {
177234 // 画像ソースを決定
178235 const imageSource = item . source ? item . source : { uri : item . uri } ;
179236
180237 return (
181- < TouchableOpacity style = { styles . photoItem } >
238+ < TouchableOpacity
239+ style = { styles . photoItem }
240+ onPress = { ( ) => handlePhotoPress ( item ) }
241+ onLongPress = { ( ) => handleDeletePhoto ( item . id , item . name ) }
242+ delayLongPress = { 500 }
243+ >
182244 < Image
183245 source = { imageSource }
184246 style = { styles . photoImage }
@@ -191,6 +253,12 @@ const PhotoGalleryScreen = () => {
191253 } }
192254 />
193255 < Text style = { styles . photoName } numberOfLines = { 1 } > { item . name } </ Text >
256+ { /* 位置情報があるかどうかを示すアイコン */ }
257+ { item . lat !== undefined && item . lng !== undefined && (
258+ < View style = { styles . locationBadge } >
259+ < Icon name = "map-marker" size = { 12 } color = "white" />
260+ </ View >
261+ ) }
194262 </ TouchableOpacity >
195263 ) ;
196264 } ;
@@ -299,9 +367,21 @@ const styles = StyleSheet.create({
299367 loadingContainer : { flex : 1 , justifyContent : 'center' , alignItems : 'center' } ,
300368 loadingText : { marginTop : 10 , color : '#666' } ,
301369 listContent : { paddingHorizontal : 5 , paddingBottom : 80 } ,
302- photoItem : { width : ITEM_SIZE , margin : 5 , alignItems : 'center' } ,
370+ photoItem : { width : ITEM_SIZE , margin : 5 , alignItems : 'center' , position : 'relative' } ,
303371 photoImage : { width : '100%' , height : ITEM_SIZE * 1.2 , borderRadius : 8 , backgroundColor : '#E0E0E0' } ,
304372 photoName : { marginTop : 5 , color : '#333' , fontSize : 12 , textAlign : 'center' } ,
373+ locationBadge : {
374+ position : 'absolute' ,
375+ top : 5 ,
376+ right : 5 ,
377+ backgroundColor : 'rgba(200, 181, 111, 0.9)' ,
378+ borderRadius : 12 ,
379+ padding : 4 ,
380+ width : 24 ,
381+ height : 24 ,
382+ justifyContent : 'center' ,
383+ alignItems : 'center' ,
384+ } ,
305385 emptyContainer : { flexGrow : 1 , justifyContent : 'center' , alignItems : 'center' , paddingVertical : 50 } ,
306386 emptyText : { marginTop : 10 , fontSize : 16 , color : '#666' } ,
307387 emptySubText : { marginTop : 5 , fontSize : 12 , color : '#999' , textAlign : 'center' } ,
0 commit comments