various updates

This commit is contained in:
spinel 2022-03-21 01:08:05 +01:00
parent f590a134b5
commit 9f1053fb99
64 changed files with 3709 additions and 1690 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"cmake.configureOnOpen": false
}

75
TODO
View File

@ -1,4 +1,25 @@
MAIN TASKS: MAIN TASKS:
Database:
☐ set name properties as unique (and add checks to forms)
☐ implement users
☐ check objectbox docs on how to make users
☐ tie all data to users
☐ add user filters to all getters
☐ change settings to not be a singleton, but only one settings instance per user and one default entry
☐ add login and authentification
☐ enable restoring data from sync
☐ find a solution for storage
☐ hosting
☐ objectbox sync (commercial use is not free)
☐ implement alternative data export for now?
☐ create default datasets for configuration (meal categories, portion types, accuracies, event types, possibly meal source)
Features:
☐ app icon
☐ add explanations to each section
☐ german language support
☐ indicate nested creation process (creating from dropdown etc)
☐ indicate read only fields
☐ app info/credits screen
Components/Framework: Components/Framework:
☐ come up with new concept for duration component ☐ come up with new concept for duration component
☐ update duration fields to use corresponding component ☐ update duration fields to use corresponding component
@ -6,59 +27,43 @@ MAIN TASKS:
☐ log event detail (reminder duration) ☐ log event detail (reminder duration)
☐ meal (bolus delay) ☐ meal (bolus delay)
☐ log bolus (delay) ☐ log bolus (delay)
☐ set name properties as unique (and add checks to forms)
☐ check through all detail forms and set required fields/according messages ☐ check through all detail forms and set required fields/according messages
☐ change placement of delete and floating button because its very easy to accidentally hit delete ☐ change placement of delete and floating button because its very easy to accidentally hit delete
☐ implement deletion by swiping left on item instead?
☐ check for changes before navigating as well (not just on cancel) ☐ check for changes before navigating as well (not just on cancel)
Reports:
☐ evaluate what type of reports there should be
☐ try out graph/diagram components
FUTURE TASKS:
Features:
☐ app icon
☐ desktop version
☐ add explanations to each section
☐ alternate languages
☐ log hba1c
☐ indicate nested creation process (creating from dropdown etc)
☐ enable restoring data from sync
☐ indicate read only fields
Components/Framework:
☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view ☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view
☐ dropdown tweaks ☐ dropdown tweaks
☐ edit item -> cancel: shouldn't clear dropdwon ☐ edit item -> cancel: shouldn't clear dropdwon
☐ keep focus on textfield when typing
☐ account for deleted/disabled elements ☐ account for deleted/disabled elements
Accuracy: Log:
☐ same icons in detail as in overview to indicate what's what
Recipe:
☐ update to use correct components, init/dispose etc
☐ change the entire concept of ingredients
☐ add functionality to create a meal from a recipe
Reports:
☐ meal tweaking
☐ bolus tweaking
☐ basal test
☐ daily graph (showing glucose curve, events, boli and meals)
Log Overview:
☐ add filters ☐ add filters
Log Entry:
☐ check if there is still an active bolus when suggesting glucose bolus ☐ check if there is still an active bolus when suggesting glucose bolus
Event Types: Log Events:
☐ add colors as indicators for log entries (and later graphs in reports) ☐ add filters
Categorization:
☐ add colors to event types as indicators for log entries and graphs in reports
☐ implement reminders as push notifications ☐ implement reminders as push notifications
Settings: Settings:
☐ add option to hide extra customization options (ie. changing pre calculated values)?
☐ option to switch theme ☐ option to switch theme
☐ add fields for glucose target tiers (as map of cutoff glucose and colors) ☐ add fields for glucose target tiers (as map of cutoff glucose and colors)
☐ add field for active insulin duration ☐ add field for active insulin duration
☐ add setting for carb units/bread units
☐ add option to switch 'save' and 'save & close' buttons ☐ add option to switch 'save' and 'save & close' buttons
☐ add functionality to delete dead records (meaning: set deleted flag and no relations to undeleted records) ☐ add functionality to delete dead records (meaning: set deleted flag and no relations to undeleted records)
Archive: Archive:
✔ switch day on swipe @done(22-03-19 23:20) @project(MAIN TASKS.Log)
✔ switch day on swipe @done(22-03-19 23:23) @project(MAIN TASKS.Log Events)
✔ switch day on swipe @done(22-03-19 23:20) @project(MAIN TASKS.Reports)
✔ don't update calculation on deleting field content @done(22-03-12 04:33) @project(MAIN TASKS.Meal)
✔ don't use suggested time if date is set to today @done(22-03-12 04:36) @project(MAIN TASKS.Log)
✔ don't use suggested time if date is set to today @done(22-03-12 04:36) @project(MAIN TASKS.Log Events)
✔ pack all categorization stuff (meal categories, meal source, portion type, accuracies, event type) into one sub menu @done(22-03-12 04:21) @project(MAIN TASKS.Categorization)
✔ same icons in accuracy detail as in overview to indicate what's what @done(22-03-12 04:24) @project(MAIN TASKS.Categorization)
✔ view daily graphs @done(22-03-12 03:47) @project(MAIN TASKS.Reports)
✔ keep focus on textfield when typing @done(22-03-10 22:15) @project(MAIN TASKS.Components/Framework)
✔ calculation ends up wrong on re-entering log meal detail screen @done(22-02-06 21:46) @project(BUG FIXES)
✔ export log tables @done(22-03-10 22:01) @project(MAIN TASKS.Components/Framework)
✔ export graphs @done(22-03-10 22:01) @project(MAIN TASKS.Components/Framework)
✔ change auto calculation to remember fields that were filled in last @done(22-02-06 21:37) @project(MAIN TASKS.Meal)
✔ only show current day @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview) ✔ only show current day @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)
✔ add calendar field on top to navigate @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview) ✔ add calendar field on top to navigate @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)
✔ use currently selected day when adding a log entry @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview) ✔ use currently selected day when adding a log entry @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)

Binary file not shown.

View File

