added memory map screen; improvements on memory sheet

This commit is contained in:
Myzel394 2022-08-19 12:04:48 +02:00
parent 8549d7c4a9
commit 4aff60a00c
8 changed files with 359 additions and 107 deletions

View File

@ -62,6 +62,20 @@
"memorySheetUpdateMemoryMakePublic": "Veröffentlichen",
"memorySheetUpdateMemoryMakePrivate": "Privat machen",
"memorySheetDeleteMemory": "Erinnerung löschen",
"memorySheetViewMoreDetails": "Mehr Details",
"memoryMapScreenTitle": "Standort",
"memoryMapScreenExpandForMoreDescription": "Erweitere für mehr Details",
"memoryMapScreenOpenNavigation": "Navigation öffnen",
"memoryMapScreenValuesAddressLabel": "Geschätzte Adresse",
"memoryMapScreenValuesAddressIsLoading": "Adresse wird geladen...",
"memoryMapScreenValuesAddressIsUnavailable": "Address nicht verfügbar",
"memoryMapScreenValuesLatitudeLabel": "Latitude",
"memoryMapScreenValuesLongitudeLabel": "Longitude",
"memoryMapScreenValuesAltitudeLabel": "Höhe",
"memoryMapScreenValuesAccuracyLabel": "Genauigkeit",
"memoryMapScreenValuesSpeedLabel": "Geschwindigkeit",
"emptyScreenTitle": "Houston, wir haben ein Problem",

View File

@ -91,7 +91,44 @@
}
},
"memorySheetMapEstimatedAddressLabel": "Estimated Address",
"memorySheetMapOpenNavigation": "Open Navigation",
"memorySheetViewMoreDetails": "View More Details",
"memoryMapScreenTitle": "Location",
"memoryMapScreenExpandForMoreDescription": "Expand for more details",
"memoryMapScreenOpenNavigation": "Open Navigation",
"memoryMapScreenValuesAddressLabel": "Estimated Address",
"memoryMapScreenValuesAddressIsLoading": "Loading address...",
"memoryMapScreenValuesAddressIsUnavailable": "Address not available",
"memoryMapScreenValuesLatitudeLabel": "Latitude",
"memoryMapScreenValuesLongitudeLabel": "Longitude",
"memoryMapScreenValuesAltitudeLabel": "Altitude",
"memoryMapScreenValuesAltitudeValue": "{valueInMeter}m",
"@memoryMapScreenValuesAltitudeValue": {
"placeholders": {
"valueInMeter": {
"type": "String"
}
}
},
"memoryMapScreenValuesAccuracyLabel": "Accuracy",
"memoryMapScreenValuesAccuracyValue": "{valueInMeter}m",
"@memoryMapScreenValuesAccuracyValue": {
"placeholders": {
"valueInMeter": {
"type": "String"
}
}
},
"memoryMapScreenValuesSpeedLabel": "Speed",
"memoryMapScreenValuesSpeedValue": "{valueInKmh} km/h",
"@memoryMapScreenValuesSpeedValue": {
"placeholders": {
"valueInKmh": {
"type": "String"
}
}
},
"emptyScreenTitle": "Houston, we have a problem",

View File

