Skip to content

Commit

Permalink
Merge branch 'add_new_stats' into main2add_new_stats
Browse files Browse the repository at this point in the history
  • Loading branch information
braniii committed Jun 7, 2024
2 parents 6e6f9f7 + eaab149 commit ecd337e
Show file tree
Hide file tree
Showing 13 changed files with 441 additions and 247 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [Unreleased]
### Bugfix 🐛:
- Fixed a bug that caused a small icon to be displayed in the F-Droid store (German).


## [0.7.0] - 2024-05-29
Expand Down
2 changes: 2 additions & 0 deletions app/lib/core/measurementDatabase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:hive_flutter/hive_flutter.dart';

import 'package:trale/core/measurement.dart';
import 'package:trale/core/measurementInterpolation.dart';
import 'package:trale/core/measurementStats.dart';
import 'package:trale/core/traleNotifier.dart';
import 'package:trale/main.dart';

Expand Down Expand Up @@ -111,6 +112,7 @@ class MeasurementDatabase {

// update interpolation
MeasurementInterpolation().reinit();
MeasurementStats().reinit();

// fire stream
fireStream();
Expand Down
72 changes: 19 additions & 53 deletions app/lib/core/measurementInterpolation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ class MeasurementInterpolation {
/// length of displayed vectors
int get NDisplay => timesDisplay.length;


List<DateTime>? _dateTimes;
/// get vector containing the times given in [ms since epoch]
List<DateTime> get dateTimes => _dateTimes ??= _createDateTimes();
Expand All @@ -95,6 +94,17 @@ class MeasurementInterpolation {

}

/// number of measurements
int get NMeasurements => times_measured.length;

/// time span of measurements
int get measuredTimeSpan => N == 0 ? 0 : N - 2 * _offsetInDays;

/// idx of last measurement
int get idxLast => N - 1 - _offsetInDays;
/// idx of last displayed measurement
int get idxLastDisplay => NDisplay - 1 - _offsetInDaysShown;

Vector? _times;
/// get vector containing the times given in [ms since epoch]
Vector get times => _times ??= _createTimes();
Expand Down Expand Up @@ -280,7 +290,7 @@ class MeasurementInterpolation {

final Vector weightsExtrapol = Vector.fromList(<double>[
for (int idx=1; idx <= _offsetInDaysShown; idx++)
finalChangeRate * idx
finalSlope * idx
]) + weightsLinear.last;

return Vector.fromList(
Expand All @@ -293,7 +303,6 @@ class MeasurementInterpolation {
);
}


Vector? _timesDisplay;
/// get vector containing the weights to display
Vector get timesDisplay => _timesDisplay ??=
Expand Down Expand Up @@ -376,7 +385,7 @@ class MeasurementInterpolation {
idxTo = idxsMeasurements[idx + 1];
if (idxFrom + 1 < idxTo) {
// estimate change rate
changeRate = _linearChangeRate(idxFrom, idxTo, weights);
changeRate = _slope(idxFrom, idxTo, weights);
for (int idxJ = idxFrom + 1; idxJ < idxTo; idxJ++) {
weightsList[idxJ] = weightsList[idxFrom] + changeRate * (
idxJ - idxFrom
Expand All @@ -387,8 +396,8 @@ class MeasurementInterpolation {
return Vector.fromList(weightsList, dtype: dtype);
}

/// estimate linear change rate between two measurements in [kg/steps]
double _linearChangeRate(int idxFrom, int idxTo, Vector weights) =>
/// estimate slope between two measurements in [kg/steps]
double _slope(int idxFrom, int idxTo, Vector weights) =>
weights.isNotEmpty
? (weights[idxTo] - weights[idxFrom]) / (idxTo - idxFrom)
: 0;
Expand All @@ -403,63 +412,20 @@ class MeasurementInterpolation {
], dtype: dtype,
);


// FROM HERE ON STATS OF INTERPOLATION

/// get time span between first and last measurement
Duration get measurementDuration => times_measured.isNotEmpty
? Duration(
milliseconds: (times_measured.last - times_measured.first).round(),
)
: Duration.zero;


/// return difference of Gaussian smoothed weights
double? deltaWeightLastNDays (int nDays) {
if (N - 2 * _offsetInDays < nDays) {
return null;
}
return weightsGaussianExtrapol[N - 1 - _offsetInDays] -
weightsGaussianExtrapol[N - 1 - _offsetInDays - nDays];
}

/// get weight change [kg] within last month from last measurement
double? get deltaWeightLastYear => deltaWeightLastNDays(365);

/// get weight change [kg] within last month from last measurement
double? get deltaWeightLastMonth => deltaWeightLastNDays(30);

/// get weight change [kg] within last week from last measurement
double? get deltaWeightLastWeek => deltaWeightLastNDays(7);

/// final change Rate
double get finalChangeRate => _linearChangeRate(
N - 1 - _offsetInDays,
N - 1 - _offsetInDays + _offsetInDaysShown,
/// final slope of extrapolation
double get finalSlope => _slope(
idxLast,
idxLast + _offsetInDaysShown,
weightsGaussianExtrapol,
);

/// get time of reaching target weight in kg
Duration? timeOfTargetWeight(double? targetWeight) {
if ((targetWeight == null) || (db.nMeasurements < 2)){
return null;
}

final int idxLast = NDisplay - 1 - _offsetInDaysShown;
final double slope = finalChangeRate;

// Crossing is in the past
if (slope * (weightsDisplay[idxLast] - targetWeight) >= 0) {
return null;
}

// in ms from last measurement
final int remainingTime = (
(targetWeight - weightsDisplay[idxLast]) / slope
).round();
return Duration(days: remainingTime);
}

/// offset of day in interpolation
static const int _offsetInDays = 21;

Expand Down
126 changes: 126 additions & 0 deletions app/lib/core/measurementStats.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'dart:math' as math;

import 'package:ml_linalg/linalg.dart';
import 'package:ml_linalg/vector.dart';

import 'package:trale/core/interpolation.dart';
import 'package:trale/core/measurement.dart';
import 'package:trale/core/measurementDatabase.dart';
import 'package:trale/core/measurementInterpolation.dart';
import 'package:trale/core/preferences.dart';


/// class providing an API to handle interpolation of measurements
class MeasurementStats {
/// singleton constructor
factory MeasurementStats() => _instance;

/// single instance creation
MeasurementStats._internal();

/// singleton instance
static final MeasurementStats _instance = MeasurementStats._internal();

/// get measurements
MeasurementDatabase get db => MeasurementDatabase();

/// get interpolation
MeasurementInterpolation get ip => MeasurementInterpolation();

/// re initialize database
void reinit() {
_streakList = null;
// recalculate all vectors
init();
}

/// initialize database
void init() {}


/// return difference of Gaussian smoothed weights
double? deltaWeightLastNDays (int nDays) {
if (ip.measuredTimeSpan < nDays) {
return null;
}
return ip.weightsGaussianExtrapol[ip.idxLast] -
ip.weightsGaussianExtrapol[ip.idxLast - nDays];
}

/// get max weight
double? get maxWeight => ip.weights_measured.max();

/// get min weight
double? get minWeight => ip.weights_measured.min();

/// get min weight
double? get meanWeight => ip.weights_measured.mean();

/// get number of measurements
int get nMeasurements => ip.NMeasurements;

/// get current streak
int get currentStreak => streakList.last.round();

/// get max streak
int get maxStreak => streakList.max().round();

Vector? _streakList;
/// get list of all streaks
Vector get streakList => _streakList ??= _estimateStreakList();
Vector _estimateStreakList() {
int streak = 0;
final List<int> streakList = <int>[];
for (
final bool isMS in ip.isMeasurement.toList().map((e) => e.round() == 1)
) {
streak++;
if (!isMS){
streakList.add(streak - 1);
streak = 0;
}
}
return Vector.fromList(streakList);
}

/// get weight change [kg] within last month from last measurement
double? get deltaWeightLastYear => deltaWeightLastNDays(365);

/// get weight change [kg] within last month from last measurement
double? get deltaWeightLastMonth => deltaWeightLastNDays(30);

/// get weight change [kg] within last week from last measurement
double? get deltaWeightLastWeek => deltaWeightLastNDays(7);

/// get time of reaching target weight in kg
Duration? timeOfTargetWeight(double? targetWeight) {
if ((targetWeight == null) || (db.nMeasurements < 2)){
return null;
}
final double slope = ip.finalSlope;

// Crossing is in the past
if (slope * (ip.weightsDisplay[ip.idxLastDisplay] - targetWeight) >= 0) {
return null;
}

// in ms from last measurement
final int remainingTime = (
(targetWeight - ip.weightsDisplay[ip.idxLastDisplay]) / slope
).round();

final DateTime timeOfReachingTargetWeight =
DateTime.fromMillisecondsSinceEpoch(
ip.times_measured.last.round() + remainingTime
);

final int dayUntilReachingTargetWeight =
timeOfReachingTargetWeight.day - DateTime.now().day;

if (dayUntilReachingTargetWeight > 0){
return Duration(days: dayUntilReachingTargetWeight);
}
return null;
}

}
58 changes: 20 additions & 38 deletions app/lib/pages/home.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:trale/core/icons.dart';
import 'package:trale/core/measurement.dart';
import 'package:trale/core/measurementDatabase.dart';
import 'package:trale/core/preferences.dart';
import 'package:trale/pages/measurementScreen.dart';
import 'package:trale/pages/overview.dart';
import 'package:trale/pages/stats.dart';
import 'package:trale/pages/statScreen.dart';
import 'package:trale/widget/addWeightDialog.dart';
import 'package:trale/widget/appDrawer.dart';
import 'package:trale/widget/customSliverAppBar.dart';
Expand Down Expand Up @@ -39,7 +39,7 @@ class _HomeState extends State<Home> with TickerProviderStateMixin{

_selectedTab = TabController(
vsync: this,
length: 2,
length: 3,
initialIndex: _selectedIndex,
);
_selectedTab.addListener(_onSlideTab);
Expand All @@ -52,25 +52,6 @@ class _HomeState extends State<Home> with TickerProviderStateMixin{
});
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
// color system bottom navigation bar
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
/// default values of flutter definition
/// https://github.com/flutter/flutter/blob/ee4e09cce01d6f2d7f4baebd247fde02e5008851/packages/flutter/lib/src/material/navigation_bar.dart#L1237
systemNavigationBarColor: ElevationOverlay.colorWithOverlay(
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.primary,
3.0,
),
systemNavigationBarDividerColor: Colors.transparent,
systemNavigationBarIconBrightness: Theme.of(context).brightness,
),
);
}

/// Starts home with category all
static int _selectedIndex = 0;
// controller for selected category
Expand Down Expand Up @@ -129,6 +110,21 @@ class _HomeState extends State<Home> with TickerProviderStateMixin{
final List<Widget> activeTabs = <Widget>[
const OverviewScreen(),
StatsScreen(tabController: _selectedTab),
MeasurementScreen(tabController: _selectedTab),
];
final List<Widget> destinations = <Widget>[
NavigationDestination(
icon: const Icon(CustomIcons.home),
label: AppLocalizations.of(context)!.home,
),
NavigationDestination(
icon: const Icon(CustomIcons.events),
label: AppLocalizations.of(context)!.achievements,
),
NavigationDestination(
icon: const Icon(Icons.assignment_outlined),
label: AppLocalizations.of(context)!.measurements,
),
];

return Scaffold(
Expand All @@ -137,21 +133,7 @@ class _HomeState extends State<Home> with TickerProviderStateMixin{
bottomNavigationBar: NavigationBar(
selectedIndex: _selectedIndex,
onDestinationSelected: _onItemTapped,
destinations: <Widget>[
NavigationDestination(
icon: const Icon(CustomIcons.home),
label: AppLocalizations.of(context)!.home,
),
NavigationDestination(
icon: const Icon(CustomIcons.events),
label: AppLocalizations.of(context)!.achievements,
),
// fake container to keep space for FAB
const NavigationDestination(
icon: SizedBox.shrink(),
label: '',
),
],
destinations: destinations,
),
body: NestedScrollView(
controller: _scrollController,
Expand All @@ -174,7 +156,7 @@ class _HomeState extends State<Home> with TickerProviderStateMixin{
onPressed: onFABpress,
show: showFAB,
),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
drawer: appDrawer(context, handlePageChanged, _pageIndex),
);
}
Expand Down
Loading

0 comments on commit ecd337e

Please sign in to comment.