@ -26,6 +26,7 @@ class AppTheme {
), ),
), ),
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith( scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
// thumbVisibility: MaterialStateProperty.all(true),
isAlwaysShown: true, isAlwaysShown: true,
), ),
textTheme: baseThemeData.textTheme.copyWith( textTheme: baseThemeData.textTheme.copyWith(

View File

@ -56,7 +56,6 @@ class _AutoCompleteDropdownButtonState<T>
void showOverlay() { void showOverlay() {
hideOverlay(); hideOverlay();
focusNode.requestFocus();
List<Widget> items = []; List<Widget> items = [];
Divider? divider; Divider? divider;
@ -116,6 +115,7 @@ class _AutoCompleteDropdownButtonState<T>
showWhenUnlinked: false, showWhenUnlinked: false,
child: Scrollbar( child: Scrollbar(
controller: _scrollController, controller: _scrollController,
// thumbVisibility: true,
isAlwaysShown: true, isAlwaysShown: true,
child: Material( child: Material(
elevation: 8, elevation: 8,
@ -141,7 +141,7 @@ class _AutoCompleteDropdownButtonState<T>
selected: item == widget.selectedItem, selected: item == widget.selectedItem,
title: Row( title: Row(
children: [ children: [
Expanded( Flexible(
child: Text(item.toString()), child: Text(item.toString()),
), ),
], ],

View File

@ -6,6 +6,7 @@ class BooleanFormField extends StatefulWidget {
final void Function(bool) onChanged; final void Function(bool) onChanged;
final bool? enabled; final bool? enabled;
final EdgeInsets? contentPadding; final EdgeInsets? contentPadding;
final Icon? icon;
const BooleanFormField( const BooleanFormField(
{Key? key, {Key? key,
@ -13,7 +14,8 @@ class BooleanFormField extends StatefulWidget {
required this.label, required this.label,
required this.onChanged, required this.onChanged,
this.enabled, this.enabled,
this.contentPadding}) this.contentPadding,
this.icon})
: super(key: key); : super(key: key);
@override @override
@ -25,6 +27,7 @@ class _BooleanFormFieldState extends State<BooleanFormField> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FormField<bool>(builder: (state) { return FormField<bool>(builder: (state) {
return ListTile( return ListTile(
leading: widget.icon,
contentPadding: widget.contentPadding, contentPadding: widget.contentPadding,
onTap: () => widget.onChanged(!widget.value), onTap: () => widget.onChanged(!widget.value),
trailing: Switch( trailing: Switch(

View File

@ -86,7 +86,7 @@ class _NumberFormFieldState extends State<NumberFormField> {
), ),
) )
: Container(), : Container(),
Expanded( Flexible(
child: TextFormField( child: TextFormField(
readOnly: widget.readOnly, readOnly: widget.readOnly,
controller: widget.controller, controller: widget.controller,

View File

@ -2,28 +2,30 @@ import 'package:diameter/components/app_theme.dart';
import 'package:diameter/config.dart'; import 'package:diameter/config.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/object_box.dart'; import 'package:diameter/object_box.dart';
import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/category/categories.dart';
import 'package:diameter/screens/category/accuracy_detail.dart';
import 'package:diameter/screens/category/accuracy_list.dart';
import 'package:diameter/screens/category/event_type_detail.dart';
import 'package:diameter/screens/category/event_type_list.dart';
import 'package:diameter/screens/category/meal_category_detail.dart';
import 'package:diameter/screens/category/meal_category_list.dart';
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
import 'package:diameter/screens/category/meal_portion_type_list.dart';
import 'package:diameter/screens/category/meal_source_detail.dart';
import 'package:diameter/screens/category/meal_source_list.dart';
import 'package:diameter/screens/basal/basal_profile_detail.dart'; import 'package:diameter/screens/basal/basal_profile_detail.dart';
import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
import 'package:diameter/screens/log/log.dart'; import 'package:diameter/screens/log/log_overview.dart';
import 'package:diameter/screens/reports/daily_chart.dart';
import 'package:diameter/screens/log/log_entry/log_entry.dart'; import 'package:diameter/screens/log/log_entry/log_entry.dart';
import 'package:diameter/screens/log/log_event/log_event_detail.dart'; import 'package:diameter/screens/log/log_event_detail.dart';
import 'package:diameter/screens/log/log_event/log_event_list.dart'; import 'package:diameter/screens/log/log_event_list.dart';
import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
import 'package:diameter/screens/log/log_event/log_event_type_list.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart';
import 'package:diameter/screens/meal/meal_category_list.dart';
import 'package:diameter/screens/meal/meal_detail.dart'; import 'package:diameter/screens/meal/meal_detail.dart';
import 'package:diameter/screens/meal/meal_list.dart'; import 'package:diameter/screens/meal/meal_list.dart';
import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; import 'package:diameter/screens/reports/export.dart';
import 'package:diameter/screens/meal/meal_portion_type_list.dart'; import 'package:diameter/screens/reports/reports.dart';
import 'package:diameter/screens/meal/meal_source_detail.dart';
import 'package:diameter/screens/meal/meal_source_list.dart';
import 'package:diameter/screens/recipe/recipe_detail.dart';
import 'package:diameter/screens/recipe/recipe_list.dart';
import 'package:diameter/settings.dart'; import 'package:diameter/settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:diameter/screens/accuracy_list.dart';
import 'package:diameter/screens/basal/basal_profile_list.dart'; import 'package:diameter/screens/basal/basal_profile_list.dart';
import 'package:diameter/screens/bolus/bolus_profile_list.dart'; import 'package:diameter/screens/bolus/bolus_profile_list.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
@ -56,15 +58,15 @@ Future<void> main() async {
Routes.log: (context) => const LogScreen(), Routes.log: (context) => const LogScreen(),
Routes.logEntry: (context) => const LogEntryScreen(), Routes.logEntry: (context) => const LogEntryScreen(),
Routes.logEvent: (context) => const LogEventDetailScreen(), Routes.logEvent: (context) => const LogEventDetailScreen(),
Routes.logEventTypes: (context) => const LogEventTypeListScreen(), Routes.eventTypes: (context) => const EventTypeListScreen(),
Routes.logEventType: (context) => const EventTypeDetailScreen(), Routes.eventType: (context) => const EventTypeDetailScreen(),
Routes.events: (context) => const LogEventListScreen(), Routes.logEvents: (context) => const LogEventListScreen(),
Routes.accuracies: (context) => const AccuracyListScreen(), Routes.reports: (context) => const ReportsOverviewScreen(),
Routes.accuracy: (context) => const AccuracyDetailScreen(), Routes.export: (context) => const ExportDialog(),
Routes.dailyChart: (context) => const DailyChart(),
Routes.meals: (context) => const MealListScreen(), Routes.meals: (context) => const MealListScreen(),
Routes.meal: (context) => const MealDetailScreen(), Routes.meal: (context) => const MealDetailScreen(),
Routes.recipes: (context) => const RecipeListScreen(), Routes.category: (context) => const CategoryOverviewScreen(),
Routes.recipe: (context) => const RecipeDetailScreen(),
Routes.mealCategories: (context) => const MealCategoryListScreen(), Routes.mealCategories: (context) => const MealCategoryListScreen(),
Routes.mealCategory: (context) => const MealCategoryDetailScreen(), Routes.mealCategory: (context) => const MealCategoryDetailScreen(),
Routes.mealPortionTypes: (context) => Routes.mealPortionTypes: (context) =>
@ -73,6 +75,8 @@ Future<void> main() async {
const MealPortionTypeDetailScreen(), const MealPortionTypeDetailScreen(),
Routes.mealSources: (context) => const MealSourceListScreen(), Routes.mealSources: (context) => const MealSourceListScreen(),
Routes.mealSource: (context) => const MealSourceDetailScreen(), Routes.mealSource: (context) => const MealSourceDetailScreen(),
Routes.accuracies: (context) => const AccuracyListScreen(),
Routes.accuracy: (context) => const AccuracyDetailScreen(),
Routes.bolusProfiles: (context) => const BolusProfileListScreen(), Routes.bolusProfiles: (context) => const BolusProfileListScreen(),
Routes.bolusProfile: (context) => const BolusProfileDetailScreen(), Routes.bolusProfile: (context) => const BolusProfileDetailScreen(),
Routes.basalProfiles: (context) => const BasalProfileListScreen(), Routes.basalProfiles: (context) => const BasalProfileListScreen(),

View File

@ -1,6 +1,7 @@
import 'package:diameter/main.dart'; import 'package:diameter/main.dart';
import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/models/basal_profile.dart';
import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart';
import 'package:objectbox/objectbox.dart'; import 'package:objectbox/objectbox.dart';
import 'package:diameter/objectbox.g.dart' show Basal_, BasalProfile_; import 'package:diameter/objectbox.g.dart' show Basal_, BasalProfile_;
@ -68,6 +69,27 @@ class Basal {
return sum; return sum;
} }
static Basal? getRateForTime(DateTime? dateTime) {
if (dateTime != null) {
final basalProfile = BasalProfile.getActive(dateTime);
final time = DateTimeUtils.convertTimeOfDayToDateTime(
TimeOfDay.fromDateTime(dateTime));
if (basalProfile != null) {
final rates = Basal.getAllForProfile(basalProfile.id);
final result = rates.where((rate) {
DateTime endTime = rate.endTime == dummyDate
? rate.endTime.add(const Duration(days: 1))
: rate.endTime;
return (time.isAfter(rate.startTime) ||
time.isAtSameMomentAs(rate.startTime)) &&
time.isBefore(endTime);
});
return result.length != 1 ? null : result.single;
}
}
return null;
}
@override @override
String toString() { String toString() {
return DateTimeUtils.displayTime(startTime); return DateTimeUtils.displayTime(startTime);

View File

@ -53,30 +53,14 @@ class LogEntry {
return false; return false;
} }
static Map<DateTime, List<LogEntry>> getDailyEntryMap() {
Map<DateTime, List<LogEntry>> dateMap = <DateTime, List<LogEntry>>{};
QueryBuilder<LogEntry> allByDate = box
.query(LogEntry_.deleted.equals(false))
..order(LogEntry_.time, flags: Order.descending);
List<LogEntry> entries = allByDate.build().find();
DateTime? date;
for (LogEntry entry in entries) {
date = DateTime.utc(entry.time.year, entry.time.month, entry.time.day);
dateMap.putIfAbsent(date, () => <LogEntry>[]).add(entry);
}
return dateMap;
}
static List<LogEntry> getAllForDate(DateTime date) { static List<LogEntry> getAllForDate(DateTime date) {
DateTime startOfDay = DateTime(date.year, date.month, date.day); DateTime startOfDay = DateTime(date.year, date.month, date.day);
DateTime endOfDay = startOfDay.add(const Duration(days: 1)); DateTime endOfDay = startOfDay.add(const Duration(days: 1));
QueryBuilder<LogEntry> builder = box.query(LogEntry_.deleted.equals(false)) QueryBuilder<LogEntry> builder = box.query(LogEntry_.deleted.equals(false))
..order(LogEntry_.time, flags: Order.descending); ..order(LogEntry_.time, flags: Order.descending);
return builder.build().find().where((entry) { return builder.build().find().where((entry) {
return (entry.time.compareTo(startOfDay) >= 0 && entry.time.isBefore(endOfDay)); return (entry.time.compareTo(startOfDay) >= 0 &&
entry.time.isBefore(endOfDay));
}).toList(); }).toList();
} }

View File

@ -44,6 +44,7 @@ class LogEvent {
// methods // methods
static LogEvent? get(int id) => box.get(id); static LogEvent? get(int id) => box.get(id);
static List<LogEvent> getAll() => box.getAll();
static void put(LogEvent logEvent) => box.put(logEvent); static void put(LogEvent logEvent) => box.put(logEvent);
static void remove(int id) { static void remove(int id) {
@ -91,55 +92,6 @@ class LogEvent {
return eventsWithEndTime.isNotEmpty; return eventsWithEndTime.isNotEmpty;
} }
static Map<DateTime, List<LogEvent>> getDailyEntryMap() {
Map<DateTime, List<LogEvent>> dateMap = <DateTime, List<LogEvent>>{};
Map<DateTime, List<LogEvent>> sortedDateMap = <DateTime, List<LogEvent>>{};
QueryBuilder<LogEvent> allByDate = box
.query(LogEvent_.deleted.equals(false))
..order(LogEvent_.time, flags: Order.descending);
List<LogEvent> events = allByDate.build().find();
DateTime? date;
for (LogEvent event in events) {
date = DateTime.utc(event.time.year, event.time.month, event.time.day);
LogEvent startEvent = event;
startEvent.title =
'${event.toString()} ${event.hasEndTime ? '(Start)' : ''}';
dateMap.putIfAbsent(date, () => <LogEvent>[]).add(startEvent);
}
QueryBuilder<LogEvent> allByEndDate = box
.query(LogEvent_.deleted.equals(false).and(LogEvent_.endTime.notNull()))
..order(LogEvent_.endTime, flags: Order.descending);
List<LogEvent> endEvents = allByEndDate.build().find();
for (LogEvent event in endEvents) {
date = DateTime.utc(
event.endTime!.year, event.endTime!.month, event.endTime!.day);
LogEvent endEvent = event;
endEvent.isEndEvent = true;
endEvent.title = '${event.toString()} (End)';
dateMap.putIfAbsent(date, () => <LogEvent>[]).add(endEvent);
}
final dates = dateMap.keys.toList();
dates.sort();
for (DateTime date in dates.reversed) {
dateMap[date]!.sort((LogEvent a, LogEvent b) {
final dateA = a.isEndEvent ? a.endTime : a.time;
final dateB = b.isEndEvent ? b.endTime : b.time;
return -(dateA!.compareTo(dateB!));
});
sortedDateMap
.putIfAbsent(date, () => <LogEvent>[])
.addAll(dateMap[date]!);
}
return sortedDateMap;
}
static List<LogEvent> getAllForDate(DateTime date) { static List<LogEvent> getAllForDate(DateTime date) {
DateTime startOfDay = DateTime(date.year, date.month, date.day); DateTime startOfDay = DateTime(date.year, date.month, date.day);
DateTime endOfDay = startOfDay.add(const Duration(days: 1)); DateTime endOfDay = startOfDay.add(const Duration(days: 1));

View File

@ -101,8 +101,7 @@ class Settings {
static NutritionMeasurement get nutritionMeasurement => static NutritionMeasurement get nutritionMeasurement =>
NutritionMeasurement.values[get().nutritionMeasurementIndex]; NutritionMeasurement.values[get().nutritionMeasurementIndex];
static GlucoseMeasurement get glucoseMeasurement => static GlucoseMeasurement get glucoseMeasurement => GlucoseMeasurement.values[get().glucoseMeasurementIndex];
GlucoseMeasurement.values[get().glucoseMeasurementIndex];
static GlucoseDisplayMode get glucoseDisplayMode => static GlucoseDisplayMode get glucoseDisplayMode =>
GlucoseDisplayMode.values[get().glucoseDisplayModeIndex]; GlucoseDisplayMode.values[get().glucoseDisplayModeIndex];

46
lib/models/user.dart Normal file
View File

@ -0,0 +1,46 @@
import 'package:diameter/main.dart';
import 'package:objectbox/objectbox.dart';
@Entity()
@Sync()
class User {
static final Box<User> box = objectBox.store.box<User>();
// properties
int id;
bool deleted;
String name;
String email;
String password;
// constructor
User({
this.id = 0,
this.deleted = false,
required this.name,
required this.email,
required this.password,
});
// methods
static User? get(int id) => box.get(id);
static void put(User user) => box.put(user);
// static String login(String name, String password) {
// password.h
// return
// }
static void remove(int id) {
final item = box.get(id);
if (item != null) {
item.deleted = true;
box.put(item);
}
}
@override
String toString() {
return name;
}
}

View File

@ -1,28 +1,31 @@
import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/category/categories.dart';
import 'package:diameter/screens/accuracy_list.dart'; import 'package:diameter/screens/category/accuracy_detail.dart';
import 'package:diameter/screens/category/accuracy_list.dart';
import 'package:diameter/screens/category/event_type_detail.dart';
import 'package:diameter/screens/category/event_type_list.dart';
import 'package:diameter/screens/category/meal_source_detail.dart';
import 'package:diameter/screens/category/meal_source_list.dart';
import 'package:diameter/screens/category/meal_category_detail.dart';
import 'package:diameter/screens/category/meal_category_list.dart';
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
import 'package:diameter/screens/category/meal_portion_type_list.dart';
import 'package:diameter/screens/basal/basal_detail.dart'; import 'package:diameter/screens/basal/basal_detail.dart';
import 'package:diameter/screens/basal/basal_profile_detail.dart'; import 'package:diameter/screens/basal/basal_profile_detail.dart';
import 'package:diameter/screens/basal/basal_profile_list.dart'; import 'package:diameter/screens/basal/basal_profile_list.dart';
import 'package:diameter/screens/bolus/bolus_detail.dart'; import 'package:diameter/screens/bolus/bolus_detail.dart';
import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
import 'package:diameter/screens/bolus/bolus_profile_list.dart'; import 'package:diameter/screens/bolus/bolus_profile_list.dart';
import 'package:diameter/screens/log/log.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_detail.dart';
import 'package:diameter/screens/log/log_overview.dart';
import 'package:diameter/screens/reports/daily_chart.dart';
import 'package:diameter/screens/log/log_entry/log_entry.dart'; import 'package:diameter/screens/log/log_entry/log_entry.dart';
import 'package:diameter/screens/log/log_event/log_event_detail.dart'; import 'package:diameter/screens/log/log_event_detail.dart';
import 'package:diameter/screens/log/log_event/log_event_list.dart'; import 'package:diameter/screens/log/log_event_list.dart';
import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
import 'package:diameter/screens/log/log_event/log_event_type_list.dart';
import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart';
import 'package:diameter/screens/meal/meal_category_list.dart';
import 'package:diameter/screens/meal/meal_detail.dart'; import 'package:diameter/screens/meal/meal_detail.dart';
import 'package:diameter/screens/meal/meal_list.dart'; import 'package:diameter/screens/meal/meal_list.dart';
import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; import 'package:diameter/screens/reports/export.dart';
import 'package:diameter/screens/meal/meal_portion_type_list.dart'; import 'package:diameter/screens/reports/reports.dart';
import 'package:diameter/screens/meal/meal_source_detail.dart';
import 'package:diameter/screens/meal/meal_source_list.dart';
import 'package:diameter/screens/recipe/recipe_detail.dart';
import 'package:diameter/screens/recipe/recipe_list.dart';
import 'package:diameter/settings.dart'; import 'package:diameter/settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -39,30 +42,38 @@ class Routes {
static const String log = LogScreen.routeName; static const String log = LogScreen.routeName;
static const String logEntry = LogEntryScreen.routeName; static const String logEntry = LogEntryScreen.routeName;
static const String logEvent = LogEventDetailScreen.routeName;
static const String logMeal = LogMealDetailScreen.routeName; static const String logMeal = LogMealDetailScreen.routeName;
static const List<String> logEntryRoutes = [logEntry, logEvent, logMeal]; static const String logBolus = LogBolusDetailScreen.routeName;
static const String logEventType = EventTypeDetailScreen.routeName; static const List<String> logEntryRoutes = [log, logEntry, logMeal, logBolus];
static const String logEventTypes = LogEventTypeListScreen.routeName;
static const List<String> logEventTypeRoutes = [logEventType, logEventTypes];
static const String events = LogEventListScreen.routeName;
static const String recipe = RecipeDetailScreen.routeName; static const String logEvent = LogEventDetailScreen.routeName;
static const String recipes = RecipeListScreen.routeName; static const String logEvents = LogEventListScreen.routeName;
static const List<String> recipeRoutes = [recipe, recipes]; static const List<String> logEventRoutes = [logEvent, logEvents];
static const String reports = ReportsOverviewScreen.routeName;
static const String export = ExportDialog.routeName;
static const String dailyChart = DailyChart.routeName;
static const List<String> reportRoutes = [export, reports, dailyChart];
static const String meal = MealDetailScreen.routeName; static const String meal = MealDetailScreen.routeName;
static const String meals = MealListScreen.routeName; static const String meals = MealListScreen.routeName;
static const List<String> mealRoutes = [meal, meals]; static const List<String> mealRoutes = [meal, meals];
static const String eventType = EventTypeDetailScreen.routeName;
static const String eventTypes = EventTypeListScreen.routeName;
static const List<String> eventTypeRoutes = [eventType, eventTypes];
static const String mealCategory = MealCategoryDetailScreen.routeName; static const String mealCategory = MealCategoryDetailScreen.routeName;
static const String mealCategories = MealCategoryListScreen.routeName; static const String mealCategories = MealCategoryListScreen.routeName;
static const List<String> mealCategoryRoutes = [mealCategory, mealCategories]; static const List<String> mealCategoryRoutes = [mealCategory, mealCategories];
static const String mealPortionType = MealPortionTypeDetailScreen.routeName; static const String mealPortionType = MealPortionTypeDetailScreen.routeName;
static const String mealPortionTypes = MealPortionTypeListScreen.routeName; static const String mealPortionTypes = MealPortionTypeListScreen.routeName;
static const List<String> mealPortionTypeRoutes = [ static const List<String> mealPortionTypeRoutes = [
mealPortionType, mealPortionType,
mealPortionTypes mealPortionTypes
]; ];
static const String mealSource = MealSourceDetailScreen.routeName; static const String mealSource = MealSourceDetailScreen.routeName;
static const String mealSources = MealSourceListScreen.routeName; static const String mealSources = MealSourceListScreen.routeName;
static const List<String> mealSourceRoutes = [mealSource, mealSources]; static const List<String> mealSourceRoutes = [mealSource, mealSources];
@ -71,6 +82,16 @@ class Routes {
static const String accuracies = AccuracyListScreen.routeName; static const String accuracies = AccuracyListScreen.routeName;
static const List<String> accuracyRoutes = [accuracy, accuracies]; static const List<String> accuracyRoutes = [accuracy, accuracies];
static const String category = CategoryOverviewScreen.routeName;
static const List<String> categoryRoutes = [
category,
...eventTypeRoutes,
...mealCategoryRoutes,
...mealPortionTypeRoutes,
...mealSourceRoutes,
...accuracyRoutes
];
static const String settings = SettingsScreen.routeName; static const String settings = SettingsScreen.routeName;
} }
@ -109,18 +130,18 @@ class _NavigationState extends State<Navigation> {
title: const Text('Log Events'), title: const Text('Log Events'),
leading: const Icon(Icons.event), leading: const Icon(Icons.event),
onTap: () { onTap: () {
selectDestination(Routes.events); selectDestination(Routes.logEvents);
}, },
selected: widget.currentLocation == Routes.events, selected: widget.currentLocation == Routes.logEvents,
),
ListTile(
title: const Text('Reports'),
leading: const Icon(Icons.show_chart),
onTap: () {
selectDestination(Routes.reports);
},
selected: Routes.reportRoutes.contains(widget.currentLocation),
), ),
// ListTile(
// title: const Text('Recipes'),
// leading: const Icon(Icons.local_dining),
// onTap: () {
// selectDestination(Routes.recipes);
// },
// selected: Routes.recipeRoutes.contains(widget.currentLocation),
// ),
ListTile( ListTile(
title: const Text('Meals'), title: const Text('Meals'),
leading: const Icon(Icons.dinner_dining), leading: const Icon(Icons.dinner_dining),
@ -129,46 +150,6 @@ class _NavigationState extends State<Navigation> {
}, },
selected: Routes.mealRoutes.contains(widget.currentLocation), selected: Routes.mealRoutes.contains(widget.currentLocation),
), ),
ListTile(
title: const Text('Meal Categories'),
leading: const Icon(Icons.category),
onTap: () {
selectDestination(Routes.mealCategories);
},
selected: Routes.mealCategoryRoutes.contains(widget.currentLocation),
),
ListTile(
title: const Text('Meal Portion Types'),
leading: const Icon(Icons.pie_chart),
onTap: () {
selectDestination(Routes.mealPortionTypes);
},
selected: Routes.mealPortionTypeRoutes.contains(widget.currentLocation),
),
ListTile(
title: const Text('Meal Sources'),
leading: const Icon(Icons.local_grocery_store),
onTap: () {
selectDestination(Routes.mealSources);
},
selected: Routes.mealSourceRoutes.contains(widget.currentLocation),
),
ListTile(
title: const Text('Accuracies'),
leading: const Icon(Icons.architecture),
onTap: () {
selectDestination(Routes.accuracies);
},
selected: Routes.accuracyRoutes.contains(widget.currentLocation),
),
ListTile(
title: const Text('Log Event Types'),
leading: const Icon(Icons.event),
onTap: () {
selectDestination(Routes.logEventTypes);
},
selected: Routes.logEventTypeRoutes.contains(widget.currentLocation),
),
ListTile( ListTile(
title: const Text('Basal Profiles'), title: const Text('Basal Profiles'),
leading: const Icon(Icons.access_time), leading: const Icon(Icons.access_time),
@ -185,6 +166,14 @@ class _NavigationState extends State<Navigation> {
}, },
selected: Routes.bolusRoutes.contains(widget.currentLocation), selected: Routes.bolusRoutes.contains(widget.currentLocation),
), ),
ListTile(
title: const Text('Categorization'),
leading: const Icon(Icons.category),
onTap: () {
selectDestination(Routes.category);
},
selected: Routes.categoryRoutes.contains(widget.currentLocation),
),
ListTile( ListTile(
title: const Text('Settings'), title: const Text('Settings'),
leading: const Icon(Icons.settings), leading: const Icon(Icons.settings),

View File

@ -818,7 +818,7 @@
}, },
{ {
"id": "16:3989341091218179227", "id": "16:3989341091218179227",
"lastPropertyId": "23:3611447442844013652", "lastPropertyId": "27:3553639710779248831",
"name": "Settings", "name": "Settings",
"flags": 2, "flags": 2,
"properties": [ "properties": [
@ -892,6 +892,26 @@
"id": "23:3611447442844013652", "id": "23:3611447442844013652",
"name": "useDarkTheme", "name": "useDarkTheme",
"type": 1 "type": 1
},
{
"id": "24:3003519517071020397",
"name": "insulinIncrements",
"type": 8
},
{
"id": "25:402003851844402390",
"name": "nutritionIncrements",
"type": 8
},
{
"id": "26:3104312506075410259",
"name": "mmolPerLIncrements",
"type": 8
},
{
"id": "27:3553639710779248831",
"name": "amountIncrements",
"type": 8
} }
], ],
"relations": [] "relations": []
@ -1024,9 +1044,44 @@
} }
], ],
"relations": [] "relations": []
},
{
"id": "20:4237292160526261631",
"lastPropertyId": "5:3477783279940695158",
"name": "User",
"flags": 2,
"properties": [
{
"id": "1:785183194548024772",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:2837394475791909830",
"name": "deleted",
"type": 1
},
{
"id": "3:6612000015066380372",
"name": "name",
"type": 9
},
{
"id": "4:861275034953202445",
"name": "email",
"type": 9
},
{
"id": "5:3477783279940695158",
"name": "password",
"type": 9
}
],
"relations": []
} }
], ],
"lastEntityId": "19:6950311793136068892", "lastEntityId": "20:4237292160526261631",
"lastIndexId": "31:3277019237664417023", "lastIndexId": "31:3277019237664417023",
"lastRelationId": "0:0", "lastRelationId": "0:0",
"lastSequenceId": "0:0", "lastSequenceId": "0:0",

View File

@ -1,10 +1,14 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// This code was generated by ObjectBox. To update it run the generator again:
// With a Flutter package, run `flutter pub run build_runner build`.
// With a Dart package, run `dart run build_runner build`.
// See also https://docs.objectbox.io/getting-started#generate-objectbox-code
// ignore_for_file: camel_case_types // ignore_for_file: camel_case_types
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:objectbox/flatbuffers/flat_buffers.dart' as fb; import 'package:flat_buffers/flat_buffers.dart' as fb;
import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality
import 'package:objectbox/objectbox.dart'; import 'package:objectbox/objectbox.dart';
import 'package:objectbox_sync_flutter_libs/objectbox_sync_flutter_libs.dart'; import 'package:objectbox_sync_flutter_libs/objectbox_sync_flutter_libs.dart';
@ -27,6 +31,7 @@ import 'models/meal_portion_type.dart';
import 'models/meal_source.dart'; import 'models/meal_source.dart';
import 'models/recipe.dart'; import 'models/recipe.dart';
import 'models/settings.dart'; import 'models/settings.dart';
import 'models/user.dart';
export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
@ -807,7 +812,7 @@ final _entities = <ModelEntity>[
ModelEntity( ModelEntity(
id: const IdUid(16, 3989341091218179227), id: const IdUid(16, 3989341091218179227),
name: 'Settings', name: 'Settings',
lastPropertyId: const IdUid(23, 3611447442844013652), lastPropertyId: const IdUid(27, 3553639710779248831),
flags: 2, flags: 2,
properties: <ModelProperty>[ properties: <ModelProperty>[
ModelProperty( ModelProperty(
@ -879,6 +884,26 @@ final _entities = <ModelEntity>[
id: const IdUid(23, 3611447442844013652), id: const IdUid(23, 3611447442844013652),
name: 'useDarkTheme', name: 'useDarkTheme',
type: 1, type: 1,
flags: 0),
ModelProperty(
id: const IdUid(24, 3003519517071020397),
name: 'insulinIncrements',
type: 8,
flags: 0),
ModelProperty(
id: const IdUid(25, 402003851844402390),
name: 'nutritionIncrements',
type: 8,
flags: 0),
ModelProperty(
id: const IdUid(26, 3104312506075410259),
name: 'mmolPerLIncrements',
type: 8,
flags: 0),
ModelProperty(
id: const IdUid(27, 3553639710779248831),
name: 'amountIncrements',
type: 8,
flags: 0) flags: 0)
], ],
relations: <ModelRelation>[], relations: <ModelRelation>[],
@ -1005,6 +1030,40 @@ final _entities = <ModelEntity>[
relationTarget: 'Meal') relationTarget: 'Meal')
], ],
relations: <ModelRelation>[], relations: <ModelRelation>[],
backlinks: <ModelBacklink>[]),
ModelEntity(
id: const IdUid(20, 4237292160526261631),
name: 'User',
lastPropertyId: const IdUid(5, 3477783279940695158),
flags: 2,
properties: <ModelProperty>[
ModelProperty(
id: const IdUid(1, 785183194548024772),
name: 'id',
type: 6,
flags: 1),
ModelProperty(
id: const IdUid(2, 2837394475791909830),
name: 'deleted',
type: 1,
flags: 0),
ModelProperty(
id: const IdUid(3, 6612000015066380372),
name: 'name',
type: 9,
flags: 0),
ModelProperty(
id: const IdUid(4, 861275034953202445),
name: 'email',
type: 9,
flags: 0),
ModelProperty(
id: const IdUid(5, 3477783279940695158),
name: 'password',
type: 9,
flags: 0)
],
relations: <ModelRelation>[],
backlinks: <ModelBacklink>[]) backlinks: <ModelBacklink>[])
]; ];
@ -1028,7 +1087,7 @@ Future<Store> openStore(
ModelDefinition getObjectBoxModel() { ModelDefinition getObjectBoxModel() {
final model = ModelInfo( final model = ModelInfo(
entities: _entities, entities: _entities,
lastEntityId: const IdUid(19, 6950311793136068892), lastEntityId: const IdUid(20, 4237292160526261631),
lastIndexId: const IdUid(31, 3277019237664417023), lastIndexId: const IdUid(31, 3277019237664417023),
lastRelationId: const IdUid(0, 0), lastRelationId: const IdUid(0, 0),
lastSequenceId: const IdUid(0, 0), lastSequenceId: const IdUid(0, 0),
@ -1140,11 +1199,11 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 12, false), .vTableGet(buffer, rootOffset, 12, false),
name: name: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
active: active:
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false), const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 10)); .vTableGetNullable(buffer, rootOffset, 10));
return object; return object;
@ -1225,11 +1284,11 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 12, false), .vTableGet(buffer, rootOffset, 12, false),
name: name: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
active: active:
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false), const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 10)); .vTableGetNullable(buffer, rootOffset, 10));
return object; return object;
@ -1272,7 +1331,7 @@ ModelDefinition getObjectBoxModel() {
.vTableGetNullable(buffer, rootOffset, 10), .vTableGetNullable(buffer, rootOffset, 10),
glucoseTrend: const fb.Float64Reader() glucoseTrend: const fb.Float64Reader()
.vTableGetNullable(buffer, rootOffset, 22), .vTableGetNullable(buffer, rootOffset, 22),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 18)); .vTableGetNullable(buffer, rootOffset, 18));
return object; return object;
@ -1321,7 +1380,7 @@ ModelDefinition getObjectBoxModel() {
.vTableGet(buffer, rootOffset, 10, false), .vTableGet(buffer, rootOffset, 10, false),
reminderDuration: const fb.Int64Reader() reminderDuration: const fb.Int64Reader()
.vTableGetNullable(buffer, rootOffset, 26), .vTableGetNullable(buffer, rootOffset, 26),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 12)); .vTableGetNullable(buffer, rootOffset, 12));
object.eventType.targetId = object.eventType.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0); const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
@ -1367,13 +1426,13 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 14, false), .vTableGet(buffer, rootOffset, 14, false),
value: value: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
hasEndTime: hasEndTime:
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false), const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
defaultReminderDuration: const fb.Int64Reader() defaultReminderDuration: const fb.Int64Reader()
.vTableGetNullable(buffer, rootOffset, 10), .vTableGetNullable(buffer, rootOffset, 10),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 12)); .vTableGetNullable(buffer, rootOffset, 12));
object.bolusProfile.targetId = object.bolusProfile.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0); const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
@ -1431,8 +1490,8 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 36, false), .vTableGet(buffer, rootOffset, 36, false),
value: value: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
amount: amount:
const fb.Float64Reader().vTableGet(buffer, rootOffset, 38, 0), const fb.Float64Reader().vTableGet(buffer, rootOffset, 38, 0),
carbsRatio: const fb.Float64Reader() carbsRatio: const fb.Float64Reader()
@ -1441,7 +1500,7 @@ ModelDefinition getObjectBoxModel() {
.vTableGetNullable(buffer, rootOffset, 10), .vTableGetNullable(buffer, rootOffset, 10),
totalCarbs: const fb.Float64Reader() totalCarbs: const fb.Float64Reader()
.vTableGetNullable(buffer, rootOffset, 40), .vTableGetNullable(buffer, rootOffset, 40),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 20)) .vTableGetNullable(buffer, rootOffset, 20))
..bolus = const fb.Float64Reader() ..bolus = const fb.Float64Reader()
.vTableGetNullable(buffer, rootOffset, 14); .vTableGetNullable(buffer, rootOffset, 14);
@ -1512,8 +1571,8 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 30, false), .vTableGet(buffer, rootOffset, 30, false),
value: value: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
carbsRatio: const fb.Float64Reader() carbsRatio: const fb.Float64Reader()
.vTableGetNullable(buffer, rootOffset, 8), .vTableGetNullable(buffer, rootOffset, 8),
portionSize: const fb.Float64Reader() portionSize: const fb.Float64Reader()
@ -1524,7 +1583,7 @@ ModelDefinition getObjectBoxModel() {
.vTableGetNullable(buffer, rootOffset, 14), .vTableGetNullable(buffer, rootOffset, 14),
delayedBolusPercentage: const fb.Float64Reader() delayedBolusPercentage: const fb.Float64Reader()
.vTableGetNullable(buffer, rootOffset, 32), .vTableGetNullable(buffer, rootOffset, 32),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 18)); .vTableGetNullable(buffer, rootOffset, 18));
object.mealSource.targetId = object.mealSource.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0); const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
@ -1571,9 +1630,9 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 10, false), .vTableGet(buffer, rootOffset, 10, false),
value: value: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 8)); .vTableGetNullable(buffer, rootOffset, 8));
return object; return object;
@ -1606,9 +1665,9 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 10, false), .vTableGet(buffer, rootOffset, 10, false),
value: value: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 8)); .vTableGetNullable(buffer, rootOffset, 8));
return object; return object;
@ -1650,9 +1709,9 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 18, false), .vTableGet(buffer, rootOffset, 18, false),
value: value: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 8)); .vTableGetNullable(buffer, rootOffset, 8));
object.defaultMealCategory.targetId = object.defaultMealCategory.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0); const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0);
@ -1728,7 +1787,7 @@ ModelDefinition getObjectBoxModel() {
.vTableGetNullable(buffer, rootOffset, 38), .vTableGetNullable(buffer, rootOffset, 38),
setManually: const fb.BoolReader() setManually: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 16, false), .vTableGet(buffer, rootOffset, 16, false),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 18)); .vTableGetNullable(buffer, rootOffset, 18));
object.logEntry.targetId = object.logEntry.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0); const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
@ -1772,15 +1831,15 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: const fb.BoolReader() deleted: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 16, false), .vTableGet(buffer, rootOffset, 16, false),
value: value: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), .vTableGet(buffer, rootOffset, 6, ''),
forCarbsRatio: forCarbsRatio:
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false), const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
forPortionSize: const fb.BoolReader() forPortionSize: const fb.BoolReader()
.vTableGet(buffer, rootOffset, 10, false), .vTableGet(buffer, rootOffset, 10, false),
confidenceRating: const fb.Int64Reader() confidenceRating: const fb.Int64Reader()
.vTableGetNullable(buffer, rootOffset, 12), .vTableGetNullable(buffer, rootOffset, 12),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 14)); .vTableGetNullable(buffer, rootOffset, 14));
return object; return object;
@ -1802,7 +1861,7 @@ ModelDefinition getObjectBoxModel() {
final longTimeFormatOffset = object.longTimeFormat == null final longTimeFormatOffset = object.longTimeFormat == null
? null ? null
: fbb.writeString(object.longTimeFormat!); : fbb.writeString(object.longTimeFormat!);
fbb.startTable(24); fbb.startTable(28);
fbb.addInt64(0, object.id); fbb.addInt64(0, object.id);
fbb.addOffset(1, dateFormatOffset); fbb.addOffset(1, dateFormatOffset);
fbb.addOffset(2, longDateFormatOffset); fbb.addOffset(2, longDateFormatOffset);
@ -1817,6 +1876,10 @@ ModelDefinition getObjectBoxModel() {
fbb.addInt64(20, object.targetGlucoseMgPerDl); fbb.addInt64(20, object.targetGlucoseMgPerDl);
fbb.addFloat64(21, object.targetGlucoseMmolPerL); fbb.addFloat64(21, object.targetGlucoseMmolPerL);
fbb.addBool(22, object.useDarkTheme); fbb.addBool(22, object.useDarkTheme);
fbb.addFloat64(23, object.insulinIncrements);
fbb.addFloat64(24, object.nutritionIncrements);
fbb.addFloat64(25, object.mmolPerLIncrements);
fbb.addFloat64(26, object.amountIncrements);
fbb.finish(fbb.endTable()); fbb.finish(fbb.endTable());
return object.id; return object.id;
}, },
@ -1832,24 +1895,27 @@ ModelDefinition getObjectBoxModel() {
const fb.Int64Reader().vTableGet(buffer, rootOffset, 40, 0), const fb.Int64Reader().vTableGet(buffer, rootOffset, 40, 0),
glucoseMeasurementIndex: glucoseMeasurementIndex:
const fb.Int64Reader().vTableGet(buffer, rootOffset, 42, 0), const fb.Int64Reader().vTableGet(buffer, rootOffset, 42, 0),
dateFormat: insulinIncrements:
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), const fb.Float64Reader().vTableGet(buffer, rootOffset, 50, 0),
longDateFormat: const fb.StringReader() nutritionIncrements:
const fb.Float64Reader().vTableGet(buffer, rootOffset, 52, 0),
mmolPerLIncrements:
const fb.Float64Reader().vTableGet(buffer, rootOffset, 54, 0),
amountIncrements:
const fb.Float64Reader().vTableGet(buffer, rootOffset, 56, 0),
dateFormat: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 6, ''),
longDateFormat: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 8), .vTableGetNullable(buffer, rootOffset, 8),
timeFormat: timeFormat: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 10, ''), .vTableGet(buffer, rootOffset, 10, ''),
longTimeFormat: const fb.StringReader() longTimeFormat: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 12), .vTableGetNullable(buffer, rootOffset, 12),
showConfirmationDialogOnCancel: const fb.BoolReader() showConfirmationDialogOnCancel: const fb.BoolReader().vTableGet(buffer, rootOffset, 14, false),
.vTableGet(buffer, rootOffset, 14, false), showConfirmationDialogOnDelete: const fb.BoolReader().vTableGet(buffer, rootOffset, 16, false),
showConfirmationDialogOnDelete: const fb.BoolReader() showConfirmationDialogOnStopEvent: const fb.BoolReader().vTableGet(buffer, rootOffset, 18, false),
.vTableGet(buffer, rootOffset, 16, false), targetGlucoseMgPerDl: const fb.Int64Reader().vTableGet(buffer, rootOffset, 44, 0),
showConfirmationDialogOnStopEvent: const fb.BoolReader() targetGlucoseMmolPerL: const fb.Float64Reader().vTableGet(buffer, rootOffset, 46, 0),
.vTableGet(buffer, rootOffset, 18, false),
targetGlucoseMgPerDl:
const fb.Int64Reader().vTableGet(buffer, rootOffset, 44, 0),
targetGlucoseMmolPerL:
const fb.Float64Reader().vTableGet(buffer, rootOffset, 46, 0),
useDarkTheme: const fb.BoolReader().vTableGet(buffer, rootOffset, 48, false)); useDarkTheme: const fb.BoolReader().vTableGet(buffer, rootOffset, 48, false));
return object; return object;
@ -1925,11 +1991,11 @@ ModelDefinition getObjectBoxModel() {
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted: deleted:
const fb.BoolReader().vTableGet(buffer, rootOffset, 6, false), const fb.BoolReader().vTableGet(buffer, rootOffset, 6, false),
name: name: const fb.StringReader(asciiOptimization: true)
const fb.StringReader().vTableGet(buffer, rootOffset, 8, ''), .vTableGet(buffer, rootOffset, 8, ''),
servings: const fb.Float64Reader() servings: const fb.Float64Reader()
.vTableGetNullable(buffer, rootOffset, 24), .vTableGetNullable(buffer, rootOffset, 24),
notes: const fb.StringReader() notes: const fb.StringReader(asciiOptimization: true)
.vTableGetNullable(buffer, rootOffset, 20)); .vTableGetNullable(buffer, rootOffset, 20));
object.portion.targetId = object.portion.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0); const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
@ -1972,6 +2038,44 @@ ModelDefinition getObjectBoxModel() {
const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0); const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0);
object.ingredient.attach(store); object.ingredient.attach(store);
return object; return object;
}),
User: EntityDefinition<User>(
model: _entities[18],
toOneRelations: (User object) => [],
toManyRelations: (User object) => {},
getId: (User object) => object.id,
setId: (User object, int id) {
object.id = id;
},
objectToFB: (User object, fb.Builder fbb) {
final nameOffset = fbb.writeString(object.name);
final emailOffset = fbb.writeString(object.email);
final passwordOffset = fbb.writeString(object.password);
fbb.startTable(6);
fbb.addInt64(0, object.id);
fbb.addBool(1, object.deleted);
fbb.addOffset(2, nameOffset);
fbb.addOffset(3, emailOffset);
fbb.addOffset(4, passwordOffset);
fbb.finish(fbb.endTable());
return object.id;
},
objectFromFB: (Store store, ByteData fbData) {
final buffer = fb.BufferContext(fbData);
final rootOffset = buffer.derefObject(0);
final object = User(
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
deleted:
const fb.BoolReader().vTableGet(buffer, rootOffset, 6, false),
name: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 8, ''),
email: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 10, ''),
password: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 12, ''));
return object;
}) })
}; };
@ -2537,6 +2641,22 @@ class Settings_ {
/// see [Settings.useDarkTheme] /// see [Settings.useDarkTheme]
static final useDarkTheme = static final useDarkTheme =
QueryBooleanProperty<Settings>(_entities[14].properties[13]); QueryBooleanProperty<Settings>(_entities[14].properties[13]);
/// see [Settings.insulinIncrements]
static final insulinIncrements =
QueryDoubleProperty<Settings>(_entities[14].properties[14]);
/// see [Settings.nutritionIncrements]
static final nutritionIncrements =
QueryDoubleProperty<Settings>(_entities[14].properties[15]);
/// see [Settings.mmolPerLIncrements]
static final mmolPerLIncrements =
QueryDoubleProperty<Settings>(_entities[14].properties[16]);
/// see [Settings.amountIncrements]
static final amountIncrements =
QueryDoubleProperty<Settings>(_entities[14].properties[17]);
} }
/// [GlucoseTarget] entity fields to define ObjectBox queries. /// [GlucoseTarget] entity fields to define ObjectBox queries.
@ -2616,3 +2736,23 @@ class Ingredient_ {
static final ingredient = static final ingredient =
QueryRelationToOne<Ingredient, Meal>(_entities[17].properties[4]); QueryRelationToOne<Ingredient, Meal>(_entities[17].properties[4]);
} }
/// [User] entity fields to define ObjectBox queries.
class User_ {
/// see [User.id]
static final id = QueryIntegerProperty<User>(_entities[18].properties[0]);
/// see [User.deleted]
static final deleted =
QueryBooleanProperty<User>(_entities[18].properties[1]);
/// see [User.name]
static final name = QueryStringProperty<User>(_entities[18].properties[2]);
/// see [User.email]
static final email = QueryStringProperty<User>(_entities[18].properties[3]);
/// see [User.password]
static final password =
QueryStringProperty<User>(_entities[18].properties[4]);
}