@ -0,0 +1,204 @@
import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:quid_faciam_hodie/constants/spacing.dart';
import 'package:quid_faciam_hodie/foreign_types/memory_location.dart';
import 'package:quid_faciam_hodie/utils/loadable.dart';
import 'package:quid_faciam_hodie/utils/lookup_address.dart';
import 'package:quid_faciam_hodie/utils/theme.dart';
import 'package:quid_faciam_hodie/widgets/icon_button_child.dart';
import 'package:quid_faciam_hodie/widgets/key_value_info.dart';
import 'package:quid_faciam_hodie/widgets/sheet_indicator.dart';
import 'package:url_launcher/url_launcher.dart';
class MemoryMapScreen extends StatefulWidget {
final MemoryLocation location;
const MemoryMapScreen({
Key? key,
required this.location,
}) : super(key: key);
@override
State<MemoryMapScreen> createState() => _MemoryMapScreenState();
}
class _MemoryMapScreenState extends State<MemoryMapScreen> with Loadable {
late final MapController controller;
String? address;
@override
void initState() {
super.initState();
callWithLoading(fetchAddress);
controller = MapController(
initPosition: GeoPoint(
latitude: widget.location.latitude,
longitude: widget.location.longitude,
),
);
}
Future<void> fetchAddress() async {
try {
final foundAddress = await lookupAddress(
latitude: widget.location.latitude,
longitude: widget.location.longitude,
);
setState(() {
address = foundAddress;
});
} catch (error) {
setState(() {
address = null;
});
}
}
void drawCircle() => controller.drawCircle(
CircleOSM(
key: 'accuracy',
color: Colors.blue,
centerPoint: GeoPoint(
latitude: widget.location.latitude,
longitude: widget.location.longitude,
),
radius: widget.location.accuracy,
strokeWidth: 4,
),
);
List<StaticPositionGeoPoint> get staticPoints => [
StaticPositionGeoPoint(
'position',
const MarkerIcon(
icon: Icon(Icons.location_on, size: 150, color: Colors.blue),
),
[
GeoPoint(
latitude: widget.location.latitude,
longitude: widget.location.longitude,
)
],
)
];
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
final backgroundColor = getSheetColor(context);
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(localizations.memoryMapScreenTitle),
),
body: ExpandableBottomSheet(
enableToggle: true,
background: OSMFlutter(
controller: controller,
initZoom: 13,
stepZoom: 1.0,
trackMyPosition: true,
staticPoints: staticPoints,
onMapIsReady: (_) {
drawCircle();
},
),
persistentHeader: Container(
width: double.infinity,
padding: const EdgeInsets.all(SMALL_SPACE),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(LARGE_SPACE),
topRight: Radius.circular(LARGE_SPACE),
),
),
child: Padding(
padding: const EdgeInsets.all(MEDIUM_SPACE),
child: Column(
children: <Widget>[
const SheetIndicator(),
const SizedBox(height: MEDIUM_SPACE),
Text(
localizations.memoryMapScreenExpandForMoreDescription,
style: getBodyTextTextStyle(context),
),
],
),
),
),
expandableContent: Container(
color: backgroundColor,
padding: const EdgeInsets.all(MEDIUM_SPACE),
child: Column(
children: <Widget>[
KeyValueInfo(
title: localizations.memoryMapScreenValuesAddressLabel,
value: () {
if (isLoading) {
return localizations.memoryMapScreenValuesAddressIsLoading;
}
if (address == null) {
return localizations
.memoryMapScreenValuesAddressIsUnavailable;
}
return address!;
}(),
),
const SizedBox(height: SMALL_SPACE),
PlatformTextButton(
onPressed: () {
final url =
'geo:0,0?q=${widget.location.latitude},${widget.location.longitude} (${address ?? ''})';
launchUrl(Uri.parse(url));
},
child: IconButtonChild(
icon: Icon(context.platformIcons.location),
label: Text(localizations.memoryMapScreenOpenNavigation),
),
),
const SizedBox(height: SMALL_SPACE),
KeyValueInfo(
title: localizations.memoryMapScreenValuesLatitudeLabel,
value: widget.location.latitude.toString(),
),
KeyValueInfo(
title: localizations.memoryMapScreenValuesLongitudeLabel,
value: widget.location.longitude.toString(),
),
KeyValueInfo(
title: localizations.memoryMapScreenValuesAccuracyLabel,
value: localizations.memoryMapScreenValuesAccuracyValue(
widget.location.accuracy.toString(),
),
icon: Icons.circle,
),
KeyValueInfo(
title: localizations.memoryMapScreenValuesSpeedLabel,
value: localizations.memoryMapScreenValuesSpeedValue(
widget.location.speed.toString(),
),
icon: Icons.speed_rounded,
),
KeyValueInfo(
title: localizations.memoryMapScreenValuesAltitudeLabel,
value: localizations.memoryMapScreenValuesAltitudeValue(
widget.location.altitude.toString(),
),
icon: Icons.landscape_rounded,
),
],
),
),
),
);
}
}

