Center map on user location
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m28s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m28s
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<application
|
||||
android:label="mapflow"
|
||||
android:name="${applicationName}"
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>MapFlow uses your location to show places around you.</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
||||
32
lib/location/current_location.dart
Normal file
32
lib/location/current_location.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class CurrentLocation {
|
||||
Future<LatLng?> resolve() async {
|
||||
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final permission = await _resolvePermission();
|
||||
if (permission != LocationPermission.whileInUse &&
|
||||
permission != LocationPermission.always) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
locationSettings: const LocationSettings(accuracy: LocationAccuracy.high),
|
||||
);
|
||||
|
||||
return LatLng(position.latitude, position.longitude);
|
||||
}
|
||||
|
||||
Future<LocationPermission> _resolvePermission() async {
|
||||
final permission = await Geolocator.checkPermission();
|
||||
if (permission != LocationPermission.denied) {
|
||||
return permission;
|
||||
}
|
||||
|
||||
return Geolocator.requestPermission();
|
||||
}
|
||||
}
|
||||
@@ -39,13 +39,15 @@ class MapflowShell extends ConsumerWidget {
|
||||
class _MapContent extends ConsumerWidget {
|
||||
const _MapContent({required this.state});
|
||||
|
||||
static const _center = LatLng(10.7718, 106.6982);
|
||||
static const _fallbackCenter = LatLng(10.7718, 106.6982);
|
||||
|
||||
final PlaceState state;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selected = state.selectedPlace;
|
||||
final userCoordinate = state.userCoordinate;
|
||||
final mapCenter = userCoordinate ?? selected?.coordinate ?? _fallbackCenter;
|
||||
final availableTraits = {
|
||||
for (final place in state.recommendations) ...place.traits,
|
||||
}.toList();
|
||||
@@ -55,7 +57,7 @@ class _MapContent extends ConsumerWidget {
|
||||
children: [
|
||||
FlutterMap(
|
||||
options: MapOptions(
|
||||
initialCenter: selected?.coordinate ?? _center,
|
||||
initialCenter: mapCenter,
|
||||
initialZoom: 14.2,
|
||||
minZoom: 3,
|
||||
maxZoom: 18,
|
||||
@@ -76,6 +78,13 @@ class _MapContent extends ConsumerWidget {
|
||||
.selectPlace(place.id),
|
||||
),
|
||||
),
|
||||
if (userCoordinate != null)
|
||||
Marker(
|
||||
width: 30,
|
||||
height: 30,
|
||||
point: userCoordinate,
|
||||
child: const _UserLocationMarker(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const _MapAttribution(),
|
||||
@@ -122,7 +131,10 @@ class _MapContent extends ConsumerWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: FloatingActionButton(
|
||||
onPressed: () => _openAddFlow(context, selected?.coordinate),
|
||||
onPressed: () => _openAddFlow(
|
||||
context,
|
||||
userCoordinate ?? selected?.coordinate,
|
||||
),
|
||||
child: const Icon(Icons.add_location_alt_outlined),
|
||||
),
|
||||
),
|
||||
@@ -138,7 +150,7 @@ class _MapContent extends ConsumerWidget {
|
||||
MaterialPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => AddExperienceFlow(
|
||||
coordinate: coordinate ?? _center,
|
||||
coordinate: coordinate ?? _fallbackCenter,
|
||||
hasTelegramAuth: state.hasTelegramAuth,
|
||||
),
|
||||
),
|
||||
@@ -146,6 +158,32 @@ class _MapContent extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _UserLocationMarker extends StatelessWidget {
|
||||
const _UserLocationMarker();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = Theme.of(context).colorScheme.primary;
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color.withValues(alpha: 0.18),
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 14,
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color,
|
||||
border: Border.all(color: Colors.white, width: 3),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UserAvatar extends StatelessWidget {
|
||||
const _UserAvatar({required this.user, required this.onLogout});
|
||||
|
||||
@@ -403,20 +441,7 @@ class _MapLoading extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
FlutterMap(
|
||||
options: const MapOptions(
|
||||
initialCenter: LatLng(10.7718, 106.6982),
|
||||
initialZoom: 14.2,
|
||||
),
|
||||
children: [const _BaseMapTileLayer(), const _MapAttribution()],
|
||||
),
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
),
|
||||
);
|
||||
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../api/mapflow_api.dart';
|
||||
import '../location/current_location.dart';
|
||||
import '../models/place_models.dart';
|
||||
|
||||
final placeControllerProvider =
|
||||
@@ -14,14 +15,18 @@ class PlaceState {
|
||||
required this.selectedPlaceId,
|
||||
required this.currentUser,
|
||||
required this.hasTelegramAuth,
|
||||
required this.userCoordinate,
|
||||
required this.reviewDraft,
|
||||
});
|
||||
|
||||
static final _distance = Distance();
|
||||
|
||||
final PlaceTrait selectedTrait;
|
||||
final List<PlaceRecommendation> places;
|
||||
final String? selectedPlaceId;
|
||||
final AppUser? currentUser;
|
||||
final bool hasTelegramAuth;
|
||||
final LatLng? userCoordinate;
|
||||
final VoiceReviewDraft reviewDraft;
|
||||
|
||||
List<PlaceRecommendation> get recommendations {
|
||||
@@ -29,7 +34,20 @@ class PlaceState {
|
||||
..sort((a, b) {
|
||||
final aScore = a.traits.contains(selectedTrait) ? 1 : 0;
|
||||
final bScore = b.traits.contains(selectedTrait) ? 1 : 0;
|
||||
return bScore.compareTo(aScore);
|
||||
final scoreOrder = bScore.compareTo(aScore);
|
||||
if (scoreOrder != 0) {
|
||||
return scoreOrder;
|
||||
}
|
||||
|
||||
final coordinate = userCoordinate;
|
||||
if (coordinate == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _distance(
|
||||
coordinate,
|
||||
a.coordinate,
|
||||
).compareTo(_distance(coordinate, b.coordinate));
|
||||
});
|
||||
return ranked.take(4).toList();
|
||||
}
|
||||
@@ -49,6 +67,7 @@ class PlaceState {
|
||||
String? selectedPlaceId,
|
||||
AppUser? currentUser,
|
||||
bool? hasTelegramAuth,
|
||||
LatLng? userCoordinate,
|
||||
VoiceReviewDraft? reviewDraft,
|
||||
}) {
|
||||
return PlaceState(
|
||||
@@ -57,6 +76,7 @@ class PlaceState {
|
||||
selectedPlaceId: selectedPlaceId ?? this.selectedPlaceId,
|
||||
currentUser: currentUser ?? this.currentUser,
|
||||
hasTelegramAuth: hasTelegramAuth ?? this.hasTelegramAuth,
|
||||
userCoordinate: userCoordinate ?? this.userCoordinate,
|
||||
reviewDraft: reviewDraft ?? this.reviewDraft,
|
||||
);
|
||||
}
|
||||
@@ -64,6 +84,7 @@ class PlaceState {
|
||||
|
||||
class PlaceController extends AsyncNotifier<PlaceState> {
|
||||
final _api = MapflowApi();
|
||||
final _location = CurrentLocation();
|
||||
|
||||
@override
|
||||
Future<PlaceState> build() async {
|
||||
@@ -74,6 +95,7 @@ class PlaceController extends AsyncNotifier<PlaceState> {
|
||||
selectedPlaceId: null,
|
||||
currentUser: null,
|
||||
hasTelegramAuth: false,
|
||||
userCoordinate: null,
|
||||
reviewDraft: VoiceReviewDraft(
|
||||
placeName: '',
|
||||
duration: Duration.zero,
|
||||
@@ -84,6 +106,7 @@ class PlaceController extends AsyncNotifier<PlaceState> {
|
||||
}
|
||||
|
||||
final currentUser = await _api.authenticateTelegram();
|
||||
final userCoordinate = await _location.resolve();
|
||||
final places = await _api.fetchPlaces();
|
||||
return PlaceState(
|
||||
selectedTrait: PlaceTrait.calm,
|
||||
@@ -91,6 +114,7 @@ class PlaceController extends AsyncNotifier<PlaceState> {
|
||||
selectedPlaceId: places.isEmpty ? null : places.first.id,
|
||||
currentUser: currentUser,
|
||||
hasTelegramAuth: _api.hasTelegramAuth,
|
||||
userCoordinate: userCoordinate,
|
||||
reviewDraft: const VoiceReviewDraft(
|
||||
placeName: '',
|
||||
duration: Duration.zero,
|
||||
@@ -147,7 +171,8 @@ class PlaceController extends AsyncNotifier<PlaceState> {
|
||||
final placeName = draft.placeName.trim().isEmpty
|
||||
? 'Место на карте'
|
||||
: draft.placeName.trim();
|
||||
final point = coordinate ?? const LatLng(10.7729, 106.7004);
|
||||
final point =
|
||||
coordinate ?? value.userCoordinate ?? const LatLng(10.7729, 106.7004);
|
||||
|
||||
await _api.createVoiceExperience(
|
||||
googlePlaceId: 'manual-${point.latitude}-${point.longitude}-$placeName',
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import geolocator_apple
|
||||
import package_info_plus
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
}
|
||||
|
||||
@@ -8,5 +8,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.location</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>$(PRODUCT_COPYRIGHT)</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>MapFlow uses your location to show places around you.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.location</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
125
pubspec.lock
125
pubspec.lock
@@ -137,6 +137,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.12"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -203,6 +211,11 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -211,6 +224,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
geoclue:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geoclue
|
||||
sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.0.2"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.13"
|
||||
geolocator_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_linux
|
||||
sha256: d64112a205931926f4363bb6bd48f14cb38e7326833041d170615586cd143797
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.4"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.6"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.3"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.5"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -219,6 +296,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
gsettings:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gsettings
|
||||
sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.8"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -411,6 +496,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
package_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.1"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -467,6 +568,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -744,6 +853,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.15.0"
|
||||
wkt_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -760,6 +877,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -39,6 +39,7 @@ dependencies:
|
||||
latlong2: ^0.9.1
|
||||
http: ^1.6.0
|
||||
web: ^1.1.1
|
||||
geolocator: ^14.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user