View File

@ -156,15 +156,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
}, },
), ),
BooleanFormField( BooleanFormField(
value: _forCarbsRatio, icon: const Icon(Icons.square_foot),
label: 'for carbs ratio',
onChanged: (value) {
setState(() {
_forCarbsRatio = value;
});
},
),
BooleanFormField(
value: _forPortionSize, value: _forPortionSize,
label: 'for portion size', label: 'for portion size',
onChanged: (value) { onChanged: (value) {
@ -173,6 +165,16 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
}); });
}, },
), ),
BooleanFormField(
icon: const Icon(Icons.pie_chart),
value: _forCarbsRatio,
label: 'for carbs ratio',
onChanged: (value) {
setState(() {
_forCarbsRatio = value;
});
},
),
NumberFormField( NumberFormField(
controller: _confidenceRatingController, controller: _confidenceRatingController,
label: 'Confidence Rating', label: 'Confidence Rating',

View File

@ -1,7 +1,7 @@
import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/category/accuracy_detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:diameter/models/accuracy.dart'; import 'package:diameter/models/accuracy.dart';

View File

@ -0,0 +1,145 @@
import 'package:diameter/navigation.dart';
import 'package:flutter/material.dart';
class CategoryOverviewScreen extends StatefulWidget {
static const String routeName = '/category';
const CategoryOverviewScreen({Key? key}) : super(key: key);
@override
_CategoryOverviewScreenState createState() => _CategoryOverviewScreenState();
}
class _CategoryOverviewScreenState extends State<CategoryOverviewScreen> {
final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Reports'),
),
drawer:
const Navigation(currentLocation: CategoryOverviewScreen.routeName),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: GridView.count(
crossAxisCount: 2,
padding: const EdgeInsets.all(10.0),
children: [
GestureDetector(
onTap: () => Navigator.pushNamed(context, Routes.mealSources),
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(Icons.local_grocery_store, size: 50, color: Theme.of(context).textTheme.subtitle2?.color),
),
Text(
'MEAL SOURCES',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center
),
],
),
),
),
GestureDetector(
onTap: () => Navigator.pushNamed(context, Routes.mealCategories),
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(Icons.category, size: 50, color: Theme.of(context).textTheme.subtitle2?.color),
),
Text(
'MEAL CATEGORIES',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center
),
],
),
),
),
),
GestureDetector(
onTap: () => Navigator.pushNamed(context, Routes.mealPortionTypes),
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(Icons.pie_chart, size: 50, color: Theme.of(context).textTheme.subtitle2?.color),
),
Text(
'PORTION TYPES',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center
),
],
),
),
),
GestureDetector(
onTap: () => Navigator.pushNamed(context, Routes.accuracies),
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(Icons.architecture, size: 50, color: Theme.of(context).textTheme.subtitle2?.color),
),
Text(
'ACCURACIES',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center
),
],
),
),
),
),
GestureDetector(
onTap: () => Navigator.pushNamed(context, Routes.eventTypes),
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(Icons.event, size: 50, color: Theme.of(context).textTheme.subtitle2?.color),
),
Text(
'EVENT TYPES',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center
),
],
),
),
),
],
),
),
],
),
);
}
}