View File

@ -128,12 +128,7 @@ class _SettingsScreenState extends AuthRequiredState<SettingsScreen>
Text(
localizations
.settingsScreenAccountSectionCreationDateLabel,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodySmall,
cupertino: (data) =>
data.textTheme.tabLabelTextStyle,
),
style: getCaptionTextStyle(context),
)
],
),

View File

@ -1,17 +1,12 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:http/http.dart' as http;
import 'package:quid_faciam_hodie/constants/spacing.dart';
import 'package:quid_faciam_hodie/constants/values.dart';
import 'package:quid_faciam_hodie/foreign_types/memory_location.dart';
import 'package:quid_faciam_hodie/widgets/fade_and_move_in_animation.dart';
import 'package:quid_faciam_hodie/screens/memory_map_screen.dart';
import 'package:quid_faciam_hodie/widgets/icon_button_child.dart';
import 'package:quid_faciam_hodie/widgets/key_value_info.dart';
import 'package:url_launcher/url_launcher.dart';
class MemoryLocationView extends StatefulWidget {
final MemoryLocation location;
@ -27,14 +22,11 @@ class MemoryLocationView extends StatefulWidget {
class _MemoryLocationViewState extends State<MemoryLocationView> {
late final MapController controller;
String address = '';
@override
void initState() {
super.initState();
lookupAddress();
controller = MapController(
initPosition: GeoPoint(
latitude: widget.location.latitude,
@ -43,22 +35,11 @@ class _MemoryLocationViewState extends State<MemoryLocationView> {
);
}
void lookupAddress() async {
final url =
'https://geocode.maps.co/reverse?lat=${widget.location.latitude}&lon=${widget.location.longitude}';
final uri = Uri.parse(url);
@override
void dispose() {
controller.dispose();
final response = await http.get(uri);
if (response.statusCode != 200) {
setState(() {
address = '';
});
} else {
setState(() {
address = jsonDecode(response.body)['display_name'];
});
}
super.dispose();
}
void drawCircle() => controller.drawCircle(
@ -104,45 +85,45 @@ class _MemoryLocationViewState extends State<MemoryLocationView> {
SizedBox(
width: double.infinity,
height: 400,
child: OSMFlutter(
controller: controller,
initZoom: 14,
stepZoom: 1.0,
staticPoints: staticPoints,
onMapIsReady: (_) {
drawCircle();
},
child: GestureDetector(
// Avoid panning, map is view-only
onDoubleTap: () {},
child: OSMFlutter(
controller: controller,
initZoom: 14,
minZoomLevel: 14,
maxZoomLevel: 14,
staticPoints: staticPoints,
onMapIsReady: (_) {
drawCircle();
},
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
child: Column(
children: <Widget>[
const SizedBox(height: MEDIUM_SPACE),
if (address.isNotEmpty) ...[
FadeAndMoveInAnimation(
child: KeyValueInfo(
title: localizations.memorySheetMapEstimatedAddressLabel,
value: address,
icon: context.platformIcons.location,
),
),
const SizedBox(height: MEDIUM_SPACE),
],
PlatformTextButton(
onPressed: () {
final url =
'geo:0,0?q=${widget.location.latitude},${widget.location.longitude} ($address)';
launchUrl(Uri.parse(url));
},
child: IconButtonChild(
icon: Icon(context.platformIcons.location),
label: Text(localizations.memorySheetMapOpenNavigation),
const SizedBox(height: MEDIUM_SPACE),
PlatformTextButton(
child: IconButtonChild(
icon: Icon(context.platformIcons.fullscreen),
label: Text(localizations.memorySheetViewMoreDetails),
),
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => MemoryMapScreen(
location: widget.location,
),
),
],
),
);
if (!mounted) {
return;
}
Navigator.pop(context);
},
),
const SizedBox(height: MEDIUM_SPACE),
],
);
}

View File

@ -129,12 +129,7 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
final backgroundColor = platformThemeData(
context,
material: (data) =>
data.bottomSheetTheme.modalBackgroundColor ?? data.bottomAppBarColor,
cupertino: (data) => data.barBackgroundColor,
);
final backgroundColor = getSheetColor(context);
return ExpandableBottomSheet(
background: GestureDetector(
@ -151,13 +146,13 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
),
child: Column(
children: <Widget>[
const Padding(
padding: EdgeInsets.symmetric(
vertical: MEDIUM_SPACE,
horizontal: MEDIUM_SPACE,
if (widget.memory.location != null) ...[
const Padding(
padding: EdgeInsets.all(SMALL_SPACE),
child: SheetIndicator(),
),
child: SheetIndicator(),
),
const SizedBox(height: MEDIUM_SPACE),
],
Text(
localizations.memorySheetTitle,
style: getTitleTextStyle(context),
@ -224,43 +219,41 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
? buildLoadingIndicator()
: null,
),
],
),
),
expandableContent: Container(
width: double.infinity,
color: backgroundColor,
child: Column(
children: <Widget>[
widget.memory.location == null
? const SizedBox.shrink()
: MemoryLocationView(
location: widget.memory.location!,
const SizedBox(height: MEDIUM_SPACE),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
context.platformIcons.time,
size: platformThemeData(
context,
material: (data) => data.textTheme.bodyLarge!.fontSize,
cupertino: (data) => data.textTheme.textStyle.fontSize,
),
Padding(
padding: const EdgeInsets.all(MEDIUM_SPACE),
child: Column(
children: <Widget>[
Text(
localizations.memorySheetCreatedAtDataKey(
DateFormat.jms().format(
widget.memory.creationDate,
),
),
const SizedBox(width: TINY_SPACE),
Text(
localizations.memorySheetCreatedAtDataKey(
DateFormat.jms().format(
widget.memory.creationDate,
),
style: getBodyTextTextStyle(context),
),
const SizedBox(height: SMALL_SPACE),
Text(
widget.memory.id,
textAlign: TextAlign.center,
style: getBodyTextTextStyle(context),
),
],
),
style: getBodyTextTextStyle(context),
),
],
),
],
),
),
expandableContent: widget.memory.location == null
? const SizedBox.shrink()
: Container(
width: double.infinity,
color: backgroundColor,
child: MemoryLocationView(
location: widget.memory.location!,
),
),
);
}
}

View File

@ -0,0 +1,15 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<String> lookupAddress({
required final double latitude,
required final double longitude,
}) async {
final url = 'https://geocode.maps.co/reverse?lat=$latitude&lon=$longitude';
final uri = Uri.parse(url);
final response = await http.get(uri);
return jsonDecode(response.body)['display_name'];
}

View File

@ -24,3 +24,16 @@ TextStyle getSubTitleTextStyle(final BuildContext context) => platformThemeData(
material: (data) => data.textTheme.subtitle1!,
cupertino: (data) => data.textTheme.navTitleTextStyle,
);
TextStyle getCaptionTextStyle(final BuildContext context) => platformThemeData(
context,
material: (data) => data.textTheme.caption!,
cupertino: (data) => data.textTheme.tabLabelTextStyle,
);
Color getSheetColor(final BuildContext context) => platformThemeData(
context,
material: (data) =>
data.bottomSheetTheme.modalBackgroundColor ?? data.bottomAppBarColor,
cupertino: (data) => data.barBackgroundColor,
);