From 268d35e9793a8ca1f8ee8ad225d83de57793902b Mon Sep 17 00:00:00 2001 From: JanEuster Date: Wed, 15 Mar 2023 23:05:51 +0100 Subject: [PATCH 01/20] real data: steps & distance day totals --- lib/src/application/location.dart | 42 ++++++++++++++++++++++++++++--- lib/src/presentation/home.dart | 25 +++++++++--------- lib/src/presentation/today.dart | 4 +-- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index 08b7d47..71f8473 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -82,6 +82,44 @@ class LocationService { return _initialized; } + /// total steps + /// + /// from last measured steps count - first measured steps count + int get stepsTotal { + if (hasPositions) { + var stepsStart = 0; + var stepsEnd = 0; + for (final p in dataPoints) { + if (p.steps != null) { + stepsStart = p.steps!; + stepsEnd = p.steps!; + break; + } + } + for (final p in dataPoints.reversed) { + if (p.steps != null) { + stepsEnd = p.steps!; + break; + } + } + return stepsEnd - stepsStart; + } else { + return 0; + } + } + + /// total distance in meters + /// TODO: more accurate distance calculation + double get distanceTotal { + double dist = 0; + for (var i = 0; i record({Function(Position)? onReady}) async { log("start recording position data"); @@ -307,9 +345,7 @@ class LocationService { } if (ext["speed"] != null) speed = double.parse(ext["speed"] ?? "0"); if (ext["steps"] != null) { - steps = gpxDesc["steps"] != "null" - ? int.parse(gpxDesc["steps"] ?? "0") - : null; + steps = int.tryParse(ext["steps"]!); } posList.add(LocationDataPoint( diff --git a/lib/src/presentation/home.dart b/lib/src/presentation/home.dart index c225985..0e2041f 100644 --- a/lib/src/presentation/home.dart +++ b/lib/src/presentation/home.dart @@ -32,10 +32,9 @@ class _MyHomePageState extends State { locationService = LocationService(); AppSettings.instance.trackingLocation.get().then((value) { - setState(() { - isTrackingLocation = value; - locationService.init().then((value) => locationService.loadToday()); - }); + isTrackingLocation = value; + locationService.init().then((value) => + locationService.loadToday().then((value) => setState(() {}))); }); } @@ -51,13 +50,14 @@ class _MyHomePageState extends State { Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, - children: const [ - Text("3.742", - style: - TextStyle(fontSize: 75, fontWeight: FontWeight.w900)), - Text("10,2 km", - style: - TextStyle(fontSize: 40, fontWeight: FontWeight.w700)), + children: [ + Text(locationService.stepsTotal.toString(), + style: const TextStyle( + fontSize: 75, fontWeight: FontWeight.w900)), + Text( + "${(locationService.distanceTotal / 1000).toStringAsFixed(1)} km", + style: const TextStyle( + fontSize: 40, fontWeight: FontWeight.w700)), ], ), Container( @@ -187,7 +187,8 @@ class _MyHomePageState extends State { ), ], ))), - if (locationService.isInitialized) ActivityMap(locationService: locationService), + if (locationService.isInitialized) + ActivityMap(locationService: locationService), const Padding(padding: EdgeInsets.only(bottom: 50)), ]); } diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index 098e355..00f78d9 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -250,8 +250,8 @@ class _TodaysMapState extends State ? [ OverviewTotals( timeFrameString: "Today", - totalSteps: 6929, - totalDistance: 4200, + totalSteps: locationService.stepsTotal, + totalDistance: locationService.distanceTotal, ), Expanded(child: Container()), ] From 629bfd74b96d926c23eef09f574fb7075ff4d010 Mon Sep 17 00:00:00 2001 From: JanEuster Date: Wed, 15 Mar 2023 23:13:44 +0100 Subject: [PATCH 02/20] dont show tracking uptime stuff when tracking is not running --- lib/src/presentation/home.dart | 58 +++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/src/presentation/home.dart b/lib/src/presentation/home.dart index 0e2041f..1698b3d 100644 --- a/lib/src/presentation/home.dart +++ b/lib/src/presentation/home.dart @@ -95,34 +95,42 @@ class _MyHomePageState extends State { style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700)), const Padding(padding: EdgeInsets.only(top: 12)), - Row( - children: const [ - Text("24 ", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.w700)), - Text("days", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w400)), - ], - ), - const Padding(padding: EdgeInsets.only(top: 6)), - Row( - children: const [ - Text("154.00 ", + (isTrackingLocation != true) + ? const Text("tracking currently stopped", textAlign: TextAlign.left, style: TextStyle( fontSize: 24, - fontWeight: FontWeight.w700)), - Text("steps", - textAlign: TextAlign.left, - style: - TextStyle(fontWeight: FontWeight.w400)), - ], - ), + fontWeight: FontWeight.w400)) + : Row( + children: const [ + Text("24 ", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.w700)), + Text("days", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w400)), + ], + ), + if (isTrackingLocation == true) + const Padding(padding: EdgeInsets.only(top: 6)), + if (isTrackingLocation == true) + Row( + children: const [ + Text("154.00 ", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w700)), + Text("steps", + textAlign: TextAlign.left, + style: + TextStyle(fontWeight: FontWeight.w400)), + ], + ), SizedBox( height: 40, child: Row( From 0c205c2cd30228beb29b74369bc6843e23a111ac Mon Sep 17 00:00:00 2001 From: JanEuster Date: Thu, 16 Mar 2023 22:09:12 +0100 Subject: [PATCH 03/20] center homepoint modal map to basedOn position --- lib/src/presentation/homepoints.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/src/presentation/homepoints.dart b/lib/src/presentation/homepoints.dart index e3f5f09..00723d3 100644 --- a/lib/src/presentation/homepoints.dart +++ b/lib/src/presentation/homepoints.dart @@ -169,7 +169,7 @@ class AddHomepointModal extends StatefulWidget { } class _AddHomepointModalState extends State { - late MapController mapController; + late MapController mapController = MapController(); String name = "homepoint 1"; double radius = 80; LatLng? point; @@ -188,13 +188,15 @@ class _AddHomepointModalState extends State { }); } - mapController = MapController(); - Geolocator.getLastKnownPosition().then((pos) { - mapController.move(LatLng(pos!.latitude, pos!.longitude), 14); - }); - Geolocator.getCurrentPosition().then((pos) { - mapController.move(LatLng(pos.latitude, pos.longitude), 14); - }); + if (widget.basedOn != null) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + mapController.move(widget.basedOn!.position, 14.75); + }); + } else { + Geolocator.getLastKnownPosition().then((pos) { + mapController.move(LatLng(pos!.latitude, pos!.longitude), 14); + }); + } } void submit() { From a0350d6031ed9e7ff187f7c20649fb0921b7cb7b Mon Sep 17 00:00:00 2001 From: JanEuster Date: Thu, 16 Mar 2023 23:13:24 +0100 Subject: [PATCH 04/20] real data: todays hourly steps activity --- lib/src/application/location.dart | 89 ++++++++++++---------- lib/src/presentation/components/stats.dart | 34 ++------- lib/src/presentation/today.dart | 2 +- 3 files changed, 58 insertions(+), 67 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index 71f8473..7d82a9a 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -82,20 +82,22 @@ class LocationService { return _initialized; } + int get stepsMin { + for (final p in dataPoints) { + if (p.steps != null) { + return p.steps!; + } + } + return 0; + } + /// total steps /// /// from last measured steps count - first measured steps count int get stepsTotal { if (hasPositions) { - var stepsStart = 0; - var stepsEnd = 0; - for (final p in dataPoints) { - if (p.steps != null) { - stepsStart = p.steps!; - stepsEnd = p.steps!; - break; - } - } + var stepsStart = stepsMin; + var stepsEnd = stepsMin; for (final p in dataPoints.reversed) { if (p.steps != null) { stepsEnd = p.steps!; @@ -108,19 +110,37 @@ class LocationService { } } + List get hourlyStepsTotal { + List hours = List.generate(24, (index) => 0); + var stepsBefore = stepsMin; + for (final p in dataPoints) { + if (p.timestamp != null) { + var i = p.timestamp!.toLocal().hour; + if (p.steps != null && p.steps! > stepsBefore) { + log("${p.steps! - stepsBefore}"); + hours[i] += p.steps! - stepsBefore; + stepsBefore = p.steps!; + } + + } + } + log("hourly steps $hours"); + return hours; + } + /// total distance in meters /// TODO: more accurate distance calculation double get distanceTotal { double dist = 0; - for (var i = 0; i record({Function(Position)? onReady}) async { log("start recording position data"); Geolocator.getLastKnownPosition().then((p) { @@ -210,7 +230,6 @@ class LocationService { return false; } - void addPosition(Position position) { // LocationDataPoint can only have steps > 0 if ped status is not stopped // -> detecting stops clearer? @@ -218,8 +237,6 @@ class LocationService { dataPoints.add(LocationDataPoint(position, _newSteps, _newPedStatus)); } - - void optimizeCapturedData() { // remove redundant data points from positions list // - keep only start and end of a stop @@ -323,9 +340,6 @@ class LocationService { segmentedData = segments; } - - - List fromGPX(String xml, {bool setPos = true}) { var xmlGpx = GpxReader().fromString(xml); List posList = []; @@ -415,19 +429,19 @@ class LocationService { trksegs.add(Trkseg( trkpts: seg.dataPoints .map((p) => Wpt( - ele: p.altitude, - lat: p.latitude, - lon: p.longitude, - time: p.timestamp, - type: typeBasedOnSpeed(p.pedStatus, p.speed), - desc: - "steps:${p.steps};heading:${p.heading};speed:${p.speed}", - extensions: { - "pedStatus": p.pedStatus, - "steps": p.steps.toString(), - "heading": p.heading.toStringAsFixed(2), - "speed": p.speed.toStringAsFixed(2), - })) + ele: p.altitude, + lat: p.latitude, + lon: p.longitude, + time: p.timestamp, + type: typeBasedOnSpeed(p.pedStatus, p.speed), + desc: + "steps:${p.steps};heading:${p.heading};speed:${p.speed}", + extensions: { + "pedStatus": p.pedStatus, + "steps": p.steps.toString(), + "heading": p.heading.toStringAsFixed(2), + "speed": p.speed.toStringAsFixed(2), + })) .toList(), extensions: { "duration": "${seg.duration().inSeconds}s", @@ -484,9 +498,6 @@ class LocationService { log("gpx file exported to $gpxFilePath"); } - - - Future loadToday() async { String date = DateTime.now().toLocal().toIso8601String().split("T").first; var gpxDirPath = "${appDir.path}/gpxData"; @@ -538,7 +549,6 @@ class LocationService { dataPoints = []; } - StreamSubscription streamPosition(Function(Position) addPosition) { late LocationSettings locationSettings; @@ -776,6 +786,7 @@ class MinMax { T max; MinMax(this.min, this.max); + static MinMax fromList(List list) { double min = list.first; double max = list.first; @@ -801,7 +812,6 @@ extension Double on MinMax { } } - class LonLat { double longitude; double latitude; @@ -816,9 +826,10 @@ class LonLat { extension Range on MinMax { double get latRange { - return max.latitude-min.latitude; + return max.latitude - min.latitude; } + double get lngRange { - return max.longitude-min.longitude; + return max.longitude - min.longitude; } } diff --git a/lib/src/presentation/components/stats.dart b/lib/src/presentation/components/stats.dart index d7d565b..a460e0e 100644 --- a/lib/src/presentation/components/stats.dart +++ b/lib/src/presentation/components/stats.dart @@ -294,8 +294,12 @@ class BarChart extends StatelessWidget { class HourlyActivity extends StatefulWidget { final double hourWidth = 50; + final List data; + late double max; - const HourlyActivity({super.key}); + HourlyActivity({super.key, required this.data}) { + max = MinMax.fromList(data).max; + } @override State createState() => _HourlyActivityState(); @@ -360,32 +364,7 @@ class _HourlyActivityState extends State { scrollDirection: Axis.horizontal, controller: scrollController, itemBuilder: (BuildContext context, int index) { - List hoursPercent = [ - 0, - 0, - 0, - 0, - 0, - .1, - .4, - .6, - 0, - 0, - .1, - 0, - .3, - 1, - .5, - 1, - .1, - .3, - .2, - 0, - 0, - 0, - 0, - 0 - ]; // TODO: use real data + List hoursPercent = widget.data.map((h) => h/widget.max).toList(); return Container( padding: index < 23 ? const EdgeInsets.only(right: 5) @@ -414,6 +393,7 @@ class _HourlyActivityState extends State { Container( width: widget.hourWidth, height: 105 * hoursPercent[index], + constraints: const BoxConstraints(minHeight: 0, maxHeight: 105), color: index == selectedHourIndex ? null : Colors.black, diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index 00f78d9..b6223b6 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -268,7 +268,7 @@ class _TodaysMapState extends State height: 2, ) : null), - const HourlyActivity(), + HourlyActivity(data: locationService.hourlyStepsTotal), if (showDetails) ...[ const Padding( padding: EdgeInsets.symmetric( From ae36d711ca2babdc5254d77384038a2a6f8bb39c Mon Sep 17 00:00:00 2001 From: JanEuster Date: Fri, 17 Mar 2023 11:19:06 +0100 Subject: [PATCH 05/20] fix 100% on all hours when there is no activity at all that day --- lib/src/presentation/components/stats.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/presentation/components/stats.dart b/lib/src/presentation/components/stats.dart index a460e0e..844cb6d 100644 --- a/lib/src/presentation/components/stats.dart +++ b/lib/src/presentation/components/stats.dart @@ -364,7 +364,7 @@ class _HourlyActivityState extends State { scrollDirection: Axis.horizontal, controller: scrollController, itemBuilder: (BuildContext context, int index) { - List hoursPercent = widget.data.map((h) => h/widget.max).toList(); + List hoursPercent = widget.data.map((h) => widget.max == 0 ? 0.0 : h/widget.max).toList(); return Container( padding: index < 23 ? const EdgeInsets.only(right: 5) From 7f7f2c1aef551874e0a3baab96980c990ff2475c Mon Sep 17 00:00:00 2001 From: JanEuster Date: Fri, 17 Mar 2023 11:26:00 +0100 Subject: [PATCH 06/20] maybe fix gpx files being saved with utc timezone dates --- lib/src/application/location.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index 7d82a9a..f14c853 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -521,9 +521,9 @@ class LocationService { // check if its a new day and if so, remove all data from previous day // necessary because a new gpx file is created for every day -> no overlay in data // dates are converted to utc, because gpx stores dates as utc -> gpx files will not start before 0:00 and not end after 23:59 - if (lastDate.day != now.day) { + if (lastDate.toLocal().day != now.toLocal().day) { dataPoints = - dataPoints.where((p) => p.timestamp!.day == now.day).toList(); + dataPoints.where((p) => p.timestamp!.toLocal().day == now.toLocal().day).toList(); lastDate = now; } String date = lastDate.toLocal().toIso8601String().split("T").first; From b8444022164cf2320e3278ca8b45d6cb33fedbf2 Mon Sep 17 00:00:00 2001 From: JanEuster Date: Fri, 17 Mar 2023 11:44:28 +0100 Subject: [PATCH 07/20] totals --- lib/src/presentation/components/stats.dart | 15 ++------------- lib/src/presentation/overview.dart | 5 ++--- lib/src/presentation/today.dart | 1 - 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/lib/src/presentation/components/stats.dart b/lib/src/presentation/components/stats.dart index 844cb6d..a9a5ef8 100644 --- a/lib/src/presentation/components/stats.dart +++ b/lib/src/presentation/components/stats.dart @@ -6,7 +6,6 @@ import 'package:geo_steps/src/presentation/components/lines.dart'; import 'package:geo_steps/src/utils/sizing.dart'; class OverviewTotals extends StatelessWidget { - final String timeFrameString; final int totalSteps; /// total distance in meters @@ -14,15 +13,15 @@ class OverviewTotals extends StatelessWidget { OverviewTotals( {super.key, - required this.timeFrameString, required this.totalSteps, required this.totalDistance}); @override Widget build(BuildContext context) { - var textStyle = const TextStyle(fontWeight: FontWeight.w500, fontSize: 24); + var textStyle = const TextStyle(fontWeight: FontWeight.w500, fontSize: 24, color: Colors.white); return Container( decoration: BoxDecoration( + color: Colors.black, border: Border.all(color: Colors.black, width: 2), ), child: Padding( @@ -31,16 +30,6 @@ class OverviewTotals extends StatelessWidget { width: 140, child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Totals - $timeFrameString", - style: - const TextStyle(fontWeight: FontWeight.w900, fontSize: 18), - ), - const Padding( - padding: EdgeInsets.only(top: 5, bottom: 10), - child: Line( - height: 2, - )), Text( "$totalSteps steps", style: textStyle, diff --git a/lib/src/presentation/overview.dart b/lib/src/presentation/overview.dart index 25e909e..7c5dd61 100644 --- a/lib/src/presentation/overview.dart +++ b/lib/src/presentation/overview.dart @@ -204,9 +204,8 @@ class _OverviewPageState extends State { child: Row( children: [ OverviewTotals( - timeFrameString: timeFrameString, - totalSteps: 6929, - totalDistance: 4200, + totalSteps: locationService!.stepsTotal, + totalDistance: locationService!.distanceTotal, ), Expanded(child: Container()), ], diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index b6223b6..14ed0a1 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -249,7 +249,6 @@ class _TodaysMapState extends State children: showDetails ? [ OverviewTotals( - timeFrameString: "Today", totalSteps: locationService.stepsTotal, totalDistance: locationService.distanceTotal, ), From 7d479d70d632c29262c354895952118f5976752f Mon Sep 17 00:00:00 2001 From: JanEuster Date: Fri, 17 Mar 2023 12:08:28 +0100 Subject: [PATCH 08/20] fix hourly activity scroll pos index --- lib/src/application/location.dart | 1 - lib/src/presentation/components/stats.dart | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index f14c853..3d40156 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -117,7 +117,6 @@ class LocationService { if (p.timestamp != null) { var i = p.timestamp!.toLocal().hour; if (p.steps != null && p.steps! > stepsBefore) { - log("${p.steps! - stepsBefore}"); hours[i] += p.steps! - stepsBefore; stepsBefore = p.steps!; } diff --git a/lib/src/presentation/components/stats.dart b/lib/src/presentation/components/stats.dart index a9a5ef8..1256962 100644 --- a/lib/src/presentation/components/stats.dart +++ b/lib/src/presentation/components/stats.dart @@ -283,6 +283,7 @@ class BarChart extends StatelessWidget { class HourlyActivity extends StatefulWidget { final double hourWidth = 50; + final double hourPad = 5; final List data; late double max; @@ -330,9 +331,9 @@ class _HourlyActivityState extends State { } setSelectedHour() { - var pixels = scrollController.position.pixels + widget.hourWidth / 2; + var pixels = scrollController.position.pixels; int newIndex = - (pixels / scrollController.position.maxScrollExtent * 23).floor(); + (pixels / (scrollController.position.maxScrollExtent + widget.hourPad/2) * 24).floor(); setState(() { selectedHourIndex = newIndex; }); @@ -345,11 +346,11 @@ class _HourlyActivityState extends State { width: sizer.width, height: 139, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 0), + padding: EdgeInsets.symmetric(vertical: widget.hourPad, horizontal: 0), child: ListView.builder( itemCount: 24, padding: EdgeInsets.symmetric( - horizontal: sizer.width / 2 - 25, vertical: 0), + horizontal: sizer.width / 2, vertical: 0), scrollDirection: Axis.horizontal, controller: scrollController, itemBuilder: (BuildContext context, int index) { From 95edfcf023987617cb761ae02d05171c8ac18e77 Mon Sep 17 00:00:00 2001 From: JanEuster Date: Fri, 17 Mar 2023 22:22:53 +0100 Subject: [PATCH 09/20] move map center & marker to hourly activity scroll timestamp --- lib/src/application/location.dart | 115 ++++++++++++++++++++- lib/src/presentation/components/stats.dart | 15 ++- lib/src/presentation/today.dart | 31 +++++- 3 files changed, 152 insertions(+), 9 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index 3d40156..865e852 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -120,7 +120,6 @@ class LocationService { hours[i] += p.steps! - stepsBefore; stepsBefore = p.steps!; } - } } log("hourly steps $hours"); @@ -140,6 +139,74 @@ class LocationService { return dist; } + LocationDataPoint dataPointClosestTo(DateTime time) { + int? smallestDiff; + int smallestDiffI = 0; + var timeMillis = time.dayInMillis(); + + var i = 0; + while (i < dataPoints.length) { + final p = dataPoints[i]; + if (p.timestamp != null) { + var pMillis = p.timestamp!.dayInMillis(); + final diff = (timeMillis - pMillis).abs(); + + if (smallestDiff == null) { + smallestDiff = diff; + smallestDiffI = 0; + } + + log("$i - $diff"); + if (smallestDiff != null && diff < smallestDiff!) { + log("index: $i"); + smallestDiffI = i; + smallestDiff = diff; + } + i++; + } else { + i++; + } + } + log("--- index $smallestDiffI ---"); + return dataPoints[smallestDiffI]; + } + + /// list of closest datapoint for every minute of the day + /// + /// dataPointPerMinute.length = 24 * 60 = 1440 + List get dataPointPerMinute { + List minutesNullable = List.generate(1440, (index) => null); + for (final p in dataPoints) { + final pMinute = p.timestamp!.dayInMinutes(); + if (minutesNullable[pMinute] == null) { + minutesNullable[pMinute] = p; + } else { + // add up miutes of datapoints in the same minute + if (minutesNullable[pMinute]!.steps != null) { + minutesNullable[pMinute]!.steps = + (minutesNullable[pMinute]!.steps ?? 0) + (p.steps ?? 0); + } + // set pedStatus if its unknown on the current datapoint + if (minutesNullable[pMinute]!.pedStatus == LocationDataPoint.STATUS_UNKNOWN && + p.pedStatus != LocationDataPoint.STATUS_UNKNOWN) { + minutesNullable[pMinute]!.pedStatus == p.pedStatus; + } + } + } + List minutes = []; + for (var i = 0; i < minutesNullable.length - 1; i++) { + var p = minutesNullable[i]; + if (p == null) { + var closestI = minutesNullable.closestNonNull(i); + if (closestI != null) { + p = minutesNullable[closestI]; + } + } + minutes.add(p ?? dataPoints.first); + } + return minutes; + } + Future record({Function(Position)? onReady}) async { log("start recording position data"); Geolocator.getLastKnownPosition().then((p) { @@ -445,7 +512,8 @@ class LocationService { extensions: { "duration": "${seg.duration().inSeconds}s", "startTime": seg.startTime != null ? seg.startTime.toString() : "", - "endTime": seg.endTime != null ? seg.endTime.toString() : "", + "endTime": + seg.endTime != null ? seg.endTime.toString() : "", })); } @@ -521,8 +589,9 @@ class LocationService { // necessary because a new gpx file is created for every day -> no overlay in data // dates are converted to utc, because gpx stores dates as utc -> gpx files will not start before 0:00 and not end after 23:59 if (lastDate.toLocal().day != now.toLocal().day) { - dataPoints = - dataPoints.where((p) => p.timestamp!.toLocal().day == now.toLocal().day).toList(); + dataPoints = dataPoints + .where((p) => p.timestamp!.toLocal().day == now.toLocal().day) + .toList(); lastDate = now; } String date = lastDate.toLocal().toIso8601String().split("T").first; @@ -832,3 +901,41 @@ extension Range on MinMax { return max.longitude - min.longitude; } } + +extension DayIn on DateTime { + /// minutes from start of day + int dayInMinutes() { + return (hour * 60) + minute; + } + + /// milliseconds from start of day + int dayInMillis() { + return (hour * 3600 * 1000) + + (minute * 60 * 1000) + + (second * 1000) + + millisecond; + } +} + +extension NullableList on List { + /// find index of closest list entry that is not null + int? closestNonNull(int fromI) { + var walkedDistance = 1; + while (true) { + // negative/down walk index + var mI = fromI - walkedDistance; + // positive/up walk index + var pI = fromI + walkedDistance; + if (mI >= 0 && this[mI] != null) { + return mI; + } else if (pI < length && this[pI] != null) { + return pI; + } else { + walkedDistance++; + } + if (mI < 0 && pI >= length) { + return null; + } + } + } +} diff --git a/lib/src/presentation/components/stats.dart b/lib/src/presentation/components/stats.dart index 1256962..efc39c3 100644 --- a/lib/src/presentation/components/stats.dart +++ b/lib/src/presentation/components/stats.dart @@ -287,7 +287,9 @@ class HourlyActivity extends StatefulWidget { final List data; late double max; - HourlyActivity({super.key, required this.data}) { + Function(double)? onScroll = (percentage) {}; + + HourlyActivity({super.key, required this.data, this.onScroll}) { max = MinMax.fromList(data).max; } @@ -305,6 +307,7 @@ class _HourlyActivityState extends State { void initState() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { scrollController.addListener(() { + onScroll(); if (scrollController.position.pixels == scrollController.position.maxScrollExtent || scrollController.position.pixels == @@ -315,6 +318,7 @@ class _HourlyActivityState extends State { scrollController.position.isScrollingNotifier.addListener(() { if (scrollController.positions.isNotEmpty) { var scrollBool = scrollController.position.isScrollingNotifier.value; + if (scrollBool != isScrolling) { setState(() { isScrolling = scrollBool; @@ -330,10 +334,17 @@ class _HourlyActivityState extends State { super.initState(); } + onScroll() { + final pixels = scrollController.position.pixels; + final percentage = pixels / (scrollController.position.maxScrollExtent+0.1); // +0.1 so its never 24 + widget.onScroll!(percentage); + } + setSelectedHour() { - var pixels = scrollController.position.pixels; + final pixels = scrollController.position.pixels; int newIndex = (pixels / (scrollController.position.maxScrollExtent + widget.hourPad/2) * 24).floor(); + setState(() { selectedHourIndex = newIndex; }); diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index 14ed0a1..06c7c3f 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -58,6 +58,9 @@ class _TodaysMapState extends State bool showDetails = false; static const double mapHeightDetails = 150; + LatLng markerPosition = LatLng(0, 0); + List? minutes; + @override void initState() { super.initState(); @@ -71,6 +74,10 @@ class _TodaysMapState extends State LatLng(locationService.lastPos!.latitude, locationService.lastPos!.longitude), 12.8)); + minutes = locationService.dataPointPerMinute; + + final firstP = locationService.dataPoints.first; + markerPosition = LatLng(firstP.latitude, firstP.longitude); } })); @@ -190,8 +197,7 @@ class _TodaysMapState extends State Marker( width: 46, height: 46, - point: LatLng(locationService.lastPos!.latitude, - locationService.lastPos!.longitude), + point: markerPosition, builder: (context) => Transform.translate( offset: const Offset(0, -23), child: Container( @@ -267,7 +273,26 @@ class _TodaysMapState extends State height: 2, ) : null), - HourlyActivity(data: locationService.hourlyStepsTotal), + HourlyActivity(data: locationService.hourlyStepsTotal, onScroll: (percentage) { + var thisDate = DateTime.now(); + final millisecondsToday = (percentage * 24 * 60 * 60 * 1000).round(); + final minutesToday = (percentage * 24 * 60).round(); + thisDate = DateTime(thisDate.year, thisDate.month, thisDate.day); + thisDate = DateTime.fromMillisecondsSinceEpoch(thisDate.millisecondsSinceEpoch + millisecondsToday); + + if (locationService.hasPositions) { + // log("$minutes"); + // var newPos = locationService.dataPointClosestTo(thisDate.toLocal()); + // log("$newPos"); + if (minutes != null) { + var newPos = minutes![minutesToday]; + setState(() { + markerPosition = LatLng(newPos.latitude, newPos.longitude); + mapController.move(markerPosition, 13.5); + }); + } + } + },), if (showDetails) ...[ const Padding( padding: EdgeInsets.symmetric( From 66b0daa2b01ab54786d616039723d9017d2de20b Mon Sep 17 00:00:00 2001 From: JanEuster Date: Mon, 20 Mar 2023 14:14:06 +0100 Subject: [PATCH 10/20] fix minutes of day having len 0 1439 where it should be 1440 --- lib/src/application/location.dart | 2 +- lib/src/presentation/today.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index 865e852..c323522 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -194,7 +194,7 @@ class LocationService { } } List minutes = []; - for (var i = 0; i < minutesNullable.length - 1; i++) { + for (var i = 0; i < minutesNullable.length; i++) { var p = minutesNullable[i]; if (p == null) { var closestI = minutesNullable.closestNonNull(i); diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index 06c7c3f..cbd4d5a 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -276,7 +276,7 @@ class _TodaysMapState extends State HourlyActivity(data: locationService.hourlyStepsTotal, onScroll: (percentage) { var thisDate = DateTime.now(); final millisecondsToday = (percentage * 24 * 60 * 60 * 1000).round(); - final minutesToday = (percentage * 24 * 60).round(); + final minutesToday = (percentage * ((24 * 60)-1)).round(); thisDate = DateTime(thisDate.year, thisDate.month, thisDate.day); thisDate = DateTime.fromMillisecondsSinceEpoch(thisDate.millisecondsSinceEpoch + millisecondsToday); From 5d06733c621377df5144d82cf2abb6e5bfc4100a Mon Sep 17 00:00:00 2001 From: JanEuster Date: Mon, 20 Mar 2023 14:30:01 +0100 Subject: [PATCH 11/20] display correct per hour step count --- lib/src/application/location.dart | 29 ++++-- lib/src/presentation/components/stats.dart | 113 +++++++++++++-------- 2 files changed, 91 insertions(+), 51 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index c323522..3d5dd2a 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -122,7 +122,6 @@ class LocationService { } } } - log("hourly steps $hours"); return hours; } @@ -175,11 +174,12 @@ class LocationService { /// /// dataPointPerMinute.length = 24 * 60 = 1440 List get dataPointPerMinute { - List minutesNullable = List.generate(1440, (index) => null); + List minutesNullable = + List.generate(1440, (index) => null); for (final p in dataPoints) { final pMinute = p.timestamp!.dayInMinutes(); if (minutesNullable[pMinute] == null) { - minutesNullable[pMinute] = p; + minutesNullable[pMinute] = p.clone(); } else { // add up miutes of datapoints in the same minute if (minutesNullable[pMinute]!.steps != null) { @@ -187,7 +187,8 @@ class LocationService { (minutesNullable[pMinute]!.steps ?? 0) + (p.steps ?? 0); } // set pedStatus if its unknown on the current datapoint - if (minutesNullable[pMinute]!.pedStatus == LocationDataPoint.STATUS_UNKNOWN && + if (minutesNullable[pMinute]!.pedStatus == + LocationDataPoint.STATUS_UNKNOWN && p.pedStatus != LocationDataPoint.STATUS_UNKNOWN) { minutesNullable[pMinute]!.pedStatus == p.pedStatus; } @@ -432,7 +433,7 @@ class LocationService { Position( longitude: trkpt.lon!, latitude: trkpt.lat!, - timestamp: trkpt.time, + timestamp: trkpt.time?.toLocal(), accuracy: 0, altitude: trkpt.ele!, heading: heading, @@ -512,8 +513,7 @@ class LocationService { extensions: { "duration": "${seg.duration().inSeconds}s", "startTime": seg.startTime != null ? seg.startTime.toString() : "", - "endTime": - seg.endTime != null ? seg.endTime.toString() : "", + "endTime": seg.endTime != null ? seg.endTime.toString() : "", })); } @@ -739,6 +739,21 @@ class LocationDataPoint { pedStatus = ped; } + LocationDataPoint clone() { + return LocationDataPoint( + Position( + longitude: longitude, + latitude: latitude, + timestamp: timestamp, + accuracy: 0, + altitude: altitude, + heading: heading, + speed: speed, + speedAccuracy: 0), + steps, + pedStatus); + } + @override String toString() { return "LocationDataPoint at $timestamp { lat: $latitude lon: $longitude alt: $altitude dir: $heading° spe: $speed m/s with status: $pedStatus - $steps steps }"; diff --git a/lib/src/presentation/components/stats.dart b/lib/src/presentation/components/stats.dart index efc39c3..bdc1071 100644 --- a/lib/src/presentation/components/stats.dart +++ b/lib/src/presentation/components/stats.dart @@ -12,13 +12,12 @@ class OverviewTotals extends StatelessWidget { final double totalDistance; OverviewTotals( - {super.key, - required this.totalSteps, - required this.totalDistance}); + {super.key, required this.totalSteps, required this.totalDistance}); @override Widget build(BuildContext context) { - var textStyle = const TextStyle(fontWeight: FontWeight.w500, fontSize: 24, color: Colors.white); + var textStyle = const TextStyle( + fontWeight: FontWeight.w500, fontSize: 24, color: Colors.white); return Container( decoration: BoxDecoration( color: Colors.black, @@ -66,7 +65,6 @@ class NamedBarGraph extends StatelessWidget { max = MinMax.fromList(dataValues.map((e) => e.toDouble()).toList()).max; } - @override Widget build(BuildContext context) { var textStyleOnWhite = const TextStyle( @@ -92,7 +90,10 @@ class NamedBarGraph extends StatelessWidget { (index) => Container( alignment: Alignment.center, height: rowHeight, - child: Text(dataKeys[index], style: textStyleOnBlack,)), + child: Text( + dataKeys[index], + style: textStyleOnBlack, + )), ), ), ), @@ -122,7 +123,8 @@ class NamedBarGraph extends StatelessWidget { (index) => Container( alignment: Alignment.center, height: rowHeight, - child: Text(dataValues[index].toString(), style: textStyleOnBlack)), + child: Text(dataValues[index].toString(), + style: textStyleOnBlack)), ), ), ), @@ -193,11 +195,9 @@ class OverviewBarGraph extends StatelessWidget { children: List.generate(4, (i) { String value; if (i == 0) { - value = - valuesMinMax.min.toStringAsFixed(1); + value = valuesMinMax.min.toStringAsFixed(1); } else if (i == 3) { - value = - valuesMinMax.max.toStringAsFixed(1); + value = valuesMinMax.max.toStringAsFixed(1); } else { value = (valuesMinMax.diff / 3 * i) .toStringAsFixed(1); @@ -282,7 +282,7 @@ class BarChart extends StatelessWidget { } class HourlyActivity extends StatefulWidget { - final double hourWidth = 50; + final double hourWidth = 60; final double hourPad = 5; final List data; late double max; @@ -336,14 +336,18 @@ class _HourlyActivityState extends State { onScroll() { final pixels = scrollController.position.pixels; - final percentage = pixels / (scrollController.position.maxScrollExtent+0.1); // +0.1 so its never 24 + final percentage = pixels / + (scrollController.position.maxScrollExtent + + 0.1); // +0.1 so its never 24 widget.onScroll!(percentage); } setSelectedHour() { final pixels = scrollController.position.pixels; - int newIndex = - (pixels / (scrollController.position.maxScrollExtent + widget.hourPad/2) * 24).floor(); + int newIndex = (pixels / + (scrollController.position.maxScrollExtent + widget.hourPad / 2) * + 24) + .floor(); setState(() { selectedHourIndex = newIndex; @@ -357,7 +361,8 @@ class _HourlyActivityState extends State { width: sizer.width, height: 139, child: Padding( - padding: EdgeInsets.symmetric(vertical: widget.hourPad, horizontal: 0), + padding: + EdgeInsets.symmetric(vertical: widget.hourPad, horizontal: 0), child: ListView.builder( itemCount: 24, padding: EdgeInsets.symmetric( @@ -365,7 +370,9 @@ class _HourlyActivityState extends State { scrollDirection: Axis.horizontal, controller: scrollController, itemBuilder: (BuildContext context, int index) { - List hoursPercent = widget.data.map((h) => widget.max == 0 ? 0.0 : h/widget.max).toList(); + List hoursPercent = widget.data + .map((h) => widget.max == 0 ? 0.0 : h / widget.max) + .toList(); return Container( padding: index < 23 ? const EdgeInsets.only(right: 5) @@ -383,33 +390,51 @@ class _HourlyActivityState extends State { height: 3), SizedBox( height: 105, - child: Column(children: [ - Expanded( - child: Container( - width: widget.hourWidth, - decoration: BoxDecoration( - border: Border.all( - color: Colors.black, width: 1)), - )), - Container( - width: widget.hourWidth, - height: 105 * hoursPercent[index], - constraints: const BoxConstraints(minHeight: 0, maxHeight: 105), - color: index == selectedHourIndex - ? null - : Colors.black, - decoration: index == selectedHourIndex - ? BoxDecoration( - border: Border.all( - color: Colors.black, width: 1), - image: const DecorationImage( - fit: BoxFit.none, - scale: 2.5, - image: AssetImage( - "assets/line_pattern.jpg"), - repeat: ImageRepeat.repeat)) - : null), - ])), + child: Stack( + children: [ + Column(children: [ + Expanded( + child: Container( + width: widget.hourWidth, + decoration: BoxDecoration( + border: Border.all( + color: Colors.black, width: 1)), + )), + Container( + width: widget.hourWidth, + height: 105 * hoursPercent[index], + constraints: const BoxConstraints( + minHeight: 0, maxHeight: 105), + color: index == selectedHourIndex + ? null + : Colors.black, + decoration: index == selectedHourIndex + ? BoxDecoration( + border: Border.all( + color: Colors.black, width: 1), + image: const DecorationImage( + fit: BoxFit.none, + scale: 2.5, + image: AssetImage( + "assets/line_pattern.jpg"), + repeat: ImageRepeat.repeat)) + : null), + ]), + Positioned( + top: 80 * (1-hoursPercent[index]), + child: SizedBox( + width: widget.hourWidth, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Container( + height: 16, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(), + ), child: Center(child: Text(widget.data[index].toStringAsFixed(0), style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),))), + ))), + ], + )), SizedBox(height: 16, child: Text("$index")) ], ), From 64dcefdb584bbe7c832d5e779f8c46595f7cd38d Mon Sep 17 00:00:00 2001 From: JanEuster Date: Mon, 20 Mar 2023 15:41:40 +0100 Subject: [PATCH 12/20] show data for minute selected --- lib/src/presentation/components/helper.dart | 32 +++++ lib/src/presentation/today.dart | 135 ++++++++++++++++---- 2 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 lib/src/presentation/components/helper.dart diff --git a/lib/src/presentation/components/helper.dart b/lib/src/presentation/components/helper.dart new file mode 100644 index 0000000..8b73849 --- /dev/null +++ b/lib/src/presentation/components/helper.dart @@ -0,0 +1,32 @@ + + +import 'package:flutter/material.dart'; + +class MapMarkerTriangle extends CustomPainter { + MapMarkerTriangle(); + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..color = Colors.black + ..strokeWidth = 1; + + // Offset start = Offset(0, size.height / 2); + // Offset end = Offset(size.width, size.height / 2); + // + // canvas.drawLine(start, end, paint); + + var path = Path(); + path.moveTo(0, 11); + path.lineTo(18, 0); + path.lineTo(18, 38); + path.lineTo(0, 11); + path.close(); + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } +} \ No newline at end of file diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index cbd4d5a..06b6778 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -1,10 +1,12 @@ import 'dart:developer'; import 'dart:async'; +import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong2/latlong.dart'; +import 'package:geo_steps/src/presentation/components/helper.dart'; +import 'package:latlong2/latlong.dart' as latlng; import 'package:intl/intl.dart'; // local imports @@ -55,10 +57,12 @@ class _TodaysMapState extends State late LocationService locationService; late TargetPlatform defaultTargetPlatform = TargetPlatform.iOS; final mapController = MapController(); - bool showDetails = false; static const double mapHeightDetails = 150; - LatLng markerPosition = LatLng(0, 0); + bool showDetails = false; + LocationDataPoint? selectedMinute; + + latlng.LatLng markerPosition = latlng.LatLng(0, 0); List? minutes; @override @@ -71,13 +75,14 @@ class _TodaysMapState extends State .whenComplete(() => locationService.loadToday().then((wasLoaded) { if (wasLoaded && locationService.hasPositions) { setState(() => mapController.move( - LatLng(locationService.lastPos!.latitude, + latlng.LatLng(locationService.lastPos!.latitude, locationService.lastPos!.longitude), 12.8)); minutes = locationService.dataPointPerMinute; final firstP = locationService.dataPoints.first; - markerPosition = LatLng(firstP.latitude, firstP.longitude); + markerPosition = + latlng.LatLng(firstP.latitude, firstP.longitude); } })); @@ -89,7 +94,7 @@ class _TodaysMapState extends State var changed = locationService.dataPointsFromKV(receivedDataPoints); if (changed) { mapController.move( - LatLng(locationService.lastPos!.latitude, + latlng.LatLng(locationService.lastPos!.latitude, locationService.lastPos!.longitude), 14.5); } @@ -205,7 +210,72 @@ class _TodaysMapState extends State image: DecorationImage( image: AssetImage( "assets/map_pin.png")))), - )) + )), + if (selectedMinute != null) + Marker( + point: markerPosition, + builder: (context) => Transform.translate( + offset: const Offset(28, -30), + child: SizedBox( + height: 40, + child: Stack( + clipBehavior: Clip.none, + children: [ + SizedBox( + child: CustomPaint( + painter: + MapMarkerTriangle())), + Positioned( + left: 18, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + width: 60, + height: 38, + decoration: const BoxDecoration( + color: Colors.white,), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (selectedMinute! + .timestamp != + null) + Text( + DateFormat("HH:mm").format(selectedMinute!.timestamp!), + style: const TextStyle( + fontSize: 9, + fontWeight: + FontWeight + .w500), + ), + Text( + selectedMinute!.pedStatus, + style: const TextStyle( + fontSize: 9, + fontWeight: + FontWeight + .w500), + ), + Text( + "${selectedMinute!.steps ?? 0} steps", + style: const TextStyle( + fontSize: 9, + fontWeight: + FontWeight + .w500), + ) + ], + ), + )) + ], + ), + ), + )) + // Container( + // width: 40, + // height: 40, + // decoration: + // BoxDecoration(color: Colors.white, border: Border.all()), + // ) ], ) ], @@ -234,7 +304,7 @@ class _TodaysMapState extends State const Padding( padding: EdgeInsets.only(bottom: 5)), Transform.rotate( - angle: showDetails ? 0 : 1 * pi, + angle: showDetails ? 0.0 : 1.0 * math.pi, child: const Icon( Icomoon.arrow, color: Colors.white, @@ -256,7 +326,8 @@ class _TodaysMapState extends State ? [ OverviewTotals( totalSteps: locationService.stepsTotal, - totalDistance: locationService.distanceTotal, + totalDistance: + locationService.distanceTotal, ), Expanded(child: Container()), ] @@ -273,26 +344,36 @@ class _TodaysMapState extends State height: 2, ) : null), - HourlyActivity(data: locationService.hourlyStepsTotal, onScroll: (percentage) { - var thisDate = DateTime.now(); - final millisecondsToday = (percentage * 24 * 60 * 60 * 1000).round(); - final minutesToday = (percentage * ((24 * 60)-1)).round(); - thisDate = DateTime(thisDate.year, thisDate.month, thisDate.day); - thisDate = DateTime.fromMillisecondsSinceEpoch(thisDate.millisecondsSinceEpoch + millisecondsToday); + HourlyActivity( + data: locationService.hourlyStepsTotal, + onScroll: (percentage) { + var thisDate = DateTime.now(); + final millisecondsToday = + (percentage * 24 * 60 * 60 * 1000).round(); + final minutesToday = + (percentage * ((24 * 60) - 1)).round(); + thisDate = DateTime( + thisDate.year, thisDate.month, thisDate.day); + thisDate = DateTime.fromMillisecondsSinceEpoch( + thisDate.millisecondsSinceEpoch + + millisecondsToday); - if (locationService.hasPositions) { - // log("$minutes"); - // var newPos = locationService.dataPointClosestTo(thisDate.toLocal()); - // log("$newPos"); - if (minutes != null) { - var newPos = minutes![minutesToday]; - setState(() { - markerPosition = LatLng(newPos.latitude, newPos.longitude); - mapController.move(markerPosition, 13.5); - }); + if (locationService.hasPositions) { + // log("$minutes"); + // var newPos = locationService.dataPointClosestTo(thisDate.toLocal()); + // log("$newPos"); + if (minutes != null) { + setState(() { + selectedMinute = minutes![minutesToday]; + markerPosition = latlng.LatLng( + selectedMinute!.latitude, + selectedMinute!.longitude); + mapController.move(markerPosition, 13.5); + }); + } } - } - },), + }, + ), if (showDetails) ...[ const Padding( padding: EdgeInsets.symmetric( From f1394662df1a49b42db9ca91336c73b74a62cdac Mon Sep 17 00:00:00 2001 From: JanEuster Date: Mon, 20 Mar 2023 15:57:38 +0100 Subject: [PATCH 13/20] fix isTrackingLocation check on first app launch --- lib/main.dart | 2 +- lib/src/application/background_tasks.dart | 2 +- lib/src/presentation/today.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c1385b2..51506ad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -69,7 +69,7 @@ void main() async { var isTrackingLocation = await AppSettings.instance.trackingLocation.get(); log("isTrackingLocation: $isTrackingLocation"); - if (isTrackingLocation!) { + if (isTrackingLocation == true) { await FlutterBackgroundService().startService(); // FlutterBackgroundService().invoke("startTracking"); } diff --git a/lib/src/application/background_tasks.dart b/lib/src/application/background_tasks.dart index b1f9c84..a2524dc 100644 --- a/lib/src/application/background_tasks.dart +++ b/lib/src/application/background_tasks.dart @@ -49,7 +49,7 @@ FutureOr onStart(ServiceInstance service) async { }); service.on("saveTrackingData").listen((event) async { var isTrackingLocation = await AppSettings.instance.trackingLocation.get(); - if (isTrackingLocation != null && isTrackingLocation) { + if (isTrackingLocation == true) { await locationService.saveToday(); } }); diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index 06b6778..39f4e49 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -87,7 +87,7 @@ class _TodaysMapState extends State })); AppSettings().trackingLocation.get().then((isTrackingLocation) { - if (isTrackingLocation != null && isTrackingLocation) { + if (isTrackingLocation != true) { FlutterBackgroundService().on("sendTrackingData").listen((event) { setState(() { List receivedDataPoints = event!["trackingData"]; From ab2f8fc1aeb0d28464d337e3b1f460f6f6708456 Mon Sep 17 00:00:00 2001 From: JanEuster Date: Mon, 20 Mar 2023 15:58:12 +0100 Subject: [PATCH 14/20] per minute step count correct??? --- lib/src/application/location.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index 3d5dd2a..e91d276 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -176,15 +176,23 @@ class LocationService { List get dataPointPerMinute { List minutesNullable = List.generate(1440, (index) => null); - for (final p in dataPoints) { + for (var i = 0; i < dataPoints.length; i++) { + var beforeP = i > 0 ? dataPoints[i - 1] : dataPoints[i]; + var p = dataPoints[i]; + final pMinute = p.timestamp!.dayInMinutes(); if (minutesNullable[pMinute] == null) { minutesNullable[pMinute] = p.clone(); + if (minutesNullable[pMinute]!.steps != null) { + minutesNullable[pMinute]!.steps = minutesNullable[pMinute]!.steps! - + (minutesNullable[pMinute]!.steps ?? 0); + } } else { // add up miutes of datapoints in the same minute if (minutesNullable[pMinute]!.steps != null) { minutesNullable[pMinute]!.steps = - (minutesNullable[pMinute]!.steps ?? 0) + (p.steps ?? 0); + (minutesNullable[pMinute]!.steps ?? 0) + + (p.steps != null ? (p.steps! - (beforeP.steps ?? 0)) : 0); } // set pedStatus if its unknown on the current datapoint if (minutesNullable[pMinute]!.pedStatus == From 4402361ae52fe36b9ed1ddbe9b62e046d9930c10 Mon Sep 17 00:00:00 2001 From: JanEuster Date: Mon, 20 Mar 2023 16:11:35 +0100 Subject: [PATCH 15/20] km/h graph --- lib/src/application/location.dart | 16 ++++++++++++++++ lib/src/presentation/today.dart | 17 +---------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/src/application/location.dart b/lib/src/application/location.dart index e91d276..9f07aff 100644 --- a/lib/src/application/location.dart +++ b/lib/src/application/location.dart @@ -138,6 +138,22 @@ class LocationService { return dist; } + List get hourlyDistanceTotal { + List hours = List.generate(24, (index) => 0); + for (var i = 1; i < dataPoints.length; i++) { + var beforeP = dataPoints[i - 1]; + var p = dataPoints[i]; + + if (p.timestamp != null && beforeP.timestamp != null) { + var i = p.timestamp!.toLocal().hour; + hours[i] += Distance().distance( + LatLng(beforeP.latitude, beforeP.longitude), + LatLng(p.latitude, p.longitude)); + } + } + return hours; + } + LocationDataPoint dataPointClosestTo(DateTime time) { int? smallestDiff; int smallestDiffI = 0; diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index 39f4e49..fc15ccd 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -383,22 +383,7 @@ class _TodaysMapState extends State )), Padding( padding: const EdgeInsets.all(10), - child: OverviewBarGraph(data: const [ - 1, - 2, - 6, - 2, - 3, - 1, - 12, - 42, - 10, - 1, - 1, - 3, - 95, - 32 - ], title: "stat 1")), + child: OverviewBarGraph(data: locationService.hourlyDistanceTotal.map((e) => e/1000).toList(), title: "km/h")), Padding( padding: const EdgeInsets.all(8.0), child: NamedBarGraph( From 577bac5b1311a7a212d2e1272cec2f0be082ba59 Mon Sep 17 00:00:00 2001 From: JanEuster Date: Sat, 25 Mar 2023 13:05:26 +0100 Subject: [PATCH 16/20] visited homepoints chart --- lib/src/application/homepoints.dart | 36 ++++++++ lib/src/presentation/today.dart | 122 +++++++++++++++++----------- 2 files changed, 111 insertions(+), 47 deletions(-) diff --git a/lib/src/application/homepoints.dart b/lib/src/application/homepoints.dart index c80b81f..13e2a57 100644 --- a/lib/src/application/homepoints.dart +++ b/lib/src/application/homepoints.dart @@ -15,6 +15,8 @@ class HomepointManager { late Directory _appDir; late File _homepointFile; + List>? visits; + HomepointManager() {} Map get homepoints { @@ -50,6 +52,40 @@ class HomepointManager { jsonMap.map((key, value) => MapEntry(key, Homepoint.fromJson(value))); } + List> getVisits(List dataPoints) { + Map visited = + Map.fromEntries(homepoints.entries.map((e) => MapEntry(e.key, 0))); + for (final h in homepoints.entries) { + final homeLatLng = h.value.position; + + var atHP = false; + var i = 0; + while (i < dataPoints.length) { + var dp = dataPoints[i]; + final dpPos = LatLng(dp.position.latitude, dp.position.longitude); + if (const Distance().distance(dpPos, homeLatLng) < h.value.radius) { + atHP = true; + } else if (atHP) { + // true when leaving HP + // dataPoint is within homepoint radius + visited.update(h.key, (value) => value + 1); + atHP = false; + } + i++; + } + if (atHP) { + // true when last index was atHP + // dataPoint is within homepoint radius + visited.update(h.key, (value) => value + 1); + atHP = false; + } + } + visits = visited.entries + .map((e) => MapEntry(homepoints[e.key]!.name, e.value)) + .toList(); + return visits!; + } + addPoint(Homepoint point) { _homepoints.putIfAbsent(_uuid.v4(), () => point); save(); diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index fc15ccd..768baca 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_map/flutter_map.dart'; +import 'package:geo_steps/src/application/homepoints.dart'; import 'package:geo_steps/src/presentation/components/helper.dart'; import 'package:latlong2/latlong.dart' as latlng; import 'package:intl/intl.dart'; @@ -56,6 +57,7 @@ class _TodaysMapState extends State late LocationService locationService; late TargetPlatform defaultTargetPlatform = TargetPlatform.iOS; + HomepointManager? homepointManager; final mapController = MapController(); static const double mapHeightDetails = 150; @@ -72,19 +74,21 @@ class _TodaysMapState extends State locationService = LocationService(); locationService .init() - .whenComplete(() => locationService.loadToday().then((wasLoaded) { - if (wasLoaded && locationService.hasPositions) { - setState(() => mapController.move( + .whenComplete(() => + locationService.loadToday().then((wasLoaded) { + if (wasLoaded && locationService.hasPositions) { + setState(() => + mapController.move( latlng.LatLng(locationService.lastPos!.latitude, locationService.lastPos!.longitude), 12.8)); - minutes = locationService.dataPointPerMinute; + minutes = locationService.dataPointPerMinute; - final firstP = locationService.dataPoints.first; - markerPosition = - latlng.LatLng(firstP.latitude, firstP.longitude); - } - })); + final firstP = locationService.dataPoints.first; + markerPosition = + latlng.LatLng(firstP.latitude, firstP.longitude); + } + })); AppSettings().trackingLocation.get().then((isTrackingLocation) { if (isTrackingLocation != true) { @@ -114,12 +118,20 @@ class _TodaysMapState extends State detailsController = AnimationController( vsync: this, duration: const Duration(milliseconds: 150)); detailsAnimation = Tween( - begin: mapHeightDetails + 58, - end: SizeHelper().heightWithoutNav - 200) + begin: mapHeightDetails + 58, + end: SizeHelper().heightWithoutNav - 200) .animate(detailsController) ..addListener(() { setState(() {}); }); + + homepointManager = HomepointManager(); + homepointManager!.init().then((value) => + homepointManager!.load().then((value) => + setState(() { + homepointManager!.getVisits(locationService.dataPoints); + })) + ); } @override @@ -142,7 +154,9 @@ class _TodaysMapState extends State @override Widget build(BuildContext context) { - defaultTargetPlatform = Theme.of(context).platform; + defaultTargetPlatform = Theme + .of(context) + .platform; SizeHelper sizer = SizeHelper(); return SizedBox( height: sizer.heightWithoutNav, @@ -162,7 +176,7 @@ class _TodaysMapState extends State maxZoom: 19.0, keepAlive: true, interactiveFlags: // all interactions except rotation - InteractiveFlag.all & ~InteractiveFlag.rotate), + InteractiveFlag.all & ~InteractiveFlag.rotate), nonRotatedChildren: [ CustomAttributionWidget.defaultWidget( source: '© OpenStreetMap contributors', @@ -174,7 +188,7 @@ class _TodaysMapState extends State children: [ TileLayer( urlTemplate: - "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "https://tile.openstreetmap.org/{z}/{x}/{y}.png", userAgentPackageName: 'dev.janeuster.geo_steps', ), // kinda cool, shit res outside us, unknown projection - /{z}/{y}/{x} does not work @@ -203,7 +217,8 @@ class _TodaysMapState extends State width: 46, height: 46, point: markerPosition, - builder: (context) => Transform.translate( + builder: (context) => + Transform.translate( offset: const Offset(0, -23), child: Container( decoration: const BoxDecoration( @@ -214,7 +229,8 @@ class _TodaysMapState extends State if (selectedMinute != null) Marker( point: markerPosition, - builder: (context) => Transform.translate( + builder: (context) => + Transform.translate( offset: const Offset(28, -30), child: SizedBox( height: 40, @@ -224,31 +240,38 @@ class _TodaysMapState extends State SizedBox( child: CustomPaint( painter: - MapMarkerTriangle())), + MapMarkerTriangle())), Positioned( left: 18, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + padding: const EdgeInsets + .symmetric(horizontal: 4, + vertical: 2), width: 60, height: 38, decoration: const BoxDecoration( - color: Colors.white,), + color: Colors.white,), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment + .start, children: [ if (selectedMinute! - .timestamp != + .timestamp != null) Text( - DateFormat("HH:mm").format(selectedMinute!.timestamp!), + DateFormat("HH:mm") + .format( + selectedMinute! + .timestamp!), style: const TextStyle( fontSize: 9, fontWeight: - FontWeight - .w500), + FontWeight + .w500), ), Text( - selectedMinute!.pedStatus, + selectedMinute! + .pedStatus, style: const TextStyle( fontSize: 9, fontWeight: @@ -256,12 +279,13 @@ class _TodaysMapState extends State .w500), ), Text( - "${selectedMinute!.steps ?? 0} steps", + "${selectedMinute! + .steps ?? 0} steps", style: const TextStyle( fontSize: 9, fontWeight: - FontWeight - .w500), + FontWeight + .w500), ) ], ), @@ -297,7 +321,10 @@ class _TodaysMapState extends State child: Column( children: [ Text( - "show ${showDetails ? "less" : "more"} info for ${DateFormat.yMMMMEEEEd().format(widget.date)}", + "show ${showDetails + ? "less" + : "more"} info for ${DateFormat + .yMMMMEEEEd().format(widget.date)}", style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold)), @@ -324,13 +351,13 @@ class _TodaysMapState extends State child: Row( children: showDetails ? [ - OverviewTotals( - totalSteps: locationService.stepsTotal, - totalDistance: - locationService.distanceTotal, - ), - Expanded(child: Container()), - ] + OverviewTotals( + totalSteps: locationService.stepsTotal, + totalDistance: + locationService.distanceTotal, + ), + Expanded(child: Container()), + ] : [], ), ), @@ -338,20 +365,20 @@ class _TodaysMapState extends State padding: !showDetails ? EdgeInsets.zero : const EdgeInsets.symmetric( - vertical: 16, horizontal: 10), + vertical: 16, horizontal: 10), child: showDetails ? const DottedLine( - height: 2, - ) + height: 2, + ) : null), HourlyActivity( data: locationService.hourlyStepsTotal, onScroll: (percentage) { var thisDate = DateTime.now(); final millisecondsToday = - (percentage * 24 * 60 * 60 * 1000).round(); + (percentage * 24 * 60 * 60 * 1000).round(); final minutesToday = - (percentage * ((24 * 60) - 1)).round(); + (percentage * ((24 * 60) - 1)).round(); thisDate = DateTime( thisDate.year, thisDate.month, thisDate.day); thisDate = DateTime.fromMillisecondsSinceEpoch( @@ -383,14 +410,15 @@ class _TodaysMapState extends State )), Padding( padding: const EdgeInsets.all(10), - child: OverviewBarGraph(data: locationService.hourlyDistanceTotal.map((e) => e/1000).toList(), title: "km/h")), - Padding( + child: OverviewBarGraph( + data: locationService.hourlyDistanceTotal + .map((e) => e / 1000).toList(), + title: "hourly average speed in km/h")), + if (homepointManager != null && homepointManager!.visits != null) Padding( padding: const EdgeInsets.all(8.0), child: NamedBarGraph( - data: {"home": 4, "a": 1, "b": 2} - .entries - .toList(), - title: "stat 2"), + data: homepointManager!.visits!, + title: "visited homepoints today"), ), ], ], From 8d91bf0b921c3042f77645410ba32e4511fc226b Mon Sep 17 00:00:00 2001 From: JanEuster Date: Sat, 25 Mar 2023 13:23:57 +0100 Subject: [PATCH 17/20] chart legend comp --- lib/src/presentation/components/stats.dart | 73 +++++++++++++--------- lib/src/presentation/overview.dart | 2 +- lib/src/presentation/today.dart | 8 +-- 3 files changed, 48 insertions(+), 35 deletions(-) diff --git a/lib/src/presentation/components/stats.dart b/lib/src/presentation/components/stats.dart index bdc1071..8922275 100644 --- a/lib/src/presentation/components/stats.dart +++ b/lib/src/presentation/components/stats.dart @@ -43,7 +43,7 @@ class OverviewTotals extends StatelessWidget { } } -class NamedBarGraph extends StatelessWidget { +class NamedBarChart extends StatelessWidget { final String title; final double height; final bool scrollable; @@ -54,7 +54,7 @@ class NamedBarGraph extends StatelessWidget { late List dataValues; late double max; - NamedBarGraph( + NamedBarChart( {super.key, required this.data, this.title = "geo_steps stats", @@ -68,7 +68,7 @@ class NamedBarGraph extends StatelessWidget { @override Widget build(BuildContext context) { var textStyleOnWhite = const TextStyle( - color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500); + color: Colors.white, fontSize: 10, fontWeight: FontWeight.w700); var textStyleOnBlack = const TextStyle(fontSize: 14, fontWeight: FontWeight.w600); @@ -130,18 +130,7 @@ class NamedBarGraph extends StatelessWidget { ), ], ), - Container( - height: 18, - color: Colors.black, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(title, style: textStyleOnWhite), - Text("${data.length} bars", style: textStyleOnWhite) - ], - ), - ) + ChartLegend(left: Text(title, style: textStyleOnWhite), right: Text("${data.length} bars", style: textStyleOnWhite)), ], ), ), @@ -149,7 +138,42 @@ class NamedBarGraph extends StatelessWidget { } } -class OverviewBarGraph extends StatelessWidget { +class ChartLegend extends StatelessWidget { + Widget left; + Widget right; + ChartLegend({super.key, required this.left, required this.right}); + + @override + Widget build(BuildContext context) { + return Container( + height: 16, + color: Colors.black, + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + left, + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 6), + child: Container(decoration: const BoxDecoration( + border: Border.symmetric(vertical: BorderSide(color: Colors.white, width: 3),), + image: DecorationImage(fit: BoxFit.none, + scale: 4, + image: AssetImage("assets/line_pattern.jpg"), + repeat: ImageRepeat.repeat)),), + ), + ), + + right, + ], + ), + ); + } + +} + +class OverviewBarChart extends StatelessWidget { final String title; final double height; final bool scrollable; @@ -159,7 +183,7 @@ class OverviewBarGraph extends StatelessWidget { final List data; late MinMax valuesMinMax; - OverviewBarGraph( + OverviewBarChart( {super.key, required this.data, this.title = "geo_steps stats", @@ -172,7 +196,7 @@ class OverviewBarGraph extends StatelessWidget { @override Widget build(BuildContext context) { var textStyleOnWhite = const TextStyle( - color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500); + color: Colors.white, fontSize: 10, fontWeight: FontWeight.w700); var textStyleOnBlack = const TextStyle(fontSize: 14, fontWeight: FontWeight.w500); var sizer = SizeHelper(); @@ -223,18 +247,7 @@ class OverviewBarGraph extends StatelessWidget { ], ), ))), - Container( - height: 18, - color: Colors.black, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(title, style: textStyleOnWhite), - Text("${data.length} bars", style: textStyleOnWhite) - ], - ), - ) + ChartLegend(left: Text(title, style: textStyleOnWhite), right: Text("${data.length} bars", style: textStyleOnWhite)), ], ), ), diff --git a/lib/src/presentation/overview.dart b/lib/src/presentation/overview.dart index 7c5dd61..1380a9c 100644 --- a/lib/src/presentation/overview.dart +++ b/lib/src/presentation/overview.dart @@ -214,7 +214,7 @@ class _OverviewPageState extends State { ActivityMap(data: locationService!.dataPoints), Padding( padding: const EdgeInsets.all(10), - child: OverviewBarGraph( + child: OverviewBarChart( scrollable: true, data: [1, 2, 6, 2, 3, 1, 12, 42, 10, 1, 1, 3, 95, 32])), ], diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index 768baca..2754425 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -409,14 +409,14 @@ class _TodaysMapState extends State height: 2, )), Padding( - padding: const EdgeInsets.all(10), - child: OverviewBarGraph( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: OverviewBarChart( data: locationService.hourlyDistanceTotal .map((e) => e / 1000).toList(), title: "hourly average speed in km/h")), if (homepointManager != null && homepointManager!.visits != null) Padding( - padding: const EdgeInsets.all(8.0), - child: NamedBarGraph( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: NamedBarChart( data: homepointManager!.visits!, title: "visited homepoints today"), ), From 3c16e8880801aa79d66555810877163e3115e692 Mon Sep 17 00:00:00 2001 From: JanEuster Date: Sat, 25 Mar 2023 13:58:31 +0100 Subject: [PATCH 18/20] auto go hour of most activity (hourly activity) --- lib/src/presentation/components/stats.dart | 66 +++++++++++++++------- lib/src/presentation/today.dart | 16 ++++-- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/lib/src/presentation/components/stats.dart b/lib/src/presentation/components/stats.dart index 8922275..a0643a5 100644 --- a/lib/src/presentation/components/stats.dart +++ b/lib/src/presentation/components/stats.dart @@ -130,7 +130,9 @@ class NamedBarChart extends StatelessWidget { ), ], ), - ChartLegend(left: Text(title, style: textStyleOnWhite), right: Text("${data.length} bars", style: textStyleOnWhite)), + ChartLegend( + left: Text(title, style: textStyleOnWhite), + right: Text("${data.length} bars", style: textStyleOnWhite)), ], ), ), @@ -141,6 +143,7 @@ class NamedBarChart extends StatelessWidget { class ChartLegend extends StatelessWidget { Widget left; Widget right; + ChartLegend({super.key, required this.left, required this.right}); @override @@ -153,24 +156,27 @@ class ChartLegend extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ left, - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 6), - child: Container(decoration: const BoxDecoration( - border: Border.symmetric(vertical: BorderSide(color: Colors.white, width: 3),), - image: DecorationImage(fit: BoxFit.none, + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 6), + child: Container( + decoration: const BoxDecoration( + border: Border.symmetric( + vertical: BorderSide(color: Colors.white, width: 3), + ), + image: DecorationImage( + fit: BoxFit.none, scale: 4, image: AssetImage("assets/line_pattern.jpg"), - repeat: ImageRepeat.repeat)),), + repeat: ImageRepeat.repeat)), ), ), - + ), right, ], ), ); } - } class OverviewBarChart extends StatelessWidget { @@ -247,7 +253,9 @@ class OverviewBarChart extends StatelessWidget { ], ), ))), - ChartLegend(left: Text(title, style: textStyleOnWhite), right: Text("${data.length} bars", style: textStyleOnWhite)), + ChartLegend( + left: Text(title, style: textStyleOnWhite), + right: Text("${data.length} bars", style: textStyleOnWhite)), ], ), ), @@ -299,10 +307,12 @@ class HourlyActivity extends StatefulWidget { final double hourPad = 5; final List data; late double max; + final int initialHour; Function(double)? onScroll = (percentage) {}; - HourlyActivity({super.key, required this.data, this.onScroll}) { + HourlyActivity( + {super.key, required this.data, this.onScroll, this.initialHour = 12}) { max = MinMax.fromList(data).max; } @@ -311,14 +321,21 @@ class HourlyActivity extends StatefulWidget { } class _HourlyActivityState extends State { - ScrollController scrollController = - ScrollController(initialScrollOffset: 715); + ScrollController scrollController = ScrollController(initialScrollOffset: 0); bool isScrolling = false; int selectedHourIndex = 0; @override void initState() { + setState(() { + selectedHourIndex = widget.initialHour; + }); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + scrollController.animateTo( + (widget.hourWidth + widget.hourPad) * selectedHourIndex + + widget.hourWidth / 2, + duration: const Duration(seconds: 1), + curve: Curves.decelerate); scrollController.addListener(() { onScroll(); if (scrollController.position.pixels == @@ -342,7 +359,6 @@ class _HourlyActivityState extends State { } } }); - setSelectedHour(); }); super.initState(); } @@ -434,17 +450,25 @@ class _HourlyActivityState extends State { : null), ]), Positioned( - top: 80 * (1-hoursPercent[index]), + top: 80 * (1 - hoursPercent[index]), child: SizedBox( width: widget.hourWidth, child: Padding( padding: const EdgeInsets.all(4.0), child: Container( - height: 16, - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(), - ), child: Center(child: Text(widget.data[index].toStringAsFixed(0), style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),))), + height: 16, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(), + ), + child: Center( + child: Text( + widget.data[index] + .toStringAsFixed(0), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500), + ))), ))), ], )), diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index 2754425..bb89d0e 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -66,6 +66,7 @@ class _TodaysMapState extends State latlng.LatLng markerPosition = latlng.LatLng(0, 0); List? minutes; + int? initialHourIndex; @override void initState() { @@ -87,6 +88,9 @@ class _TodaysMapState extends State final firstP = locationService.dataPoints.first; markerPosition = latlng.LatLng(firstP.latitude, firstP.longitude); + + initialHourIndex = locationService.hourlyStepsTotal.indexOf( + locationService.hourlyStepsTotal.reduce(math.max)); } })); @@ -371,8 +375,9 @@ class _TodaysMapState extends State height: 2, ) : null), - HourlyActivity( + if (initialHourIndex != null) HourlyActivity( data: locationService.hourlyStepsTotal, + initialHour: initialHourIndex!, onScroll: (percentage) { var thisDate = DateTime.now(); final millisecondsToday = @@ -409,13 +414,16 @@ class _TodaysMapState extends State height: 2, )), Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 12), child: OverviewBarChart( data: locationService.hourlyDistanceTotal .map((e) => e / 1000).toList(), title: "hourly average speed in km/h")), - if (homepointManager != null && homepointManager!.visits != null) Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + if (homepointManager != null && homepointManager! + .visits != null) Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 12), child: NamedBarChart( data: homepointManager!.visits!, title: "visited homepoints today"), From a4b442ed5c4fd5b78ff78fcca77f0de7758f2363 Mon Sep 17 00:00:00 2001 From: JanEuster Date: Sat, 25 Mar 2023 14:19:00 +0100 Subject: [PATCH 19/20] show homepoints on today map --- lib/src/presentation/home.dart | 12 +- lib/src/presentation/today.dart | 294 ++++++++++++++++---------------- 2 files changed, 155 insertions(+), 151 deletions(-) diff --git a/lib/src/presentation/home.dart b/lib/src/presentation/home.dart index 1698b3d..c8e201e 100644 --- a/lib/src/presentation/home.dart +++ b/lib/src/presentation/home.dart @@ -218,11 +218,13 @@ class _ActivityMapState extends State { void initState() { super.initState(); widget.locationService.loadToday().then((wasLoaded) { - if (wasLoaded) { - setState(() { - dataToday = widget.locationService.dataPoints; - }); - } + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (wasLoaded) { + setState(() { + dataToday = widget.locationService.dataPoints; + }); + } + }); }); } diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index bb89d0e..b7f689c 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -67,6 +67,7 @@ class _TodaysMapState extends State latlng.LatLng markerPosition = latlng.LatLng(0, 0); List? minutes; int? initialHourIndex; + List homepointCircles = []; @override void initState() { @@ -75,24 +76,22 @@ class _TodaysMapState extends State locationService = LocationService(); locationService .init() - .whenComplete(() => - locationService.loadToday().then((wasLoaded) { - if (wasLoaded && locationService.hasPositions) { - setState(() => - mapController.move( + .whenComplete(() => locationService.loadToday().then((wasLoaded) { + if (wasLoaded && locationService.hasPositions) { + setState(() => mapController.move( latlng.LatLng(locationService.lastPos!.latitude, locationService.lastPos!.longitude), 12.8)); - minutes = locationService.dataPointPerMinute; + minutes = locationService.dataPointPerMinute; - final firstP = locationService.dataPoints.first; - markerPosition = - latlng.LatLng(firstP.latitude, firstP.longitude); + final firstP = locationService.dataPoints.first; + markerPosition = + latlng.LatLng(firstP.latitude, firstP.longitude); - initialHourIndex = locationService.hourlyStepsTotal.indexOf( - locationService.hourlyStepsTotal.reduce(math.max)); - } - })); + initialHourIndex = locationService.hourlyStepsTotal + .indexOf(locationService.hourlyStepsTotal.reduce(math.max)); + } + })); AppSettings().trackingLocation.get().then((isTrackingLocation) { if (isTrackingLocation != true) { @@ -122,20 +121,37 @@ class _TodaysMapState extends State detailsController = AnimationController( vsync: this, duration: const Duration(milliseconds: 150)); detailsAnimation = Tween( - begin: mapHeightDetails + 58, - end: SizeHelper().heightWithoutNav - 200) + begin: mapHeightDetails + 58, + end: SizeHelper().heightWithoutNav - 200) .animate(detailsController) ..addListener(() { setState(() {}); }); homepointManager = HomepointManager(); - homepointManager!.init().then((value) => - homepointManager!.load().then((value) => - setState(() { + homepointManager! + .init() + .then((value) => homepointManager!.load().then((value) => setState(() { homepointManager!.getVisits(locationService.dataPoints); - })) - ); + + homepointManager!.homepoints.forEach((key, point) { + homepointCircles.addAll([CircleMarker( + point: point.position, + radius: point.radius, + useRadiusInMeter: true, + color: Colors.white.withOpacity(0.3), + borderColor: Colors.black, + borderStrokeWidth: 2, + ), + CircleMarker( + point: point.position, + radius: point.radius/3, + useRadiusInMeter: true, + color: Colors.white.withOpacity(0.4), + ) + ]); + }); + }))); } @override @@ -158,9 +174,7 @@ class _TodaysMapState extends State @override Widget build(BuildContext context) { - defaultTargetPlatform = Theme - .of(context) - .platform; + defaultTargetPlatform = Theme.of(context).platform; SizeHelper sizer = SizeHelper(); return SizedBox( height: sizer.heightWithoutNav, @@ -180,7 +194,7 @@ class _TodaysMapState extends State maxZoom: 19.0, keepAlive: true, interactiveFlags: // all interactions except rotation - InteractiveFlag.all & ~InteractiveFlag.rotate), + InteractiveFlag.all & ~InteractiveFlag.rotate), nonRotatedChildren: [ CustomAttributionWidget.defaultWidget( source: '© OpenStreetMap contributors', @@ -192,13 +206,16 @@ class _TodaysMapState extends State children: [ TileLayer( urlTemplate: - "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "https://tile.openstreetmap.org/{z}/{x}/{y}.png", userAgentPackageName: 'dev.janeuster.geo_steps', ), // kinda cool, shit res outside us, unknown projection - /{z}/{y}/{x} does not work // TileLayer(urlTemplate: "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/4/5/5?blankTile=false", // userAgentPackageName: 'dev.janeuster.geo_steps'), // ), + CircleLayer( + circles: homepointCircles, + ), PolylineLayer( polylineCulling: false, polylines: [ @@ -221,8 +238,7 @@ class _TodaysMapState extends State width: 46, height: 46, point: markerPosition, - builder: (context) => - Transform.translate( + builder: (context) => Transform.translate( offset: const Offset(0, -23), child: Container( decoration: const BoxDecoration( @@ -231,73 +247,8 @@ class _TodaysMapState extends State "assets/map_pin.png")))), )), if (selectedMinute != null) - Marker( - point: markerPosition, - builder: (context) => - Transform.translate( - offset: const Offset(28, -30), - child: SizedBox( - height: 40, - child: Stack( - clipBehavior: Clip.none, - children: [ - SizedBox( - child: CustomPaint( - painter: - MapMarkerTriangle())), - Positioned( - left: 18, - child: Container( - padding: const EdgeInsets - .symmetric(horizontal: 4, - vertical: 2), - width: 60, - height: 38, - decoration: const BoxDecoration( - color: Colors.white,), - child: Column( - crossAxisAlignment: CrossAxisAlignment - .start, - children: [ - if (selectedMinute! - .timestamp != - null) - Text( - DateFormat("HH:mm") - .format( - selectedMinute! - .timestamp!), - style: const TextStyle( - fontSize: 9, - fontWeight: - FontWeight - .w500), - ), - Text( - selectedMinute! - .pedStatus, - style: const TextStyle( - fontSize: 9, - fontWeight: - FontWeight - .w500), - ), - Text( - "${selectedMinute! - .steps ?? 0} steps", - style: const TextStyle( - fontSize: 9, - fontWeight: - FontWeight - .w500), - ) - ], - ), - )) - ], - ), - ), - )) + LocationDetailsMarker( + markerPosition, selectedMinute!), // Container( // width: 40, // height: 40, @@ -325,10 +276,7 @@ class _TodaysMapState extends State child: Column( children: [ Text( - "show ${showDetails - ? "less" - : "more"} info for ${DateFormat - .yMMMMEEEEd().format(widget.date)}", + "show ${showDetails ? "less" : "more"} info for ${DateFormat.yMMMMEEEEd().format(widget.date)}", style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold)), @@ -355,13 +303,13 @@ class _TodaysMapState extends State child: Row( children: showDetails ? [ - OverviewTotals( - totalSteps: locationService.stepsTotal, - totalDistance: - locationService.distanceTotal, - ), - Expanded(child: Container()), - ] + OverviewTotals( + totalSteps: locationService.stepsTotal, + totalDistance: + locationService.distanceTotal, + ), + Expanded(child: Container()), + ] : [], ), ), @@ -369,43 +317,44 @@ class _TodaysMapState extends State padding: !showDetails ? EdgeInsets.zero : const EdgeInsets.symmetric( - vertical: 16, horizontal: 10), + vertical: 16, horizontal: 10), child: showDetails ? const DottedLine( - height: 2, - ) + height: 2, + ) : null), - if (initialHourIndex != null) HourlyActivity( - data: locationService.hourlyStepsTotal, - initialHour: initialHourIndex!, - onScroll: (percentage) { - var thisDate = DateTime.now(); - final millisecondsToday = - (percentage * 24 * 60 * 60 * 1000).round(); - final minutesToday = - (percentage * ((24 * 60) - 1)).round(); - thisDate = DateTime( - thisDate.year, thisDate.month, thisDate.day); - thisDate = DateTime.fromMillisecondsSinceEpoch( - thisDate.millisecondsSinceEpoch + - millisecondsToday); + if (initialHourIndex != null) + HourlyActivity( + data: locationService.hourlyStepsTotal, + initialHour: initialHourIndex!, + onScroll: (percentage) { + var thisDate = DateTime.now(); + final millisecondsToday = + (percentage * 24 * 60 * 60 * 1000).round(); + final minutesToday = + (percentage * ((24 * 60) - 1)).round(); + thisDate = DateTime( + thisDate.year, thisDate.month, thisDate.day); + thisDate = DateTime.fromMillisecondsSinceEpoch( + thisDate.millisecondsSinceEpoch + + millisecondsToday); - if (locationService.hasPositions) { - // log("$minutes"); - // var newPos = locationService.dataPointClosestTo(thisDate.toLocal()); - // log("$newPos"); - if (minutes != null) { - setState(() { - selectedMinute = minutes![minutesToday]; - markerPosition = latlng.LatLng( - selectedMinute!.latitude, - selectedMinute!.longitude); - mapController.move(markerPosition, 13.5); - }); + if (locationService.hasPositions) { + // log("$minutes"); + // var newPos = locationService.dataPointClosestTo(thisDate.toLocal()); + // log("$newPos"); + if (minutes != null) { + setState(() { + selectedMinute = minutes![minutesToday]; + markerPosition = latlng.LatLng( + selectedMinute!.latitude, + selectedMinute!.longitude); + mapController.move(markerPosition, 13.5); + }); + } } - } - }, - ), + }, + ), if (showDetails) ...[ const Padding( padding: EdgeInsets.symmetric( @@ -418,16 +367,18 @@ class _TodaysMapState extends State vertical: 8, horizontal: 12), child: OverviewBarChart( data: locationService.hourlyDistanceTotal - .map((e) => e / 1000).toList(), + .map((e) => e / 1000) + .toList(), title: "hourly average speed in km/h")), - if (homepointManager != null && homepointManager! - .visits != null) Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 12), - child: NamedBarChart( - data: homepointManager!.visits!, - title: "visited homepoints today"), - ), + if (homepointManager != null && + homepointManager!.visits != null) + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 12), + child: NamedBarChart( + data: homepointManager!.visits!, + title: "visited homepoints today"), + ), ], ], ), @@ -441,3 +392,54 @@ class _TodaysMapState extends State ); } } + +Marker LocationDetailsMarker( + latlng.LatLng markerPosition, LocationDataPoint selectedMinute) { + return Marker( + point: markerPosition, + builder: (context) => Transform.translate( + offset: const Offset(28, -30), + child: SizedBox( + height: 40, + child: Stack( + clipBehavior: Clip.none, + children: [ + SizedBox(child: CustomPaint(painter: MapMarkerTriangle())), + Positioned( + left: 18, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 2), + width: 60, + height: 38, + decoration: const BoxDecoration( + color: Colors.white, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (selectedMinute.timestamp != null) + Text( + DateFormat("HH:mm") + .format(selectedMinute.timestamp!), + style: const TextStyle( + fontSize: 9, fontWeight: FontWeight.w500), + ), + Text( + selectedMinute.pedStatus, + style: const TextStyle( + fontSize: 9, fontWeight: FontWeight.w500), + ), + Text( + "${selectedMinute.steps ?? 0} steps", + style: const TextStyle( + fontSize: 9, fontWeight: FontWeight.w500), + ) + ], + ), + )) + ], + ), + ), + )); +} From db906be50ed9cca823de3fd316dcbabad5dab41a Mon Sep 17 00:00:00 2001 From: JanEuster Date: Sat, 25 Mar 2023 14:26:31 +0100 Subject: [PATCH 20/20] keep user zoom level --- lib/src/presentation/today.dart | 42 ++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/lib/src/presentation/today.dart b/lib/src/presentation/today.dart index b7f689c..fb50814 100644 --- a/lib/src/presentation/today.dart +++ b/lib/src/presentation/today.dart @@ -54,20 +54,21 @@ class _TodaysMapState extends State with SingleTickerProviderStateMixin { late Animation detailsAnimation; late AnimationController detailsController; + bool showDetails = false; late LocationService locationService; late TargetPlatform defaultTargetPlatform = TargetPlatform.iOS; HomepointManager? homepointManager; + final mapController = MapController(); + double zoomLevel = 12.8; static const double mapHeightDetails = 150; + latlng.LatLng markerPosition = latlng.LatLng(0, 0); + List homepointCircles = []; - bool showDetails = false; LocationDataPoint? selectedMinute; - - latlng.LatLng markerPosition = latlng.LatLng(0, 0); List? minutes; int? initialHourIndex; - List homepointCircles = []; @override void initState() { @@ -81,7 +82,7 @@ class _TodaysMapState extends State setState(() => mapController.move( latlng.LatLng(locationService.lastPos!.latitude, locationService.lastPos!.longitude), - 12.8)); + zoomLevel)); minutes = locationService.dataPointPerMinute; final firstP = locationService.dataPoints.first; @@ -135,17 +136,18 @@ class _TodaysMapState extends State homepointManager!.getVisits(locationService.dataPoints); homepointManager!.homepoints.forEach((key, point) { - homepointCircles.addAll([CircleMarker( - point: point.position, - radius: point.radius, - useRadiusInMeter: true, - color: Colors.white.withOpacity(0.3), - borderColor: Colors.black, - borderStrokeWidth: 2, - ), + homepointCircles.addAll([ + CircleMarker( + point: point.position, + radius: point.radius, + useRadiusInMeter: true, + color: Colors.white.withOpacity(0.3), + borderColor: Colors.black, + borderStrokeWidth: 2, + ), CircleMarker( point: point.position, - radius: point.radius/3, + radius: point.radius / 3, useRadiusInMeter: true, color: Colors.white.withOpacity(0.4), ) @@ -190,7 +192,15 @@ class _TodaysMapState extends State onMapReady: () { // set initial position on map }, - zoom: 13.0, + onMapEvent: (e) { + // set zoomLevel on change + if (e.runtimeType == MapEventMoveEnd) { + setState(() { + zoomLevel = e.zoom; + }); + } + }, + zoom: zoomLevel, maxZoom: 19.0, keepAlive: true, interactiveFlags: // all interactions except rotation @@ -349,7 +359,7 @@ class _TodaysMapState extends State markerPosition = latlng.LatLng( selectedMinute!.latitude, selectedMinute!.longitude); - mapController.move(markerPosition, 13.5); + mapController.move(markerPosition, zoomLevel); }); } }