View File

@ -1,17 +1,17 @@
import 'package:diameter/models/log_event_type.dart'; import 'package:diameter/models/log_event_type.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/log/log_event/log_event_type_detail.dart'; import 'package:diameter/screens/category/event_type_detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class LogEventTypeListScreen extends StatefulWidget { class EventTypeListScreen extends StatefulWidget {
static const String routeName = '/log-event-types'; static const String routeName = '/log-event-types';
const LogEventTypeListScreen({Key? key}) : super(key: key); const EventTypeListScreen({Key? key}) : super(key: key);
@override @override
_LogEventTypeListScreenState createState() => _LogEventTypeListScreenState(); _EventTypeListScreenState createState() => _EventTypeListScreenState();
} }
class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> { class _EventTypeListScreenState extends State<EventTypeListScreen> {
List<LogEventType> _logEventTypes = []; List<LogEventType> _logEventTypes = [];
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
@ -52,7 +52,7 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
]), ]),
drawer: drawer:
const Navigation(currentLocation: LogEventTypeListScreen.routeName), const Navigation(currentLocation: EventTypeListScreen.routeName),
body: Column( body: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[

View File

@ -1,7 +1,7 @@
import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/category/meal_category_detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:diameter/models/meal_category.dart'; import 'package:diameter/models/meal_category.dart';

View File

@ -1,7 +1,7 @@
import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; import 'package:diameter/screens/category/meal_portion_type_detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:diameter/models/meal_portion_type.dart'; import 'package:diameter/models/meal_portion_type.dart';

View File

@ -8,9 +8,9 @@ import 'package:diameter/models/meal_portion_type.dart';
import 'package:diameter/models/meal_source.dart'; import 'package:diameter/models/meal_source.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/category/accuracy_detail.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/category/meal_category_detail.dart';
import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; import 'package:diameter/screens/category/meal_portion_type_detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MealSourceDetailScreen extends StatefulWidget { class MealSourceDetailScreen extends StatefulWidget {

View File

@ -2,7 +2,7 @@ import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/meal_source.dart'; import 'package:diameter/models/meal_source.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/meal/meal_source_detail.dart'; import 'package:diameter/screens/category/meal_source_detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MealSourceListScreen extends StatefulWidget { class MealSourceListScreen extends StatefulWidget {

View File

@ -1,331 +0,0 @@
import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/glucose_target.dart';
import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/screens/log/log_entry/log_entry.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class LogScreen extends StatefulWidget {
static const String routeName = '/log';
const LogScreen({Key? key}) : super(key: key);
@override
_LogScreenState createState() => _LogScreenState();
}
class _LogScreenState extends State<LogScreen> {
late List<LogEntry> _logEntries;
final ScrollController _scrollController = ScrollController();
final TextEditingController _dateController = TextEditingController(text: '');
late DateTime _date;
@override
void initState() {
super.initState();
_date = DateTime.now();
_dateController.text = DateTimeUtils.displayDate(_date);
reload();
}
@override
void dispose() {
_scrollController.dispose();
_dateController.dispose();
super.dispose();
}
void reload({String? message}) {
setState(() {
_logEntries = LogEntry.getAllForDate(_date);
});
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void onDelete(LogEntry logEntry) {
LogEntry.remove(logEntry.id);
reload(message: 'Log Entry deleted');
}
void handleDeleteAction(LogEntry logEntry) async {
if (Settings.get().showConfirmationDialogOnDelete) {
DialogUtils.showConfirmationDialog(
context: context,
onConfirm: () => onDelete(logEntry),
message: 'Are you sure you want to delete this Log Entry?',
);
} else {
onDelete(logEntry);
}
}
void onChangeDate(DateTime? date) {
if (date != null) {
setState(() {
_date = DateTime(date.year, date.month, date.day);
_dateController.text = DateTimeUtils.displayDate(date);
});
reload();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Log Entries'),
actions: <Widget>[
IconButton(
onPressed: () => onChangeDate(DateTime.now()),
icon: const Icon(Icons.today)),
IconButton(onPressed: reload, icon: const Icon(Icons.refresh)),
],
),
drawer: const Navigation(currentLocation: LogScreen.routeName),
body: Column(
children: [
Row(
children: [
IconButton(
onPressed: _date.isAtSameMomentAs(DateTime(2000, 1, 1))
? null
: () =>
onChangeDate(_date.subtract(const Duration(days: 1))),
icon: const Icon(Icons.arrow_back),
),
Expanded(
child: GestureDetector(
onTap: () async {
final newTime = await showDatePicker(
context: context,
initialDate: _date,
firstDate: DateTime(2000, 1, 1),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
onChangeDate(newTime);
},
child: Expanded(
child: Text(
DateTimeUtils.displayDate(_date).toUpperCase(),
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center,
),
),
),
),
IconButton(
onPressed:
_date.add(const Duration(days: 1)).isBefore(DateTime.now())
? () => onChangeDate(_date.add(const Duration(days: 1)))
: null,
icon: const Icon(Icons.arrow_forward),
),
],
),
Expanded(
child: _logEntries.isNotEmpty
? Scrollbar(
controller: _scrollController,
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(10.0),
shrinkWrap: true,
itemCount: _logEntries.length,
itemBuilder: (context, index) {
LogEntry logEntry = _logEntries[index];
double bolus =
LogBolus.getTotalBolusForEntry(logEntry.id);
double carbs =
LogMeal.getTotalCarbsForEntry(logEntry.id);
TextStyle glucoseStyle = TextStyle(
color: GlucoseTarget.getColorForGlucose(
mgPerDl: logEntry.mgPerDl ?? 0,
mmolPerL: logEntry.mmolPerL ?? 0));
return Card(
child: ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LogEntryScreen(id: logEntry.id),
),
).then((result) => reload(message: result?[0]));
},
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
DateTimeUtils.displayTime(logEntry.time),
),
),
Expanded(
child: Column(
children: logEntry.mgPerDl != null &&
(Settings.glucoseMeasurement ==
GlucoseMeasurement
.mgPerDl ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode.both ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode
.bothForList)
? [
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
logEntry.mgPerDl.toString(),
style: glucoseStyle),
logEntry.glucoseTrend != null
? Transform.rotate(
angle: logEntry
.glucoseTrend! *
math.pi /
180,
child: Icon(
Icons.arrow_upward,
color: glucoseStyle
.color,
size: 16.0,
),
)
: Container(),
],
),
const Text(
'mg/dl',
textScaleFactor: 0.75,
),
]
: [],
),
),
Expanded(
child: Column(
children: logEntry.mmolPerL != null &&
(Settings.glucoseMeasurement ==
GlucoseMeasurement
.mmolPerL ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode.both ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode
.bothForList)
? [
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
logEntry.mmolPerL
.toString(),
style: glucoseStyle),
logEntry.glucoseTrend != null
? Transform.rotate(
angle: logEntry
.glucoseTrend! *
math.pi /
180,
child: Icon(
Icons.arrow_upward,
color: glucoseStyle
.color,
size: 16.0,
),
)
: Container(),
],
),
const Text(
'mmol/l',
textScaleFactor: 0.75,
),
]
: [],
),
),
Expanded(
child: Column(
children: (bolus > 0)
? [
Text(bolus.toStringAsPrecision(3)),
const Text('U',
textScaleFactor: 0.75),
]
: [],
),
),
Expanded(
child: Column(
children: (carbs > 0)
? [
Text(carbs.toStringAsPrecision(3)),
Text(
'${Settings.nutritionMeasurementSuffix} carbs',
textScaleFactor: 0.75),
]
: [],
),
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () => handleDeleteAction(logEntry),
icon: const Icon(Icons.delete,
color: Colors.blue),
)
],
),
),
);
},
),
)
: const Center(
child: Text(
'You have not created any Log Entries for this date yet!'),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
final now = DateTime.now();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LogEntryScreen(
suggestedDate: _date.isAtSameMomentAs(DateTime(now.year, now.month, now.day)) ? now : _date),
),
).then((result) => reload(message: result?[0]));
},
child: const Icon(Icons.add),
),
);
}
}

View File

@ -12,11 +12,11 @@ import 'package:diameter/models/meal_portion_type.dart';
import 'package:diameter/models/meal_source.dart'; import 'package:diameter/models/meal_source.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/category/accuracy_detail.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/category/meal_category_detail.dart';
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
import 'package:diameter/screens/category/meal_source_detail.dart';
import 'package:diameter/screens/meal/meal_detail.dart'; import 'package:diameter/screens/meal/meal_detail.dart';
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
import 'package:diameter/screens/meal/meal_source_detail.dart';
import 'package:diameter/utils/utils.dart'; import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -33,6 +33,8 @@ class LogMealDetailScreen extends StatefulWidget {
_LogMealDetailScreenState createState() => _LogMealDetailScreenState(); _LogMealDetailScreenState createState() => _LogMealDetailScreenState();
} }
enum NutritionProperty { totalCarbs, carbsRatio, portionSize }
class _LogMealDetailScreenState extends State<LogMealDetailScreen> { class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
LogMeal? _logMeal; LogMeal? _logMeal;
bool _isNew = true; bool _isNew = true;
@ -40,6 +42,9 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
bool _isExpanded = false; bool _isExpanded = false;
bool _setManually = false; bool _setManually = false;
NutritionProperty? lastChanged;
NutritionProperty? secondToLastChanged;
final GlobalKey<FormState> _logMealForm = GlobalKey<FormState>(); final GlobalKey<FormState> _logMealForm = GlobalKey<FormState>();
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
@ -91,6 +96,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
_portionSizeController.text = (_logMeal!.portionSize ?? '').toString(); _portionSizeController.text = (_logMeal!.portionSize ?? '').toString();
_totalCarbsController.text = (_logMeal!.totalCarbs ?? '').toString(); _totalCarbsController.text = (_logMeal!.totalCarbs ?? '').toString();
_amountController.text = (_logMeal!.amount).toString(); _amountController.text = (_logMeal!.amount).toString();
_amount = _logMeal!.amount;
_notesController.text = _logMeal!.notes ?? ''; _notesController.text = _logMeal!.notes ?? '';
_meal = _logMeal!.meal.target; _meal = _logMeal!.meal.target;
_mealController.text = (_meal ?? '').toString(); _mealController.text = (_meal ?? '').toString();
@ -299,6 +305,8 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
if (basePortionSize != null) { if (basePortionSize != null) {
setState(() { setState(() {
portionSize = basePortionSize! * newAmount; portionSize = basePortionSize! * newAmount;
secondToLastChanged = NutritionProperty.carbsRatio;
lastChanged = NutritionProperty.portionSize;
_portionSizeController.text = _portionSizeController.text =
Utils.toStringMatchingTemplateFractionPrecision( Utils.toStringMatchingTemplateFractionPrecision(
portionSize!, Settings.nutritionSteps); portionSize!, Settings.nutritionSteps);
@ -331,25 +339,34 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
const calcPortionSize = 3; const calcPortionSize = 3;
if (carbsRatioUpdate != null) { if (carbsRatioUpdate != null) {
if (portionSize != null && portionSize != 0) { if (secondToLastChanged == NutritionProperty.portionSize &&
portionSize != null &&
portionSize != 0) {
toCalculate = calcTotalCarbs; toCalculate = calcTotalCarbs;
} else if (totalCarbs != null && totalCarbs != 0) { } else if (totalCarbs != null && totalCarbs != 0) {
toCalculate = calcPortionSize; toCalculate = calcPortionSize;
} }
} else if (portionSizeUpdate != null) { } else if (portionSizeUpdate != null) {
if (carbsRatio != null && carbsRatio != 0) { if (secondToLastChanged == NutritionProperty.carbsRatio &&
carbsRatio != null &&
carbsRatio != 0) {
toCalculate = calcTotalCarbs; toCalculate = calcTotalCarbs;
} else if (totalCarbs != null && totalCarbs != 0) { } else if (totalCarbs != null && totalCarbs != 0) {
toCalculate = calcCarbsRatio; toCalculate = calcCarbsRatio;
} }
} else if (totalCarbsUpdate != null) { } else if (totalCarbsUpdate != null) {
if (carbsRatio != null && carbsRatio != 0) { if (secondToLastChanged == NutritionProperty.carbsRatio &&
carbsRatio != null &&
carbsRatio != 0) {
toCalculate = calcPortionSize; toCalculate = calcPortionSize;
} else if (portionSize != null && portionSize != 0) { } else if (portionSize != null && portionSize != 0) {
toCalculate = calcCarbsRatio; toCalculate = calcCarbsRatio;
} }
} else { } else {
if (carbsRatio != null && carbsRatio != 0) { if ((secondToLastChanged == NutritionProperty.carbsRatio ||
lastChanged == NutritionProperty.carbsRatio) &&
carbsRatio != null &&
carbsRatio != 0) {
if (portionSize != null && portionSize != 0) { if (portionSize != null && portionSize != 0) {
toCalculate = calcTotalCarbs; toCalculate = calcTotalCarbs;
} else if (totalCarbs != null && totalCarbs != 0) { } else if (totalCarbs != null && totalCarbs != 0) {
@ -514,9 +531,17 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
autoRoundToMultipleOfStep: true, autoRoundToMultipleOfStep: true,
step: Settings.nutritionSteps, step: Settings.nutritionSteps,
onChanged: (value) async { onChanged: (value) async {
if (lastChanged != NutritionProperty.portionSize) {
setState(() {
secondToLastChanged = lastChanged;
lastChanged = NutritionProperty.portionSize;
});
}
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
calculateThirdMeasurementOfPortionCarbsRelation( if (value != null && value != 0) {
portionSizeUpdate: value); calculateThirdMeasurementOfPortionCarbsRelation(
portionSizeUpdate: value);
}
}, },
), ),
), ),
@ -528,9 +553,17 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
controller: _carbsRatioController, controller: _carbsRatioController,
showSteppers: false, showSteppers: false,
onChanged: (value) async { onChanged: (value) async {
if (lastChanged != NutritionProperty.carbsRatio) {
setState(() {
secondToLastChanged = lastChanged;
lastChanged = NutritionProperty.carbsRatio;
});
}
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
calculateThirdMeasurementOfPortionCarbsRelation( if (value != null && value != 0) {
carbsRatioUpdate: value); calculateThirdMeasurementOfPortionCarbsRelation(
carbsRatioUpdate: value);
}
}, },
), ),
), ),
@ -545,24 +578,30 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
step: Settings.nutritionSteps, step: Settings.nutritionSteps,
onChanged: (value) async { onChanged: (value) async {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
calculateThirdMeasurementOfPortionCarbsRelation( if (lastChanged != NutritionProperty.totalCarbs) {
totalCarbsUpdate: value); setState(() {
secondToLastChanged = lastChanged;
lastChanged = NutritionProperty.totalCarbs;
});
}
if (value != null && value != 0) {
calculateThirdMeasurementOfPortionCarbsRelation(
totalCarbsUpdate: value);
}
}, },
), ),
), ),
], ],
), ),
Expanded( BooleanFormField(
child: BooleanFormField( value: _setManually,
value: _setManually, label: 'set carbs ratio manually',
label: 'set carbs ratio manually', onChanged: (value) {
onChanged: (value) { setState(() {
setState(() { _setManually = value;
_setManually = value; calculateThirdMeasurementOfPortionCarbsRelation();
calculateThirdMeasurementOfPortionCarbsRelation(); });
}); },
},
),
), ),
TextFormField( TextFormField(
controller: _notesController, controller: _notesController,
@ -600,15 +639,12 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
const EdgeInsets.symmetric(vertical: 5.0), const EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
Expanded( AutoCompleteDropdownButton<MealSource>(
child: controller: _mealSourceController,
AutoCompleteDropdownButton<MealSource>( selectedItem: _mealSource,
controller: _mealSourceController, label: 'Meal Source',
selectedItem: _mealSource, items: _mealSources,
label: 'Meal Source', onChanged: updateMealSource,
items: _mealSources,
onChanged: updateMealSource,
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
@ -638,15 +674,12 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
const EdgeInsets.symmetric(vertical: 5.0), const EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
Expanded( AutoCompleteDropdownButton<MealCategory>(
child: AutoCompleteDropdownButton< controller: _mealCategoryController,
MealCategory>( selectedItem: _mealCategory,
controller: _mealCategoryController, label: 'Meal Category',
selectedItem: _mealCategory, items: _mealCategories,
label: 'Meal Category', onChanged: updateMealCategory,
items: _mealCategories,
onChanged: updateMealCategory,
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
@ -676,15 +709,12 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
const EdgeInsets.symmetric(vertical: 5.0), const EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
Expanded( AutoCompleteDropdownButton<MealPortionType>(
child: AutoCompleteDropdownButton< controller: _mealPortionTypeController,
MealPortionType>( selectedItem: _mealPortionType,
controller: _mealPortionTypeController, label: 'Meal Portion Type',
selectedItem: _mealPortionType, items: _mealPortionTypes,
label: 'Meal Portion Type', onChanged: updateMealPortionType,
items: _mealPortionTypes,
onChanged: updateMealPortionType,
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
@ -714,15 +744,12 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
const EdgeInsets.symmetric(vertical: 5.0), const EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
Expanded( AutoCompleteDropdownButton<Accuracy>(
child: AutoCompleteDropdownButton<Accuracy>( controller: _portionSizeAccuracyController,
controller: selectedItem: _portionSizeAccuracy,
_portionSizeAccuracyController, label: 'Portion Size Accuracy',
selectedItem: _portionSizeAccuracy, items: _portionSizeAccuracies,
label: 'Portion Size Accuracy', onChanged: updatePortionSizeAccuracy,
items: _portionSizeAccuracies,
onChanged: updatePortionSizeAccuracy,
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
@ -753,14 +780,12 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
const EdgeInsets.symmetric(vertical: 5.0), const EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
Expanded( AutoCompleteDropdownButton<Accuracy>(
child: AutoCompleteDropdownButton<Accuracy>( controller: _carbsRatioAccuracyController,
controller: _carbsRatioAccuracyController, selectedItem: _carbsRatioAccuracy,
selectedItem: _carbsRatioAccuracy, label: 'Carbs Ratio Accuracy',
label: 'Carbs Ratio Accuracy', items: _carbsRatioAccuracies,
items: _carbsRatioAccuracies, onChanged: updateCarbsRatioAccuracy,
onChanged: updateCarbsRatioAccuracy,
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {

View File

@ -1,366 +0,0 @@
import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/screens/log/log_event/log_event_detail.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart';
import 'package:diameter/navigation.dart';
class LogEventListScreen extends StatefulWidget {
static const String routeName = '/log-events';
const LogEventListScreen({Key? key}) : super(key: key);
@override
_LogEventListScreenState createState() => _LogEventListScreenState();
}
class _LogEventListScreenState extends State<LogEventListScreen> {
List<LogEvent> _activeEvents = [];
late List<LogEvent> _logEvents;
final ScrollController _scrollController = ScrollController();
final TextEditingController _dateController = TextEditingController(text: '');
late DateTime _date;
bool _showActive = true;
@override
void initState() {
super.initState();
_date = DateTime.now();
_dateController.text = DateTimeUtils.displayDate(_date);
reload();
}
@override
void dispose() {
_scrollController.dispose();
_dateController.dispose();
super.dispose();
}
void reload({String? message}) {
setState(() {
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
_logEvents = LogEvent.getAllForDate(_date);
});
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void handleAddNewEvent() async {
final now = DateTime.now();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return LogEventDetailScreen(
suggestedDate: _date.isAtSameMomentAs(DateTime(now.year, now.month, now.day)) ? now : _date,
);
},
),
).then((result) => reload(message: result?[0]));
}
void handleEditAction(LogEvent event) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LogEventDetailScreen(
id: event.id,
),
),
).then((result) => reload(message: result?[0]));
}
void onDelete(LogEvent logEvent) {
LogEvent.remove(logEvent.id);
reload(message: 'Event deleted');
}
void handleDeleteAction(LogEvent logEvent) async {
if (Settings.get().showConfirmationDialogOnDelete) {
DialogUtils.showConfirmationDialog(
context: context,
onConfirm: () => onDelete(logEvent),
message: 'Are you sure you want to delete this Event?',
);
} else {
onDelete(logEvent);
}
}
void onStop(LogEvent event) async {
event.endTime = DateTime.now();
LogEvent.put(event);
reload(message: 'Event ended');
}
void handleStopAction(LogEvent event) async {
if (Settings.get().showConfirmationDialogOnStopEvent) {
DialogUtils.showConfirmationDialog(
context: context,
onConfirm: () => onStop(event),
message: 'Are you sure you want to end this Event?',
confirmationLabel: 'END EVENT',
);
} else {
onStop(event);
}
}
void onChangeDate(DateTime? date) {
if (date != null) {
setState(() {
_date = DateTime(date.year, date.month, date.day);
_dateController.text = DateTimeUtils.displayDate(date);
});
reload();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Log Events'),
actions: <Widget>[
IconButton(
onPressed: () => onChangeDate(DateTime.now()),
icon: const Icon(Icons.today)),
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
],
),
drawer: const Navigation(currentLocation: LogEventListScreen.routeName),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () => setState(() {
_showActive = !_showActive;
}),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Text(
'ACTIVE EVENTS',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center,
),
),
Icon(_showActive
? Icons.expand_less
: Icons.expand_more),
],
),
),
),
!_showActive ? Container() :
_activeEvents.isNotEmpty
? ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(10.0),
itemCount: _activeEvents.length,
itemBuilder: (context, index) {
LogEvent event = _activeEvents[index];
return Card(
child: ListTile(
onTap: () {
handleEditAction(event);
},
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
DateTimeUtils.displayDateTime(event.time),
),
const SizedBox(width: 24),
Expanded(
child: Text(
(event.title ??
event.eventType.target?.value ??
'')
.toUpperCase(),
style: Theme.of(context).textTheme.subtitle2,
),
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
event.hasEndTime && event.endTime == null
? IconButton(
icon: const Icon(
Icons.stop,
color: Colors.blue,
),
onPressed: () => handleStopAction(event),
)
: const SizedBox(width: 50),
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.blue,
),
onPressed: () => handleEditAction(event),
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.blue,
),
onPressed: () => handleDeleteAction(event),
),
],
),
),
);
},
)
: const Center(
child: Text('There are no Active Events!'),
),
const Padding(
padding: EdgeInsets.all(10.0),
child: Divider(),
),
Row(
children: [
IconButton(
onPressed: _date.isAtSameMomentAs(DateTime(2000, 1, 1))
? null
: () =>
onChangeDate(_date.subtract(const Duration(days: 1))),
icon: const Icon(Icons.arrow_back),
),
Expanded(
child: GestureDetector(
onTap: () async {
final newTime = await showDatePicker(
context: context,
initialDate: _date,
firstDate: DateTime(2000, 1, 1),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
onChangeDate(newTime);
},
child: Expanded(
child: Text(
DateTimeUtils.displayDate(_date).toUpperCase(),
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center,
),
),
),
),
IconButton(
onPressed:
_date.add(const Duration(days: 1)).isBefore(DateTime.now())
? () => onChangeDate(_date.add(const Duration(days: 1)))
: null,
icon: const Icon(Icons.arrow_forward),
),
],
),
Expanded(
child: _logEvents.isNotEmpty
? Scrollbar(
controller: _scrollController,
child: ListView.builder(
controller: _scrollController,
shrinkWrap: true,
padding: const EdgeInsets.all(10.0),
itemCount: _logEvents.length,
itemBuilder: (context, index) {
LogEvent event = _logEvents[index];
return Card(
child: ListTile(
onTap: () {
handleEditAction(event);
},
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
DateTimeUtils.displayTime(event.isEndEvent
? event.endTime
: event.time),
),
const SizedBox(width: 24),
Expanded(
child: Text(
(event.title ??
event.eventType.target?.value ??
'')
.toUpperCase(),
style:
Theme.of(context).textTheme.subtitle2,
),
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
event.hasEndTime && event.endTime == null
? IconButton(
icon: const Icon(
Icons.stop,
color: Colors.blue,
),
onPressed: () =>
handleStopAction(event),
)
: const SizedBox(width: 50),
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.blue,
),
onPressed: () => handleEditAction(event),
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.blue,
),
onPressed: () => handleDeleteAction(event),
),
],
),
),
);
},
))
: const Center(
child: Text('There are no Events for that date!'),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: handleAddNewEvent,
child: const Icon(Icons.add),
),
);
}
}

