Compare commits
3 Commits
361bd0b741
...
481dc60996
Author | SHA1 | Date | |
---|---|---|---|
|
481dc60996 | ||
|
9f1053fb99 | ||
|
f590a134b5 |
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"cmake.configureOnOpen": false
|
||||
}
|
75
TODO
75
TODO
@ -1,4 +1,25 @@
|
||||
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:
|
||||
☐ come up with new concept for duration component
|
||||
☐ update duration fields to use corresponding component
|
||||
@ -6,59 +27,43 @@ MAIN TASKS:
|
||||
☐ log event detail (reminder duration)
|
||||
☐ meal (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
|
||||
☐ 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)
|
||||
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
|
||||
☐ dropdown tweaks
|
||||
☐ edit item -> cancel: shouldn't clear dropdwon
|
||||
☐ keep focus on textfield when typing
|
||||
☐ account for deleted/disabled elements
|
||||
Accuracy:
|
||||
☐ 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:
|
||||
Log:
|
||||
☐ add filters
|
||||
Log Entry:
|
||||
☐ check if there is still an active bolus when suggesting glucose bolus
|
||||
Event Types:
|
||||
☐ add colors as indicators for log entries (and later graphs in reports)
|
||||
Log Events:
|
||||
☐ add filters
|
||||
Categorization:
|
||||
☐ add colors to event types as indicators for log entries and graphs in reports
|
||||
☐ implement reminders as push notifications
|
||||
Settings:
|
||||
☐ add option to hide extra customization options (ie. changing pre calculated values)?
|
||||
☐ option to switch theme
|
||||
☐ add fields for glucose target tiers (as map of cutoff glucose and colors)
|
||||
☐ add field for active insulin duration
|
||||
☐ add setting for carb units/bread units
|
||||
☐ 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)
|
||||
|
||||
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)
|
||||
✔ 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)
|
||||
|
BIN
google-chrome-stable_current_amd64.deb
Normal file
BIN
google-chrome-stable_current_amd64.deb
Normal file
Binary file not shown.
@ -26,6 +26,7 @@ class AppTheme {
|
||||
),
|
||||
),
|
||||
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
|
||||
// thumbVisibility: MaterialStateProperty.all(true),
|
||||
isAlwaysShown: true,
|
||||
),
|
||||
textTheme: baseThemeData.textTheme.copyWith(
|
||||
|
@ -56,7 +56,6 @@ class _AutoCompleteDropdownButtonState<T>
|
||||
|
||||
void showOverlay() {
|
||||
hideOverlay();
|
||||
focusNode.requestFocus();
|
||||
|
||||
List<Widget> items = [];
|
||||
Divider? divider;
|
||||
@ -116,6 +115,7 @@ class _AutoCompleteDropdownButtonState<T>
|
||||
showWhenUnlinked: false,
|
||||
child: Scrollbar(
|
||||
controller: _scrollController,
|
||||
// thumbVisibility: true,
|
||||
isAlwaysShown: true,
|
||||
child: Material(
|
||||
elevation: 8,
|
||||
@ -141,7 +141,7 @@ class _AutoCompleteDropdownButtonState<T>
|
||||
selected: item == widget.selectedItem,
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
Flexible(
|
||||
child: Text(item.toString()),
|
||||
),
|
||||
],
|
||||
|
@ -6,6 +6,7 @@ class BooleanFormField extends StatefulWidget {
|
||||
final void Function(bool) onChanged;
|
||||
final bool? enabled;
|
||||
final EdgeInsets? contentPadding;
|
||||
final Icon? icon;
|
||||
|
||||
const BooleanFormField(
|
||||
{Key? key,
|
||||
@ -13,7 +14,8 @@ class BooleanFormField extends StatefulWidget {
|
||||
required this.label,
|
||||
required this.onChanged,
|
||||
this.enabled,
|
||||
this.contentPadding})
|
||||
this.contentPadding,
|
||||
this.icon})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -25,6 +27,7 @@ class _BooleanFormFieldState extends State<BooleanFormField> {
|
||||
Widget build(BuildContext context) {
|
||||
return FormField<bool>(builder: (state) {
|
||||
return ListTile(
|
||||
leading: widget.icon,
|
||||
contentPadding: widget.contentPadding,
|
||||
onTap: () => widget.onChanged(!widget.value),
|
||||
trailing: Switch(
|
||||
|
@ -86,7 +86,7 @@ class _NumberFormFieldState extends State<NumberFormField> {
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
Expanded(
|
||||
Flexible(
|
||||
child: TextFormField(
|
||||
readOnly: widget.readOnly,
|
||||
controller: widget.controller,
|
||||
|
@ -2,28 +2,30 @@ import 'package:diameter/components/app_theme.dart';
|
||||
import 'package:diameter/config.dart';
|
||||
import 'package:diameter/models/settings.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/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_event/log_event_detail.dart';
|
||||
import 'package:diameter/screens/log/log_event/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/log/log_event_detail.dart';
|
||||
import 'package:diameter/screens/log/log_event_list.dart';
|
||||
import 'package:diameter/screens/meal/meal_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_list.dart';
|
||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_portion_type_list.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/screens/reports/export.dart';
|
||||
import 'package:diameter/screens/reports/reports.dart';
|
||||
import 'package:diameter/settings.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/bolus/bolus_profile_list.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
@ -56,15 +58,15 @@ Future<void> main() async {
|
||||
Routes.log: (context) => const LogScreen(),
|
||||
Routes.logEntry: (context) => const LogEntryScreen(),
|
||||
Routes.logEvent: (context) => const LogEventDetailScreen(),
|
||||
Routes.logEventTypes: (context) => const LogEventTypeListScreen(),
|
||||
Routes.logEventType: (context) => const EventTypeDetailScreen(),
|
||||
Routes.events: (context) => const LogEventListScreen(),
|
||||
Routes.accuracies: (context) => const AccuracyListScreen(),
|
||||
Routes.accuracy: (context) => const AccuracyDetailScreen(),
|
||||
Routes.eventTypes: (context) => const EventTypeListScreen(),
|
||||
Routes.eventType: (context) => const EventTypeDetailScreen(),
|
||||
Routes.logEvents: (context) => const LogEventListScreen(),
|
||||
Routes.reports: (context) => const ReportsOverviewScreen(),
|
||||
Routes.export: (context) => const ExportDialog(),
|
||||
Routes.dailyChart: (context) => const DailyChart(),
|
||||
Routes.meals: (context) => const MealListScreen(),
|
||||
Routes.meal: (context) => const MealDetailScreen(),
|
||||
Routes.recipes: (context) => const RecipeListScreen(),
|
||||
Routes.recipe: (context) => const RecipeDetailScreen(),
|
||||
Routes.category: (context) => const CategoryOverviewScreen(),
|
||||
Routes.mealCategories: (context) => const MealCategoryListScreen(),
|
||||
Routes.mealCategory: (context) => const MealCategoryDetailScreen(),
|
||||
Routes.mealPortionTypes: (context) =>
|
||||
@ -73,6 +75,8 @@ Future<void> main() async {
|
||||
const MealPortionTypeDetailScreen(),
|
||||
Routes.mealSources: (context) => const MealSourceListScreen(),
|
||||
Routes.mealSource: (context) => const MealSourceDetailScreen(),
|
||||
Routes.accuracies: (context) => const AccuracyListScreen(),
|
||||
Routes.accuracy: (context) => const AccuracyDetailScreen(),
|
||||
Routes.bolusProfiles: (context) => const BolusProfileListScreen(),
|
||||
Routes.bolusProfile: (context) => const BolusProfileDetailScreen(),
|
||||
Routes.basalProfiles: (context) => const BasalProfileListScreen(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:diameter/main.dart';
|
||||
import 'package:diameter/models/basal_profile.dart';
|
||||
import 'package:diameter/utils/date_time_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
import 'package:diameter/objectbox.g.dart' show Basal_, BasalProfile_;
|
||||
|
||||
@ -68,6 +69,27 @@ class Basal {
|
||||
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
|
||||
String toString() {
|
||||
return DateTimeUtils.displayTime(startTime);
|
||||
|
@ -53,21 +53,20 @@ class LogEntry {
|
||||
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))
|
||||
static List<LogEntry> getAllForDate(DateTime date) {
|
||||
DateTime startOfDay = DateTime(date.year, date.month, date.day);
|
||||
DateTime endOfDay = startOfDay.add(const Duration(days: 1));
|
||||
QueryBuilder<LogEntry> builder = 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 builder.build().find().where((entry) {
|
||||
return (entry.time.compareTo(startOfDay) >= 0 &&
|
||||
entry.time.isBefore(endOfDay));
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return dateMap;
|
||||
@override
|
||||
String toString() {
|
||||
return DateTimeUtils.displayDateTime(time);
|
||||
}
|
||||
|
||||
static List<LogEntry> getAllForDate(DateTime date) {
|
||||
|
@ -91,55 +91,6 @@ class LogEvent {
|
||||
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) {
|
||||
DateTime startOfDay = DateTime(date.year, date.month, date.day);
|
||||
DateTime endOfDay = startOfDay.add(const Duration(days: 1));
|
||||
|
@ -101,8 +101,7 @@ class Settings {
|
||||
|
||||
static NutritionMeasurement get nutritionMeasurement =>
|
||||
NutritionMeasurement.values[get().nutritionMeasurementIndex];
|
||||
static GlucoseMeasurement get glucoseMeasurement =>
|
||||
GlucoseMeasurement.values[get().glucoseMeasurementIndex];
|
||||
static GlucoseMeasurement get glucoseMeasurement => GlucoseMeasurement.values[get().glucoseMeasurementIndex];
|
||||
static GlucoseDisplayMode get glucoseDisplayMode =>
|
||||
GlucoseDisplayMode.values[get().glucoseDisplayModeIndex];
|
||||
|
||||
|
46
lib/models/user.dart
Normal file
46
lib/models/user.dart
Normal 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;
|
||||
}
|
||||
}
|
@ -1,28 +1,31 @@
|
||||
import 'package:diameter/screens/accuracy_detail.dart';
|
||||
import 'package:diameter/screens/accuracy_list.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_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_profile_detail.dart';
|
||||
import 'package:diameter/screens/basal/basal_profile_list.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_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_event/log_event_detail.dart';
|
||||
import 'package:diameter/screens/log/log_event/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_event_detail.dart';
|
||||
import 'package:diameter/screens/log/log_event_list.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_list.dart';
|
||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_portion_type_list.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/screens/reports/export.dart';
|
||||
import 'package:diameter/screens/reports/reports.dart';
|
||||
import 'package:diameter/settings.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -39,30 +42,38 @@ class Routes {
|
||||
|
||||
static const String log = LogScreen.routeName;
|
||||
static const String logEntry = LogEntryScreen.routeName;
|
||||
static const String logEvent = LogEventDetailScreen.routeName;
|
||||
static const String logMeal = LogMealDetailScreen.routeName;
|
||||
static const List<String> logEntryRoutes = [logEntry, logEvent, logMeal];
|
||||
static const String logEventType = EventTypeDetailScreen.routeName;
|
||||
static const String logEventTypes = LogEventTypeListScreen.routeName;
|
||||
static const List<String> logEventTypeRoutes = [logEventType, logEventTypes];
|
||||
static const String events = LogEventListScreen.routeName;
|
||||
static const String logBolus = LogBolusDetailScreen.routeName;
|
||||
static const List<String> logEntryRoutes = [log, logEntry, logMeal, logBolus];
|
||||
|
||||
static const String recipe = RecipeDetailScreen.routeName;
|
||||
static const String recipes = RecipeListScreen.routeName;
|
||||
static const List<String> recipeRoutes = [recipe, recipes];
|
||||
static const String logEvent = LogEventDetailScreen.routeName;
|
||||
static const String logEvents = LogEventListScreen.routeName;
|
||||
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 meals = MealListScreen.routeName;
|
||||
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 mealCategories = MealCategoryListScreen.routeName;
|
||||
static const List<String> mealCategoryRoutes = [mealCategory, mealCategories];
|
||||
|
||||
static const String mealPortionType = MealPortionTypeDetailScreen.routeName;
|
||||
static const String mealPortionTypes = MealPortionTypeListScreen.routeName;
|
||||
static const List<String> mealPortionTypeRoutes = [
|
||||
mealPortionType,
|
||||
mealPortionTypes
|
||||
];
|
||||
|
||||
static const String mealSource = MealSourceDetailScreen.routeName;
|
||||
static const String mealSources = MealSourceListScreen.routeName;
|
||||
static const List<String> mealSourceRoutes = [mealSource, mealSources];
|
||||
@ -71,6 +82,16 @@ class Routes {
|
||||
static const String accuracies = AccuracyListScreen.routeName;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -109,18 +130,18 @@ class _NavigationState extends State<Navigation> {
|
||||
title: const Text('Log Events'),
|
||||
leading: const Icon(Icons.event),
|
||||
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(
|
||||
title: const Text('Meals'),
|
||||
leading: const Icon(Icons.dinner_dining),
|
||||
@ -129,46 +150,6 @@ class _NavigationState extends State<Navigation> {
|
||||
},
|
||||
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(
|
||||
title: const Text('Basal Profiles'),
|
||||
leading: const Icon(Icons.access_time),
|
||||
@ -185,6 +166,14 @@ class _NavigationState extends State<Navigation> {
|
||||
},
|
||||
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(
|
||||
title: const Text('Settings'),
|
||||
leading: const Icon(Icons.settings),
|
||||
|
@ -818,7 +818,7 @@
|
||||
},
|
||||
{
|
||||
"id": "16:3989341091218179227",
|
||||
"lastPropertyId": "23:3611447442844013652",
|
||||
"lastPropertyId": "27:3553639710779248831",
|
||||
"name": "Settings",
|
||||
"flags": 2,
|
||||
"properties": [
|
||||
@ -892,6 +892,26 @@
|
||||
"id": "23:3611447442844013652",
|
||||
"name": "useDarkTheme",
|
||||
"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": []
|
||||
@ -1024,52 +1044,62 @@
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
],
|
||||
"lastEntityId": "19:6950311793136068892",
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "20:4237292160526261631",
|
||||
"lastIndexId": "31:3277019237664417023",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [
|
||||
3095978685310268382
|
||||
],
|
||||
"retiredIndexUids": [
|
||||
3670661188280692002,
|
||||
7379712902406481832
|
||||
],
|
||||
"retiredEntityUids": [3095978685310268382],
|
||||
"retiredIndexUids": [3670661188280692002, 7379712902406481832],
|
||||
"retiredPropertyUids": [
|
||||
3455702077061719523,
|
||||
1048198814030724077,
|
||||
9003780003858349085,
|
||||
5421422436108145565,
|
||||
7741631874181070179,
|
||||
5471636804765937328,
|
||||
6855574218883169324,
|
||||
5313708456544000157,
|
||||
3678829169126156351,
|
||||
1568597071506264632,
|
||||
8795268969829293398,
|
||||
3247926313599127440,
|
||||
8789440370359282572,
|
||||
7838546213550447420,
|
||||
8031421171668506924,
|
||||
1614362036318874174,
|
||||
1675040259141389754,
|
||||
7518219134349037920,
|
||||
2172890064639236018,
|
||||
310032577683835406,
|
||||
5588897884422150510,
|
||||
7638848982383620744,
|
||||
3282706593658092097,
|
||||
596980591281311896,
|
||||
3633551763915044903,
|
||||
2215708755581938580,
|
||||
241621230513128588,
|
||||
4678123663117222609,
|
||||
780211923138281722,
|
||||
763575433624979013,
|
||||
3455702077061719523, 1048198814030724077, 9003780003858349085,
|
||||
5421422436108145565, 7741631874181070179, 5471636804765937328,
|
||||
6855574218883169324, 5313708456544000157, 3678829169126156351,
|
||||
1568597071506264632, 8795268969829293398, 3247926313599127440,
|
||||
8789440370359282572, 7838546213550447420, 8031421171668506924,
|
||||
1614362036318874174, 1675040259141389754, 7518219134349037920,
|
||||
2172890064639236018, 310032577683835406, 5588897884422150510,
|
||||
7638848982383620744, 3282706593658092097, 596980591281311896,
|
||||
3633551763915044903, 2215708755581938580, 241621230513128588,
|
||||
4678123663117222609, 780211923138281722, 763575433624979013,
|
||||
1225271130099322691
|
||||
],
|
||||
"retiredRelationUids": [],
|
||||
|
@ -1,10 +1,14 @@
|
||||
// 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
|
||||
|
||||
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/objectbox.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/recipe.dart';
|
||||
import 'models/settings.dart';
|
||||
import 'models/user.dart';
|
||||
|
||||
export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
|
||||
|
||||
@ -807,7 +812,7 @@ final _entities = <ModelEntity>[
|
||||
ModelEntity(
|
||||
id: const IdUid(16, 3989341091218179227),
|
||||
name: 'Settings',
|
||||
lastPropertyId: const IdUid(23, 3611447442844013652),
|
||||
lastPropertyId: const IdUid(27, 3553639710779248831),
|
||||
flags: 2,
|
||||
properties: <ModelProperty>[
|
||||
ModelProperty(
|
||||
@ -879,6 +884,26 @@ final _entities = <ModelEntity>[
|
||||
id: const IdUid(23, 3611447442844013652),
|
||||
name: 'useDarkTheme',
|
||||
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)
|
||||
],
|
||||
relations: <ModelRelation>[],
|
||||
@ -1005,6 +1030,40 @@ final _entities = <ModelEntity>[
|
||||
relationTarget: 'Meal')
|
||||
],
|
||||
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>[])
|
||||
];
|
||||
|
||||
@ -1028,7 +1087,7 @@ Future<Store> openStore(
|
||||
ModelDefinition getObjectBoxModel() {
|
||||
final model = ModelInfo(
|
||||
entities: _entities,
|
||||
lastEntityId: const IdUid(19, 6950311793136068892),
|
||||
lastEntityId: const IdUid(20, 4237292160526261631),
|
||||
lastIndexId: const IdUid(31, 3277019237664417023),
|
||||
lastRelationId: const IdUid(0, 0),
|
||||
lastSequenceId: const IdUid(0, 0),
|
||||
@ -1140,11 +1199,11 @@ ModelDefinition getObjectBoxModel() {
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 12, false),
|
||||
name:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||
name: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
active:
|
||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||
notes: const fb.StringReader()
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 10));
|
||||
|
||||
return object;
|
||||
@ -1175,7 +1234,7 @@ ModelDefinition getObjectBoxModel() {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = Bolus(
|
||||
final object = LogEventType(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 20, false),
|
||||
@ -1191,190 +1250,6 @@ ModelDefinition getObjectBoxModel() {
|
||||
.vTableGetNullable(buffer, rootOffset, 14),
|
||||
mmolPerL: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 16));
|
||||
object.bolusProfile.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
||||
object.bolusProfile.attach(store);
|
||||
return object;
|
||||
}),
|
||||
BolusProfile: EntityDefinition<BolusProfile>(
|
||||
model: _entities[3],
|
||||
toOneRelations: (BolusProfile object) => [],
|
||||
toManyRelations: (BolusProfile object) => {},
|
||||
getId: (BolusProfile object) => object.id,
|
||||
setId: (BolusProfile object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (BolusProfile object, fb.Builder fbb) {
|
||||
final nameOffset = fbb.writeString(object.name);
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(6);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addOffset(1, nameOffset);
|
||||
fbb.addBool(2, object.active);
|
||||
fbb.addOffset(3, notesOffset);
|
||||
fbb.addBool(4, object.deleted);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = BolusProfile(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 12, false),
|
||||
name:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||
active:
|
||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||
notes: const fb.StringReader()
|
||||
.vTableGetNullable(buffer, rootOffset, 10));
|
||||
|
||||
return object;
|
||||
}),
|
||||
LogEntry: EntityDefinition<LogEntry>(
|
||||
model: _entities[4],
|
||||
toOneRelations: (LogEntry object) => [],
|
||||
toManyRelations: (LogEntry object) => {},
|
||||
getId: (LogEntry object) => object.id,
|
||||
setId: (LogEntry object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (LogEntry object, fb.Builder fbb) {
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(11);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addInt64(1, object.time.millisecondsSinceEpoch);
|
||||
fbb.addInt64(2, object.mgPerDl);
|
||||
fbb.addFloat64(3, object.mmolPerL);
|
||||
fbb.addOffset(7, notesOffset);
|
||||
fbb.addBool(8, object.deleted);
|
||||
fbb.addFloat64(9, object.glucoseTrend);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = LogEntry(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 20, false),
|
||||
time: DateTime.fromMillisecondsSinceEpoch(
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)),
|
||||
mgPerDl: const fb.Int64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 8),
|
||||
mmolPerL: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 10),
|
||||
glucoseTrend: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 22),
|
||||
notes: const fb.StringReader()
|
||||
.vTableGetNullable(buffer, rootOffset, 18));
|
||||
|
||||
return object;
|
||||
}),
|
||||
LogEvent: EntityDefinition<LogEvent>(
|
||||
model: _entities[5],
|
||||
toOneRelations: (LogEvent object) =>
|
||||
[object.eventType, object.bolusProfile, object.basalProfile],
|
||||
toManyRelations: (LogEvent object) => {},
|
||||
getId: (LogEvent object) => object.id,
|
||||
setId: (LogEvent object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (LogEvent object, fb.Builder fbb) {
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(13);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addInt64(1, object.time.millisecondsSinceEpoch);
|
||||
fbb.addInt64(2, object.endTime?.millisecondsSinceEpoch);
|
||||
fbb.addBool(3, object.hasEndTime);
|
||||
fbb.addOffset(4, notesOffset);
|
||||
fbb.addInt64(7, object.eventType.targetId);
|
||||
fbb.addBool(8, object.deleted);
|
||||
fbb.addInt64(9, object.bolusProfile.targetId);
|
||||
fbb.addInt64(10, object.basalProfile.targetId);
|
||||
fbb.addInt64(11, object.reminderDuration);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
final endTimeValue =
|
||||
const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 8);
|
||||
final object = LogEvent(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 20, false),
|
||||
time: DateTime.fromMillisecondsSinceEpoch(
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)),
|
||||
endTime: endTimeValue == null
|
||||
? null
|
||||
: DateTime.fromMillisecondsSinceEpoch(endTimeValue),
|
||||
hasEndTime: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 10, false),
|
||||
reminderDuration: const fb.Int64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 26),
|
||||
notes: const fb.StringReader()
|
||||
.vTableGetNullable(buffer, rootOffset, 12));
|
||||
object.eventType.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
||||
object.eventType.attach(store);
|
||||
object.bolusProfile.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||
object.bolusProfile.attach(store);
|
||||
object.basalProfile.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0);
|
||||
object.basalProfile.attach(store);
|
||||
return object;
|
||||
}),
|
||||
LogEventType: EntityDefinition<LogEventType>(
|
||||
model: _entities[6],
|
||||
toOneRelations: (LogEventType object) =>
|
||||
[object.bolusProfile, object.basalProfile],
|
||||
toManyRelations: (LogEventType object) => {},
|
||||
getId: (LogEventType object) => object.id,
|
||||
setId: (LogEventType object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (LogEventType object, fb.Builder fbb) {
|
||||
final valueOffset = fbb.writeString(object.value);
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(9);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addOffset(1, valueOffset);
|
||||
fbb.addBool(2, object.hasEndTime);
|
||||
fbb.addInt64(3, object.defaultReminderDuration);
|
||||
fbb.addOffset(4, notesOffset);
|
||||
fbb.addBool(5, object.deleted);
|
||||
fbb.addInt64(6, object.bolusProfile.targetId);
|
||||
fbb.addInt64(7, object.basalProfile.targetId);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = LogEventType(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 14, false),
|
||||
value:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||
hasEndTime:
|
||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||
defaultReminderDuration: const fb.Int64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 10),
|
||||
notes: const fb.StringReader()
|
||||
.vTableGetNullable(buffer, rootOffset, 12));
|
||||
object.bolusProfile.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
|
||||
object.bolusProfile.attach(store);
|
||||
@ -1543,6 +1418,353 @@ ModelDefinition getObjectBoxModel() {
|
||||
object.carbsRatioAccuracy.attach(store);
|
||||
return object;
|
||||
}),
|
||||
BolusProfile: EntityDefinition<BolusProfile>(
|
||||
model: _entities[3],
|
||||
toOneRelations: (BolusProfile object) => [],
|
||||
toManyRelations: (BolusProfile object) => {},
|
||||
getId: (BolusProfile object) => object.id,
|
||||
setId: (BolusProfile object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (MealCategory object, fb.Builder fbb) {
|
||||
final valueOffset = fbb.writeString(object.value);
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(6);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addOffset(1, nameOffset);
|
||||
fbb.addBool(2, object.active);
|
||||
fbb.addOffset(3, notesOffset);
|
||||
fbb.addBool(4, object.deleted);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = MealCategory(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 12, false),
|
||||
name: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
active:
|
||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 10));
|
||||
|
||||
return object;
|
||||
}),
|
||||
LogEntry: EntityDefinition<LogEntry>(
|
||||
model: _entities[4],
|
||||
toOneRelations: (LogEntry object) => [],
|
||||
toManyRelations: (LogEntry object) => {},
|
||||
getId: (LogEntry object) => object.id,
|
||||
setId: (LogEntry object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (MealPortionType object, fb.Builder fbb) {
|
||||
final valueOffset = fbb.writeString(object.value);
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(11);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addInt64(1, object.time.millisecondsSinceEpoch);
|
||||
fbb.addInt64(2, object.mgPerDl);
|
||||
fbb.addFloat64(3, object.mmolPerL);
|
||||
fbb.addOffset(7, notesOffset);
|
||||
fbb.addBool(8, object.deleted);
|
||||
fbb.addFloat64(9, object.glucoseTrend);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = MealPortionType(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 20, false),
|
||||
time: DateTime.fromMillisecondsSinceEpoch(
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)),
|
||||
mgPerDl: const fb.Int64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 8),
|
||||
mmolPerL: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 10),
|
||||
glucoseTrend: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 22),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 18));
|
||||
|
||||
return object;
|
||||
}),
|
||||
LogEvent: EntityDefinition<LogEvent>(
|
||||
model: _entities[5],
|
||||
toOneRelations: (LogEvent object) =>
|
||||
[object.eventType, object.bolusProfile, object.basalProfile],
|
||||
toManyRelations: (LogEvent object) => {},
|
||||
getId: (LogEvent object) => object.id,
|
||||
setId: (LogEvent object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (MealSource object, fb.Builder fbb) {
|
||||
final valueOffset = fbb.writeString(object.value);
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(13);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addInt64(1, object.time.millisecondsSinceEpoch);
|
||||
fbb.addInt64(2, object.endTime?.millisecondsSinceEpoch);
|
||||
fbb.addBool(3, object.hasEndTime);
|
||||
fbb.addOffset(4, notesOffset);
|
||||
fbb.addInt64(7, object.eventType.targetId);
|
||||
fbb.addBool(8, object.deleted);
|
||||
fbb.addInt64(9, object.bolusProfile.targetId);
|
||||
fbb.addInt64(10, object.basalProfile.targetId);
|
||||
fbb.addInt64(11, object.reminderDuration);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = MealSource(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 20, false),
|
||||
time: DateTime.fromMillisecondsSinceEpoch(
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)),
|
||||
endTime: endTimeValue == null
|
||||
? null
|
||||
: DateTime.fromMillisecondsSinceEpoch(endTimeValue),
|
||||
hasEndTime: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 10, false),
|
||||
reminderDuration: const fb.Int64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 26),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 12));
|
||||
object.eventType.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
||||
object.eventType.attach(store);
|
||||
object.bolusProfile.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||
object.bolusProfile.attach(store);
|
||||
object.basalProfile.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0);
|
||||
object.basalProfile.attach(store);
|
||||
return object;
|
||||
}),
|
||||
LogEventType: EntityDefinition<LogEventType>(
|
||||
model: _entities[6],
|
||||
toOneRelations: (LogEventType object) =>
|
||||
[object.bolusProfile, object.basalProfile],
|
||||
toManyRelations: (LogEventType object) => {},
|
||||
getId: (LogEventType object) => object.id,
|
||||
setId: (LogEventType object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (LogBolus object, fb.Builder fbb) {
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(9);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addOffset(1, valueOffset);
|
||||
fbb.addBool(2, object.hasEndTime);
|
||||
fbb.addInt64(3, object.defaultReminderDuration);
|
||||
fbb.addOffset(4, notesOffset);
|
||||
fbb.addBool(5, object.deleted);
|
||||
fbb.addInt64(6, object.bolusProfile.targetId);
|
||||
fbb.addInt64(7, object.basalProfile.targetId);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = LogBolus(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 14, false),
|
||||
value: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
hasEndTime:
|
||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||
defaultReminderDuration: const fb.Int64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 10),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 12));
|
||||
object.bolusProfile.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
|
||||
object.bolusProfile.attach(store);
|
||||
object.basalProfile.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
||||
object.basalProfile.attach(store);
|
||||
return object;
|
||||
}),
|
||||
LogMeal: EntityDefinition<LogMeal>(
|
||||
model: _entities[7],
|
||||
toOneRelations: (LogMeal object) => [
|
||||
object.logEntry,
|
||||
object.meal,
|
||||
object.mealSource,
|
||||
object.mealCategory,
|
||||
object.mealPortionType,
|
||||
object.portionSizeAccuracy,
|
||||
object.carbsRatioAccuracy
|
||||
],
|
||||
toManyRelations: (LogMeal object) => {},
|
||||
getId: (LogMeal object) => object.id,
|
||||
setId: (LogMeal object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (Accuracy object, fb.Builder fbb) {
|
||||
final valueOffset = fbb.writeString(object.value);
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(20);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addOffset(1, valueOffset);
|
||||
fbb.addFloat64(2, object.carbsRatio);
|
||||
fbb.addFloat64(3, object.portionSize);
|
||||
fbb.addFloat64(5, object.bolus);
|
||||
fbb.addOffset(8, notesOffset);
|
||||
fbb.addInt64(9, object.logEntry.targetId);
|
||||
fbb.addInt64(10, object.meal.targetId);
|
||||
fbb.addInt64(11, object.mealSource.targetId);
|
||||
fbb.addInt64(12, object.mealCategory.targetId);
|
||||
fbb.addInt64(13, object.mealPortionType.targetId);
|
||||
fbb.addInt64(14, object.portionSizeAccuracy.targetId);
|
||||
fbb.addInt64(15, object.carbsRatioAccuracy.targetId);
|
||||
fbb.addBool(16, object.deleted);
|
||||
fbb.addFloat64(17, object.amount);
|
||||
fbb.addFloat64(18, object.totalCarbs);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = Accuracy(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 36, false),
|
||||
value: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
amount:
|
||||
const fb.Float64Reader().vTableGet(buffer, rootOffset, 38, 0),
|
||||
carbsRatio: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 8),
|
||||
portionSize: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 10),
|
||||
totalCarbs: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 40),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 20))
|
||||
..bolus = const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 14);
|
||||
object.logEntry.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||
object.logEntry.attach(store);
|
||||
object.meal.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0);
|
||||
object.meal.attach(store);
|
||||
object.mealSource.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 26, 0);
|
||||
object.mealSource.attach(store);
|
||||
object.mealCategory.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 28, 0);
|
||||
object.mealCategory.attach(store);
|
||||
object.mealPortionType.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 30, 0);
|
||||
object.mealPortionType.attach(store);
|
||||
object.portionSizeAccuracy.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 32, 0);
|
||||
object.portionSizeAccuracy.attach(store);
|
||||
object.carbsRatioAccuracy.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 34, 0);
|
||||
object.carbsRatioAccuracy.attach(store);
|
||||
return object;
|
||||
}),
|
||||
Meal: EntityDefinition<Meal>(
|
||||
model: _entities[8],
|
||||
toOneRelations: (Meal object) => [
|
||||
object.mealSource,
|
||||
object.mealCategory,
|
||||
object.mealPortionType,
|
||||
object.portionSizeAccuracy,
|
||||
object.carbsRatioAccuracy
|
||||
],
|
||||
toManyRelations: (Meal object) => {},
|
||||
getId: (Meal object) => object.id,
|
||||
setId: (Meal object, int id) {
|
||||
object.id = id;
|
||||
},
|
||||
objectToFB: (Meal object, fb.Builder fbb) {
|
||||
final valueOffset = fbb.writeString(object.value);
|
||||
final notesOffset =
|
||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||
fbb.startTable(16);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addOffset(1, valueOffset);
|
||||
fbb.addFloat64(2, object.carbsRatio);
|
||||
fbb.addFloat64(3, object.portionSize);
|
||||
fbb.addFloat64(4, object.carbsPerPortion);
|
||||
fbb.addInt64(5, object.delayedBolusDuration);
|
||||
fbb.addOffset(7, notesOffset);
|
||||
fbb.addInt64(8, object.mealSource.targetId);
|
||||
fbb.addInt64(9, object.mealCategory.targetId);
|
||||
fbb.addInt64(10, object.mealPortionType.targetId);
|
||||
fbb.addInt64(11, object.portionSizeAccuracy.targetId);
|
||||
fbb.addInt64(12, object.carbsRatioAccuracy.targetId);
|
||||
fbb.addBool(13, object.deleted);
|
||||
fbb.addFloat64(14, object.delayedBolusPercentage);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.id;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = Meal(
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 30, false),
|
||||
value: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
carbsRatio: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 8),
|
||||
portionSize: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 10),
|
||||
carbsPerPortion: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 12),
|
||||
delayedBolusDuration: const fb.Int64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 14),
|
||||
delayedBolusPercentage: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 32),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 18));
|
||||
object.mealSource.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
|
||||
object.mealSource.attach(store);
|
||||
object.mealCategory.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||
object.mealCategory.attach(store);
|
||||
object.mealPortionType.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0);
|
||||
object.mealPortionType.attach(store);
|
||||
object.portionSizeAccuracy.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 26, 0);
|
||||
object.portionSizeAccuracy.attach(store);
|
||||
object.carbsRatioAccuracy.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 28, 0);
|
||||
object.carbsRatioAccuracy.attach(store);
|
||||
return object;
|
||||
}),
|
||||
MealCategory: EntityDefinition<MealCategory>(
|
||||
model: _entities[9],
|
||||
toOneRelations: (MealCategory object) => [],
|
||||
@ -1571,9 +1793,9 @@ ModelDefinition getObjectBoxModel() {
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 10, false),
|
||||
value:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||
notes: const fb.StringReader()
|
||||
value: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 8));
|
||||
|
||||
return object;
|
||||
@ -1606,9 +1828,9 @@ ModelDefinition getObjectBoxModel() {
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 10, false),
|
||||
value:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||
notes: const fb.StringReader()
|
||||
value: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 8));
|
||||
|
||||
return object;
|
||||
@ -1650,9 +1872,9 @@ ModelDefinition getObjectBoxModel() {
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 18, false),
|
||||
value:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||
notes: const fb.StringReader()
|
||||
value: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 8));
|
||||
object.defaultMealCategory.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0);
|
||||
@ -1728,7 +1950,7 @@ ModelDefinition getObjectBoxModel() {
|
||||
.vTableGetNullable(buffer, rootOffset, 38),
|
||||
setManually: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 16, false),
|
||||
notes: const fb.StringReader()
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 18));
|
||||
object.logEntry.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
|
||||
@ -1772,15 +1994,15 @@ ModelDefinition getObjectBoxModel() {
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 16, false),
|
||||
value:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||
value: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
forCarbsRatio:
|
||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||
forPortionSize: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 10, false),
|
||||
confidenceRating: const fb.Int64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 12),
|
||||
notes: const fb.StringReader()
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 14));
|
||||
|
||||
return object;
|
||||
@ -1802,7 +2024,7 @@ ModelDefinition getObjectBoxModel() {
|
||||
final longTimeFormatOffset = object.longTimeFormat == null
|
||||
? null
|
||||
: fbb.writeString(object.longTimeFormat!);
|
||||
fbb.startTable(24);
|
||||
fbb.startTable(28);
|
||||
fbb.addInt64(0, object.id);
|
||||
fbb.addOffset(1, dateFormatOffset);
|
||||
fbb.addOffset(2, longDateFormatOffset);
|
||||
@ -1817,6 +2039,10 @@ ModelDefinition getObjectBoxModel() {
|
||||
fbb.addInt64(20, object.targetGlucoseMgPerDl);
|
||||
fbb.addFloat64(21, object.targetGlucoseMmolPerL);
|
||||
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());
|
||||
return object.id;
|
||||
},
|
||||
@ -1832,24 +2058,27 @@ ModelDefinition getObjectBoxModel() {
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 40, 0),
|
||||
glucoseMeasurementIndex:
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 42, 0),
|
||||
dateFormat:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||
longDateFormat: const fb.StringReader()
|
||||
insulinIncrements:
|
||||
const fb.Float64Reader().vTableGet(buffer, rootOffset, 50, 0),
|
||||
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),
|
||||
timeFormat:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 10, ''),
|
||||
longTimeFormat: const fb.StringReader()
|
||||
timeFormat: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 10, ''),
|
||||
longTimeFormat: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 12),
|
||||
showConfirmationDialogOnCancel: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 14, false),
|
||||
showConfirmationDialogOnDelete: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 16, false),
|
||||
showConfirmationDialogOnStopEvent: const fb.BoolReader()
|
||||
.vTableGet(buffer, rootOffset, 18, false),
|
||||
targetGlucoseMgPerDl:
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 44, 0),
|
||||
targetGlucoseMmolPerL:
|
||||
const fb.Float64Reader().vTableGet(buffer, rootOffset, 46, 0),
|
||||
showConfirmationDialogOnCancel: const fb.BoolReader().vTableGet(buffer, rootOffset, 14, false),
|
||||
showConfirmationDialogOnDelete: const fb.BoolReader().vTableGet(buffer, rootOffset, 16, false),
|
||||
showConfirmationDialogOnStopEvent: const fb.BoolReader().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));
|
||||
|
||||
return object;
|
||||
@ -1925,11 +2154,11 @@ ModelDefinition getObjectBoxModel() {
|
||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
deleted:
|
||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 6, false),
|
||||
name:
|
||||
const fb.StringReader().vTableGet(buffer, rootOffset, 8, ''),
|
||||
name: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 8, ''),
|
||||
servings: const fb.Float64Reader()
|
||||
.vTableGetNullable(buffer, rootOffset, 24),
|
||||
notes: const fb.StringReader()
|
||||
notes: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGetNullable(buffer, rootOffset, 20));
|
||||
object.portion.targetId =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||
@ -1972,6 +2201,44 @@ ModelDefinition getObjectBoxModel() {
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0);
|
||||
object.ingredient.attach(store);
|
||||
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 +2804,22 @@ class Settings_ {
|
||||
/// see [Settings.useDarkTheme]
|
||||
static final useDarkTheme =
|
||||
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.
|
||||
@ -2616,3 +2899,23 @@ class Ingredient_ {
|
||||
static final ingredient =
|
||||
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]);
|
||||
}
|
||||
|
@ -156,15 +156,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
||||
},
|
||||
),
|
||||
BooleanFormField(
|
||||
value: _forCarbsRatio,
|
||||
label: 'for carbs ratio',
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_forCarbsRatio = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
BooleanFormField(
|
||||
icon: const Icon(Icons.square_foot),
|
||||
value: _forPortionSize,
|
||||
label: 'for portion size',
|
||||
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(
|
||||
controller: _confidenceRatingController,
|
||||
label: 'Confidence Rating',
|
@ -1,7 +1,7 @@
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.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:diameter/models/accuracy.dart';
|
||||
|
145
lib/screens/category/categories.dart
Normal file
145
lib/screens/category/categories.dart
Normal 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
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
304
lib/screens/category/event_type_detail.dart
Normal file
304
lib/screens/category/event_type_detail.dart
Normal file
@ -0,0 +1,304 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/forms/boolean_form_field.dart';
|
||||
import 'package:diameter/components/forms/duration_form_field.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/basal_profile.dart';
|
||||
import 'package:diameter/models/bolus_profile.dart';
|
||||
import 'package:diameter/models/log_event_type.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/basal/basal_profile_detail.dart';
|
||||
import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EventTypeDetailScreen extends StatefulWidget {
|
||||
static const String routeName = '/log-event-type';
|
||||
final int id;
|
||||
const EventTypeDetailScreen({Key? key, this.id = 0}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EventTypeDetailScreenState createState() => _EventTypeDetailScreenState();
|
||||
}
|
||||
|
||||
class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
|
||||
LogEventType? _logEventType;
|
||||
bool _isNew = true;
|
||||
bool _isSaving = false;
|
||||
|
||||
List<BolusProfile> _bolusProfiles = [];
|
||||
List<BasalProfile> _basalProfiles = [];
|
||||
|
||||
final GlobalKey<FormState> _logEventTypeForm = GlobalKey<FormState>();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
final _valueController = TextEditingController(text: '');
|
||||
final _notesController = TextEditingController(text: '');
|
||||
|
||||
bool _hasEndTime = false;
|
||||
int _defaultReminderDuration = 0;
|
||||
BolusProfile? _bolusProfile;
|
||||
BasalProfile? _basalProfile;
|
||||
final _bolusProfileController = TextEditingController(text: '');
|
||||
final _basalProfileController = TextEditingController(text: '');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
reload();
|
||||
|
||||
_bolusProfiles = BolusProfile.getAll();
|
||||
_basalProfiles = BasalProfile.getAll();
|
||||
|
||||
if (_logEventType != null) {
|
||||
_valueController.text = _logEventType!.value;
|
||||
_defaultReminderDuration =
|
||||
_logEventType!.defaultReminderDuration ?? 0;
|
||||
_hasEndTime = _logEventType!.hasEndTime;
|
||||
_notesController.text = _logEventType!.notes ?? '';
|
||||
_basalProfile = _logEventType!.basalProfile.target;
|
||||
_basalProfileController.text = (_basalProfile ?? '').toString();
|
||||
_bolusProfile = _logEventType!.bolusProfile.target;
|
||||
_bolusProfileController.text = (_bolusProfile ?? '').toString();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_valueController.dispose();
|
||||
_notesController.dispose();
|
||||
_bolusProfileController.dispose();
|
||||
_basalProfileController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
_logEventType = LogEventType.get(widget.id);
|
||||
});
|
||||
}
|
||||
_isNew = _logEventType == null;
|
||||
|
||||
setState(() {
|
||||
if (message != null) {
|
||||
var snackBar = SnackBar(
|
||||
content: Text(message),
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
ScaffoldMessenger.of(context)
|
||||
..removeCurrentSnackBar()
|
||||
..showSnackBar(snackBar);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void updateBasalProfile(BasalProfile? value) {
|
||||
setState(() {
|
||||
_basalProfile = value;
|
||||
_basalProfileController.text = (_basalProfile ?? '').toString();
|
||||
});
|
||||
}
|
||||
|
||||
void updateBolusProfile(BolusProfile? value) {
|
||||
setState(() {
|
||||
_bolusProfile = value;
|
||||
_bolusProfileController.text = (_bolusProfile ?? '').toString();
|
||||
});
|
||||
}
|
||||
|
||||
void handleSaveAction() async {
|
||||
setState(() {
|
||||
_isSaving = true;
|
||||
});
|
||||
if (_logEventTypeForm.currentState!.validate()) {
|
||||
LogEventType eventType = LogEventType(
|
||||
id: widget.id,
|
||||
value: _valueController.text,
|
||||
notes: _notesController.text,
|
||||
defaultReminderDuration: _defaultReminderDuration,
|
||||
hasEndTime: _hasEndTime,
|
||||
);
|
||||
eventType.basalProfile.target = _basalProfile;
|
||||
eventType.bolusProfile.target = _bolusProfile;
|
||||
LogEventType.put(eventType);
|
||||
Navigator.pop(
|
||||
context, ['${_isNew ? 'New' : ''} Log Event Type Saved', eventType]);
|
||||
}
|
||||
setState(() {
|
||||
_isSaving = false;
|
||||
});
|
||||
}
|
||||
|
||||
void handleCancelAction() {
|
||||
bool isNew = _logEventType == null;
|
||||
if (Settings.get().showConfirmationDialogOnCancel &&
|
||||
((isNew &&
|
||||
(_valueController.text != '' ||
|
||||
_defaultReminderDuration != 0 ||
|
||||
_notesController.text != '' ||
|
||||
_hasEndTime)) ||
|
||||
(!isNew &&
|
||||
(_valueController.text != _logEventType!.value ||
|
||||
_defaultReminderDuration !=
|
||||
_logEventType!.defaultReminderDuration ||
|
||||
_notesController.text != (_logEventType!.notes ?? '') ||
|
||||
_hasEndTime != _logEventType!.hasEndTime)))) {
|
||||
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 Log Event Type' : _logEventType!.value),
|
||||
),
|
||||
drawer:
|
||||
const Navigation(currentLocation: EventTypeDetailScreen.routeName),
|
||||
body: Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
FormWrapper(formState: _logEventTypeForm, fields: [
|
||||
TextFormField(
|
||||
controller: _valueController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty) {
|
||||
return 'Empty name';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
BooleanFormField(
|
||||
value: _hasEndTime,
|
||||
label: 'has end time',
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_hasEndTime = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
Column(
|
||||
children: _hasEndTime
|
||||
? [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: DurationFormField(
|
||||
minutes: _defaultReminderDuration,
|
||||
label: 'Default Reminder Duration',
|
||||
onChanged: (value) => _defaultReminderDuration = value ?? 0,
|
||||
showSteppers: true,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<
|
||||
BolusProfile>(
|
||||
selectedItem: _bolusProfile,
|
||||
controller: _bolusProfileController,
|
||||
label: 'Bolus Profile',
|
||||
items: _bolusProfiles,
|
||||
onChanged: updateBolusProfile,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => _bolusProfile ==
|
||||
null
|
||||
? const BolusProfileDetailScreen()
|
||||
: BolusProfileDetailScreen(
|
||||
id: _bolusProfile!.id),
|
||||
),
|
||||
).then((result) {
|
||||
setState(() {
|
||||
updateBolusProfile(result?[1]);
|
||||
});
|
||||
reload(message: result?[0]);
|
||||
});
|
||||
},
|
||||
icon: Icon(_bolusProfile == null
|
||||
? Icons.add
|
||||
: Icons.edit),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child:
|
||||
AutoCompleteDropdownButton<BasalProfile>(
|
||||
controller: _basalProfileController,
|
||||
selectedItem: _basalProfile,
|
||||
label: 'Basal Profile',
|
||||
items: _basalProfiles,
|
||||
onChanged: updateBasalProfile,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => _basalProfile ==
|
||||
null
|
||||
? const BasalProfileDetailScreen()
|
||||
: BasalProfileDetailScreen(
|
||||
id: _basalProfile!.id),
|
||||
),
|
||||
).then((result) {
|
||||
updateBasalProfile(result?[1]);
|
||||
reload(message: result?[0]);
|
||||
});
|
||||
},
|
||||
icon: Icon(_basalProfile == null
|
||||
? Icons.add
|
||||
: Icons.edit),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
: []),
|
||||
TextFormField(
|
||||
controller: _notesController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Notes',
|
||||
),
|
||||
keyboardType: TextInputType.multiline,
|
||||
minLines: 2,
|
||||
maxLines: 5,
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: DetailBottomRow(
|
||||
onCancel: handleCancelAction,
|
||||
onAction: _isSaving ? null : handleSaveAction,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
123
lib/screens/category/event_type_list.dart
Normal file
123
lib/screens/category/event_type_list.dart
Normal file
@ -0,0 +1,123 @@
|
||||
import 'package:diameter/models/log_event_type.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/category/event_type_detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EventTypeListScreen extends StatefulWidget {
|
||||
static const String routeName = '/log-event-types';
|
||||
const EventTypeListScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EventTypeListScreenState createState() => _EventTypeListScreenState();
|
||||
}
|
||||
|
||||
class _EventTypeListScreenState extends State<EventTypeListScreen> {
|
||||
List<LogEventType> _logEventTypes = [];
|
||||
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_logEventTypes = LogEventType.getAll();
|
||||
});
|
||||
setState(() {
|
||||
if (message != null) {
|
||||
var snackBar = SnackBar(
|
||||
content: Text(message),
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
ScaffoldMessenger.of(context)
|
||||
..removeCurrentSnackBar()
|
||||
..showSnackBar(snackBar);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Log Event Types'), actions: <Widget>[
|
||||
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
||||
]),
|
||||
drawer:
|
||||
const Navigation(currentLocation: EventTypeListScreen.routeName),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: _logEventTypes.isNotEmpty
|
||||
? Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
itemCount: _logEventTypes.length,
|
||||
itemBuilder: (context, index) {
|
||||
final logEventType = _logEventTypes[index];
|
||||
return Card(
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EventTypeDetailScreen(
|
||||
id: logEventType.id),
|
||||
),
|
||||
).then((result) => reload(message: result?[0]));
|
||||
},
|
||||
title: Text(
|
||||
logEventType.value.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
),
|
||||
subtitle: Text(logEventType.notes ?? ''),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
LogEventType.remove(logEventType.id);
|
||||
reload(message: 'Log Event Type deleted');
|
||||
},
|
||||
icon: const Icon(Icons.delete,
|
||||
color: Colors.blue),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const Center(
|
||||
child:
|
||||
Text('You have not created any Log Event Types yet!'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const EventTypeDetailScreen(),
|
||||
),
|
||||
).then((result) => reload(message: result?[0]));
|
||||
},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.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:diameter/models/meal_category.dart';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.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:diameter/models/meal_portion_type.dart';
|
||||
|
@ -8,9 +8,9 @@ import 'package:diameter/models/meal_portion_type.dart';
|
||||
import 'package:diameter/models/meal_source.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/accuracy_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
||||
import 'package:diameter/screens/category/accuracy_detail.dart';
|
||||
import 'package:diameter/screens/category/meal_category_detail.dart';
|
||||
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MealSourceDetailScreen extends StatefulWidget {
|
@ -2,7 +2,7 @@ import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/meal_source.dart';
|
||||
import 'package:diameter/models/settings.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';
|
||||
|
||||
class MealSourceListScreen extends StatefulWidget {
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -12,11 +12,11 @@ import 'package:diameter/models/meal_portion_type.dart';
|
||||
import 'package:diameter/models/meal_source.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/accuracy_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
||||
import 'package:diameter/screens/category/accuracy_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_portion_type_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_source_detail.dart';
|
||||
import 'package:diameter/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -33,6 +33,8 @@ class LogMealDetailScreen extends StatefulWidget {
|
||||
_LogMealDetailScreenState createState() => _LogMealDetailScreenState();
|
||||
}
|
||||
|
||||
enum NutritionProperty { totalCarbs, carbsRatio, portionSize }
|
||||
|
||||
class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
LogMeal? _logMeal;
|
||||
bool _isNew = true;
|
||||
@ -40,6 +42,9 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
bool _isExpanded = false;
|
||||
bool _setManually = false;
|
||||
|
||||
NutritionProperty? lastChanged;
|
||||
NutritionProperty? secondToLastChanged;
|
||||
|
||||
final GlobalKey<FormState> _logMealForm = GlobalKey<FormState>();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@ -91,6 +96,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
_portionSizeController.text = (_logMeal!.portionSize ?? '').toString();
|
||||
_totalCarbsController.text = (_logMeal!.totalCarbs ?? '').toString();
|
||||
_amountController.text = (_logMeal!.amount).toString();
|
||||
_amount = _logMeal!.amount;
|
||||
_notesController.text = _logMeal!.notes ?? '';
|
||||
_meal = _logMeal!.meal.target;
|
||||
_mealController.text = (_meal ?? '').toString();
|
||||
@ -299,6 +305,8 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
if (basePortionSize != null) {
|
||||
setState(() {
|
||||
portionSize = basePortionSize! * newAmount;
|
||||
secondToLastChanged = NutritionProperty.carbsRatio;
|
||||
lastChanged = NutritionProperty.portionSize;
|
||||
_portionSizeController.text =
|
||||
Utils.toStringMatchingTemplateFractionPrecision(
|
||||
portionSize!, Settings.nutritionSteps);
|
||||
@ -331,25 +339,34 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
const calcPortionSize = 3;
|
||||
|
||||
if (carbsRatioUpdate != null) {
|
||||
if (portionSize != null && portionSize != 0) {
|
||||
if (secondToLastChanged == NutritionProperty.portionSize &&
|
||||
portionSize != null &&
|
||||
portionSize != 0) {
|
||||
toCalculate = calcTotalCarbs;
|
||||
} else if (totalCarbs != null && totalCarbs != 0) {
|
||||
toCalculate = calcPortionSize;
|
||||
}
|
||||
} else if (portionSizeUpdate != null) {
|
||||
if (carbsRatio != null && carbsRatio != 0) {
|
||||
if (secondToLastChanged == NutritionProperty.carbsRatio &&
|
||||
carbsRatio != null &&
|
||||
carbsRatio != 0) {
|
||||
toCalculate = calcTotalCarbs;
|
||||
} else if (totalCarbs != null && totalCarbs != 0) {
|
||||
toCalculate = calcCarbsRatio;
|
||||
}
|
||||
} else if (totalCarbsUpdate != null) {
|
||||
if (carbsRatio != null && carbsRatio != 0) {
|
||||
if (secondToLastChanged == NutritionProperty.carbsRatio &&
|
||||
carbsRatio != null &&
|
||||
carbsRatio != 0) {
|
||||
toCalculate = calcPortionSize;
|
||||
} else if (portionSize != null && portionSize != 0) {
|
||||
toCalculate = calcCarbsRatio;
|
||||
}
|
||||
} else {
|
||||
if (carbsRatio != null && carbsRatio != 0) {
|
||||
if ((secondToLastChanged == NutritionProperty.carbsRatio ||
|
||||
lastChanged == NutritionProperty.carbsRatio) &&
|
||||
carbsRatio != null &&
|
||||
carbsRatio != 0) {
|
||||
if (portionSize != null && portionSize != 0) {
|
||||
toCalculate = calcTotalCarbs;
|
||||
} else if (totalCarbs != null && totalCarbs != 0) {
|
||||
@ -514,9 +531,17 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
autoRoundToMultipleOfStep: true,
|
||||
step: Settings.nutritionSteps,
|
||||
onChanged: (value) async {
|
||||
if (lastChanged != NutritionProperty.portionSize) {
|
||||
setState(() {
|
||||
secondToLastChanged = lastChanged;
|
||||
lastChanged = NutritionProperty.portionSize;
|
||||
});
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (value != null && value != 0) {
|
||||
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||
portionSizeUpdate: value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -528,9 +553,17 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
controller: _carbsRatioController,
|
||||
showSteppers: false,
|
||||
onChanged: (value) async {
|
||||
if (lastChanged != NutritionProperty.carbsRatio) {
|
||||
setState(() {
|
||||
secondToLastChanged = lastChanged;
|
||||
lastChanged = NutritionProperty.carbsRatio;
|
||||
});
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (value != null && value != 0) {
|
||||
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||
carbsRatioUpdate: value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -545,15 +578,22 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
step: Settings.nutritionSteps,
|
||||
onChanged: (value) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (lastChanged != NutritionProperty.totalCarbs) {
|
||||
setState(() {
|
||||
secondToLastChanged = lastChanged;
|
||||
lastChanged = NutritionProperty.totalCarbs;
|
||||
});
|
||||
}
|
||||
if (value != null && value != 0) {
|
||||
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||
totalCarbsUpdate: value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: BooleanFormField(
|
||||
BooleanFormField(
|
||||
value: _setManually,
|
||||
label: 'set carbs ratio manually',
|
||||
onChanged: (value) {
|
||||
@ -563,7 +603,6 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _notesController,
|
||||
decoration: const InputDecoration(
|
||||
@ -600,8 +639,6 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child:
|
||||
AutoCompleteDropdownButton<MealSource>(
|
||||
controller: _mealSourceController,
|
||||
selectedItem: _mealSource,
|
||||
@ -609,7 +646,6 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
items: _mealSources,
|
||||
onChanged: updateMealSource,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
@ -638,16 +674,13 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<
|
||||
MealCategory>(
|
||||
AutoCompleteDropdownButton<MealCategory>(
|
||||
controller: _mealCategoryController,
|
||||
selectedItem: _mealCategory,
|
||||
label: 'Meal Category',
|
||||
items: _mealCategories,
|
||||
onChanged: updateMealCategory,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
@ -676,16 +709,13 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<
|
||||
MealPortionType>(
|
||||
AutoCompleteDropdownButton<MealPortionType>(
|
||||
controller: _mealPortionTypeController,
|
||||
selectedItem: _mealPortionType,
|
||||
label: 'Meal Portion Type',
|
||||
items: _mealPortionTypes,
|
||||
onChanged: updateMealPortionType,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
@ -714,16 +744,13 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<Accuracy>(
|
||||
controller:
|
||||
_portionSizeAccuracyController,
|
||||
AutoCompleteDropdownButton<Accuracy>(
|
||||
controller: _portionSizeAccuracyController,
|
||||
selectedItem: _portionSizeAccuracy,
|
||||
label: 'Portion Size Accuracy',
|
||||
items: _portionSizeAccuracies,
|
||||
onChanged: updatePortionSizeAccuracy,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
@ -753,15 +780,13 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<Accuracy>(
|
||||
AutoCompleteDropdownButton<Accuracy>(
|
||||
controller: _carbsRatioAccuracyController,
|
||||
selectedItem: _carbsRatioAccuracy,
|
||||
label: 'Carbs Ratio Accuracy',
|
||||
items: _carbsRatioAccuracies,
|
||||
onChanged: updateCarbsRatioAccuracy,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
|
364
lib/screens/log/log_overview.dart
Normal file
364
lib/screens/log/log_overview.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -11,10 +11,10 @@ import 'package:diameter/models/meal_portion_type.dart';
|
||||
import 'package:diameter/models/meal_source.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/accuracy_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
||||
import 'package:diameter/screens/meal/meal_source_detail.dart';
|
||||
import 'package:diameter/screens/category/accuracy_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/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -435,7 +435,10 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
||||
showSteppers: false,
|
||||
onChanged: (value) async {
|
||||
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,
|
||||
onChanged: (value) async {
|
||||
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,
|
||||
onChanged: (value) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
calculateThirdMeasurementOfPortionCarbsRelation(carbsPerPortionUpdate: value);
|
||||
if (value != null && value != 0) {
|
||||
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||
carbsPerPortionUpdate: value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -562,16 +571,13 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
||||
const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<
|
||||
MealCategory>(
|
||||
AutoCompleteDropdownButton<MealCategory>(
|
||||
controller: _mealCategoryController,
|
||||
selectedItem: _mealCategory,
|
||||
label: 'Meal Category',
|
||||
items: _mealCategories,
|
||||
onChanged: updateMealCategory,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
@ -600,16 +606,13 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
||||
const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<Accuracy>(
|
||||
controller:
|
||||
_portionSizeAccuracyController,
|
||||
AutoCompleteDropdownButton<Accuracy>(
|
||||
controller: _portionSizeAccuracyController,
|
||||
selectedItem: _portionSizeAccuracy,
|
||||
label: 'Portion Size Accuracy',
|
||||
items: _portionSizeAccuracies,
|
||||
onChanged: updatePortionSizeAccuracy,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
@ -639,15 +642,13 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
||||
const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<Accuracy>(
|
||||
AutoCompleteDropdownButton<Accuracy>(
|
||||
controller: _carbsRatioAccuracyController,
|
||||
selectedItem: _carbsRatioAccuracy,
|
||||
label: 'Carbs Ratio Accuracy',
|
||||
items: _carbsRatioAccuracies,
|
||||
onChanged: updateCarbsRatioAccuracy,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
|
391
lib/screens/reports/daily_chart.dart
Normal file
391
lib/screens/reports/daily_chart.dart
Normal 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!'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
476
lib/screens/reports/export.dart
Normal file
476
lib/screens/reports/export.dart
Normal 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();
|
||||
}
|
91
lib/screens/reports/reports.dart
Normal file
91
lib/screens/reports/reports.dart
Normal 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
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -122,10 +122,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
void saveSettings() {
|
||||
Settings.put(Settings(
|
||||
id: _settings.id,
|
||||
nutritionMeasurementIndex: nutritionMeasurementLabels
|
||||
.indexOf(_nutritionMeasurementLabelController.text),
|
||||
glucoseMeasurementIndex: glucoseMeasurementLabels
|
||||
.indexOf(_glucoseMeasurementLabelController.text),
|
||||
nutritionMeasurementIndex: _nutritionMeasurementLabelController.text != '' ? nutritionMeasurementLabels
|
||||
.indexOf(_nutritionMeasurementLabelController.text) : 0,
|
||||
glucoseMeasurementIndex: _glucoseMeasurementLabelController.text != '' ? glucoseMeasurementLabels
|
||||
.indexOf(_glucoseMeasurementLabelController.text) : 0,
|
||||
glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement
|
||||
? GlucoseDisplayMode.activeOnly.index
|
||||
: _displayBothGlucoseMeasurementsInDetailView &&
|
||||
|
@ -5,6 +5,11 @@ import 'package:intl/intl.dart';
|
||||
final DateTime dummyDate = DateTime(2000);
|
||||
|
||||
class DateTimeUtils {
|
||||
static DateTime today() {
|
||||
DateTime now = DateTime.now();
|
||||
return DateTime(now.year, now.month, now.day);
|
||||
}
|
||||
|
||||
static String displayDateTime(DateTime? date, {String fallback = ''}) {
|
||||
if (date == null) {
|
||||
return fallback;
|
||||
@ -26,7 +31,7 @@ class DateTimeUtils {
|
||||
}
|
||||
|
||||
static String displayTime(DateTime? date,
|
||||
{String fallback = '', bool? longFormat}) {
|
||||
{String fallback = '', bool longFormat = false}) {
|
||||
if (date == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:diameter/models/settings.dart';
|
||||
|
||||
class Utils {
|
||||
// static double roundToDecimalPlaces(double value, int precision) {
|
||||
// double mod = pow(10.0, precision).toDouble();
|
||||
@ -8,15 +10,17 @@ class Utils {
|
||||
|
||||
static double roundToMultipleOfBase(double value, double base) {
|
||||
double result = value;
|
||||
if (base != 0) {
|
||||
double remainder = value % base;
|
||||
int precision = Utils.getFractionDigitsLength(base);
|
||||
|
||||
if (remainder != 0) {
|
||||
result = Utils.addDoublesWithPrecision(result, -remainder, precision);
|
||||
if (remainder > base / 2) {
|
||||
if (remainder > (base / 2)) {
|
||||
result = Utils.addDoublesWithPrecision(result, base, precision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -24,9 +28,14 @@ class Utils {
|
||||
static double addDoublesWithPrecision(double a, double b, int precision) {
|
||||
double mod = pow(10.0, precision).toDouble();
|
||||
double difference = (a * mod) + (b * mod);
|
||||
|
||||
if (mod != 0) {
|
||||
return difference.round() / mod;
|
||||
}
|
||||
|
||||
return difference.round().toDouble();
|
||||
}
|
||||
|
||||
static int getFractionDigitsLength(double value) {
|
||||
final fractionDigits = value.toString().split('.');
|
||||
return fractionDigits[1] == '0' ? 0 : fractionDigits[1].length;
|
||||
@ -51,17 +60,33 @@ class Utils {
|
||||
return Utils.roundToMultipleOfBase(carbsRatio * portionSize / 100, step);
|
||||
}
|
||||
|
||||
static double calculateCarbsRatio(
|
||||
double carbsPerPortion, double portionSize, {double step = 0.01}) {
|
||||
static double calculateCarbsRatio(double carbsPerPortion, double portionSize,
|
||||
{double step = 0.01}) {
|
||||
return portionSize > 0
|
||||
? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / portionSize, step)
|
||||
: 0;
|
||||
}
|
||||
|
||||
static double calculatePortionSize(
|
||||
double carbsRatio, double carbsPerPortion, {double step = 0.01}) {
|
||||
static double calculatePortionSize(double carbsRatio, double carbsPerPortion,
|
||||
{double step = 0.01}) {
|
||||
return carbsRatio > 0
|
||||
? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / carbsRatio, step)
|
||||
: 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.
154
pubspec.lock
154
pubspec.lock
@ -7,14 +7,21 @@ packages:
|
||||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "31.0.0"
|
||||
version: "36.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -29,6 +36,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
barcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: barcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -42,7 +56,7 @@ packages:
|
||||
name: build
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.2.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -63,21 +77,21 @@ packages:
|
||||
name: build_resolvers
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
version: "2.0.6"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
version: "2.1.8"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "7.2.2"
|
||||
version: "7.2.3"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -91,7 +105,7 @@ packages:
|
||||
name: built_value
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.1.3"
|
||||
version: "8.1.4"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -106,6 +120,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -113,13 +141,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -168,7 +189,7 @@ packages:
|
||||
name: dart_style
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.2"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -197,6 +218,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -248,7 +276,7 @@ packages:
|
||||
name: http_multi_server
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.2.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -256,6 +284,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -339,21 +374,28 @@ packages:
|
||||
name: objectbox
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.1"
|
||||
objectbox_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: objectbox_generator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.1"
|
||||
objectbox_sync_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: objectbox_sync_flutter_libs
|
||||
url: "https://pub.dartlang.org"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -368,55 +410,76 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
version: "2.0.9"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
version: "2.0.12"
|
||||
path_provider_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
version: "2.0.8"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.5"
|
||||
path_provider_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
version: "2.0.5"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.3"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -430,7 +493,7 @@ packages:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.2"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -438,6 +501,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
printing:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: printing
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.7.4"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -451,14 +521,14 @@ packages:
|
||||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
version: "6.0.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -466,6 +536,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -491,7 +568,7 @@ packages:
|
||||
name: source_gen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -582,14 +659,21 @@ packages:
|
||||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.4.2"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
url: "https://pub.dartlang.org"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -598,5 +682,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
sdks:
|
||||
dart: ">=2.14.0 <3.0.0"
|
||||
flutter: ">=2.5.0"
|
||||
dart: ">=2.15.0 <3.0.0"
|
||||
flutter: ">=2.8.0"
|
||||
|
@ -11,12 +11,16 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
path_provider: ^2.0.5
|
||||
path_provider: ^2.0.9
|
||||
cupertino_icons: ^1.0.2
|
||||
flex_color_scheme: ^3.0.1
|
||||
intl: ^0.17.0
|
||||
objectbox: ^1.2.0
|
||||
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:
|
||||
flutter_test:
|
||||
@ -25,6 +29,7 @@ dev_dependencies:
|
||||
provider: ^6.0.1
|
||||
build_runner: ^2.0.0
|
||||
objectbox_generator: any
|
||||
analyzer: ^3.3.1
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
17
windows/.gitignore
vendored
Normal file
17
windows/.gitignore
vendored
Normal 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
101
windows/CMakeLists.txt
Normal 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)
|
104
windows/flutter/CMakeLists.txt
Normal file
104
windows/flutter/CMakeLists.txt
Normal 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}
|
||||
)
|
17
windows/flutter/generated_plugin_registrant.cc
Normal file
17
windows/flutter/generated_plugin_registrant.cc
Normal 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"));
|
||||
}
|
15
windows/flutter/generated_plugin_registrant.h
Normal file
15
windows/flutter/generated_plugin_registrant.h
Normal 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_
|
17
windows/flutter/generated_plugins.cmake
Normal file
17
windows/flutter/generated_plugins.cmake
Normal 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)
|
32
windows/runner/CMakeLists.txt
Normal file
32
windows/runner/CMakeLists.txt
Normal 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
121
windows/runner/Runner.rc
Normal 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
|
61
windows/runner/flutter_window.cpp
Normal file
61
windows/runner/flutter_window.cpp
Normal 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);
|
||||
}
|
33
windows/runner/flutter_window.h
Normal file
33
windows/runner/flutter_window.h
Normal 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
43
windows/runner/main.cpp
Normal 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
16
windows/runner/resource.h
Normal 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
|
BIN
windows/runner/resources/app_icon.ico
Normal file
BIN
windows/runner/resources/app_icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
20
windows/runner/runner.exe.manifest
Normal file
20
windows/runner/runner.exe.manifest
Normal 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
64
windows/runner/utils.cpp
Normal 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
19
windows/runner/utils.h
Normal 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_
|
245
windows/runner/win32_window.cpp
Normal file
245
windows/runner/win32_window.cpp
Normal 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.
|
||||
}
|
98
windows/runner/win32_window.h
Normal file
98
windows/runner/win32_window.h
Normal 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_
|
Loading…
Reference in New Issue
Block a user