Skip to content
Open
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
13 changes: 13 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ PODS:
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- connectivity_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_secure_storage (6.0.0):
- Flutter
Expand Down Expand Up @@ -56,16 +58,21 @@ PODS:
- Flutter
- FlutterMacOS
- PromisesObjC (2.4.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- Turf (4.0.0)
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
- mapbox_maps_flutter (from `.symlinks/plugins/mapbox_maps_flutter/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

SPEC REPOS:
Expand All @@ -83,6 +90,8 @@ SPEC REPOS:
- Turf

EXTERNAL SOURCES:
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
Flutter:
:path: Flutter
flutter_secure_storage:
Expand All @@ -93,12 +102,15 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/mapbox_maps_flutter/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
Expand All @@ -112,6 +124,7 @@ SPEC CHECKSUMS:
MapboxMaps: db05259e1ea68e9a8a4e6f9014027418025f2ebe
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
Turf: c9eb11a65d96af58cac523460fd40fec5061b081
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b

Expand Down
85 changes: 66 additions & 19 deletions lib/features/map/presentation/map_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import 'package:memomap/features/map/providers/current_map_provider.dart';
import 'package:memomap/features/map/providers/map_provider.dart';
import 'package:memomap/features/map/providers/pin_provider.dart';
import 'package:memomap/features/map/providers/drawing_provider.dart';
import 'package:memomap/features/map/providers/map_bounds_provider.dart';
import 'package:memomap/features/map/models/drawing_path.dart';
import 'package:memomap/features/map/presentation/widgets/controls.dart';
import 'package:memomap/features/map/presentation/widgets/pin_list.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;
import 'dart:convert';
import 'dart:async';

class MapScreen extends ConsumerStatefulWidget {
const MapScreen({super.key});
Expand All @@ -37,12 +39,18 @@ class _MapScreenState extends ConsumerState<MapScreen> {
final Map<String, PointAnnotation> _pinToAnnotation = {};

double? _cachedZoom;
Timer? _boundsUpdateTimer;

@override
void initState() {
super.initState();
_loadPinImage();
MapboxMapsOptions.setLanguage("ja");

// 定期的に地図の表示範囲を更新
_boundsUpdateTimer = Timer.periodic(const Duration(seconds: 1), (_) {
_updateMapBounds();
});
}

Future<void> _loadPinImage() async {
Expand All @@ -69,6 +77,7 @@ class _MapScreenState extends ConsumerState<MapScreen> {
);

_updatePins();
_updateMapBounds();

setState(() {});
}
Expand All @@ -80,6 +89,38 @@ class _MapScreenState extends ConsumerState<MapScreen> {
});
}

Future<void> _updateMapBounds() async {
if (_mapboxMap == null || !mounted) return;

// 画面サイズを取得
final screenSize = MediaQuery.of(context).size;

// 画面中心と左上のピクセル座標を緯度経度に変換
final center = await _mapboxMap!.coordinateForPixel(
ScreenCoordinate(x: screenSize.width / 2, y: screenSize.height / 2),
);
final topLeft = await _mapboxMap!.coordinateForPixel(
ScreenCoordinate(x: 0, y: 0),
);

// 中心から左上までの距離を計算(外接円の半径)
final centerLat = center.coordinates.lat;
final centerLng = center.coordinates.lng;
final topLeftLat = topLeft.coordinates.lat;
final topLeftLng = topLeft.coordinates.lng;

final latDiff = centerLat - topLeftLat;
final lngDiff = centerLng - topLeftLng;
final radius = math.sqrt(latDiff * latDiff + lngDiff * lngDiff).toDouble();

final bounds = MapBounds(
center: LatLng(centerLat.toDouble(), centerLng.toDouble()),
radius: radius,
);

ref.read(mapBoundsProvider.notifier).state = bounds;
}

Future<void> _updatePins({bool fullRebuild = false}) async {
if (pointAnnotationManager == null || _pinImageData == null) return;

Expand Down Expand Up @@ -118,10 +159,7 @@ class _MapScreenState extends ConsumerState<MapScreen> {
final annotation = await pointAnnotationManager!.create(
PointAnnotationOptions(
geometry: Point(
coordinates: Position(
pin.position.longitude,
pin.position.latitude,
),
coordinates: Position(pin.position.longitude, pin.position.latitude),
),
image: _pinImageData,
iconSize: 0.5,
Expand Down Expand Up @@ -208,7 +246,9 @@ class _MapScreenState extends ConsumerState<MapScreen> {
"width",
]);
await style.setStyleLayerProperty(
"existing_paths_layer", "line-opacity", 1.0,
"existing_paths_layer",
"line-opacity",
1.0,
);

await style.addSource(GeoJsonSource(id: "current_path_source"));
Expand Down Expand Up @@ -478,6 +518,7 @@ class _MapScreenState extends ConsumerState<MapScreen> {

@override
void dispose() {
_boundsUpdateTimer?.cancel();
_annotationToPin.clear();
_pinToAnnotation.clear();
super.dispose();
Expand All @@ -497,7 +538,10 @@ class _MapScreenState extends ConsumerState<MapScreen> {
final isDrawingMode = drawingState?.isDrawingMode ?? false;
final strokeWidth = drawingState?.strokeWidth ?? 3.0;

ref.listen(drawingProvider.select((s) => s.valueOrNull?.paths), (previous, next) {
ref.listen(drawingProvider.select((s) => s.valueOrNull?.paths), (
previous,
next,
) {
_updateLines();
});

Expand All @@ -521,7 +565,10 @@ class _MapScreenState extends ConsumerState<MapScreen> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(currentMap?.name ?? (currentMapId != null ? 'Loading...' : 'Memomap')),
Text(
currentMap?.name ??
(currentMapId != null ? 'Loading...' : 'Memomap'),
),
const SizedBox(width: 4),
const Icon(Icons.arrow_drop_down, size: 20),
],
Expand Down Expand Up @@ -572,13 +619,11 @@ class _MapScreenState extends ConsumerState<MapScreen> {
onMapCreated: _onMapCreated,
onStyleLoadedListener: _onStyleLoaded,
onTapListener: _onMapTap,
gestureRecognizers: drawingState.isDrawingMode
? {}
: null,
gestureRecognizers: isDrawingMode ? {} : null,
),
if (_mapboxMap != null)
IgnorePointer(
ignoring: !drawingState.isDrawingMode,
ignoring: !isDrawingMode,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: _onPanStart,
Expand All @@ -590,14 +635,12 @@ class _MapScreenState extends ConsumerState<MapScreen> {
if (_eraserPosition != null)
Positioned(
left:
_eraserPosition!.dx -
drawingState.strokeWidth * 2,
_eraserPosition!.dx - strokeWidth * 2,
top:
_eraserPosition!.dy -
drawingState.strokeWidth * 2,
_eraserPosition!.dy - strokeWidth * 2,
child: Container(
width: drawingState.strokeWidth * 4,
height: drawingState.strokeWidth * 4,
width: strokeWidth * 4,
height: strokeWidth * 4,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
Expand Down Expand Up @@ -659,7 +702,9 @@ class _MapScreenState extends ConsumerState<MapScreen> {
const Controls(),
],
),
if (currentMap == null && currentMapId == null && !mapsAsync.isLoading)
if (currentMap == null &&
currentMapId == null &&
!mapsAsync.isLoading)
Positioned(
top: 16,
left: 16,
Expand All @@ -678,7 +723,9 @@ class _MapScreenState extends ConsumerState<MapScreen> {
const Icon(Icons.info_outline, color: Colors.amber),
const SizedBox(width: 12),
const Expanded(
child: Text('No map selected. Create or select a map to add pins and drawings.'),
child: Text(
'No map selected. Create or select a map to add pins and drawings.',
),
),
TextButton(
onPressed: () => context.push('/maps'),
Expand Down
29 changes: 14 additions & 15 deletions lib/features/map/presentation/widgets/controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class Controls extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final drawingStateAsync = ref.watch(drawingProvider);
final drawingState = drawingStateAsync.valueOrNull;
final isDrawingMode = drawingState?.isDrawingMode ?? false;
final isEraserMode = drawingState?.isEraserMode ?? false;
final selectedColor = drawingState?.selectedColor ?? Colors.red;
final strokeWidth = drawingState?.strokeWidth ?? 3;
final drawingNotifier = ref.read(drawingProvider.notifier);
final colorScheme = Theme.of(context).colorScheme;

Expand Down Expand Up @@ -41,9 +45,7 @@ class Controls extends ConsumerWidget {
Icon(
Icons.explore,
size: 60,
color: !drawingState.isDrawingMode
? colorScheme.primary
: Colors.grey,
color: !isDrawingMode ? colorScheme.primary : Colors.grey,
),
],
),
Expand All @@ -64,7 +66,7 @@ class Controls extends ConsumerWidget {
),
);
},
child: drawingState.isDrawingMode
child: isDrawingMode
? Container(
key: const ValueKey('expanded_controls'),
padding: const EdgeInsets.only(bottom: 24, top: 12),
Expand All @@ -83,15 +85,13 @@ class Controls extends ConsumerWidget {
IconButton(
icon: Icon(
MyFlutterApp.eraser_1,
color: drawingState.isEraserMode
color: isEraserMode
? colorScheme.primary
: colorScheme.onSurface,
),
tooltip: '消しゴム',
onPressed: () =>
drawingNotifier.setEraserMode(
!drawingState.isEraserMode,
),
onPressed: () => drawingNotifier
.setEraserMode(!isEraserMode),
),
],
),
Expand All @@ -117,9 +117,8 @@ class Controls extends ConsumerWidget {
(entry) => _ColorCircle(
index: entry.key,
isSelected:
!drawingState.isEraserMode &&
drawingState.selectedColor ==
entry.value,
!isEraserMode &&
selectedColor == entry.value,
color: entry.value,
onTap: () => drawingNotifier
.selectColor(entry.value),
Expand All @@ -129,10 +128,10 @@ class Controls extends ConsumerWidget {
),
const SizedBox(height: 10),
_StrokeWidthSlider(
color: drawingState.isEraserMode
color: isEraserMode
? Colors.grey
: drawingState.selectedColor,
width: drawingState.strokeWidth,
: selectedColor,
width: strokeWidth,
setWidth: (newWidth) => drawingNotifier
.changeStrokeWidth(newWidth),
),
Expand Down
31 changes: 30 additions & 1 deletion lib/features/map/presentation/widgets/pin_list.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:memomap/features/map/providers/pin_provider.dart';
import 'package:memomap/features/map/providers/map_bounds_provider.dart';

class PinList extends ConsumerWidget {
const PinList({super.key, this.onSheetSizeChanged});
Expand All @@ -12,6 +13,7 @@ class PinList extends ConsumerWidget {
final pinsAsync = ref.watch(pinsProvider);
final pinsNotifier = ref.watch(pinsProvider.notifier);
final colorScheme = Theme.of(context).colorScheme;
final mapBounds = ref.watch(mapBoundsProvider);
return DraggableScrollableSheet(
initialChildSize: 0.2,
minChildSize: 0.05,
Expand Down Expand Up @@ -43,6 +45,9 @@ class PinList extends ConsumerWidget {
itemCount: pins.length,
itemBuilder: (context, index) {
final pin = pins[index];
final isOutOfBounds = mapBounds != null &&
!mapBounds.contains(pin.position);

return Dismissible(
key: ValueKey(pin),
onDismissed: (direction) {
Expand All @@ -65,7 +70,31 @@ class PinList extends ConsumerWidget {
},
child: ListTile(
leading: Image.asset('assets/pin.png'),
title: Text('ピン'),
title: Row(
children: [
const Text('ピン'),
if (isOutOfBounds) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: colorScheme.errorContainer,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'表示範囲外',
style: TextStyle(
fontSize: 10,
color: colorScheme.onErrorContainer,
),
),
),
],
],
),
subtitle: Text(
'緯度: ${pin.position.latitude.toStringAsFixed(4)}, 経度: ${pin.position.longitude.toStringAsFixed(4)}',
),
Expand Down
Loading