View File

@ -188,7 +188,7 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
} }
Future<void> checkIfActiveEventOfTypeExistsBeforeSaving() async { Future<void> checkIfActiveEventOfTypeExistsBeforeSaving() async {
if (_eventType != null && if (_isNew && _eventType != null &&
LogEvent.eventTypeExistsForTime(_eventType!.id, _time)) { LogEvent.eventTypeExistsForTime(_eventType!.id, _time)) {
await showDialog( await showDialog(
context: context, context: context,

View File

@ -0,0 +1,386 @@
import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/screens/log/log_event_detail.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart';
import 'package:diameter/navigation.dart';
class LogEventListScreen extends StatefulWidget {
static const String routeName = '/log-events';
const LogEventListScreen({Key? key}) : super(key: key);
@override
_LogEventListScreenState createState() => _LogEventListScreenState();
}
class _LogEventListScreenState extends State<LogEventListScreen> {
List<LogEvent> _activeEvents = [];
late List<LogEvent> _logEvents;
final ScrollController _scrollController = ScrollController();
final TextEditingController _dateController = TextEditingController(text: '');
late DateTime _date;
bool _showActive = true;
String? swipeDirection;
@override
void initState() {
super.initState();
_date = DateTime.now();
_dateController.text = DateTimeUtils.displayDate(_date);
reload();
}
@override
void dispose() {
_scrollController.dispose();
_dateController.dispose();
super.dispose();
}
void reload({String? message}) {
setState(() {
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
_logEvents = LogEvent.getAllForDate(_date);
});
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void handleAddNewEvent() async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return _date.isAtSameMomentAs(DateTimeUtils.today())
? const LogEventDetailScreen()
: LogEventDetailScreen(
suggestedDate: _date,
);
},
),
).then((result) => reload(message: result?[0]));
}
void handleEditAction(LogEvent event) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LogEventDetailScreen(
id: event.id,
),
),
).then((result) => reload(message: result?[0]));
}
void onDelete(LogEvent logEvent) {
LogEvent.remove(logEvent.id);
reload(message: 'Event deleted');
}
void handleDeleteAction(LogEvent logEvent) async {
if (Settings.get().showConfirmationDialogOnDelete) {
DialogUtils.showConfirmationDialog(
context: context,
onConfirm: () => onDelete(logEvent),
message: 'Are you sure you want to delete this Event?',
);
} else {
onDelete(logEvent);
}
}
void onStop(LogEvent event) async {
event.endTime = DateTime.now();
LogEvent.put(event);
reload(message: 'Event ended');
}
void handleStopAction(LogEvent event) async {
if (Settings.get().showConfirmationDialogOnStopEvent) {
DialogUtils.showConfirmationDialog(
context: context,
onConfirm: () => onStop(event),
message: 'Are you sure you want to end this Event?',
confirmationLabel: 'END EVENT',
);
} else {
onStop(event);
}
}
void onChangeDate(DateTime? date) {
if (date != null) {
setState(() {
_date = DateTime(date.year, date.month, date.day);
_dateController.text = DateTimeUtils.displayDate(date);
});
reload();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Log Events'),
actions: <Widget>[
IconButton(
onPressed: () => onChangeDate(DateTime.now()),
icon: const Icon(Icons.today)),
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
],
),
drawer: const Navigation(currentLocation: LogEventListScreen.routeName),
body: GestureDetector(
onPanUpdate: (details) {
swipeDirection = details.delta.dx < 0 ? 'left' : 'right';
},
onPanEnd: (details) {
if (swipeDirection == null) {
return;
}
if (swipeDirection == 'right' &&
!_date.isAtSameMomentAs(DateTime(2000, 1, 1))) {
onChangeDate(_date.subtract(const Duration(days: 1)));
}
if (swipeDirection == 'left' &&
_date.add(const Duration(days: 1)).isBefore(DateTime.now())) {
onChangeDate(_date.add(const Duration(days: 1)));
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () => setState(() {
_showActive = !_showActive;
}),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Text(
'ACTIVE EVENTS',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center,
),
),
Icon(_showActive ? Icons.expand_less : Icons.expand_more),
],
),
),
),
!_showActive
? Container()
: _activeEvents.isNotEmpty
? ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(10.0),
itemCount: _activeEvents.length,
itemBuilder: (context, index) {
LogEvent event = _activeEvents[index];
return Card(
child: ListTile(
onTap: () {
handleEditAction(event);
},
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
DateTimeUtils.displayDateTime(event.time),
),
const SizedBox(width: 24),
Expanded(
child: Text(
(event.title ??
event.eventType.target?.value ??
'')
.toUpperCase(),
style:
Theme.of(context).textTheme.subtitle2,
),
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
event.hasEndTime && event.endTime == null
? IconButton(
icon: const Icon(
Icons.stop,
color: Colors.blue,
),
onPressed: () =>
handleStopAction(event),
)
: const SizedBox(width: 50),
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.blue,
),
onPressed: () => handleEditAction(event),
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.blue,
),
onPressed: () => handleDeleteAction(event),
),
],
),
),
);
},
)
: const Center(
child: Text('There are no Active Events!'),
),
const Padding(
padding: EdgeInsets.all(10.0),
child: Divider(),
),
Row(
children: [
IconButton(
onPressed: _date.isAtSameMomentAs(DateTime(2000, 1, 1))
? null
: () =>
onChangeDate(_date.subtract(const Duration(days: 1))),
icon: const Icon(Icons.arrow_back),
),
Expanded(
child: GestureDetector(
onTap: () async {
final newTime = await showDatePicker(
context: context,
initialDate: _date,
firstDate: DateTime(2000, 1, 1),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
onChangeDate(newTime);
},
child: Text(
DateTimeUtils.displayDate(_date).toUpperCase(),
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center,
),
),
),
IconButton(
onPressed: _date
.add(const Duration(days: 1))
.isBefore(DateTime.now())
? () => onChangeDate(_date.add(const Duration(days: 1)))
: null,
icon: const Icon(Icons.arrow_forward),
),
],
),
Expanded(
child: _logEvents.isNotEmpty
? Scrollbar(
controller: _scrollController,
child: ListView.builder(
controller: _scrollController,
shrinkWrap: true,
padding: const EdgeInsets.all(10.0),
itemCount: _logEvents.length,
itemBuilder: (context, index) {
LogEvent event = _logEvents[index];
return Card(
child: ListTile(
onTap: () {
handleEditAction(event);
},
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
DateTimeUtils.displayTime(event.isEndEvent
? event.endTime
: event.time),
),
const SizedBox(width: 24),
Expanded(
child: Text(
(event.title ??
event.eventType.target?.value ??
'')
.toUpperCase(),
style:
Theme.of(context).textTheme.subtitle2,
),
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
event.hasEndTime && event.endTime == null
? IconButton(
icon: const Icon(
Icons.stop,
color: Colors.blue,
),
onPressed: () =>
handleStopAction(event),
)
: const SizedBox(width: 50),
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.blue,
),
onPressed: () => handleEditAction(event),
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.blue,
),
onPressed: () => handleDeleteAction(event),
),
],
),
),
);
},
))
: const Center(
child: Text('There are no Events for that date!'),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: handleAddNewEvent,
child: const Icon(Icons.add),
),
);
}
}

View File

@ -0,0 +1,364 @@
import 'package:diameter/screens/reports/export.dart';
import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/glucose_target.dart';
import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/screens/log/log_entry/log_entry.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class LogScreen extends StatefulWidget {
static const String routeName = '/log';
const LogScreen({Key? key}) : super(key: key);
@override
_LogScreenState createState() => _LogScreenState();
}
class _LogScreenState extends State<LogScreen> {
late List<LogEntry> _logEntries;
final ScrollController _scrollController = ScrollController();
final TextEditingController _dateController = TextEditingController(text: '');
String? swipeDirection;
late DateTime _date;
@override
void initState() {
super.initState();
_date = DateTime.now();
_dateController.text = DateTimeUtils.displayDate(_date);
reload();
}
@override
void dispose() {
_scrollController.dispose();
_dateController.dispose();
super.dispose();
}
void reload({String? message}) {
setState(() {
_logEntries = LogEntry.getAllForDate(_date);
});
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void onDelete(LogEntry logEntry) {
LogEntry.remove(logEntry.id);
reload(message: 'Log Entry deleted');
}
void handleDeleteAction(LogEntry logEntry) async {
if (Settings.get().showConfirmationDialogOnDelete) {
DialogUtils.showConfirmationDialog(
context: context,
onConfirm: () => onDelete(logEntry),
message: 'Are you sure you want to delete this Log Entry?',
);
} else {
onDelete(logEntry);
}
}
void onChangeDate(DateTime? date) {
if (date != null) {
setState(() {
_date = DateTime(date.year, date.month, date.day);
_dateController.text = DateTimeUtils.displayDate(date);
});
reload();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Log Entries'),
actions: <Widget>[
IconButton(
onPressed: () => onChangeDate(DateTime.now()),
icon: const Icon(Icons.today)),
IconButton(
onPressed: () => showDialog(
context: context,
builder: (context) => ExportDialog(date: _date)),
icon: const Icon(Icons.file_download)),
IconButton(onPressed: reload, icon: const Icon(Icons.refresh)),
],
),
drawer: const Navigation(currentLocation: LogScreen.routeName),
body: GestureDetector(
onPanUpdate: (details) {
swipeDirection = details.delta.dx < 0 ? 'left' : 'right';
},
onPanEnd: (details) {
if (swipeDirection == null) {
return;
}
if (swipeDirection == 'right' &&
!_date.isAtSameMomentAs(DateTime(2000, 1, 1))) {
onChangeDate(_date.subtract(const Duration(days: 1)));
}
if (swipeDirection == 'left' &&
_date.add(const Duration(days: 1)).isBefore(DateTime.now())) {
onChangeDate(_date.add(const Duration(days: 1)));
}
},
child: Column(
children: [
Row(
children: [
IconButton(
onPressed: _date.isAtSameMomentAs(DateTime(2000, 1, 1))
? null
: () =>
onChangeDate(_date.subtract(const Duration(days: 1))),
icon: const Icon(Icons.arrow_back),
),
Expanded(
child: GestureDetector(
onTap: () async {
final newTime = await showDatePicker(
context: context,
initialDate: _date,
firstDate: DateTime(2000, 1, 1),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
onChangeDate(newTime);
},
child: Text(
DateTimeUtils.displayDate(_date).toUpperCase(),
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center,
),
),
),
IconButton(
onPressed: _date
.add(const Duration(days: 1))
.isBefore(DateTime.now())
? () => onChangeDate(_date.add(const Duration(days: 1)))
: null,
icon: const Icon(Icons.arrow_forward),
),
],
),
Expanded(
child: _logEntries.isNotEmpty
? Scrollbar(
controller: _scrollController,
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(10.0),
shrinkWrap: true,
itemCount: _logEntries.length,
itemBuilder: (context, index) {
LogEntry logEntry = _logEntries[index];
double bolus =
LogBolus.getTotalBolusForEntry(logEntry.id);
double carbs =
LogMeal.getTotalCarbsForEntry(logEntry.id);
TextStyle glucoseStyle = TextStyle(
color: GlucoseTarget.getColorForGlucose(
mgPerDl: logEntry.mgPerDl ?? 0,
mmolPerL: logEntry.mmolPerL ?? 0));
return Card(
child: ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LogEntryScreen(id: logEntry.id),
),
).then((result) => reload(message: result?[0]));
},
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
DateTimeUtils.displayTime(logEntry.time),
),
),
Expanded(
child: Column(
children: logEntry.mgPerDl != null &&
(Settings.glucoseMeasurement ==
GlucoseMeasurement
.mgPerDl ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode.both ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode
.bothForList)
? [
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
logEntry.mgPerDl
.toString(),
style: glucoseStyle),
logEntry.glucoseTrend != null
? Transform.rotate(
angle: logEntry
.glucoseTrend! *
math.pi /
180,
child: Icon(
Icons.arrow_upward,
color: glucoseStyle
.color,
size: 16.0,
),
)
: Container(),
],
),
const Text(
'mg/dl',
textScaleFactor: 0.75,
),
]
: [],
),
),
Expanded(
child: Column(
children: logEntry.mmolPerL != null &&
(Settings.glucoseMeasurement ==
GlucoseMeasurement
.mmolPerL ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode.both ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode
.bothForList)
? [
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
logEntry.mmolPerL
.toString(),
style: glucoseStyle),
logEntry.glucoseTrend != null
? Transform.rotate(
angle: logEntry
.glucoseTrend! *
math.pi /
180,
child: Icon(
Icons.arrow_upward,
color: glucoseStyle
.color,
size: 16.0,
),
)
: Container(),
],
),
const Text(
'mmol/l',
textScaleFactor: 0.75,
),
]
: [],
),
),
Expanded(
child: Column(
children: (bolus > 0)
? [
Text(
bolus.toStringAsPrecision(3)),
const Text('U',
textScaleFactor: 0.75),
]
: [],
),
),
Expanded(
child: Column(
children: (carbs > 0)
? [
Text(
carbs.toStringAsPrecision(3)),
Text(
'${Settings.nutritionMeasurementSuffix} carbs',
textScaleFactor: 0.75),
]
: [],
),
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () =>
handleDeleteAction(logEntry),
icon: const Icon(Icons.delete,
color: Colors.blue),
)
],
),
),
);
},
),
)
: const Center(
child: Text(
'You have not created any Log Entries for this date yet!'),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return _date.isAtSameMomentAs(DateTimeUtils.today())
? const LogEntryScreen()
: LogEntryScreen(
suggestedDate: _date,
);
},
),
).then((result) => reload(message: result?[0]));
},
child: const Icon(Icons.add),
),
);
}
}

View File

@ -11,10 +11,10 @@ import 'package:diameter/models/meal_portion_type.dart';
import 'package:diameter/models/meal_source.dart'; import 'package:diameter/models/meal_source.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/category/accuracy_detail.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/category/meal_category_detail.dart';
import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; import 'package:diameter/screens/category/meal_portion_type_detail.dart';
import 'package:diameter/screens/meal/meal_source_detail.dart'; import 'package:diameter/screens/category/meal_source_detail.dart';
import 'package:diameter/utils/utils.dart'; import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -272,49 +272,49 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
double? carbsPerPortionUpdate}) { double? carbsPerPortionUpdate}) {
if (!_setManually) { if (!_setManually) {
double? carbsRatio = double? carbsRatio =
carbsRatioUpdate ?? double.tryParse(_carbsRatioController.text); carbsRatioUpdate ?? double.tryParse(_carbsRatioController.text);
double? portionSize = double? portionSize =
portionSizeUpdate ?? double.tryParse(_portionSizeController.text); portionSizeUpdate ?? double.tryParse(_portionSizeController.text);
double? carbsPerPortion = carbsPerPortionUpdate ?? double? carbsPerPortion = carbsPerPortionUpdate ??
double.tryParse(_carbsPerPortionController.text); double.tryParse(_carbsPerPortionController.text);
int toCalculate = 0; int toCalculate = 0;
const calcCarbsRatio = 1; const calcCarbsRatio = 1;
const calcCarbsPerPortion = 2; const calcCarbsPerPortion = 2;
const calcPortionSize = 3; const calcPortionSize = 3;
if (carbsRatioUpdate != null) { if (carbsRatioUpdate != null) {
if (portionSize != null && portionSize != 0) {
toCalculate = calcCarbsPerPortion;
} else if (carbsPerPortion != null && carbsPerPortion != 0) {
toCalculate = calcPortionSize;
}
} else if (portionSizeUpdate != null) {
if (carbsRatio != null && carbsRatio != 0) {
toCalculate = calcCarbsPerPortion;
} else if (carbsPerPortion != null && carbsPerPortion != 0) {
toCalculate = calcCarbsRatio;
}
} else if (carbsPerPortionUpdate != null) {
if (carbsRatio != null && carbsRatio != 0) {
toCalculate = calcPortionSize;
} else if (portionSize != null && portionSize != 0) {
toCalculate = calcCarbsRatio;
}
} else {
if (carbsRatio != null && carbsRatio != 0) {
if (portionSize != null && portionSize != 0) { if (portionSize != null && portionSize != 0) {
toCalculate = calcCarbsPerPortion; toCalculate = calcCarbsPerPortion;
} else if (carbsPerPortion != null && carbsPerPortion != 0) { } else if (carbsPerPortion != null && carbsPerPortion != 0) {
toCalculate = calcPortionSize; toCalculate = calcPortionSize;
} }
} else if (portionSize != null && } else if (portionSizeUpdate != null) {
portionSize != 0 && if (carbsRatio != null && carbsRatio != 0) {
carbsPerPortion != null && toCalculate = calcCarbsPerPortion;
carbsPerPortion != 0) { } else if (carbsPerPortion != null && carbsPerPortion != 0) {
toCalculate = calcCarbsRatio; toCalculate = calcCarbsRatio;
}
} else if (carbsPerPortionUpdate != null) {
if (carbsRatio != null && carbsRatio != 0) {
toCalculate = calcPortionSize;
} else if (portionSize != null && portionSize != 0) {
toCalculate = calcCarbsRatio;
}
} else {
if (carbsRatio != null && carbsRatio != 0) {
if (portionSize != null && portionSize != 0) {
toCalculate = calcCarbsPerPortion;
} else if (carbsPerPortion != null && carbsPerPortion != 0) {
toCalculate = calcPortionSize;
}
} else if (portionSize != null &&
portionSize != 0 &&
carbsPerPortion != null &&
carbsPerPortion != 0) {
toCalculate = calcCarbsRatio;
}
} }
}
setState(() { setState(() {
if (toCalculate == calcCarbsRatio) { if (toCalculate == calcCarbsRatio) {
@ -435,7 +435,10 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
showSteppers: false, showSteppers: false,
onChanged: (value) async { onChanged: (value) async {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
calculateThirdMeasurementOfPortionCarbsRelation(carbsRatioUpdate: value); if (value != null && value != 0) {
calculateThirdMeasurementOfPortionCarbsRelation(
carbsRatioUpdate: value);
}
}, },
), ),
), ),
@ -448,7 +451,10 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
showSteppers: false, showSteppers: false,
onChanged: (value) async { onChanged: (value) async {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
calculateThirdMeasurementOfPortionCarbsRelation(portionSizeUpdate: value); if (value != null && value != 0) {
calculateThirdMeasurementOfPortionCarbsRelation(
portionSizeUpdate: value);
}
}, },
), ),
), ),
@ -461,7 +467,10 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
showSteppers: false, showSteppers: false,
onChanged: (value) async { onChanged: (value) async {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
calculateThirdMeasurementOfPortionCarbsRelation(carbsPerPortionUpdate: value); if (value != null && value != 0) {
calculateThirdMeasurementOfPortionCarbsRelation(
carbsPerPortionUpdate: value);
}
}, },
), ),
), ),
@ -562,15 +571,12 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
const EdgeInsets.symmetric(vertical: 5.0), const EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
Expanded( AutoCompleteDropdownButton<MealCategory>(
child: AutoCompleteDropdownButton< controller: _mealCategoryController,
MealCategory>( selectedItem: _mealCategory,
controller: _mealCategoryController, label: 'Meal Category',
selectedItem: _mealCategory, items: _mealCategories,
label: 'Meal Category', onChanged: updateMealCategory,
items: _mealCategories,
onChanged: updateMealCategory,
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
@ -600,15 +606,12 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
const EdgeInsets.symmetric(vertical: 5.0), const EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
Expanded( AutoCompleteDropdownButton<Accuracy>(
child: AutoCompleteDropdownButton<Accuracy>( controller: _portionSizeAccuracyController,
controller: selectedItem: _portionSizeAccuracy,
_portionSizeAccuracyController, label: 'Portion Size Accuracy',
selectedItem: _portionSizeAccuracy, items: _portionSizeAccuracies,
label: 'Portion Size Accuracy', onChanged: updatePortionSizeAccuracy,
items: _portionSizeAccuracies,
onChanged: updatePortionSizeAccuracy,
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
@ -639,14 +642,12 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
const EdgeInsets.symmetric(vertical: 5.0), const EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
Expanded( AutoCompleteDropdownButton<Accuracy>(
child: AutoCompleteDropdownButton<Accuracy>( controller: _carbsRatioAccuracyController,
controller: _carbsRatioAccuracyController, selectedItem: _carbsRatioAccuracy,
selectedItem: _carbsRatioAccuracy, label: 'Carbs Ratio Accuracy',
label: 'Carbs Ratio Accuracy', items: _carbsRatioAccuracies,
items: _carbsRatioAccuracies, onChanged: updateCarbsRatioAccuracy,
onChanged: updateCarbsRatioAccuracy,
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {

View File

@ -1,322 +0,0 @@
import 'package:diameter/components/detail.dart';
import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
import 'package:diameter/components/forms/form_wrapper.dart';
import 'package:diameter/models/ingredient.dart';
import 'package:diameter/models/meal.dart';
import 'package:diameter/models/recipe.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/screens/meal/meal_detail.dart';
import 'package:flutter/material.dart';
class RecipeDetailScreen extends StatefulWidget {
static const String routeName = '/recipe';
final int id;
const RecipeDetailScreen({Key? key, this.id = 0}) : super(key: key);
@override
_RecipeDetailScreenState createState() => _RecipeDetailScreenState();
}
class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
Recipe? _recipe;
List<Ingredient> _ingredients = [];
bool _isNew = true;
bool _isSaving = false;
final GlobalKey<FormState> _recipeForm = GlobalKey<FormState>();
final ScrollController _scrollController = ScrollController();
final _nameController = TextEditingController(text: '');
final _notesController = TextEditingController(text: '');
double _servings = 1;
final List<TextEditingController> _ingredientControllers = [];
List<Meal> _meals = [];
@override
void initState() {
super.initState();
reload();
_meals = Meal.getAll();
if (_recipe != null) {
_nameController.text = _recipe!.name;
_servings = _recipe!.servings ?? 1;
_notesController.text = _recipe!.notes ?? '';
if (_ingredients.isNotEmpty) {
for (Ingredient ingredient in _ingredients) {
_ingredientControllers.add(
TextEditingController(text: ingredient.ingredient.target?.value));
}
}
}
}
void reload({String? message}) {
if (widget.id != 0) {
setState(() {
_recipe = Recipe.get(widget.id);
_ingredients = Ingredient.getAllForRecipe(widget.id);
});
}
_isNew = _recipe == null;
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void onAddIngredient() {
final newIngredient = Ingredient(amount: 0);
setState(() {
newIngredient.recipe.target = _recipe;
_ingredients.add(newIngredient);
_ingredientControllers.add(TextEditingController(text: ''));
});
}
void handleSaveAction({bool close = false}) async {
setState(() {
_isSaving = true;
});
if (_recipeForm.currentState!.validate()) {
Recipe recipe = Recipe(
id: widget.id,
name: _nameController.text,
servings: _servings,
notes: _notesController.text,
);
Recipe.put(recipe);
List<Ingredient> ingredients = _ingredients.map((ingredient) {
if (ingredient.id != 0 &&
(!ingredient.ingredient.hasValue || ingredient.amount == 0)) {
ingredient.deleted = true;
}
return ingredient;
}).toList();
ingredients.retainWhere((ingredient) {
return ingredient.id != 0 ||
(ingredient.amount > 0 && ingredient.ingredient.hasValue);
});
Ingredient.putMany(ingredients);
if (close) {
Navigator.pop(context, ['${_isNew ? 'New' : ''} Recipe Saved', recipe]);
} else {
if (_isNew) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RecipeDetailScreen(id: recipe.id),
),
).then((result) => Navigator.pop(context, result));
} else {
reload(message: 'Recipe saved');
}
}
setState(() {
_isSaving = false;
});
}
}
void handleCancelAction() {
if (Settings.get().showConfirmationDialogOnCancel &&
((_isNew &&
(_nameController.text != '' ||
_servings != 1 ||
_notesController.text != '')) ||
(!_isNew &&
(_nameController.text != _recipe!.name ||
_servings != _recipe!.servings ||
_notesController.text != (_recipe!.notes ?? ''))))) {
DialogUtils.showCancelConfirmationDialog(
context: context,
isNew: _isNew,
onSave: handleSaveAction,
);
} else {
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_isNew ? 'New Recipe' : _recipe!.name),
),
drawer: const Navigation(currentLocation: RecipeDetailScreen.routeName),
body: Scrollbar(
controller: _scrollController,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: <Widget>[
FormWrapper(
formState: _recipeForm,
fields: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Name',
),
validator: (value) {
if (value!.trim().isEmpty) {
return 'Empty title';
}
return null;
},
),
// NumberFormField(
// value: _servings,
// label: 'Servings',
// suffix: ' portions',
// min: 0,
// onChanged: (value) {
// if (value != null && value >= 0) {
// setState(() {
// _servings = value.toDouble();
// });
// }
// },
// ),
TextFormField(
keyboardType: TextInputType.multiline,
controller: _notesController,
decoration: const InputDecoration(
labelText: 'Notes',
),
minLines: 2,
maxLines: 5,
),
const Divider(),
GestureDetector(
onTap: onAddIngredient,
child: Row(
children: [
Text(
'INGREDIENTS',
style: Theme.of(context).textTheme.subtitle2,
),
const Spacer(),
IconButton(
onPressed: onAddIngredient,
icon: const Icon(Icons.add),
),
],
),
),
],
),
!_isNew && _ingredients.isNotEmpty
? ListBody(
children: _ingredients.map((item) {
final ingredient = item.ingredient.target;
final index = _ingredients.indexOf(item);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0, vertical: 5.0),
child: Column(
children: <Widget>[
Row(
children: [
Expanded(
child: AutoCompleteDropdownButton<Meal>(
controller: _ingredientControllers[index],
selectedItem: ingredient,
label: 'Meal Category',
items: _meals,
onChanged: (value) {
setState(() {
_ingredients[index]
.ingredient
.target = value;
_ingredientControllers[index].text =
value?.value ?? '';
});
},
),
),
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ingredient == null
? const MealDetailScreen()
: MealDetailScreen(
id: ingredient.id),
),
).then((result) {
_ingredients[index].ingredient.target =
result?[1];
_ingredientControllers[index].text =
result?[1].value ?? '';
reload(message: result?[0]);
});
},
icon: Icon(ingredient == null
? Icons.add
: Icons.edit),
),
],
),
// Padding(
// padding: const EdgeInsets.only(top: 10.0),
// child: NumberFormField(
// controller:
// _ingredients[index].amount,
// label: 'Amount',
// suffix: Settings.nutritionMeasurementSuffix,
// min: 0,
// onChanged: (value) {
// if (value != null && value >= 0) {
// setState(() {
// _ingredients[index].amount = value.toDouble();
// });
// }
// },
// ),
// ),
],
),
);
}).toList(),
)
: Center(
child: Text(_isNew
? 'Save the Recipe in order to add ingredients!'
: 'You have not added any Ingredients yet!'),
)
],
),
),
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onAction: _isSaving ? null : handleSaveAction,
onMiddleAction: _isSaving ? null : () => handleSaveAction(close: true),
),
);
}
}

View File

@ -1,200 +0,0 @@
import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/models/ingredient.dart';
import 'package:diameter/models/recipe.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/screens/recipe/recipe_detail.dart';
import 'package:flutter/material.dart';
class RecipeListScreen extends StatefulWidget {
static const String routeName = '/recipes';
const RecipeListScreen({Key? key}) : super(key: key);
@override
_RecipeListScreenState createState() => _RecipeListScreenState();
}
class _RecipeListScreenState extends State<RecipeListScreen> {
List<Recipe> _recipes = [];
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
reload();
}
void reload({String? message}) {
setState(() {
_recipes = Recipe.getAll();
});
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void onDelete(Recipe recipe) {
Recipe.remove(recipe.id);
reload(message: 'Recipe deleted');
}
void handleDeleteAction(Recipe recipe) async {
if (Settings.get().showConfirmationDialogOnDelete) {
DialogUtils.showConfirmationDialog(
context: context,
onConfirm: () => onDelete(recipe),
message: 'Are you sure you want to delete this Recipe?',
);
} else {
onDelete(recipe);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Recipes'), actions: <Widget>[
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
]),
drawer: const Navigation(currentLocation: RecipeListScreen.routeName),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: _recipes.isNotEmpty
? Scrollbar(
controller: _scrollController,
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(10.0),
itemCount: _recipes.length,
itemBuilder: (context, index) {
final recipe = _recipes[index];
final carbsRatio =
Ingredient.getCarbsRatioForRecipe(recipe.id);
final carbsPerPortion = Recipe.getCarbsPerPortion(recipe.id);
return Card(
child: ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
RecipeDetailScreen(id: recipe.id),
),
).then((result) => reload(message: result?[0]));
},
title: Text(
recipe.name.toUpperCase(),
style: Theme.of(context).textTheme.subtitle2,
),
subtitle: Padding(
padding:
const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
children: [
Column(
children: [
Text(recipe.notes ?? ''),
],
),
Expanded(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children:
((carbsRatio ?? 0) > 0)
? [
Text(carbsRatio!.toString()),
const Text('% carbs',
textScaleFactor: 0.75),
]
: [],
),
),
Expanded(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children:
(recipe.servings != null)
? [
Text(recipe.servings!
.toStringAsPrecision(3)),
const Text('servings',
textScaleFactor: 0.75),
]
: [],
),
),
Expanded(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children:
((carbsPerPortion ?? 0) > 0)
? [
Text(carbsPerPortion!
.toStringAsPrecision(3)),
Text(
'${Settings.nutritionMeasurementSuffix} carbs per serving',
textScaleFactor: 0.75),
]
: [],
),
),
],
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
onPressed: () => handleDeleteAction(recipe),
icon: const Icon(Icons.delete,
color: Colors.blue),
)
],
),
),
);
},
),
)
: const Center(
child: Text('You have not created any Recipes yet!'),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RecipeDetailScreen(),
),
).then((result) => reload(message: result?[0]));
},
child: const Icon(Icons.add),
),
);
}
}

View File

@ -0,0 +1,391 @@
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:diameter/models/basal.dart';
import 'package:diameter/models/glucose_target.dart';
import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart';
charts.TimeSeriesChart generateChart(
DateTime date,
List<LogEntry> logEntries,
List<LogEvent> logEvents,
List<GlucoseTarget> targets,
bool showMeals,
bool showBasal,
bool showBolus) {
final targetRange = targets.singleWhere((target) =>
Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
? Settings.targetMgPerDl >= target.fromMgPerDL &&
Settings.targetMgPerDl <= target.toMgPerDl
: Settings.targetMmolPerL >= target.fromMmolPerL &&
Settings.targetMmolPerL <= target.toMmolPerL);
final List<Basal> basalRate = [];
if (showBasal) {
Basal? lastBasal;
for (var time = date;
time.isBefore(date.add(const Duration(days: 1)));
time = time.add(const Duration(minutes: 1))) {
final basal = Basal.getRateForTime(time);
if (basal != null && basal.units != lastBasal?.units) {
if (basalRate.isNotEmpty) {
basalRate.last.endTime = time;
}
basalRate.add(
Basal(startTime: time, endTime: basal.endTime, units: basal.units));
lastBasal = basal;
}
}
}
return charts.TimeSeriesChart(
[
charts.Series<LogEntry, DateTime>(
id: 'Glucose',
colorFn: (LogEntry entry, _) => charts.Color.black,
domainFn: (LogEntry entry, _) => entry.time,
measureFn: (LogEntry entry, _) =>
Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
? entry.mgPerDl
: entry.mmolPerL,
data: logEntries,
)..setAttribute(charts.rendererIdKey, 'glucoseRenderer'),
charts.Series<LogEntry, DateTime>(
id: 'Carbohydrates',
colorFn: (LogEntry entry, _) =>
charts.MaterialPalette.yellow.shadeDefault,
domainFn: (LogEntry entry, _) => entry.time,
measureFn: (LogEntry entry, _) =>
LogMeal.getTotalCarbsForEntry(entry.id),
data: showMeals ? logEntries : [],
)..setAttribute(charts.rendererIdKey, 'carbsRenderer'),
charts.Series<Basal, DateTime>(
id: 'Basal',
colorFn: (Basal basal, _) => charts.Color.black.lighter,
domainFn: (Basal basal, _) => basal.startTime,
measureFn: (Basal basal, _) => basal.units,
data: showBasal ? basalRate : [],
)
..setAttribute(
charts.measureAxisIdKey, charts.Axis.secondaryMeasureAxisId)
..setAttribute(charts.rendererIdKey, 'basalRenderer'),
charts.Series<LogEntry, DateTime>(
id: 'Bolus',
colorFn: (LogEntry entry, _) =>
charts.MaterialPalette.blue.shadeDefault,
domainFn: (LogEntry entry, _) => entry.time,
measureFn: (LogEntry entry, _) =>
LogBolus.getTotalBolusForEntry(entry.id),
data: showBolus ? logEntries : [],
)
..setAttribute(
charts.measureAxisIdKey, charts.Axis.secondaryMeasureAxisId)
..setAttribute(charts.rendererIdKey, 'bolusRenderer'),
],
domainAxis: const charts.DateTimeAxisSpec(
tickFormatterSpec: charts.AutoDateTimeTickFormatterSpec(
hour: charts.TimeFormatterSpec(
format: 'HH',
transitionFormat: 'HH',
),
),
),
primaryMeasureAxis: const charts.NumericAxisSpec(
tickProviderSpec: charts.BasicNumericTickProviderSpec(
desiredTickCount: 5,
),
),
secondaryMeasureAxis: const charts.NumericAxisSpec(
tickProviderSpec: charts.BasicNumericTickProviderSpec(
desiredTickCount: 5,
),
),
animate: false,
customSeriesRenderers: [
charts.LineRendererConfig(
includeLine: true,
includePoints: false,
customRendererId: 'glucoseRenderer',
),
charts.LineRendererConfig(
includeLine: true,
includePoints: false,
includeArea: true,
roundEndCaps: true,
strokeWidthPx: 1,
customRendererId: 'basalRenderer',
),
charts.LineRendererConfig(
includeLine: false,
includePoints: true,
customRendererId: 'bolusRenderer',
),
charts.LineRendererConfig(
includeLine: false,
includePoints: true,
customRendererId: 'carbsRenderer',
),
],
behaviors: [
charts.RangeAnnotation([
charts.RangeAnnotationSegment<num>(
Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
? targetRange.fromMgPerDL
: targetRange.fromMmolPerL,
Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
? targetRange.toMgPerDl
: targetRange.toMmolPerL,
charts.RangeAnnotationAxisType.measure,
color: charts.MaterialPalette.green.shadeDefault.lighter,
),
...logEvents
.where((element) => element.hasEndTime)
.map((LogEvent event) => charts.RangeAnnotationSegment<DateTime>(
event.time,
event.endTime ?? DateTime.now(),
charts.RangeAnnotationAxisType.domain,
startLabel: event.eventType.target?.value,
color: charts.MaterialPalette.gray.shade300,
))
.toList(),
...logEvents
.where((element) => !element.hasEndTime)
.map((LogEvent event) => charts.LineAnnotationSegment<DateTime>(
event.time,
charts.RangeAnnotationAxisType.domain,
startLabel: '${event.eventType.target?.value}',
))
.toList(),
]),
],
);
}
class DailyChart extends StatefulWidget {
static const String routeName = '/reports/base';
const DailyChart({Key? key}) : super(key: key);
@override
_DailyChartState createState() => _DailyChartState();
}
class _DailyChartState extends State<DailyChart> {
late List<LogEntry> logEntries;
late List<LogEvent> logEvents;
late List<GlucoseTarget> targets;
final ScrollController _scrollController = ScrollController();
final TextEditingController _dateController = TextEditingController(text: '');
late DateTime date;
String? swipeDirection;
bool showChart = true;
bool showBolus = true;
bool showBasal = true;
bool showMeals = true;
@override
void initState() {
super.initState();
date = DateTimeUtils.today();
logEntries = LogEntry.getAllForDate(date);
logEvents = LogEvent.getAllForDate(date);
targets = GlucoseTarget.getAll();
_dateController.text = DateTimeUtils.displayDate(date);
}
@override
void dispose() {
_scrollController.dispose();
_dateController.dispose();
super.dispose();
}
void reload({String? message}) {
setState(() {
logEntries = LogEntry.getAllForDate(date);
logEvents = LogEvent.getAllForDate(date);
});
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void onChangeDate(DateTime? newDate) {
if (newDate != null) {
setState(() {
date = DateTime(newDate.year, newDate.month, newDate.day);
_dateController.text = DateTimeUtils.displayDate(newDate);
});
reload();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Daily Chart'),
actions: <Widget>[
IconButton(
onPressed: () => onChangeDate(DateTime.now()),
icon: const Icon(Icons.today)),
IconButton(
onPressed: () => showDialog(
context: context,
builder: (context) => AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CheckboxListTile(
value: showChart,
onChanged: (_) =>
setState(() => showChart = !showChart),
title: const Text('show Chart'),
controlAffinity: ListTileControlAffinity.leading,
),
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: CheckboxListTile(
value: showBolus,
onChanged: showChart
? (_) =>
setState(() => showBolus = !showBolus)
: null,
title: const Text('show Bolus'),
controlAffinity:
ListTileControlAffinity.leading,
),
),
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: CheckboxListTile(
value: showBasal,
onChanged: showChart
? (_) =>
setState(() => showBasal = !showBasal)
: null,
title: const Text('show Basal'),
controlAffinity:
ListTileControlAffinity.leading,
),
),
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: CheckboxListTile(
value: showMeals,
onChanged: showChart
? (_) =>
setState(() => showMeals = !showMeals)
: null,
title: const Text('show Meals'),
controlAffinity:
ListTileControlAffinity.leading,
),
),
],
),
actions: <Widget>[
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: const Text('CLOSE'),
),
]),
),
icon: const Icon(Icons.settings)),
IconButton(
onPressed: reload,
icon: const Icon(Icons.refresh),
),
],
),
drawer: const Navigation(currentLocation: DailyChart.routeName),
body: GestureDetector(
onPanUpdate: (details) {
swipeDirection = details.delta.dx < 0 ? 'left' : 'right';
},
onPanEnd: (details) {
if (swipeDirection == null) {
return;
}
if (swipeDirection == 'right' &&
!date.isAtSameMomentAs(DateTime(2000, 1, 1))) {
onChangeDate(date.subtract(const Duration(days: 1)));
}
if (swipeDirection == 'left' &&
date.add(const Duration(days: 1)).isBefore(DateTime.now())) {
onChangeDate(date.add(const Duration(days: 1)));
}
},
child: Column(
children: [
Row(
children: [
IconButton(
onPressed: date.isAtSameMomentAs(DateTime(2000, 1, 1))
? null
: () =>
onChangeDate(date.subtract(const Duration(days: 1))),
icon: const Icon(Icons.arrow_back),
),
Expanded(
child: GestureDetector(
onTap: () async {
final newTime = await showDatePicker(
context: context,
initialDate: date,
firstDate: DateTime(2000, 1, 1),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
onChangeDate(newTime);
},
child: Text(
DateTimeUtils.displayDate(date).toUpperCase(),
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center,
),
),
),
IconButton(
onPressed:
date.add(const Duration(days: 1)).isBefore(DateTime.now())
? () => onChangeDate(date.add(const Duration(days: 1)))
: null,
icon: const Icon(Icons.arrow_forward),
),
],
),
Expanded(
child: logEntries.isNotEmpty
? generateChart(date, logEntries, logEvents, targets, showMeals,
showBasal, showBolus)
: const Center(
child: Text(
'You have not created any Log Entries for this date yet!'),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,476 @@
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:diameter/components/forms/date_time_form_field.dart';
import 'package:diameter/models/glucose_target.dart';
import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/screens/reports/daily_chart.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
class MealData {
int id;
String text;
String portion;
String carbs;
String notes;
String boli;
MealData({
this.id = 0,
this.text = '',
this.portion = '',
this.carbs = '',
this.notes = '',
this.boli = '',
});
}
class ExportDialog extends StatefulWidget {
static const String routeName = '/export';
final DateTime? date;
const ExportDialog({Key? key, this.date}) : super(key: key);
@override
_ExportDialogState createState() => _ExportDialogState();
}
class _ExportDialogState extends State<ExportDialog> {
final ScrollController _scrollController = ScrollController();
late DateTime exportStartDate;
late DateTime exportEndDate;
TextEditingController exportStartDateController =
TextEditingController(text: '');
TextEditingController exportEndDateController =
TextEditingController(text: '');
bool _isSaving = false;
bool exportRange = false;
bool showChart = true;
bool showBolus = true;
bool showBasal = true;
bool showMeals = true;
bool showTable = true;
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
exportStartDate = widget.date ?? DateTimeUtils.today();
exportEndDate = DateTimeUtils.today();
exportStartDateController.text = DateTimeUtils.displayDate(exportStartDate);
exportEndDateController.text = DateTimeUtils.displayDate(exportEndDate);
}
void onExport(BuildContext context) async {
setState(() {
_isSaving = true;
});
final bytes = await generateLogReport(
startDate: exportStartDate,
endDate: exportRange ? exportEndDate : exportStartDate,
showChart: showChart,
showTable: showTable,
showMeals: showMeals,
showBasal: showBasal,
showBolus: showBolus,
);
final appDocDir = await getApplicationDocumentsDirectory();
final appDocPath = appDocDir.path;
final DateFormat formatter = DateFormat(DateFormat.YEAR_MONTH_DAY);
final file = File(
'$appDocPath/diameter_${exportRange ? '${formatter.format(exportStartDate)}-${formatter.format(exportEndDate)}' : formatter.format(exportStartDate)}.pdf');
await file.writeAsBytes(bytes);
await OpenFile.open(file.path);
Navigator.pop(context);
setState(() {
_isSaving = false;
});
}
@override
Widget build(BuildContext context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
children: [
Expanded(
child: RadioListTile(
title: const Text('single date'),
groupValue: exportRange,
value: false,
onChanged: (_) {
setState(() {
exportRange = false;
});
}),
),
Expanded(
child: RadioListTile(
title: const Text('range'),
groupValue: exportRange,
value: true,
onChanged: (value) {
setState(() {
exportRange = true;
});
}),
),
],
),
Row(
children: [
Expanded(
child: DateTimeFormField(
date: exportStartDate,
label: 'Date',
controller: exportStartDateController,
onChanged: (newDate) {
if (newDate != null) {
exportStartDate =
DateTime(newDate.year, newDate.month, newDate.day);
exportStartDateController.text =
DateTimeUtils.displayDate(exportStartDate);
}
},
),
),
exportRange
? Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 5.0),
child: DateTimeFormField(
date: exportEndDate,
label: 'End Date',
controller: exportEndDateController,
onChanged: (newDate) {
if (newDate != null) {
exportEndDate = DateTime(
newDate.year, newDate.month, newDate.day);
exportEndDateController.text =
DateTimeUtils.displayDate(exportEndDate);
}
},
),
),
)
: Container(),
],
),
CheckboxListTile(
value: showChart,
onChanged: (_) => setState(() => showChart = !showChart),
title: const Text('show Chart'),
controlAffinity: ListTileControlAffinity.leading,
),
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: CheckboxListTile(
value: showBolus,
onChanged: showChart
? (_) => setState(() => showBolus = !showBolus)
: null,
title: const Text('show Bolus'),
controlAffinity: ListTileControlAffinity.leading,
),
),
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: CheckboxListTile(
value: showBasal,
onChanged: showChart
? (_) => setState(() => showBasal = !showBasal)
: null,
title: const Text('show Basal'),
controlAffinity: ListTileControlAffinity.leading,
),
),
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: CheckboxListTile(
value: showMeals,
onChanged: showChart
? (_) => setState(() => showMeals = !showMeals)
: null,
title: const Text('show Meals'),
controlAffinity: ListTileControlAffinity.leading,
),
),
CheckboxListTile(
value: showTable,
onChanged: (_) => setState(() => showTable = !showTable),
title: const Text('show Table'),
controlAffinity: ListTileControlAffinity.leading,
),
],
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('CANCEL'),
),
ElevatedButton(
onPressed: !_isSaving && (showChart || showTable)
? () => onExport(context)
: null,
child: const Text('EXPORT'),
),
]);
}
}
Future<pw.Image> generateChartAsImage(
DateTime date,
List<LogEntry> logEntries,
List<LogEvent> logEvents,
List<GlucoseTarget> targets,
bool showMeals,
bool showBasal,
bool showBolus) async {
final chart = generateChart(
date, logEntries, logEvents, targets, showMeals, showBasal, showBolus);
return pw.Image(
await WidgetWraper.fromWidget(
widget: chart,
constraints: BoxConstraints(
minWidth: PdfPageFormat.a4.landscape.availableWidth,
maxWidth: PdfPageFormat.a4.landscape.availableWidth,
minHeight: PdfPageFormat.a4.landscape.availableHeight / 2,
maxHeight: PdfPageFormat.a4.landscape.availableHeight,
),
),
);
}
pw.Table generateTable(List<LogEntry> logEntries) {
const baseColor = PdfColors.grey;
const small = pw.TextStyle(fontSize: 8.0);
final tableHeaders = [
'Time',
'Glucose',
'Bolus',
'Notes',
'Meals',
'Portion Size',
'Carbohydrates',
'Meal Bolus',
'Meal Notes',
];
final List<pw.TableRow> data = logEntries.map((logEntry) {
pw.TextStyle glucoseStyle = pw.TextStyle(
color: PdfColor.fromInt(GlucoseTarget.getColorForGlucose(
mgPerDl: logEntry.mgPerDl ?? 0, mmolPerL: logEntry.mmolPerL ?? 0)
.value),
);
final boli = LogBolus.getAllForEntry(logEntry.id);
final meals = LogMeal.getAllForEntry(logEntry.id)
.map((meal) => MealData(
id: meal.id,
text: meal.value,
portion:
'${Utils.getFractionDigitsLength(meal.amount) == 0 ? meal.amount.toInt().toString() : meal.amount} ${meal.mealPortionType.target?.value}',
carbs: Utils.displayNutritionAmount(meal.totalCarbs ?? 0,
cutExtraDigits: true),
boli: [
for (var bolus in boli
.where((bolus) => bolus.meal.targetId == meal.id)
.toList())
'${bolus.units} U${bolus.delay != null ? ' (over ${bolus.delay} min)' : ''}'
].join(' + '),
notes: meal.notes ?? '',
))
.toList();
final glucoseBoli = boli
.where((bolus) =>
Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
? bolus.mgPerDlCorrection != null
: bolus.mmolPerLCorrection != null)
.toList();
final otherMealBoli = boli
.where((bolus) =>
bolus.meal.targetId != 0 &&
!meals.any((meal) => meal.id == bolus.meal.targetId))
.map((bolus) =>
'${bolus.units} U${bolus.delay != null ? ' (over ${bolus.delay} min)' : ''}${bolus.meal.target != null ? ' (for ${bolus.meal.target!.value})' : ''}')
.toList();
return pw.TableRow(
decoration: pw.BoxDecoration(
color: logEntries.indexOf(logEntry) % 2 == 0 ? PdfColors.grey200 : null,
border: const pw.Border(
bottom: pw.BorderSide(
color: baseColor,
width: .5,
),
),
),
children: [
// Time
pw.Text(DateTimeUtils.displayTime(logEntry.time)),
// Blood Glucose
pw.Row(
children: [
pw.Text(
Utils.displayGlucose(
mgPerDl: logEntry.mgPerDl, mmolPerL: logEntry.mmolPerL),
style: glucoseStyle,
),
logEntry.glucoseTrend != null
? pw.Transform.rotate(
angle: -(logEntry.glucoseTrend! * pi / 180),
child: pw.Icon(
const pw.IconData(0xe5d8),
color: glucoseStyle.color,
size: 10.0,
),
)
: pw.Container(),
],
),
// Bolus
pw.Text(
[for (var bolus in glucoseBoli) '${bolus.units} U'].join(' + ')),
// Notes
pw.Text(logEntry.notes ?? '', style: small),
// Meals
pw.Text([for (var meal in meals) meal.text].join('\n'), style: small),
// Portion Size
pw.Text([for (var meal in meals) meal.portion].join('\n'),
style: small),
// Carbohydrates
pw.Text([for (var meal in meals) meal.carbs].join('\n'), style: small),
// Meal Bolus
pw.Text(
[
for (var meal in meals) meal.boli,
for (var bolus in otherMealBoli) bolus,
].join('\n'),
style: small),
// Meal Notes
pw.Text([for (var meal in meals) meal.notes].join('\n'), style: small),
],
);
}).toList();
return pw.Table(
border: null,
tableWidth: pw.TableWidth.max,
children: [
pw.TableRow(
decoration: const pw.BoxDecoration(
color: baseColor,
),
children: tableHeaders
.map((header) => pw.Text(
header,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
),
))
.toList()),
] +
data,
);
}
Future<Uint8List> generateLogReport(
{required DateTime startDate,
DateTime? endDate,
bool showChart = true,
bool showTable = true,
bool showMeals = true,
bool showBasal = true,
bool showBolus = true}) async {
final List<GlucoseTarget> targets = GlucoseTarget.getAll();
final document = pw.Document();
final pageFormat = PdfPageFormat.a4.landscape.applyMargin(
left: 2.0 * PdfPageFormat.cm,
top: 2.0 * PdfPageFormat.cm,
right: 2.0 * PdfPageFormat.cm,
bottom: 2.0 * PdfPageFormat.cm);
final theme = pw.ThemeData.withFont(
base: await PdfGoogleFonts.robotoRegular(),
bold: await PdfGoogleFonts.robotoBold(),
icons: await PdfGoogleFonts.materialIcons(),
).copyWith(
defaultTextStyle: const pw.TextStyle(fontSize: 9.0),
);
for (DateTime date = startDate;
endDate != null && !endDate.isBefore(startDate) && !date.isAfter(endDate);
date = date.add(const Duration(days: 1))) {
List<LogEntry> logEntries = LogEntry.getAllForDate(date);
List<LogEvent> logEvents = LogEvent.getAllForDate(date);
pw.Widget? chart = showChart
? await generateChartAsImage(date, logEntries, logEvents, targets,
showMeals, showBasal, showBolus)
: pw.Container();
pw.Widget? table = showTable ? generateTable(logEntries) : pw.Container();
document.addPage(
pw.Page(
pageFormat: pageFormat,
theme: theme,
build: (context) {
return pw.Column(
children: [
pw.Text(
'LOG REPORT',
style: pw.TextStyle(
fontWeight: pw.FontWeight.bold,
fontSize: 12,
letterSpacing: 2.0,
),
),
pw.Text(
'for ${DateTimeUtils.displayDate(date)}'.toUpperCase(),
style: const pw.TextStyle(
letterSpacing: 2.0,
),
),
chart,
pw.SizedBox(height: 10),
table,
],
);
},
),
);
}
return await document.save();
}

View File

@ -0,0 +1,91 @@
import 'package:diameter/navigation.dart';
import 'package:diameter/screens/reports/export.dart';
import 'package:flutter/material.dart';
class ReportsOverviewScreen extends StatefulWidget {
static const String routeName = '/reports';
const ReportsOverviewScreen({Key? key}) : super(key: key);
@override
_ReportsOverviewScreenState createState() => _ReportsOverviewScreenState();
}
class _ReportsOverviewScreenState extends State<ReportsOverviewScreen> {
final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Reports'),
),
drawer:
const Navigation(currentLocation: ReportsOverviewScreen.routeName),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: GridView.count(
crossAxisCount: 2,
children: [
GestureDetector(
onTap: () => Navigator.pushNamed(context, Routes.dailyChart),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(Icons.today, size: 50, color: Theme.of(context).textTheme.subtitle2?.color),
),
Text(
'DAILY REPORT',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center
),
],
),
),
),
),
GestureDetector(
onTap: () => showDialog(
context: context,
builder: (context) => const ExportDialog(),
),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(Icons.today, size: 50, color: Theme.of(context).textTheme.subtitle2?.color),
),
Text(
'PDF REPORT',
style: Theme.of(context).textTheme.subtitle2,
textAlign: TextAlign.center
),
],
),
),
),
),
],
),
),
],
),
);
}
}

View File

@ -122,10 +122,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
void saveSettings() { void saveSettings() {
Settings.put(Settings( Settings.put(Settings(
id: _settings.id, id: _settings.id,
nutritionMeasurementIndex: nutritionMeasurementLabels nutritionMeasurementIndex: _nutritionMeasurementLabelController.text != '' ? nutritionMeasurementLabels
.indexOf(_nutritionMeasurementLabelController.text), .indexOf(_nutritionMeasurementLabelController.text) : 0,
glucoseMeasurementIndex: glucoseMeasurementLabels glucoseMeasurementIndex: _glucoseMeasurementLabelController.text != '' ? glucoseMeasurementLabels
.indexOf(_glucoseMeasurementLabelController.text), .indexOf(_glucoseMeasurementLabelController.text) : 0,
glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement
? GlucoseDisplayMode.activeOnly.index ? GlucoseDisplayMode.activeOnly.index
: _displayBothGlucoseMeasurementsInDetailView && : _displayBothGlucoseMeasurementsInDetailView &&

View File

@ -5,6 +5,11 @@ import 'package:intl/intl.dart';
final DateTime dummyDate = DateTime(2000); final DateTime dummyDate = DateTime(2000);
class DateTimeUtils { class DateTimeUtils {
static DateTime today() {
DateTime now = DateTime.now();
return DateTime(now.year, now.month, now.day);
}
static String displayDateTime(DateTime? date, {String fallback = ''}) { static String displayDateTime(DateTime? date, {String fallback = ''}) {
if (date == null) { if (date == null) {
return fallback; return fallback;
@ -26,7 +31,7 @@ class DateTimeUtils {
} }
static String displayTime(DateTime? date, static String displayTime(DateTime? date,
{String fallback = '', bool? longFormat}) { {String fallback = '', bool longFormat = false}) {
if (date == null) { if (date == null) {
return fallback; return fallback;
} }

View File

@ -1,5 +1,7 @@
import 'dart:math'; import 'dart:math';
import 'package:diameter/models/settings.dart';
class Utils { class Utils {
// static double roundToDecimalPlaces(double value, int precision) { // static double roundToDecimalPlaces(double value, int precision) {
// double mod = pow(10.0, precision).toDouble(); // double mod = pow(10.0, precision).toDouble();
@ -7,24 +9,31 @@ class Utils {
// } // }
static double roundToMultipleOfBase(double value, double base) { static double roundToMultipleOfBase(double value, double base) {
double result = value; double result = value;
if (base != 0) {
double remainder = value % base; double remainder = value % base;
int precision = Utils.getFractionDigitsLength(base); int precision = Utils.getFractionDigitsLength(base);
if (remainder != 0) { if (remainder != 0) {
result = Utils.addDoublesWithPrecision(result, -remainder, precision); result = Utils.addDoublesWithPrecision(result, -remainder, precision);
if (remainder > base / 2) { if (remainder > (base / 2)) {
result = Utils.addDoublesWithPrecision(result, base, precision); result = Utils.addDoublesWithPrecision(result, base, precision);
} }
} }
}
return result; return result;
} }
static double addDoublesWithPrecision(double a, double b, int precision) { static double addDoublesWithPrecision(double a, double b, int precision) {
double mod = pow(10.0, precision).toDouble(); double mod = pow(10.0, precision).toDouble();
double difference = (a * mod) + (b * mod); double difference = (a * mod) + (b * mod);
return difference.round() / mod;
if (mod != 0) {
return difference.round() / mod;
}
return difference.round().toDouble();
} }
static int getFractionDigitsLength(double value) { static int getFractionDigitsLength(double value) {
@ -51,17 +60,33 @@ class Utils {
return Utils.roundToMultipleOfBase(carbsRatio * portionSize / 100, step); return Utils.roundToMultipleOfBase(carbsRatio * portionSize / 100, step);
} }
static double calculateCarbsRatio( static double calculateCarbsRatio(double carbsPerPortion, double portionSize,
double carbsPerPortion, double portionSize, {double step = 0.01}) { {double step = 0.01}) {
return portionSize > 0 return portionSize > 0
? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / portionSize, step) ? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / portionSize, step)
: 0; : 0;
} }
static double calculatePortionSize( static double calculatePortionSize(double carbsRatio, double carbsPerPortion,
double carbsRatio, double carbsPerPortion, {double step = 0.01}) { {double step = 0.01}) {
return carbsRatio > 0 return carbsRatio > 0
? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / carbsRatio, step) ? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / carbsRatio, step)
: 0; : 0;
} }
static String displayGlucose({int? mgPerDl, double? mmolPerL}) {
return '${Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? (mgPerDl ?? '') : (mmolPerL ?? '')}';
}
static String displayGlucoseWithSuffix({int? mgPerDl, double? mmolPerL}) {
return '${Utils.displayGlucose(mgPerDl: mgPerDl, mmolPerL: mmolPerL)} ${Settings.glucoseMeasurementSuffix}';
}
static String displayNutritionAmount(double amount,
{bool cutExtraDigits = false}) {
final value = cutExtraDigits && Utils.getFractionDigitsLength(amount) == 0
? amount.toInt().toString()
: amount;
return '$value${Settings.nutritionMeasurementSuffix}';
}
} }

Binary file not shown.

Binary file not shown.

View File

@ -7,14 +7,21 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "31.0.0" version: "36.0.0"
analyzer: analyzer:
dependency: transitive dependency: "direct dev"
description: description:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.0" version: "3.3.1"
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.2"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -29,6 +36,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.8.2"
barcode:
dependency: transitive
description:
name: barcode
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -42,7 +56,7 @@ packages:
name: build name: build
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.2.1"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@ -63,21 +77,21 @@ packages:
name: build_resolvers name: build_resolvers
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.0.6"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.5" version: "2.1.8"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "7.2.2" version: "7.2.3"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -91,7 +105,7 @@ packages:
name: built_value name: built_value
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.1.3" version: "8.1.4"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -106,6 +120,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
charts_common:
dependency: transitive
description:
name: charts_common
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.0"
charts_flutter:
dependency: "direct main"
description:
name: charts_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.0"
checked_yaml: checked_yaml:
dependency: transitive dependency: transitive
description: description:
@ -113,13 +141,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.5"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -168,7 +189,7 @@ packages:
name: dart_style name: dart_style
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" version: "2.2.2"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -197,6 +218,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
flat_buffers:
dependency: transitive
description:
name: flat_buffers
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
flex_color_scheme: flex_color_scheme:
dependency: "direct main" dependency: "direct main"
description: description:
@ -221,6 +249,11 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -242,13 +275,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.4"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
name: http_multi_server name: http_multi_server
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.2.0"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -256,6 +296,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.3"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -339,21 +386,28 @@ packages:
name: objectbox name: objectbox
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.4.1"
objectbox_generator: objectbox_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: objectbox_generator name: objectbox_generator
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.4.1"
objectbox_sync_flutter_libs: objectbox_sync_flutter_libs:
dependency: "direct main" dependency: "direct main"
description: description:
name: objectbox_sync_flutter_libs name: objectbox_sync_flutter_libs
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.4.1"
open_file:
dependency: "direct main"
description:
name: open_file
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.1"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -368,55 +422,76 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.0"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.7" version: "2.0.9"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.9" version: "2.0.12"
path_provider_ios: path_provider_ios:
dependency: transitive dependency: transitive
description: description:
name: path_provider_ios name: path_provider_ios
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.7" version: "2.0.8"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" version: "2.1.5"
path_provider_macos: path_provider_macos:
dependency: transitive dependency: transitive
description: description:
name: path_provider_macos name: path_provider_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.5"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.3"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.5"
pdf:
dependency: "direct main"
description:
name: pdf
url: "https://pub.dartlang.org"
source: hosted
version: "3.7.3"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -430,7 +505,7 @@ packages:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.1.2"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -438,6 +513,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.0" version: "1.5.0"
printing:
dependency: "direct main"
description:
name: printing
url: "https://pub.dartlang.org"
source: hosted
version: "5.7.4"
process: process:
dependency: transitive dependency: transitive
description: description:
@ -451,14 +533,14 @@ packages:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.1" version: "6.0.2"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
@ -466,6 +548,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
qr:
dependency: transitive
description:
name: qr
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -491,7 +580,7 @@ packages:
name: source_gen name: source_gen
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -582,14 +671,21 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.1" version: "2.4.2"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0+1"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.3.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -598,5 +694,5 @@ packages:
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
sdks: sdks:
dart: ">=2.14.0 <3.0.0" dart: ">=2.15.0 <3.0.0"
flutter: ">=2.5.0" flutter: ">=2.8.0"

View File

@ -11,12 +11,16 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
path_provider: ^2.0.5 path_provider: ^2.0.9
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
flex_color_scheme: ^3.0.1 flex_color_scheme: ^3.0.1
intl: ^0.17.0 intl: ^0.17.0
objectbox: ^1.2.0 objectbox: ^1.2.0
objectbox_sync_flutter_libs: any objectbox_sync_flutter_libs: any
charts_flutter: ^0.12.0
printing: ^5.7.2
pdf: ^3.7.1
open_file: ^3.2.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -25,6 +29,7 @@ dev_dependencies:
provider: ^6.0.1 provider: ^6.0.1
build_runner: ^2.0.0 build_runner: ^2.0.0
objectbox_generator: any objectbox_generator: any
analyzer: ^3.3.1
flutter: flutter:
uses-material-design: true uses-material-design: true

17
windows/.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

101
windows/CMakeLists.txt Normal file
View File

@ -0,0 +1,101 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(diameter LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "diameter")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Define build configuration option.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
# Define settings for the Profile build mode.
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)

View File

@ -0,0 +1,104 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)

View File

@ -0,0 +1,17 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <objectbox_sync_flutter_libs/objectbox_sync_flutter_libs_plugin.h>
#include <printing/printing_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ObjectboxSyncFlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ObjectboxSyncFlutterLibsPlugin"));
PrintingPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PrintingPlugin"));
}

View File

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@ -0,0 +1,17 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
objectbox_sync_flutter_libs
printing
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)

View File

@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)

121
windows/runner/Runner.rc Normal file
View File

@ -0,0 +1,121 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#ifdef FLUTTER_BUILD_NUMBER
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
#else
#define VERSION_AS_NUMBER 1,0,0
#endif
#ifdef FLUTTER_BUILD_NAME
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "diameter" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "diameter" "\0"
VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "diameter.exe" "\0"
VALUE "ProductName", "diameter" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,61 @@
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

View File

@ -0,0 +1,33 @@
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_

43
windows/runner/main.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"diameter", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}

16
windows/runner/resource.h Normal file
View File

@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>

64
windows/runner/utils.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr);
if (target_length == 0) {
return std::string();
}
std::string utf8_string;
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, utf8_string.data(),
target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}

19
windows/runner/utils.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_

View File

@ -0,0 +1,245 @@
#include "win32_window.h"
#include <flutter_windows.h>
#include "resource.h"
namespace {
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
FreeLibrary(user32_module);
}
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
return OnCreate();
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}

View File

@ -0,0 +1,98 @@
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates and shows a win32 window with |title| and position and size using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size to will treat the width height passed in to this function
// as logical pixels and scale to appropriate for the default monitor. Returns
// true if the window was created successfully.
bool CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size);
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responsponds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_