various updates
This commit is contained in:
parent
f590a134b5
commit
9f1053fb99
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:
|
MAIN TASKS:
|
||||||
|
Database:
|
||||||
|
☐ set name properties as unique (and add checks to forms)
|
||||||
|
☐ implement users
|
||||||
|
☐ check objectbox docs on how to make users
|
||||||
|
☐ tie all data to users
|
||||||
|
☐ add user filters to all getters
|
||||||
|
☐ change settings to not be a singleton, but only one settings instance per user and one default entry
|
||||||
|
☐ add login and authentification
|
||||||
|
☐ enable restoring data from sync
|
||||||
|
☐ find a solution for storage
|
||||||
|
☐ hosting
|
||||||
|
☐ objectbox sync (commercial use is not free)
|
||||||
|
☐ implement alternative data export for now?
|
||||||
|
☐ create default datasets for configuration (meal categories, portion types, accuracies, event types, possibly meal source)
|
||||||
|
Features:
|
||||||
|
☐ app icon
|
||||||
|
☐ add explanations to each section
|
||||||
|
☐ german language support
|
||||||
|
☐ indicate nested creation process (creating from dropdown etc)
|
||||||
|
☐ indicate read only fields
|
||||||
|
☐ app info/credits screen
|
||||||
Components/Framework:
|
Components/Framework:
|
||||||
☐ come up with new concept for duration component
|
☐ come up with new concept for duration component
|
||||||
☐ update duration fields to use corresponding component
|
☐ update duration fields to use corresponding component
|
||||||
@ -6,59 +27,43 @@ MAIN TASKS:
|
|||||||
☐ log event detail (reminder duration)
|
☐ log event detail (reminder duration)
|
||||||
☐ meal (bolus delay)
|
☐ meal (bolus delay)
|
||||||
☐ log bolus (delay)
|
☐ log bolus (delay)
|
||||||
☐ set name properties as unique (and add checks to forms)
|
|
||||||
☐ check through all detail forms and set required fields/according messages
|
☐ check through all detail forms and set required fields/according messages
|
||||||
☐ change placement of delete and floating button because its very easy to accidentally hit delete
|
☐ change placement of delete and floating button because its very easy to accidentally hit delete
|
||||||
☐ implement deletion by swiping left on item instead?
|
|
||||||
☐ check for changes before navigating as well (not just on cancel)
|
☐ check for changes before navigating as well (not just on cancel)
|
||||||
Reports:
|
|
||||||
☐ evaluate what type of reports there should be
|
|
||||||
☐ try out graph/diagram components
|
|
||||||
|
|
||||||
FUTURE TASKS:
|
|
||||||
Features:
|
|
||||||
☐ app icon
|
|
||||||
☐ desktop version
|
|
||||||
☐ add explanations to each section
|
|
||||||
☐ alternate languages
|
|
||||||
☐ log hba1c
|
|
||||||
☐ indicate nested creation process (creating from dropdown etc)
|
|
||||||
☐ enable restoring data from sync
|
|
||||||
☐ indicate read only fields
|
|
||||||
Components/Framework:
|
|
||||||
☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view
|
☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view
|
||||||
☐ dropdown tweaks
|
☐ dropdown tweaks
|
||||||
☐ edit item -> cancel: shouldn't clear dropdwon
|
☐ edit item -> cancel: shouldn't clear dropdwon
|
||||||
☐ keep focus on textfield when typing
|
|
||||||
☐ account for deleted/disabled elements
|
☐ account for deleted/disabled elements
|
||||||
Accuracy:
|
Log:
|
||||||
☐ same icons in detail as in overview to indicate what's what
|
|
||||||
Recipe:
|
|
||||||
☐ update to use correct components, init/dispose etc
|
|
||||||
☐ change the entire concept of ingredients
|
|
||||||
☐ add functionality to create a meal from a recipe
|
|
||||||
Reports:
|
|
||||||
☐ meal tweaking
|
|
||||||
☐ bolus tweaking
|
|
||||||
☐ basal test
|
|
||||||
☐ daily graph (showing glucose curve, events, boli and meals)
|
|
||||||
Log Overview:
|
|
||||||
☐ add filters
|
☐ add filters
|
||||||
Log Entry:
|
|
||||||
☐ check if there is still an active bolus when suggesting glucose bolus
|
☐ check if there is still an active bolus when suggesting glucose bolus
|
||||||
Event Types:
|
Log Events:
|
||||||
☐ add colors as indicators for log entries (and later graphs in reports)
|
☐ add filters
|
||||||
|
Categorization:
|
||||||
|
☐ add colors to event types as indicators for log entries and graphs in reports
|
||||||
☐ implement reminders as push notifications
|
☐ implement reminders as push notifications
|
||||||
Settings:
|
Settings:
|
||||||
☐ add option to hide extra customization options (ie. changing pre calculated values)?
|
|
||||||
☐ option to switch theme
|
☐ option to switch theme
|
||||||
☐ add fields for glucose target tiers (as map of cutoff glucose and colors)
|
☐ add fields for glucose target tiers (as map of cutoff glucose and colors)
|
||||||
☐ add field for active insulin duration
|
☐ add field for active insulin duration
|
||||||
☐ add setting for carb units/bread units
|
|
||||||
☐ add option to switch 'save' and 'save & close' buttons
|
☐ add option to switch 'save' and 'save & close' buttons
|
||||||
☐ add functionality to delete dead records (meaning: set deleted flag and no relations to undeleted records)
|
☐ add functionality to delete dead records (meaning: set deleted flag and no relations to undeleted records)
|
||||||
|
|
||||||
Archive:
|
Archive:
|
||||||
|
✔ switch day on swipe @done(22-03-19 23:20) @project(MAIN TASKS.Log)
|
||||||
|
✔ switch day on swipe @done(22-03-19 23:23) @project(MAIN TASKS.Log Events)
|
||||||
|
✔ switch day on swipe @done(22-03-19 23:20) @project(MAIN TASKS.Reports)
|
||||||
|
✔ don't update calculation on deleting field content @done(22-03-12 04:33) @project(MAIN TASKS.Meal)
|
||||||
|
✔ don't use suggested time if date is set to today @done(22-03-12 04:36) @project(MAIN TASKS.Log)
|
||||||
|
✔ don't use suggested time if date is set to today @done(22-03-12 04:36) @project(MAIN TASKS.Log Events)
|
||||||
|
✔ pack all categorization stuff (meal categories, meal source, portion type, accuracies, event type) into one sub menu @done(22-03-12 04:21) @project(MAIN TASKS.Categorization)
|
||||||
|
✔ same icons in accuracy detail as in overview to indicate what's what @done(22-03-12 04:24) @project(MAIN TASKS.Categorization)
|
||||||
|
✔ view daily graphs @done(22-03-12 03:47) @project(MAIN TASKS.Reports)
|
||||||
|
✔ keep focus on textfield when typing @done(22-03-10 22:15) @project(MAIN TASKS.Components/Framework)
|
||||||
|
✔ calculation ends up wrong on re-entering log meal detail screen @done(22-02-06 21:46) @project(BUG FIXES)
|
||||||
|
✔ export log tables @done(22-03-10 22:01) @project(MAIN TASKS.Components/Framework)
|
||||||
|
✔ export graphs @done(22-03-10 22:01) @project(MAIN TASKS.Components/Framework)
|
||||||
|
✔ change auto calculation to remember fields that were filled in last @done(22-02-06 21:37) @project(MAIN TASKS.Meal)
|
||||||
✔ only show current day @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)
|
✔ only show current day @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)
|
||||||
✔ add calendar field on top to navigate @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)
|
✔ add calendar field on top to navigate @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)
|
||||||
✔ use currently selected day when adding a log entry @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)
|
✔ use currently selected day when adding a log entry @done(22-01-24 05:39) @project(MAIN TASKS.Log Overview)
|
||||||
|
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(
|
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
|
||||||
|
// thumbVisibility: MaterialStateProperty.all(true),
|
||||||
isAlwaysShown: true,
|
isAlwaysShown: true,
|
||||||
),
|
),
|
||||||
textTheme: baseThemeData.textTheme.copyWith(
|
textTheme: baseThemeData.textTheme.copyWith(
|
||||||
|
@ -56,7 +56,6 @@ class _AutoCompleteDropdownButtonState<T>
|
|||||||
|
|
||||||
void showOverlay() {
|
void showOverlay() {
|
||||||
hideOverlay();
|
hideOverlay();
|
||||||
focusNode.requestFocus();
|
|
||||||
|
|
||||||
List<Widget> items = [];
|
List<Widget> items = [];
|
||||||
Divider? divider;
|
Divider? divider;
|
||||||
@ -116,6 +115,7 @@ class _AutoCompleteDropdownButtonState<T>
|
|||||||
showWhenUnlinked: false,
|
showWhenUnlinked: false,
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
|
// thumbVisibility: true,
|
||||||
isAlwaysShown: true,
|
isAlwaysShown: true,
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
@ -141,7 +141,7 @@ class _AutoCompleteDropdownButtonState<T>
|
|||||||
selected: item == widget.selectedItem,
|
selected: item == widget.selectedItem,
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Flexible(
|
||||||
child: Text(item.toString()),
|
child: Text(item.toString()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -6,6 +6,7 @@ class BooleanFormField extends StatefulWidget {
|
|||||||
final void Function(bool) onChanged;
|
final void Function(bool) onChanged;
|
||||||
final bool? enabled;
|
final bool? enabled;
|
||||||
final EdgeInsets? contentPadding;
|
final EdgeInsets? contentPadding;
|
||||||
|
final Icon? icon;
|
||||||
|
|
||||||
const BooleanFormField(
|
const BooleanFormField(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
@ -13,7 +14,8 @@ class BooleanFormField extends StatefulWidget {
|
|||||||
required this.label,
|
required this.label,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
this.enabled,
|
this.enabled,
|
||||||
this.contentPadding})
|
this.contentPadding,
|
||||||
|
this.icon})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -25,6 +27,7 @@ class _BooleanFormFieldState extends State<BooleanFormField> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FormField<bool>(builder: (state) {
|
return FormField<bool>(builder: (state) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
leading: widget.icon,
|
||||||
contentPadding: widget.contentPadding,
|
contentPadding: widget.contentPadding,
|
||||||
onTap: () => widget.onChanged(!widget.value),
|
onTap: () => widget.onChanged(!widget.value),
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
|
@ -86,7 +86,7 @@ class _NumberFormFieldState extends State<NumberFormField> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
Expanded(
|
Flexible(
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
readOnly: widget.readOnly,
|
readOnly: widget.readOnly,
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
|
@ -2,28 +2,30 @@ import 'package:diameter/components/app_theme.dart';
|
|||||||
import 'package:diameter/config.dart';
|
import 'package:diameter/config.dart';
|
||||||
import 'package:diameter/models/settings.dart';
|
import 'package:diameter/models/settings.dart';
|
||||||
import 'package:diameter/object_box.dart';
|
import 'package:diameter/object_box.dart';
|
||||||
import 'package:diameter/screens/accuracy_detail.dart';
|
import 'package:diameter/screens/category/categories.dart';
|
||||||
|
import 'package:diameter/screens/category/accuracy_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/accuracy_list.dart';
|
||||||
|
import 'package:diameter/screens/category/event_type_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/event_type_list.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_category_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_category_list.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_portion_type_list.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_source_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_source_list.dart';
|
||||||
import 'package:diameter/screens/basal/basal_profile_detail.dart';
|
import 'package:diameter/screens/basal/basal_profile_detail.dart';
|
||||||
import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
|
import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
|
||||||
import 'package:diameter/screens/log/log.dart';
|
import 'package:diameter/screens/log/log_overview.dart';
|
||||||
|
import 'package:diameter/screens/reports/daily_chart.dart';
|
||||||
import 'package:diameter/screens/log/log_entry/log_entry.dart';
|
import 'package:diameter/screens/log/log_entry/log_entry.dart';
|
||||||
import 'package:diameter/screens/log/log_event/log_event_detail.dart';
|
import 'package:diameter/screens/log/log_event_detail.dart';
|
||||||
import 'package:diameter/screens/log/log_event/log_event_list.dart';
|
import 'package:diameter/screens/log/log_event_list.dart';
|
||||||
import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
|
|
||||||
import 'package:diameter/screens/log/log_event/log_event_type_list.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_category_list.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_detail.dart';
|
import 'package:diameter/screens/meal/meal_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_list.dart';
|
import 'package:diameter/screens/meal/meal_list.dart';
|
||||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
import 'package:diameter/screens/reports/export.dart';
|
||||||
import 'package:diameter/screens/meal/meal_portion_type_list.dart';
|
import 'package:diameter/screens/reports/reports.dart';
|
||||||
import 'package:diameter/screens/meal/meal_source_detail.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_source_list.dart';
|
|
||||||
import 'package:diameter/screens/recipe/recipe_detail.dart';
|
|
||||||
import 'package:diameter/screens/recipe/recipe_list.dart';
|
|
||||||
import 'package:diameter/settings.dart';
|
import 'package:diameter/settings.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:diameter/screens/accuracy_list.dart';
|
|
||||||
import 'package:diameter/screens/basal/basal_profile_list.dart';
|
import 'package:diameter/screens/basal/basal_profile_list.dart';
|
||||||
import 'package:diameter/screens/bolus/bolus_profile_list.dart';
|
import 'package:diameter/screens/bolus/bolus_profile_list.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
@ -56,15 +58,15 @@ Future<void> main() async {
|
|||||||
Routes.log: (context) => const LogScreen(),
|
Routes.log: (context) => const LogScreen(),
|
||||||
Routes.logEntry: (context) => const LogEntryScreen(),
|
Routes.logEntry: (context) => const LogEntryScreen(),
|
||||||
Routes.logEvent: (context) => const LogEventDetailScreen(),
|
Routes.logEvent: (context) => const LogEventDetailScreen(),
|
||||||
Routes.logEventTypes: (context) => const LogEventTypeListScreen(),
|
Routes.eventTypes: (context) => const EventTypeListScreen(),
|
||||||
Routes.logEventType: (context) => const EventTypeDetailScreen(),
|
Routes.eventType: (context) => const EventTypeDetailScreen(),
|
||||||
Routes.events: (context) => const LogEventListScreen(),
|
Routes.logEvents: (context) => const LogEventListScreen(),
|
||||||
Routes.accuracies: (context) => const AccuracyListScreen(),
|
Routes.reports: (context) => const ReportsOverviewScreen(),
|
||||||
Routes.accuracy: (context) => const AccuracyDetailScreen(),
|
Routes.export: (context) => const ExportDialog(),
|
||||||
|
Routes.dailyChart: (context) => const DailyChart(),
|
||||||
Routes.meals: (context) => const MealListScreen(),
|
Routes.meals: (context) => const MealListScreen(),
|
||||||
Routes.meal: (context) => const MealDetailScreen(),
|
Routes.meal: (context) => const MealDetailScreen(),
|
||||||
Routes.recipes: (context) => const RecipeListScreen(),
|
Routes.category: (context) => const CategoryOverviewScreen(),
|
||||||
Routes.recipe: (context) => const RecipeDetailScreen(),
|
|
||||||
Routes.mealCategories: (context) => const MealCategoryListScreen(),
|
Routes.mealCategories: (context) => const MealCategoryListScreen(),
|
||||||
Routes.mealCategory: (context) => const MealCategoryDetailScreen(),
|
Routes.mealCategory: (context) => const MealCategoryDetailScreen(),
|
||||||
Routes.mealPortionTypes: (context) =>
|
Routes.mealPortionTypes: (context) =>
|
||||||
@ -73,6 +75,8 @@ Future<void> main() async {
|
|||||||
const MealPortionTypeDetailScreen(),
|
const MealPortionTypeDetailScreen(),
|
||||||
Routes.mealSources: (context) => const MealSourceListScreen(),
|
Routes.mealSources: (context) => const MealSourceListScreen(),
|
||||||
Routes.mealSource: (context) => const MealSourceDetailScreen(),
|
Routes.mealSource: (context) => const MealSourceDetailScreen(),
|
||||||
|
Routes.accuracies: (context) => const AccuracyListScreen(),
|
||||||
|
Routes.accuracy: (context) => const AccuracyDetailScreen(),
|
||||||
Routes.bolusProfiles: (context) => const BolusProfileListScreen(),
|
Routes.bolusProfiles: (context) => const BolusProfileListScreen(),
|
||||||
Routes.bolusProfile: (context) => const BolusProfileDetailScreen(),
|
Routes.bolusProfile: (context) => const BolusProfileDetailScreen(),
|
||||||
Routes.basalProfiles: (context) => const BasalProfileListScreen(),
|
Routes.basalProfiles: (context) => const BasalProfileListScreen(),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:diameter/main.dart';
|
import 'package:diameter/main.dart';
|
||||||
import 'package:diameter/models/basal_profile.dart';
|
import 'package:diameter/models/basal_profile.dart';
|
||||||
import 'package:diameter/utils/date_time_utils.dart';
|
import 'package:diameter/utils/date_time_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:objectbox/objectbox.dart';
|
import 'package:objectbox/objectbox.dart';
|
||||||
import 'package:diameter/objectbox.g.dart' show Basal_, BasalProfile_;
|
import 'package:diameter/objectbox.g.dart' show Basal_, BasalProfile_;
|
||||||
|
|
||||||
@ -68,6 +69,27 @@ class Basal {
|
|||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Basal? getRateForTime(DateTime? dateTime) {
|
||||||
|
if (dateTime != null) {
|
||||||
|
final basalProfile = BasalProfile.getActive(dateTime);
|
||||||
|
final time = DateTimeUtils.convertTimeOfDayToDateTime(
|
||||||
|
TimeOfDay.fromDateTime(dateTime));
|
||||||
|
if (basalProfile != null) {
|
||||||
|
final rates = Basal.getAllForProfile(basalProfile.id);
|
||||||
|
final result = rates.where((rate) {
|
||||||
|
DateTime endTime = rate.endTime == dummyDate
|
||||||
|
? rate.endTime.add(const Duration(days: 1))
|
||||||
|
: rate.endTime;
|
||||||
|
return (time.isAfter(rate.startTime) ||
|
||||||
|
time.isAtSameMomentAs(rate.startTime)) &&
|
||||||
|
time.isBefore(endTime);
|
||||||
|
});
|
||||||
|
return result.length != 1 ? null : result.single;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return DateTimeUtils.displayTime(startTime);
|
return DateTimeUtils.displayTime(startTime);
|
||||||
|
@ -53,30 +53,14 @@ class LogEntry {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<DateTime, List<LogEntry>> getDailyEntryMap() {
|
|
||||||
Map<DateTime, List<LogEntry>> dateMap = <DateTime, List<LogEntry>>{};
|
|
||||||
|
|
||||||
QueryBuilder<LogEntry> allByDate = box
|
|
||||||
.query(LogEntry_.deleted.equals(false))
|
|
||||||
..order(LogEntry_.time, flags: Order.descending);
|
|
||||||
List<LogEntry> entries = allByDate.build().find();
|
|
||||||
DateTime? date;
|
|
||||||
|
|
||||||
for (LogEntry entry in entries) {
|
|
||||||
date = DateTime.utc(entry.time.year, entry.time.month, entry.time.day);
|
|
||||||
dateMap.putIfAbsent(date, () => <LogEntry>[]).add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dateMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<LogEntry> getAllForDate(DateTime date) {
|
static List<LogEntry> getAllForDate(DateTime date) {
|
||||||
DateTime startOfDay = DateTime(date.year, date.month, date.day);
|
DateTime startOfDay = DateTime(date.year, date.month, date.day);
|
||||||
DateTime endOfDay = startOfDay.add(const Duration(days: 1));
|
DateTime endOfDay = startOfDay.add(const Duration(days: 1));
|
||||||
QueryBuilder<LogEntry> builder = box.query(LogEntry_.deleted.equals(false))
|
QueryBuilder<LogEntry> builder = box.query(LogEntry_.deleted.equals(false))
|
||||||
..order(LogEntry_.time, flags: Order.descending);
|
..order(LogEntry_.time, flags: Order.descending);
|
||||||
return builder.build().find().where((entry) {
|
return builder.build().find().where((entry) {
|
||||||
return (entry.time.compareTo(startOfDay) >= 0 && entry.time.isBefore(endOfDay));
|
return (entry.time.compareTo(startOfDay) >= 0 &&
|
||||||
|
entry.time.isBefore(endOfDay));
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ class LogEvent {
|
|||||||
|
|
||||||
// methods
|
// methods
|
||||||
static LogEvent? get(int id) => box.get(id);
|
static LogEvent? get(int id) => box.get(id);
|
||||||
|
static List<LogEvent> getAll() => box.getAll();
|
||||||
static void put(LogEvent logEvent) => box.put(logEvent);
|
static void put(LogEvent logEvent) => box.put(logEvent);
|
||||||
|
|
||||||
static void remove(int id) {
|
static void remove(int id) {
|
||||||
@ -91,55 +92,6 @@ class LogEvent {
|
|||||||
return eventsWithEndTime.isNotEmpty;
|
return eventsWithEndTime.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<DateTime, List<LogEvent>> getDailyEntryMap() {
|
|
||||||
Map<DateTime, List<LogEvent>> dateMap = <DateTime, List<LogEvent>>{};
|
|
||||||
Map<DateTime, List<LogEvent>> sortedDateMap = <DateTime, List<LogEvent>>{};
|
|
||||||
|
|
||||||
QueryBuilder<LogEvent> allByDate = box
|
|
||||||
.query(LogEvent_.deleted.equals(false))
|
|
||||||
..order(LogEvent_.time, flags: Order.descending);
|
|
||||||
List<LogEvent> events = allByDate.build().find();
|
|
||||||
|
|
||||||
DateTime? date;
|
|
||||||
|
|
||||||
for (LogEvent event in events) {
|
|
||||||
date = DateTime.utc(event.time.year, event.time.month, event.time.day);
|
|
||||||
LogEvent startEvent = event;
|
|
||||||
startEvent.title =
|
|
||||||
'${event.toString()} ${event.hasEndTime ? '(Start)' : ''}';
|
|
||||||
dateMap.putIfAbsent(date, () => <LogEvent>[]).add(startEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<LogEvent> allByEndDate = box
|
|
||||||
.query(LogEvent_.deleted.equals(false).and(LogEvent_.endTime.notNull()))
|
|
||||||
..order(LogEvent_.endTime, flags: Order.descending);
|
|
||||||
List<LogEvent> endEvents = allByEndDate.build().find();
|
|
||||||
|
|
||||||
for (LogEvent event in endEvents) {
|
|
||||||
date = DateTime.utc(
|
|
||||||
event.endTime!.year, event.endTime!.month, event.endTime!.day);
|
|
||||||
LogEvent endEvent = event;
|
|
||||||
endEvent.isEndEvent = true;
|
|
||||||
endEvent.title = '${event.toString()} (End)';
|
|
||||||
dateMap.putIfAbsent(date, () => <LogEvent>[]).add(endEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
final dates = dateMap.keys.toList();
|
|
||||||
dates.sort();
|
|
||||||
for (DateTime date in dates.reversed) {
|
|
||||||
dateMap[date]!.sort((LogEvent a, LogEvent b) {
|
|
||||||
final dateA = a.isEndEvent ? a.endTime : a.time;
|
|
||||||
final dateB = b.isEndEvent ? b.endTime : b.time;
|
|
||||||
return -(dateA!.compareTo(dateB!));
|
|
||||||
});
|
|
||||||
sortedDateMap
|
|
||||||
.putIfAbsent(date, () => <LogEvent>[])
|
|
||||||
.addAll(dateMap[date]!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortedDateMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<LogEvent> getAllForDate(DateTime date) {
|
static List<LogEvent> getAllForDate(DateTime date) {
|
||||||
DateTime startOfDay = DateTime(date.year, date.month, date.day);
|
DateTime startOfDay = DateTime(date.year, date.month, date.day);
|
||||||
DateTime endOfDay = startOfDay.add(const Duration(days: 1));
|
DateTime endOfDay = startOfDay.add(const Duration(days: 1));
|
||||||
|
@ -101,8 +101,7 @@ class Settings {
|
|||||||
|
|
||||||
static NutritionMeasurement get nutritionMeasurement =>
|
static NutritionMeasurement get nutritionMeasurement =>
|
||||||
NutritionMeasurement.values[get().nutritionMeasurementIndex];
|
NutritionMeasurement.values[get().nutritionMeasurementIndex];
|
||||||
static GlucoseMeasurement get glucoseMeasurement =>
|
static GlucoseMeasurement get glucoseMeasurement => GlucoseMeasurement.values[get().glucoseMeasurementIndex];
|
||||||
GlucoseMeasurement.values[get().glucoseMeasurementIndex];
|
|
||||||
static GlucoseDisplayMode get glucoseDisplayMode =>
|
static GlucoseDisplayMode get glucoseDisplayMode =>
|
||||||
GlucoseDisplayMode.values[get().glucoseDisplayModeIndex];
|
GlucoseDisplayMode.values[get().glucoseDisplayModeIndex];
|
||||||
|
|
||||||
|
46
lib/models/user.dart
Normal file
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/category/categories.dart';
|
||||||
import 'package:diameter/screens/accuracy_list.dart';
|
import 'package:diameter/screens/category/accuracy_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/accuracy_list.dart';
|
||||||
|
import 'package:diameter/screens/category/event_type_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/event_type_list.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_source_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_source_list.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_category_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_category_list.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_portion_type_list.dart';
|
||||||
import 'package:diameter/screens/basal/basal_detail.dart';
|
import 'package:diameter/screens/basal/basal_detail.dart';
|
||||||
import 'package:diameter/screens/basal/basal_profile_detail.dart';
|
import 'package:diameter/screens/basal/basal_profile_detail.dart';
|
||||||
import 'package:diameter/screens/basal/basal_profile_list.dart';
|
import 'package:diameter/screens/basal/basal_profile_list.dart';
|
||||||
import 'package:diameter/screens/bolus/bolus_detail.dart';
|
import 'package:diameter/screens/bolus/bolus_detail.dart';
|
||||||
import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
|
import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
|
||||||
import 'package:diameter/screens/bolus/bolus_profile_list.dart';
|
import 'package:diameter/screens/bolus/bolus_profile_list.dart';
|
||||||
import 'package:diameter/screens/log/log.dart';
|
import 'package:diameter/screens/log/log_entry/log_bolus_detail.dart';
|
||||||
|
import 'package:diameter/screens/log/log_overview.dart';
|
||||||
|
import 'package:diameter/screens/reports/daily_chart.dart';
|
||||||
import 'package:diameter/screens/log/log_entry/log_entry.dart';
|
import 'package:diameter/screens/log/log_entry/log_entry.dart';
|
||||||
import 'package:diameter/screens/log/log_event/log_event_detail.dart';
|
import 'package:diameter/screens/log/log_event_detail.dart';
|
||||||
import 'package:diameter/screens/log/log_event/log_event_list.dart';
|
import 'package:diameter/screens/log/log_event_list.dart';
|
||||||
import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
|
|
||||||
import 'package:diameter/screens/log/log_event/log_event_type_list.dart';
|
|
||||||
import 'package:diameter/screens/log/log_entry/log_meal_detail.dart';
|
import 'package:diameter/screens/log/log_entry/log_meal_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_category_list.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_detail.dart';
|
import 'package:diameter/screens/meal/meal_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_list.dart';
|
import 'package:diameter/screens/meal/meal_list.dart';
|
||||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
import 'package:diameter/screens/reports/export.dart';
|
||||||
import 'package:diameter/screens/meal/meal_portion_type_list.dart';
|
import 'package:diameter/screens/reports/reports.dart';
|
||||||
import 'package:diameter/screens/meal/meal_source_detail.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_source_list.dart';
|
|
||||||
import 'package:diameter/screens/recipe/recipe_detail.dart';
|
|
||||||
import 'package:diameter/screens/recipe/recipe_list.dart';
|
|
||||||
import 'package:diameter/settings.dart';
|
import 'package:diameter/settings.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -39,30 +42,38 @@ class Routes {
|
|||||||
|
|
||||||
static const String log = LogScreen.routeName;
|
static const String log = LogScreen.routeName;
|
||||||
static const String logEntry = LogEntryScreen.routeName;
|
static const String logEntry = LogEntryScreen.routeName;
|
||||||
static const String logEvent = LogEventDetailScreen.routeName;
|
|
||||||
static const String logMeal = LogMealDetailScreen.routeName;
|
static const String logMeal = LogMealDetailScreen.routeName;
|
||||||
static const List<String> logEntryRoutes = [logEntry, logEvent, logMeal];
|
static const String logBolus = LogBolusDetailScreen.routeName;
|
||||||
static const String logEventType = EventTypeDetailScreen.routeName;
|
static const List<String> logEntryRoutes = [log, logEntry, logMeal, logBolus];
|
||||||
static const String logEventTypes = LogEventTypeListScreen.routeName;
|
|
||||||
static const List<String> logEventTypeRoutes = [logEventType, logEventTypes];
|
|
||||||
static const String events = LogEventListScreen.routeName;
|
|
||||||
|
|
||||||
static const String recipe = RecipeDetailScreen.routeName;
|
static const String logEvent = LogEventDetailScreen.routeName;
|
||||||
static const String recipes = RecipeListScreen.routeName;
|
static const String logEvents = LogEventListScreen.routeName;
|
||||||
static const List<String> recipeRoutes = [recipe, recipes];
|
static const List<String> logEventRoutes = [logEvent, logEvents];
|
||||||
|
|
||||||
|
static const String reports = ReportsOverviewScreen.routeName;
|
||||||
|
static const String export = ExportDialog.routeName;
|
||||||
|
static const String dailyChart = DailyChart.routeName;
|
||||||
|
static const List<String> reportRoutes = [export, reports, dailyChart];
|
||||||
|
|
||||||
static const String meal = MealDetailScreen.routeName;
|
static const String meal = MealDetailScreen.routeName;
|
||||||
static const String meals = MealListScreen.routeName;
|
static const String meals = MealListScreen.routeName;
|
||||||
static const List<String> mealRoutes = [meal, meals];
|
static const List<String> mealRoutes = [meal, meals];
|
||||||
|
|
||||||
|
static const String eventType = EventTypeDetailScreen.routeName;
|
||||||
|
static const String eventTypes = EventTypeListScreen.routeName;
|
||||||
|
static const List<String> eventTypeRoutes = [eventType, eventTypes];
|
||||||
|
|
||||||
static const String mealCategory = MealCategoryDetailScreen.routeName;
|
static const String mealCategory = MealCategoryDetailScreen.routeName;
|
||||||
static const String mealCategories = MealCategoryListScreen.routeName;
|
static const String mealCategories = MealCategoryListScreen.routeName;
|
||||||
static const List<String> mealCategoryRoutes = [mealCategory, mealCategories];
|
static const List<String> mealCategoryRoutes = [mealCategory, mealCategories];
|
||||||
|
|
||||||
static const String mealPortionType = MealPortionTypeDetailScreen.routeName;
|
static const String mealPortionType = MealPortionTypeDetailScreen.routeName;
|
||||||
static const String mealPortionTypes = MealPortionTypeListScreen.routeName;
|
static const String mealPortionTypes = MealPortionTypeListScreen.routeName;
|
||||||
static const List<String> mealPortionTypeRoutes = [
|
static const List<String> mealPortionTypeRoutes = [
|
||||||
mealPortionType,
|
mealPortionType,
|
||||||
mealPortionTypes
|
mealPortionTypes
|
||||||
];
|
];
|
||||||
|
|
||||||
static const String mealSource = MealSourceDetailScreen.routeName;
|
static const String mealSource = MealSourceDetailScreen.routeName;
|
||||||
static const String mealSources = MealSourceListScreen.routeName;
|
static const String mealSources = MealSourceListScreen.routeName;
|
||||||
static const List<String> mealSourceRoutes = [mealSource, mealSources];
|
static const List<String> mealSourceRoutes = [mealSource, mealSources];
|
||||||
@ -71,6 +82,16 @@ class Routes {
|
|||||||
static const String accuracies = AccuracyListScreen.routeName;
|
static const String accuracies = AccuracyListScreen.routeName;
|
||||||
static const List<String> accuracyRoutes = [accuracy, accuracies];
|
static const List<String> accuracyRoutes = [accuracy, accuracies];
|
||||||
|
|
||||||
|
static const String category = CategoryOverviewScreen.routeName;
|
||||||
|
static const List<String> categoryRoutes = [
|
||||||
|
category,
|
||||||
|
...eventTypeRoutes,
|
||||||
|
...mealCategoryRoutes,
|
||||||
|
...mealPortionTypeRoutes,
|
||||||
|
...mealSourceRoutes,
|
||||||
|
...accuracyRoutes
|
||||||
|
];
|
||||||
|
|
||||||
static const String settings = SettingsScreen.routeName;
|
static const String settings = SettingsScreen.routeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,18 +130,18 @@ class _NavigationState extends State<Navigation> {
|
|||||||
title: const Text('Log Events'),
|
title: const Text('Log Events'),
|
||||||
leading: const Icon(Icons.event),
|
leading: const Icon(Icons.event),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
selectDestination(Routes.events);
|
selectDestination(Routes.logEvents);
|
||||||
},
|
},
|
||||||
selected: widget.currentLocation == Routes.events,
|
selected: widget.currentLocation == Routes.logEvents,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Reports'),
|
||||||
|
leading: const Icon(Icons.show_chart),
|
||||||
|
onTap: () {
|
||||||
|
selectDestination(Routes.reports);
|
||||||
|
},
|
||||||
|
selected: Routes.reportRoutes.contains(widget.currentLocation),
|
||||||
),
|
),
|
||||||
// ListTile(
|
|
||||||
// title: const Text('Recipes'),
|
|
||||||
// leading: const Icon(Icons.local_dining),
|
|
||||||
// onTap: () {
|
|
||||||
// selectDestination(Routes.recipes);
|
|
||||||
// },
|
|
||||||
// selected: Routes.recipeRoutes.contains(widget.currentLocation),
|
|
||||||
// ),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Meals'),
|
title: const Text('Meals'),
|
||||||
leading: const Icon(Icons.dinner_dining),
|
leading: const Icon(Icons.dinner_dining),
|
||||||
@ -129,46 +150,6 @@ class _NavigationState extends State<Navigation> {
|
|||||||
},
|
},
|
||||||
selected: Routes.mealRoutes.contains(widget.currentLocation),
|
selected: Routes.mealRoutes.contains(widget.currentLocation),
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: const Text('Meal Categories'),
|
|
||||||
leading: const Icon(Icons.category),
|
|
||||||
onTap: () {
|
|
||||||
selectDestination(Routes.mealCategories);
|
|
||||||
},
|
|
||||||
selected: Routes.mealCategoryRoutes.contains(widget.currentLocation),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Meal Portion Types'),
|
|
||||||
leading: const Icon(Icons.pie_chart),
|
|
||||||
onTap: () {
|
|
||||||
selectDestination(Routes.mealPortionTypes);
|
|
||||||
},
|
|
||||||
selected: Routes.mealPortionTypeRoutes.contains(widget.currentLocation),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Meal Sources'),
|
|
||||||
leading: const Icon(Icons.local_grocery_store),
|
|
||||||
onTap: () {
|
|
||||||
selectDestination(Routes.mealSources);
|
|
||||||
},
|
|
||||||
selected: Routes.mealSourceRoutes.contains(widget.currentLocation),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Accuracies'),
|
|
||||||
leading: const Icon(Icons.architecture),
|
|
||||||
onTap: () {
|
|
||||||
selectDestination(Routes.accuracies);
|
|
||||||
},
|
|
||||||
selected: Routes.accuracyRoutes.contains(widget.currentLocation),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Log Event Types'),
|
|
||||||
leading: const Icon(Icons.event),
|
|
||||||
onTap: () {
|
|
||||||
selectDestination(Routes.logEventTypes);
|
|
||||||
},
|
|
||||||
selected: Routes.logEventTypeRoutes.contains(widget.currentLocation),
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Basal Profiles'),
|
title: const Text('Basal Profiles'),
|
||||||
leading: const Icon(Icons.access_time),
|
leading: const Icon(Icons.access_time),
|
||||||
@ -185,6 +166,14 @@ class _NavigationState extends State<Navigation> {
|
|||||||
},
|
},
|
||||||
selected: Routes.bolusRoutes.contains(widget.currentLocation),
|
selected: Routes.bolusRoutes.contains(widget.currentLocation),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Categorization'),
|
||||||
|
leading: const Icon(Icons.category),
|
||||||
|
onTap: () {
|
||||||
|
selectDestination(Routes.category);
|
||||||
|
},
|
||||||
|
selected: Routes.categoryRoutes.contains(widget.currentLocation),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Settings'),
|
title: const Text('Settings'),
|
||||||
leading: const Icon(Icons.settings),
|
leading: const Icon(Icons.settings),
|
||||||
|
@ -818,7 +818,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "16:3989341091218179227",
|
"id": "16:3989341091218179227",
|
||||||
"lastPropertyId": "23:3611447442844013652",
|
"lastPropertyId": "27:3553639710779248831",
|
||||||
"name": "Settings",
|
"name": "Settings",
|
||||||
"flags": 2,
|
"flags": 2,
|
||||||
"properties": [
|
"properties": [
|
||||||
@ -892,6 +892,26 @@
|
|||||||
"id": "23:3611447442844013652",
|
"id": "23:3611447442844013652",
|
||||||
"name": "useDarkTheme",
|
"name": "useDarkTheme",
|
||||||
"type": 1
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "24:3003519517071020397",
|
||||||
|
"name": "insulinIncrements",
|
||||||
|
"type": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "25:402003851844402390",
|
||||||
|
"name": "nutritionIncrements",
|
||||||
|
"type": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "26:3104312506075410259",
|
||||||
|
"name": "mmolPerLIncrements",
|
||||||
|
"type": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "27:3553639710779248831",
|
||||||
|
"name": "amountIncrements",
|
||||||
|
"type": 8
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": []
|
"relations": []
|
||||||
@ -1024,9 +1044,44 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": []
|
"relations": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "20:4237292160526261631",
|
||||||
|
"lastPropertyId": "5:3477783279940695158",
|
||||||
|
"name": "User",
|
||||||
|
"flags": 2,
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "1:785183194548024772",
|
||||||
|
"name": "id",
|
||||||
|
"type": 6,
|
||||||
|
"flags": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2:2837394475791909830",
|
||||||
|
"name": "deleted",
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3:6612000015066380372",
|
||||||
|
"name": "name",
|
||||||
|
"type": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4:861275034953202445",
|
||||||
|
"name": "email",
|
||||||
|
"type": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5:3477783279940695158",
|
||||||
|
"name": "password",
|
||||||
|
"type": 9
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastEntityId": "19:6950311793136068892",
|
"relations": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastEntityId": "20:4237292160526261631",
|
||||||
"lastIndexId": "31:3277019237664417023",
|
"lastIndexId": "31:3277019237664417023",
|
||||||
"lastRelationId": "0:0",
|
"lastRelationId": "0:0",
|
||||||
"lastSequenceId": "0:0",
|
"lastSequenceId": "0:0",
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// This code was generated by ObjectBox. To update it run the generator again:
|
||||||
|
// With a Flutter package, run `flutter pub run build_runner build`.
|
||||||
|
// With a Dart package, run `dart run build_runner build`.
|
||||||
|
// See also https://docs.objectbox.io/getting-started#generate-objectbox-code
|
||||||
|
|
||||||
// ignore_for_file: camel_case_types
|
// ignore_for_file: camel_case_types
|
||||||
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:objectbox/flatbuffers/flat_buffers.dart' as fb;
|
import 'package:flat_buffers/flat_buffers.dart' as fb;
|
||||||
import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality
|
import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality
|
||||||
import 'package:objectbox/objectbox.dart';
|
import 'package:objectbox/objectbox.dart';
|
||||||
import 'package:objectbox_sync_flutter_libs/objectbox_sync_flutter_libs.dart';
|
import 'package:objectbox_sync_flutter_libs/objectbox_sync_flutter_libs.dart';
|
||||||
@ -27,6 +31,7 @@ import 'models/meal_portion_type.dart';
|
|||||||
import 'models/meal_source.dart';
|
import 'models/meal_source.dart';
|
||||||
import 'models/recipe.dart';
|
import 'models/recipe.dart';
|
||||||
import 'models/settings.dart';
|
import 'models/settings.dart';
|
||||||
|
import 'models/user.dart';
|
||||||
|
|
||||||
export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
|
export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
|
||||||
|
|
||||||
@ -807,7 +812,7 @@ final _entities = <ModelEntity>[
|
|||||||
ModelEntity(
|
ModelEntity(
|
||||||
id: const IdUid(16, 3989341091218179227),
|
id: const IdUid(16, 3989341091218179227),
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
lastPropertyId: const IdUid(23, 3611447442844013652),
|
lastPropertyId: const IdUid(27, 3553639710779248831),
|
||||||
flags: 2,
|
flags: 2,
|
||||||
properties: <ModelProperty>[
|
properties: <ModelProperty>[
|
||||||
ModelProperty(
|
ModelProperty(
|
||||||
@ -879,6 +884,26 @@ final _entities = <ModelEntity>[
|
|||||||
id: const IdUid(23, 3611447442844013652),
|
id: const IdUid(23, 3611447442844013652),
|
||||||
name: 'useDarkTheme',
|
name: 'useDarkTheme',
|
||||||
type: 1,
|
type: 1,
|
||||||
|
flags: 0),
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(24, 3003519517071020397),
|
||||||
|
name: 'insulinIncrements',
|
||||||
|
type: 8,
|
||||||
|
flags: 0),
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(25, 402003851844402390),
|
||||||
|
name: 'nutritionIncrements',
|
||||||
|
type: 8,
|
||||||
|
flags: 0),
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(26, 3104312506075410259),
|
||||||
|
name: 'mmolPerLIncrements',
|
||||||
|
type: 8,
|
||||||
|
flags: 0),
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(27, 3553639710779248831),
|
||||||
|
name: 'amountIncrements',
|
||||||
|
type: 8,
|
||||||
flags: 0)
|
flags: 0)
|
||||||
],
|
],
|
||||||
relations: <ModelRelation>[],
|
relations: <ModelRelation>[],
|
||||||
@ -1005,6 +1030,40 @@ final _entities = <ModelEntity>[
|
|||||||
relationTarget: 'Meal')
|
relationTarget: 'Meal')
|
||||||
],
|
],
|
||||||
relations: <ModelRelation>[],
|
relations: <ModelRelation>[],
|
||||||
|
backlinks: <ModelBacklink>[]),
|
||||||
|
ModelEntity(
|
||||||
|
id: const IdUid(20, 4237292160526261631),
|
||||||
|
name: 'User',
|
||||||
|
lastPropertyId: const IdUid(5, 3477783279940695158),
|
||||||
|
flags: 2,
|
||||||
|
properties: <ModelProperty>[
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(1, 785183194548024772),
|
||||||
|
name: 'id',
|
||||||
|
type: 6,
|
||||||
|
flags: 1),
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(2, 2837394475791909830),
|
||||||
|
name: 'deleted',
|
||||||
|
type: 1,
|
||||||
|
flags: 0),
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(3, 6612000015066380372),
|
||||||
|
name: 'name',
|
||||||
|
type: 9,
|
||||||
|
flags: 0),
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(4, 861275034953202445),
|
||||||
|
name: 'email',
|
||||||
|
type: 9,
|
||||||
|
flags: 0),
|
||||||
|
ModelProperty(
|
||||||
|
id: const IdUid(5, 3477783279940695158),
|
||||||
|
name: 'password',
|
||||||
|
type: 9,
|
||||||
|
flags: 0)
|
||||||
|
],
|
||||||
|
relations: <ModelRelation>[],
|
||||||
backlinks: <ModelBacklink>[])
|
backlinks: <ModelBacklink>[])
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -1028,7 +1087,7 @@ Future<Store> openStore(
|
|||||||
ModelDefinition getObjectBoxModel() {
|
ModelDefinition getObjectBoxModel() {
|
||||||
final model = ModelInfo(
|
final model = ModelInfo(
|
||||||
entities: _entities,
|
entities: _entities,
|
||||||
lastEntityId: const IdUid(19, 6950311793136068892),
|
lastEntityId: const IdUid(20, 4237292160526261631),
|
||||||
lastIndexId: const IdUid(31, 3277019237664417023),
|
lastIndexId: const IdUid(31, 3277019237664417023),
|
||||||
lastRelationId: const IdUid(0, 0),
|
lastRelationId: const IdUid(0, 0),
|
||||||
lastSequenceId: const IdUid(0, 0),
|
lastSequenceId: const IdUid(0, 0),
|
||||||
@ -1140,11 +1199,11 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 12, false),
|
.vTableGet(buffer, rootOffset, 12, false),
|
||||||
name:
|
name: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
active:
|
active:
|
||||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 10));
|
.vTableGetNullable(buffer, rootOffset, 10));
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@ -1225,11 +1284,11 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 12, false),
|
.vTableGet(buffer, rootOffset, 12, false),
|
||||||
name:
|
name: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
active:
|
active:
|
||||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 10));
|
.vTableGetNullable(buffer, rootOffset, 10));
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@ -1272,7 +1331,7 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
.vTableGetNullable(buffer, rootOffset, 10),
|
.vTableGetNullable(buffer, rootOffset, 10),
|
||||||
glucoseTrend: const fb.Float64Reader()
|
glucoseTrend: const fb.Float64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 22),
|
.vTableGetNullable(buffer, rootOffset, 22),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 18));
|
.vTableGetNullable(buffer, rootOffset, 18));
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@ -1321,7 +1380,7 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
.vTableGet(buffer, rootOffset, 10, false),
|
.vTableGet(buffer, rootOffset, 10, false),
|
||||||
reminderDuration: const fb.Int64Reader()
|
reminderDuration: const fb.Int64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 26),
|
.vTableGetNullable(buffer, rootOffset, 26),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 12));
|
.vTableGetNullable(buffer, rootOffset, 12));
|
||||||
object.eventType.targetId =
|
object.eventType.targetId =
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
||||||
@ -1367,13 +1426,13 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 14, false),
|
.vTableGet(buffer, rootOffset, 14, false),
|
||||||
value:
|
value: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
hasEndTime:
|
hasEndTime:
|
||||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||||
defaultReminderDuration: const fb.Int64Reader()
|
defaultReminderDuration: const fb.Int64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 10),
|
.vTableGetNullable(buffer, rootOffset, 10),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 12));
|
.vTableGetNullable(buffer, rootOffset, 12));
|
||||||
object.bolusProfile.targetId =
|
object.bolusProfile.targetId =
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
|
||||||
@ -1431,8 +1490,8 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 36, false),
|
.vTableGet(buffer, rootOffset, 36, false),
|
||||||
value:
|
value: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
amount:
|
amount:
|
||||||
const fb.Float64Reader().vTableGet(buffer, rootOffset, 38, 0),
|
const fb.Float64Reader().vTableGet(buffer, rootOffset, 38, 0),
|
||||||
carbsRatio: const fb.Float64Reader()
|
carbsRatio: const fb.Float64Reader()
|
||||||
@ -1441,7 +1500,7 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
.vTableGetNullable(buffer, rootOffset, 10),
|
.vTableGetNullable(buffer, rootOffset, 10),
|
||||||
totalCarbs: const fb.Float64Reader()
|
totalCarbs: const fb.Float64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 40),
|
.vTableGetNullable(buffer, rootOffset, 40),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 20))
|
.vTableGetNullable(buffer, rootOffset, 20))
|
||||||
..bolus = const fb.Float64Reader()
|
..bolus = const fb.Float64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 14);
|
.vTableGetNullable(buffer, rootOffset, 14);
|
||||||
@ -1512,8 +1571,8 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 30, false),
|
.vTableGet(buffer, rootOffset, 30, false),
|
||||||
value:
|
value: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
carbsRatio: const fb.Float64Reader()
|
carbsRatio: const fb.Float64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 8),
|
.vTableGetNullable(buffer, rootOffset, 8),
|
||||||
portionSize: const fb.Float64Reader()
|
portionSize: const fb.Float64Reader()
|
||||||
@ -1524,7 +1583,7 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
.vTableGetNullable(buffer, rootOffset, 14),
|
.vTableGetNullable(buffer, rootOffset, 14),
|
||||||
delayedBolusPercentage: const fb.Float64Reader()
|
delayedBolusPercentage: const fb.Float64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 32),
|
.vTableGetNullable(buffer, rootOffset, 32),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 18));
|
.vTableGetNullable(buffer, rootOffset, 18));
|
||||||
object.mealSource.targetId =
|
object.mealSource.targetId =
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
|
||||||
@ -1571,9 +1630,9 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 10, false),
|
.vTableGet(buffer, rootOffset, 10, false),
|
||||||
value:
|
value: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 8));
|
.vTableGetNullable(buffer, rootOffset, 8));
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@ -1606,9 +1665,9 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 10, false),
|
.vTableGet(buffer, rootOffset, 10, false),
|
||||||
value:
|
value: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 8));
|
.vTableGetNullable(buffer, rootOffset, 8));
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@ -1650,9 +1709,9 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 18, false),
|
.vTableGet(buffer, rootOffset, 18, false),
|
||||||
value:
|
value: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 8));
|
.vTableGetNullable(buffer, rootOffset, 8));
|
||||||
object.defaultMealCategory.targetId =
|
object.defaultMealCategory.targetId =
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0);
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0);
|
||||||
@ -1728,7 +1787,7 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
.vTableGetNullable(buffer, rootOffset, 38),
|
.vTableGetNullable(buffer, rootOffset, 38),
|
||||||
setManually: const fb.BoolReader()
|
setManually: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 16, false),
|
.vTableGet(buffer, rootOffset, 16, false),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 18));
|
.vTableGetNullable(buffer, rootOffset, 18));
|
||||||
object.logEntry.targetId =
|
object.logEntry.targetId =
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
|
||||||
@ -1772,15 +1831,15 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 16, false),
|
.vTableGet(buffer, rootOffset, 16, false),
|
||||||
value:
|
value: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
forCarbsRatio:
|
forCarbsRatio:
|
||||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||||
forPortionSize: const fb.BoolReader()
|
forPortionSize: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 10, false),
|
.vTableGet(buffer, rootOffset, 10, false),
|
||||||
confidenceRating: const fb.Int64Reader()
|
confidenceRating: const fb.Int64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 12),
|
.vTableGetNullable(buffer, rootOffset, 12),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 14));
|
.vTableGetNullable(buffer, rootOffset, 14));
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@ -1802,7 +1861,7 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
final longTimeFormatOffset = object.longTimeFormat == null
|
final longTimeFormatOffset = object.longTimeFormat == null
|
||||||
? null
|
? null
|
||||||
: fbb.writeString(object.longTimeFormat!);
|
: fbb.writeString(object.longTimeFormat!);
|
||||||
fbb.startTable(24);
|
fbb.startTable(28);
|
||||||
fbb.addInt64(0, object.id);
|
fbb.addInt64(0, object.id);
|
||||||
fbb.addOffset(1, dateFormatOffset);
|
fbb.addOffset(1, dateFormatOffset);
|
||||||
fbb.addOffset(2, longDateFormatOffset);
|
fbb.addOffset(2, longDateFormatOffset);
|
||||||
@ -1817,6 +1876,10 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
fbb.addInt64(20, object.targetGlucoseMgPerDl);
|
fbb.addInt64(20, object.targetGlucoseMgPerDl);
|
||||||
fbb.addFloat64(21, object.targetGlucoseMmolPerL);
|
fbb.addFloat64(21, object.targetGlucoseMmolPerL);
|
||||||
fbb.addBool(22, object.useDarkTheme);
|
fbb.addBool(22, object.useDarkTheme);
|
||||||
|
fbb.addFloat64(23, object.insulinIncrements);
|
||||||
|
fbb.addFloat64(24, object.nutritionIncrements);
|
||||||
|
fbb.addFloat64(25, object.mmolPerLIncrements);
|
||||||
|
fbb.addFloat64(26, object.amountIncrements);
|
||||||
fbb.finish(fbb.endTable());
|
fbb.finish(fbb.endTable());
|
||||||
return object.id;
|
return object.id;
|
||||||
},
|
},
|
||||||
@ -1832,24 +1895,27 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 40, 0),
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 40, 0),
|
||||||
glucoseMeasurementIndex:
|
glucoseMeasurementIndex:
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 42, 0),
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 42, 0),
|
||||||
dateFormat:
|
insulinIncrements:
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
const fb.Float64Reader().vTableGet(buffer, rootOffset, 50, 0),
|
||||||
longDateFormat: const fb.StringReader()
|
nutritionIncrements:
|
||||||
|
const fb.Float64Reader().vTableGet(buffer, rootOffset, 52, 0),
|
||||||
|
mmolPerLIncrements:
|
||||||
|
const fb.Float64Reader().vTableGet(buffer, rootOffset, 54, 0),
|
||||||
|
amountIncrements:
|
||||||
|
const fb.Float64Reader().vTableGet(buffer, rootOffset, 56, 0),
|
||||||
|
dateFormat: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
|
longDateFormat: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 8),
|
.vTableGetNullable(buffer, rootOffset, 8),
|
||||||
timeFormat:
|
timeFormat: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 10, ''),
|
.vTableGet(buffer, rootOffset, 10, ''),
|
||||||
longTimeFormat: const fb.StringReader()
|
longTimeFormat: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 12),
|
.vTableGetNullable(buffer, rootOffset, 12),
|
||||||
showConfirmationDialogOnCancel: const fb.BoolReader()
|
showConfirmationDialogOnCancel: const fb.BoolReader().vTableGet(buffer, rootOffset, 14, false),
|
||||||
.vTableGet(buffer, rootOffset, 14, false),
|
showConfirmationDialogOnDelete: const fb.BoolReader().vTableGet(buffer, rootOffset, 16, false),
|
||||||
showConfirmationDialogOnDelete: const fb.BoolReader()
|
showConfirmationDialogOnStopEvent: const fb.BoolReader().vTableGet(buffer, rootOffset, 18, false),
|
||||||
.vTableGet(buffer, rootOffset, 16, false),
|
targetGlucoseMgPerDl: const fb.Int64Reader().vTableGet(buffer, rootOffset, 44, 0),
|
||||||
showConfirmationDialogOnStopEvent: const fb.BoolReader()
|
targetGlucoseMmolPerL: const fb.Float64Reader().vTableGet(buffer, rootOffset, 46, 0),
|
||||||
.vTableGet(buffer, rootOffset, 18, false),
|
|
||||||
targetGlucoseMgPerDl:
|
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 44, 0),
|
|
||||||
targetGlucoseMmolPerL:
|
|
||||||
const fb.Float64Reader().vTableGet(buffer, rootOffset, 46, 0),
|
|
||||||
useDarkTheme: const fb.BoolReader().vTableGet(buffer, rootOffset, 48, false));
|
useDarkTheme: const fb.BoolReader().vTableGet(buffer, rootOffset, 48, false));
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
@ -1925,11 +1991,11 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted:
|
deleted:
|
||||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 6, false),
|
const fb.BoolReader().vTableGet(buffer, rootOffset, 6, false),
|
||||||
name:
|
name: const fb.StringReader(asciiOptimization: true)
|
||||||
const fb.StringReader().vTableGet(buffer, rootOffset, 8, ''),
|
.vTableGet(buffer, rootOffset, 8, ''),
|
||||||
servings: const fb.Float64Reader()
|
servings: const fb.Float64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 24),
|
.vTableGetNullable(buffer, rootOffset, 24),
|
||||||
notes: const fb.StringReader()
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
.vTableGetNullable(buffer, rootOffset, 20));
|
.vTableGetNullable(buffer, rootOffset, 20));
|
||||||
object.portion.targetId =
|
object.portion.targetId =
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||||
@ -1972,6 +2038,44 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0);
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0);
|
||||||
object.ingredient.attach(store);
|
object.ingredient.attach(store);
|
||||||
return object;
|
return object;
|
||||||
|
}),
|
||||||
|
User: EntityDefinition<User>(
|
||||||
|
model: _entities[18],
|
||||||
|
toOneRelations: (User object) => [],
|
||||||
|
toManyRelations: (User object) => {},
|
||||||
|
getId: (User object) => object.id,
|
||||||
|
setId: (User object, int id) {
|
||||||
|
object.id = id;
|
||||||
|
},
|
||||||
|
objectToFB: (User object, fb.Builder fbb) {
|
||||||
|
final nameOffset = fbb.writeString(object.name);
|
||||||
|
final emailOffset = fbb.writeString(object.email);
|
||||||
|
final passwordOffset = fbb.writeString(object.password);
|
||||||
|
fbb.startTable(6);
|
||||||
|
fbb.addInt64(0, object.id);
|
||||||
|
fbb.addBool(1, object.deleted);
|
||||||
|
fbb.addOffset(2, nameOffset);
|
||||||
|
fbb.addOffset(3, emailOffset);
|
||||||
|
fbb.addOffset(4, passwordOffset);
|
||||||
|
fbb.finish(fbb.endTable());
|
||||||
|
return object.id;
|
||||||
|
},
|
||||||
|
objectFromFB: (Store store, ByteData fbData) {
|
||||||
|
final buffer = fb.BufferContext(fbData);
|
||||||
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
|
final object = User(
|
||||||
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
|
deleted:
|
||||||
|
const fb.BoolReader().vTableGet(buffer, rootOffset, 6, false),
|
||||||
|
name: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGet(buffer, rootOffset, 8, ''),
|
||||||
|
email: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGet(buffer, rootOffset, 10, ''),
|
||||||
|
password: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGet(buffer, rootOffset, 12, ''));
|
||||||
|
|
||||||
|
return object;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2537,6 +2641,22 @@ class Settings_ {
|
|||||||
/// see [Settings.useDarkTheme]
|
/// see [Settings.useDarkTheme]
|
||||||
static final useDarkTheme =
|
static final useDarkTheme =
|
||||||
QueryBooleanProperty<Settings>(_entities[14].properties[13]);
|
QueryBooleanProperty<Settings>(_entities[14].properties[13]);
|
||||||
|
|
||||||
|
/// see [Settings.insulinIncrements]
|
||||||
|
static final insulinIncrements =
|
||||||
|
QueryDoubleProperty<Settings>(_entities[14].properties[14]);
|
||||||
|
|
||||||
|
/// see [Settings.nutritionIncrements]
|
||||||
|
static final nutritionIncrements =
|
||||||
|
QueryDoubleProperty<Settings>(_entities[14].properties[15]);
|
||||||
|
|
||||||
|
/// see [Settings.mmolPerLIncrements]
|
||||||
|
static final mmolPerLIncrements =
|
||||||
|
QueryDoubleProperty<Settings>(_entities[14].properties[16]);
|
||||||
|
|
||||||
|
/// see [Settings.amountIncrements]
|
||||||
|
static final amountIncrements =
|
||||||
|
QueryDoubleProperty<Settings>(_entities[14].properties[17]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [GlucoseTarget] entity fields to define ObjectBox queries.
|
/// [GlucoseTarget] entity fields to define ObjectBox queries.
|
||||||
@ -2616,3 +2736,23 @@ class Ingredient_ {
|
|||||||
static final ingredient =
|
static final ingredient =
|
||||||
QueryRelationToOne<Ingredient, Meal>(_entities[17].properties[4]);
|
QueryRelationToOne<Ingredient, Meal>(_entities[17].properties[4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [User] entity fields to define ObjectBox queries.
|
||||||
|
class User_ {
|
||||||
|
/// see [User.id]
|
||||||
|
static final id = QueryIntegerProperty<User>(_entities[18].properties[0]);
|
||||||
|
|
||||||
|
/// see [User.deleted]
|
||||||
|
static final deleted =
|
||||||
|
QueryBooleanProperty<User>(_entities[18].properties[1]);
|
||||||
|
|
||||||
|
/// see [User.name]
|
||||||
|
static final name = QueryStringProperty<User>(_entities[18].properties[2]);
|
||||||
|
|
||||||
|
/// see [User.email]
|
||||||
|
static final email = QueryStringProperty<User>(_entities[18].properties[3]);
|
||||||
|
|
||||||
|
/// see [User.password]
|
||||||
|
static final password =
|
||||||
|
QueryStringProperty<User>(_entities[18].properties[4]);
|
||||||
|
}
|
||||||
|
@ -156,15 +156,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
BooleanFormField(
|
BooleanFormField(
|
||||||
value: _forCarbsRatio,
|
icon: const Icon(Icons.square_foot),
|
||||||
label: 'for carbs ratio',
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_forCarbsRatio = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
BooleanFormField(
|
|
||||||
value: _forPortionSize,
|
value: _forPortionSize,
|
||||||
label: 'for portion size',
|
label: 'for portion size',
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@ -173,6 +165,16 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
BooleanFormField(
|
||||||
|
icon: const Icon(Icons.pie_chart),
|
||||||
|
value: _forCarbsRatio,
|
||||||
|
label: 'for carbs ratio',
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_forCarbsRatio = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
NumberFormField(
|
NumberFormField(
|
||||||
controller: _confidenceRatingController,
|
controller: _confidenceRatingController,
|
||||||
label: 'Confidence Rating',
|
label: 'Confidence Rating',
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:diameter/utils/dialog_utils.dart';
|
import 'package:diameter/utils/dialog_utils.dart';
|
||||||
import 'package:diameter/models/settings.dart';
|
import 'package:diameter/models/settings.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
import 'package:diameter/screens/accuracy_detail.dart';
|
import 'package:diameter/screens/category/accuracy_detail.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:diameter/models/accuracy.dart';
|
import 'package:diameter/models/accuracy.dart';
|
||||||
|
|
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
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
import 'package:diameter/models/log_event_type.dart';
|
import 'package:diameter/models/log_event_type.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
|
import 'package:diameter/screens/category/event_type_detail.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class LogEventTypeListScreen extends StatefulWidget {
|
class EventTypeListScreen extends StatefulWidget {
|
||||||
static const String routeName = '/log-event-types';
|
static const String routeName = '/log-event-types';
|
||||||
const LogEventTypeListScreen({Key? key}) : super(key: key);
|
const EventTypeListScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_LogEventTypeListScreenState createState() => _LogEventTypeListScreenState();
|
_EventTypeListScreenState createState() => _EventTypeListScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
|
class _EventTypeListScreenState extends State<EventTypeListScreen> {
|
||||||
List<LogEventType> _logEventTypes = [];
|
List<LogEventType> _logEventTypes = [];
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
@ -52,7 +52,7 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
|
|||||||
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
||||||
]),
|
]),
|
||||||
drawer:
|
drawer:
|
||||||
const Navigation(currentLocation: LogEventTypeListScreen.routeName),
|
const Navigation(currentLocation: EventTypeListScreen.routeName),
|
||||||
body: Column(
|
body: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:diameter/utils/dialog_utils.dart';
|
import 'package:diameter/utils/dialog_utils.dart';
|
||||||
import 'package:diameter/models/settings.dart';
|
import 'package:diameter/models/settings.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
import 'package:diameter/screens/category/meal_category_detail.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:diameter/models/meal_category.dart';
|
import 'package:diameter/models/meal_category.dart';
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:diameter/utils/dialog_utils.dart';
|
import 'package:diameter/utils/dialog_utils.dart';
|
||||||
import 'package:diameter/models/settings.dart';
|
import 'package:diameter/models/settings.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:diameter/models/meal_portion_type.dart';
|
import 'package:diameter/models/meal_portion_type.dart';
|
||||||
|
|
@ -8,9 +8,9 @@ import 'package:diameter/models/meal_portion_type.dart';
|
|||||||
import 'package:diameter/models/meal_source.dart';
|
import 'package:diameter/models/meal_source.dart';
|
||||||
import 'package:diameter/models/settings.dart';
|
import 'package:diameter/models/settings.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
import 'package:diameter/screens/accuracy_detail.dart';
|
import 'package:diameter/screens/category/accuracy_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
import 'package:diameter/screens/category/meal_category_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class MealSourceDetailScreen extends StatefulWidget {
|
class MealSourceDetailScreen extends StatefulWidget {
|
@ -2,7 +2,7 @@ import 'package:diameter/utils/dialog_utils.dart';
|
|||||||
import 'package:diameter/models/meal_source.dart';
|
import 'package:diameter/models/meal_source.dart';
|
||||||
import 'package:diameter/models/settings.dart';
|
import 'package:diameter/models/settings.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
import 'package:diameter/screens/meal/meal_source_detail.dart';
|
import 'package:diameter/screens/category/meal_source_detail.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class MealSourceListScreen extends StatefulWidget {
|
class MealSourceListScreen extends StatefulWidget {
|
@ -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/meal_source.dart';
|
||||||
import 'package:diameter/models/settings.dart';
|
import 'package:diameter/models/settings.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
import 'package:diameter/screens/accuracy_detail.dart';
|
import 'package:diameter/screens/category/accuracy_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
import 'package:diameter/screens/category/meal_category_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
|
||||||
|
import 'package:diameter/screens/category/meal_source_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_detail.dart';
|
import 'package:diameter/screens/meal/meal_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_source_detail.dart';
|
|
||||||
import 'package:diameter/utils/utils.dart';
|
import 'package:diameter/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -33,6 +33,8 @@ class LogMealDetailScreen extends StatefulWidget {
|
|||||||
_LogMealDetailScreenState createState() => _LogMealDetailScreenState();
|
_LogMealDetailScreenState createState() => _LogMealDetailScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NutritionProperty { totalCarbs, carbsRatio, portionSize }
|
||||||
|
|
||||||
class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||||
LogMeal? _logMeal;
|
LogMeal? _logMeal;
|
||||||
bool _isNew = true;
|
bool _isNew = true;
|
||||||
@ -40,6 +42,9 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
bool _isExpanded = false;
|
bool _isExpanded = false;
|
||||||
bool _setManually = false;
|
bool _setManually = false;
|
||||||
|
|
||||||
|
NutritionProperty? lastChanged;
|
||||||
|
NutritionProperty? secondToLastChanged;
|
||||||
|
|
||||||
final GlobalKey<FormState> _logMealForm = GlobalKey<FormState>();
|
final GlobalKey<FormState> _logMealForm = GlobalKey<FormState>();
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
@ -91,6 +96,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
_portionSizeController.text = (_logMeal!.portionSize ?? '').toString();
|
_portionSizeController.text = (_logMeal!.portionSize ?? '').toString();
|
||||||
_totalCarbsController.text = (_logMeal!.totalCarbs ?? '').toString();
|
_totalCarbsController.text = (_logMeal!.totalCarbs ?? '').toString();
|
||||||
_amountController.text = (_logMeal!.amount).toString();
|
_amountController.text = (_logMeal!.amount).toString();
|
||||||
|
_amount = _logMeal!.amount;
|
||||||
_notesController.text = _logMeal!.notes ?? '';
|
_notesController.text = _logMeal!.notes ?? '';
|
||||||
_meal = _logMeal!.meal.target;
|
_meal = _logMeal!.meal.target;
|
||||||
_mealController.text = (_meal ?? '').toString();
|
_mealController.text = (_meal ?? '').toString();
|
||||||
@ -299,6 +305,8 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
if (basePortionSize != null) {
|
if (basePortionSize != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
portionSize = basePortionSize! * newAmount;
|
portionSize = basePortionSize! * newAmount;
|
||||||
|
secondToLastChanged = NutritionProperty.carbsRatio;
|
||||||
|
lastChanged = NutritionProperty.portionSize;
|
||||||
_portionSizeController.text =
|
_portionSizeController.text =
|
||||||
Utils.toStringMatchingTemplateFractionPrecision(
|
Utils.toStringMatchingTemplateFractionPrecision(
|
||||||
portionSize!, Settings.nutritionSteps);
|
portionSize!, Settings.nutritionSteps);
|
||||||
@ -331,25 +339,34 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
const calcPortionSize = 3;
|
const calcPortionSize = 3;
|
||||||
|
|
||||||
if (carbsRatioUpdate != null) {
|
if (carbsRatioUpdate != null) {
|
||||||
if (portionSize != null && portionSize != 0) {
|
if (secondToLastChanged == NutritionProperty.portionSize &&
|
||||||
|
portionSize != null &&
|
||||||
|
portionSize != 0) {
|
||||||
toCalculate = calcTotalCarbs;
|
toCalculate = calcTotalCarbs;
|
||||||
} else if (totalCarbs != null && totalCarbs != 0) {
|
} else if (totalCarbs != null && totalCarbs != 0) {
|
||||||
toCalculate = calcPortionSize;
|
toCalculate = calcPortionSize;
|
||||||
}
|
}
|
||||||
} else if (portionSizeUpdate != null) {
|
} else if (portionSizeUpdate != null) {
|
||||||
if (carbsRatio != null && carbsRatio != 0) {
|
if (secondToLastChanged == NutritionProperty.carbsRatio &&
|
||||||
|
carbsRatio != null &&
|
||||||
|
carbsRatio != 0) {
|
||||||
toCalculate = calcTotalCarbs;
|
toCalculate = calcTotalCarbs;
|
||||||
} else if (totalCarbs != null && totalCarbs != 0) {
|
} else if (totalCarbs != null && totalCarbs != 0) {
|
||||||
toCalculate = calcCarbsRatio;
|
toCalculate = calcCarbsRatio;
|
||||||
}
|
}
|
||||||
} else if (totalCarbsUpdate != null) {
|
} else if (totalCarbsUpdate != null) {
|
||||||
if (carbsRatio != null && carbsRatio != 0) {
|
if (secondToLastChanged == NutritionProperty.carbsRatio &&
|
||||||
|
carbsRatio != null &&
|
||||||
|
carbsRatio != 0) {
|
||||||
toCalculate = calcPortionSize;
|
toCalculate = calcPortionSize;
|
||||||
} else if (portionSize != null && portionSize != 0) {
|
} else if (portionSize != null && portionSize != 0) {
|
||||||
toCalculate = calcCarbsRatio;
|
toCalculate = calcCarbsRatio;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (carbsRatio != null && carbsRatio != 0) {
|
if ((secondToLastChanged == NutritionProperty.carbsRatio ||
|
||||||
|
lastChanged == NutritionProperty.carbsRatio) &&
|
||||||
|
carbsRatio != null &&
|
||||||
|
carbsRatio != 0) {
|
||||||
if (portionSize != null && portionSize != 0) {
|
if (portionSize != null && portionSize != 0) {
|
||||||
toCalculate = calcTotalCarbs;
|
toCalculate = calcTotalCarbs;
|
||||||
} else if (totalCarbs != null && totalCarbs != 0) {
|
} else if (totalCarbs != null && totalCarbs != 0) {
|
||||||
@ -514,9 +531,17 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
autoRoundToMultipleOfStep: true,
|
autoRoundToMultipleOfStep: true,
|
||||||
step: Settings.nutritionSteps,
|
step: Settings.nutritionSteps,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
|
if (lastChanged != NutritionProperty.portionSize) {
|
||||||
|
setState(() {
|
||||||
|
secondToLastChanged = lastChanged;
|
||||||
|
lastChanged = NutritionProperty.portionSize;
|
||||||
|
});
|
||||||
|
}
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
if (value != null && value != 0) {
|
||||||
calculateThirdMeasurementOfPortionCarbsRelation(
|
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||||
portionSizeUpdate: value);
|
portionSizeUpdate: value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -528,9 +553,17 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
controller: _carbsRatioController,
|
controller: _carbsRatioController,
|
||||||
showSteppers: false,
|
showSteppers: false,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
|
if (lastChanged != NutritionProperty.carbsRatio) {
|
||||||
|
setState(() {
|
||||||
|
secondToLastChanged = lastChanged;
|
||||||
|
lastChanged = NutritionProperty.carbsRatio;
|
||||||
|
});
|
||||||
|
}
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
if (value != null && value != 0) {
|
||||||
calculateThirdMeasurementOfPortionCarbsRelation(
|
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||||
carbsRatioUpdate: value);
|
carbsRatioUpdate: value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -545,15 +578,22 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
step: Settings.nutritionSteps,
|
step: Settings.nutritionSteps,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
if (lastChanged != NutritionProperty.totalCarbs) {
|
||||||
|
setState(() {
|
||||||
|
secondToLastChanged = lastChanged;
|
||||||
|
lastChanged = NutritionProperty.totalCarbs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (value != null && value != 0) {
|
||||||
calculateThirdMeasurementOfPortionCarbsRelation(
|
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||||
totalCarbsUpdate: value);
|
totalCarbsUpdate: value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
BooleanFormField(
|
||||||
child: BooleanFormField(
|
|
||||||
value: _setManually,
|
value: _setManually,
|
||||||
label: 'set carbs ratio manually',
|
label: 'set carbs ratio manually',
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@ -563,7 +603,6 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _notesController,
|
controller: _notesController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@ -600,8 +639,6 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
const EdgeInsets.symmetric(vertical: 5.0),
|
const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
|
||||||
child:
|
|
||||||
AutoCompleteDropdownButton<MealSource>(
|
AutoCompleteDropdownButton<MealSource>(
|
||||||
controller: _mealSourceController,
|
controller: _mealSourceController,
|
||||||
selectedItem: _mealSource,
|
selectedItem: _mealSource,
|
||||||
@ -609,7 +646,6 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
items: _mealSources,
|
items: _mealSources,
|
||||||
onChanged: updateMealSource,
|
onChanged: updateMealSource,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@ -638,16 +674,13 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
const EdgeInsets.symmetric(vertical: 5.0),
|
const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
AutoCompleteDropdownButton<MealCategory>(
|
||||||
child: AutoCompleteDropdownButton<
|
|
||||||
MealCategory>(
|
|
||||||
controller: _mealCategoryController,
|
controller: _mealCategoryController,
|
||||||
selectedItem: _mealCategory,
|
selectedItem: _mealCategory,
|
||||||
label: 'Meal Category',
|
label: 'Meal Category',
|
||||||
items: _mealCategories,
|
items: _mealCategories,
|
||||||
onChanged: updateMealCategory,
|
onChanged: updateMealCategory,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@ -676,16 +709,13 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
const EdgeInsets.symmetric(vertical: 5.0),
|
const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
AutoCompleteDropdownButton<MealPortionType>(
|
||||||
child: AutoCompleteDropdownButton<
|
|
||||||
MealPortionType>(
|
|
||||||
controller: _mealPortionTypeController,
|
controller: _mealPortionTypeController,
|
||||||
selectedItem: _mealPortionType,
|
selectedItem: _mealPortionType,
|
||||||
label: 'Meal Portion Type',
|
label: 'Meal Portion Type',
|
||||||
items: _mealPortionTypes,
|
items: _mealPortionTypes,
|
||||||
onChanged: updateMealPortionType,
|
onChanged: updateMealPortionType,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@ -714,16 +744,13 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
const EdgeInsets.symmetric(vertical: 5.0),
|
const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
AutoCompleteDropdownButton<Accuracy>(
|
||||||
child: AutoCompleteDropdownButton<Accuracy>(
|
controller: _portionSizeAccuracyController,
|
||||||
controller:
|
|
||||||
_portionSizeAccuracyController,
|
|
||||||
selectedItem: _portionSizeAccuracy,
|
selectedItem: _portionSizeAccuracy,
|
||||||
label: 'Portion Size Accuracy',
|
label: 'Portion Size Accuracy',
|
||||||
items: _portionSizeAccuracies,
|
items: _portionSizeAccuracies,
|
||||||
onChanged: updatePortionSizeAccuracy,
|
onChanged: updatePortionSizeAccuracy,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@ -753,15 +780,13 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
|||||||
const EdgeInsets.symmetric(vertical: 5.0),
|
const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
AutoCompleteDropdownButton<Accuracy>(
|
||||||
child: AutoCompleteDropdownButton<Accuracy>(
|
|
||||||
controller: _carbsRatioAccuracyController,
|
controller: _carbsRatioAccuracyController,
|
||||||
selectedItem: _carbsRatioAccuracy,
|
selectedItem: _carbsRatioAccuracy,
|
||||||
label: 'Carbs Ratio Accuracy',
|
label: 'Carbs Ratio Accuracy',
|
||||||
items: _carbsRatioAccuracies,
|
items: _carbsRatioAccuracies,
|
||||||
onChanged: updateCarbsRatioAccuracy,
|
onChanged: updateCarbsRatioAccuracy,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
|
@ -1,366 +0,0 @@
|
|||||||
import 'package:diameter/utils/dialog_utils.dart';
|
|
||||||
import 'package:diameter/models/log_event.dart';
|
|
||||||
import 'package:diameter/models/settings.dart';
|
|
||||||
import 'package:diameter/screens/log/log_event/log_event_detail.dart';
|
|
||||||
import 'package:diameter/utils/date_time_utils.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:diameter/navigation.dart';
|
|
||||||
|
|
||||||
class LogEventListScreen extends StatefulWidget {
|
|
||||||
static const String routeName = '/log-events';
|
|
||||||
const LogEventListScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_LogEventListScreenState createState() => _LogEventListScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LogEventListScreenState extends State<LogEventListScreen> {
|
|
||||||
List<LogEvent> _activeEvents = [];
|
|
||||||
late List<LogEvent> _logEvents;
|
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
final TextEditingController _dateController = TextEditingController(text: '');
|
|
||||||
|
|
||||||
late DateTime _date;
|
|
||||||
bool _showActive = true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
_date = DateTime.now();
|
|
||||||
_dateController.text = DateTimeUtils.displayDate(_date);
|
|
||||||
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
_dateController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reload({String? message}) {
|
|
||||||
setState(() {
|
|
||||||
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
|
|
||||||
_logEvents = LogEvent.getAllForDate(_date);
|
|
||||||
});
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
if (message != null) {
|
|
||||||
var snackBar = SnackBar(
|
|
||||||
content: Text(message),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
);
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..removeCurrentSnackBar()
|
|
||||||
..showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleAddNewEvent() async {
|
|
||||||
final now = DateTime.now();
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return LogEventDetailScreen(
|
|
||||||
suggestedDate: _date.isAtSameMomentAs(DateTime(now.year, now.month, now.day)) ? now : _date,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).then((result) => reload(message: result?[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleEditAction(LogEvent event) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => LogEventDetailScreen(
|
|
||||||
id: event.id,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).then((result) => reload(message: result?[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDelete(LogEvent logEvent) {
|
|
||||||
LogEvent.remove(logEvent.id);
|
|
||||||
reload(message: 'Event deleted');
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleDeleteAction(LogEvent logEvent) async {
|
|
||||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
|
||||||
DialogUtils.showConfirmationDialog(
|
|
||||||
context: context,
|
|
||||||
onConfirm: () => onDelete(logEvent),
|
|
||||||
message: 'Are you sure you want to delete this Event?',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
onDelete(logEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStop(LogEvent event) async {
|
|
||||||
event.endTime = DateTime.now();
|
|
||||||
LogEvent.put(event);
|
|
||||||
reload(message: 'Event ended');
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleStopAction(LogEvent event) async {
|
|
||||||
if (Settings.get().showConfirmationDialogOnStopEvent) {
|
|
||||||
DialogUtils.showConfirmationDialog(
|
|
||||||
context: context,
|
|
||||||
onConfirm: () => onStop(event),
|
|
||||||
message: 'Are you sure you want to end this Event?',
|
|
||||||
confirmationLabel: 'END EVENT',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
onStop(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChangeDate(DateTime? date) {
|
|
||||||
if (date != null) {
|
|
||||||
setState(() {
|
|
||||||
_date = DateTime(date.year, date.month, date.day);
|
|
||||||
_dateController.text = DateTimeUtils.displayDate(date);
|
|
||||||
});
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Log Events'),
|
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => onChangeDate(DateTime.now()),
|
|
||||||
icon: const Icon(Icons.today)),
|
|
||||||
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
drawer: const Navigation(currentLocation: LogEventListScreen.routeName),
|
|
||||||
body: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => setState(() {
|
|
||||||
_showActive = !_showActive;
|
|
||||||
}),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'ACTIVE EVENTS',
|
|
||||||
style: Theme.of(context).textTheme.subtitle2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(_showActive
|
|
||||||
? Icons.expand_less
|
|
||||||
: Icons.expand_more),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
!_showActive ? Container() :
|
|
||||||
_activeEvents.isNotEmpty
|
|
||||||
? ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
itemCount: _activeEvents.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
LogEvent event = _activeEvents[index];
|
|
||||||
return Card(
|
|
||||||
child: ListTile(
|
|
||||||
onTap: () {
|
|
||||||
handleEditAction(event);
|
|
||||||
},
|
|
||||||
title: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateTimeUtils.displayDateTime(event.time),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 24),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
(event.title ??
|
|
||||||
event.eventType.target?.value ??
|
|
||||||
'')
|
|
||||||
.toUpperCase(),
|
|
||||||
style: Theme.of(context).textTheme.subtitle2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
event.hasEndTime && event.endTime == null
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.stop,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleStopAction(event),
|
|
||||||
)
|
|
||||||
: const SizedBox(width: 50),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.edit,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleEditAction(event),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleDeleteAction(event),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: const Center(
|
|
||||||
child: Text('There are no Active Events!'),
|
|
||||||
),
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.all(10.0),
|
|
||||||
child: Divider(),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _date.isAtSameMomentAs(DateTime(2000, 1, 1))
|
|
||||||
? null
|
|
||||||
: () =>
|
|
||||||
onChangeDate(_date.subtract(const Duration(days: 1))),
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
final newTime = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: _date,
|
|
||||||
firstDate: DateTime(2000, 1, 1),
|
|
||||||
lastDate: DateTime.now().add(const Duration(days: 365)),
|
|
||||||
);
|
|
||||||
onChangeDate(newTime);
|
|
||||||
},
|
|
||||||
child: Expanded(
|
|
||||||
child: Text(
|
|
||||||
DateTimeUtils.displayDate(_date).toUpperCase(),
|
|
||||||
style: Theme.of(context).textTheme.subtitle2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed:
|
|
||||||
_date.add(const Duration(days: 1)).isBefore(DateTime.now())
|
|
||||||
? () => onChangeDate(_date.add(const Duration(days: 1)))
|
|
||||||
: null,
|
|
||||||
icon: const Icon(Icons.arrow_forward),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _logEvents.isNotEmpty
|
|
||||||
? Scrollbar(
|
|
||||||
controller: _scrollController,
|
|
||||||
child: ListView.builder(
|
|
||||||
controller: _scrollController,
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
itemCount: _logEvents.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
LogEvent event = _logEvents[index];
|
|
||||||
return Card(
|
|
||||||
child: ListTile(
|
|
||||||
onTap: () {
|
|
||||||
handleEditAction(event);
|
|
||||||
},
|
|
||||||
title: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateTimeUtils.displayTime(event.isEndEvent
|
|
||||||
? event.endTime
|
|
||||||
: event.time),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 24),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
(event.title ??
|
|
||||||
event.eventType.target?.value ??
|
|
||||||
'')
|
|
||||||
.toUpperCase(),
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.subtitle2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
event.hasEndTime && event.endTime == null
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.stop,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
handleStopAction(event),
|
|
||||||
)
|
|
||||||
: const SizedBox(width: 50),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.edit,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleEditAction(event),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleDeleteAction(event),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
))
|
|
||||||
: const Center(
|
|
||||||
child: Text('There are no Events for that date!'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: handleAddNewEvent,
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -188,7 +188,7 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkIfActiveEventOfTypeExistsBeforeSaving() async {
|
Future<void> checkIfActiveEventOfTypeExistsBeforeSaving() async {
|
||||||
if (_eventType != null &&
|
if (_isNew && _eventType != null &&
|
||||||
LogEvent.eventTypeExistsForTime(_eventType!.id, _time)) {
|
LogEvent.eventTypeExistsForTime(_eventType!.id, _time)) {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
386
lib/screens/log/log_event_list.dart
Normal file
386
lib/screens/log/log_event_list.dart
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
import 'package:diameter/utils/dialog_utils.dart';
|
||||||
|
import 'package:diameter/models/log_event.dart';
|
||||||
|
import 'package:diameter/models/settings.dart';
|
||||||
|
import 'package:diameter/screens/log/log_event_detail.dart';
|
||||||
|
import 'package:diameter/utils/date_time_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:diameter/navigation.dart';
|
||||||
|
|
||||||
|
class LogEventListScreen extends StatefulWidget {
|
||||||
|
static const String routeName = '/log-events';
|
||||||
|
const LogEventListScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LogEventListScreenState createState() => _LogEventListScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LogEventListScreenState extends State<LogEventListScreen> {
|
||||||
|
List<LogEvent> _activeEvents = [];
|
||||||
|
late List<LogEvent> _logEvents;
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
final TextEditingController _dateController = TextEditingController(text: '');
|
||||||
|
|
||||||
|
late DateTime _date;
|
||||||
|
bool _showActive = true;
|
||||||
|
String? swipeDirection;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_date = DateTime.now();
|
||||||
|
_dateController.text = DateTimeUtils.displayDate(_date);
|
||||||
|
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
_dateController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reload({String? message}) {
|
||||||
|
setState(() {
|
||||||
|
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
|
||||||
|
_logEvents = LogEvent.getAllForDate(_date);
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (message != null) {
|
||||||
|
var snackBar = SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
..removeCurrentSnackBar()
|
||||||
|
..showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleAddNewEvent() async {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return _date.isAtSameMomentAs(DateTimeUtils.today())
|
||||||
|
? const LogEventDetailScreen()
|
||||||
|
: LogEventDetailScreen(
|
||||||
|
suggestedDate: _date,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).then((result) => reload(message: result?[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEditAction(LogEvent event) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => LogEventDetailScreen(
|
||||||
|
id: event.id,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((result) => reload(message: result?[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDelete(LogEvent logEvent) {
|
||||||
|
LogEvent.remove(logEvent.id);
|
||||||
|
reload(message: 'Event deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleDeleteAction(LogEvent logEvent) async {
|
||||||
|
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||||
|
DialogUtils.showConfirmationDialog(
|
||||||
|
context: context,
|
||||||
|
onConfirm: () => onDelete(logEvent),
|
||||||
|
message: 'Are you sure you want to delete this Event?',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onDelete(logEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onStop(LogEvent event) async {
|
||||||
|
event.endTime = DateTime.now();
|
||||||
|
LogEvent.put(event);
|
||||||
|
reload(message: 'Event ended');
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleStopAction(LogEvent event) async {
|
||||||
|
if (Settings.get().showConfirmationDialogOnStopEvent) {
|
||||||
|
DialogUtils.showConfirmationDialog(
|
||||||
|
context: context,
|
||||||
|
onConfirm: () => onStop(event),
|
||||||
|
message: 'Are you sure you want to end this Event?',
|
||||||
|
confirmationLabel: 'END EVENT',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onStop(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChangeDate(DateTime? date) {
|
||||||
|
if (date != null) {
|
||||||
|
setState(() {
|
||||||
|
_date = DateTime(date.year, date.month, date.day);
|
||||||
|
_dateController.text = DateTimeUtils.displayDate(date);
|
||||||
|
});
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Log Events'),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => onChangeDate(DateTime.now()),
|
||||||
|
icon: const Icon(Icons.today)),
|
||||||
|
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
drawer: const Navigation(currentLocation: LogEventListScreen.routeName),
|
||||||
|
body: GestureDetector(
|
||||||
|
onPanUpdate: (details) {
|
||||||
|
swipeDirection = details.delta.dx < 0 ? 'left' : 'right';
|
||||||
|
},
|
||||||
|
onPanEnd: (details) {
|
||||||
|
if (swipeDirection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (swipeDirection == 'right' &&
|
||||||
|
!_date.isAtSameMomentAs(DateTime(2000, 1, 1))) {
|
||||||
|
onChangeDate(_date.subtract(const Duration(days: 1)));
|
||||||
|
}
|
||||||
|
if (swipeDirection == 'left' &&
|
||||||
|
_date.add(const Duration(days: 1)).isBefore(DateTime.now())) {
|
||||||
|
onChangeDate(_date.add(const Duration(days: 1)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => setState(() {
|
||||||
|
_showActive = !_showActive;
|
||||||
|
}),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'ACTIVE EVENTS',
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(_showActive ? Icons.expand_less : Icons.expand_more),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
!_showActive
|
||||||
|
? Container()
|
||||||
|
: _activeEvents.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
itemCount: _activeEvents.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
LogEvent event = _activeEvents[index];
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
onTap: () {
|
||||||
|
handleEditAction(event);
|
||||||
|
},
|
||||||
|
title: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateTimeUtils.displayDateTime(event.time),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
(event.title ??
|
||||||
|
event.eventType.target?.value ??
|
||||||
|
'')
|
||||||
|
.toUpperCase(),
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
event.hasEndTime && event.endTime == null
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.stop,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
handleStopAction(event),
|
||||||
|
)
|
||||||
|
: const SizedBox(width: 50),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.edit,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleEditAction(event),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleDeleteAction(event),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const Center(
|
||||||
|
child: Text('There are no Active Events!'),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(10.0),
|
||||||
|
child: Divider(),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _date.isAtSameMomentAs(DateTime(2000, 1, 1))
|
||||||
|
? null
|
||||||
|
: () =>
|
||||||
|
onChangeDate(_date.subtract(const Duration(days: 1))),
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
final newTime = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _date,
|
||||||
|
firstDate: DateTime(2000, 1, 1),
|
||||||
|
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||||
|
);
|
||||||
|
onChangeDate(newTime);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
DateTimeUtils.displayDate(_date).toUpperCase(),
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: _date
|
||||||
|
.add(const Duration(days: 1))
|
||||||
|
.isBefore(DateTime.now())
|
||||||
|
? () => onChangeDate(_date.add(const Duration(days: 1)))
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.arrow_forward),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _logEvents.isNotEmpty
|
||||||
|
? Scrollbar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _scrollController,
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
itemCount: _logEvents.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
LogEvent event = _logEvents[index];
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
onTap: () {
|
||||||
|
handleEditAction(event);
|
||||||
|
},
|
||||||
|
title: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateTimeUtils.displayTime(event.isEndEvent
|
||||||
|
? event.endTime
|
||||||
|
: event.time),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
(event.title ??
|
||||||
|
event.eventType.target?.value ??
|
||||||
|
'')
|
||||||
|
.toUpperCase(),
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
event.hasEndTime && event.endTime == null
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.stop,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
handleStopAction(event),
|
||||||
|
)
|
||||||
|
: const SizedBox(width: 50),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.edit,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleEditAction(event),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleDeleteAction(event),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
: const Center(
|
||||||
|
child: Text('There are no Events for that date!'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: handleAddNewEvent,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
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/meal_source.dart';
|
||||||
import 'package:diameter/models/settings.dart';
|
import 'package:diameter/models/settings.dart';
|
||||||
import 'package:diameter/navigation.dart';
|
import 'package:diameter/navigation.dart';
|
||||||
import 'package:diameter/screens/accuracy_detail.dart';
|
import 'package:diameter/screens/category/accuracy_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
import 'package:diameter/screens/category/meal_category_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
import 'package:diameter/screens/category/meal_portion_type_detail.dart';
|
||||||
import 'package:diameter/screens/meal/meal_source_detail.dart';
|
import 'package:diameter/screens/category/meal_source_detail.dart';
|
||||||
import 'package:diameter/utils/utils.dart';
|
import 'package:diameter/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -435,7 +435,10 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
|||||||
showSteppers: false,
|
showSteppers: false,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
calculateThirdMeasurementOfPortionCarbsRelation(carbsRatioUpdate: value);
|
if (value != null && value != 0) {
|
||||||
|
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||||
|
carbsRatioUpdate: value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -448,7 +451,10 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
|||||||
showSteppers: false,
|
showSteppers: false,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
calculateThirdMeasurementOfPortionCarbsRelation(portionSizeUpdate: value);
|
if (value != null && value != 0) {
|
||||||
|
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||||
|
portionSizeUpdate: value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -461,7 +467,10 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
|||||||
showSteppers: false,
|
showSteppers: false,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
calculateThirdMeasurementOfPortionCarbsRelation(carbsPerPortionUpdate: value);
|
if (value != null && value != 0) {
|
||||||
|
calculateThirdMeasurementOfPortionCarbsRelation(
|
||||||
|
carbsPerPortionUpdate: value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -562,16 +571,13 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
|||||||
const EdgeInsets.symmetric(vertical: 5.0),
|
const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
AutoCompleteDropdownButton<MealCategory>(
|
||||||
child: AutoCompleteDropdownButton<
|
|
||||||
MealCategory>(
|
|
||||||
controller: _mealCategoryController,
|
controller: _mealCategoryController,
|
||||||
selectedItem: _mealCategory,
|
selectedItem: _mealCategory,
|
||||||
label: 'Meal Category',
|
label: 'Meal Category',
|
||||||
items: _mealCategories,
|
items: _mealCategories,
|
||||||
onChanged: updateMealCategory,
|
onChanged: updateMealCategory,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@ -600,16 +606,13 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
|||||||
const EdgeInsets.symmetric(vertical: 5.0),
|
const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
AutoCompleteDropdownButton<Accuracy>(
|
||||||
child: AutoCompleteDropdownButton<Accuracy>(
|
controller: _portionSizeAccuracyController,
|
||||||
controller:
|
|
||||||
_portionSizeAccuracyController,
|
|
||||||
selectedItem: _portionSizeAccuracy,
|
selectedItem: _portionSizeAccuracy,
|
||||||
label: 'Portion Size Accuracy',
|
label: 'Portion Size Accuracy',
|
||||||
items: _portionSizeAccuracies,
|
items: _portionSizeAccuracies,
|
||||||
onChanged: updatePortionSizeAccuracy,
|
onChanged: updatePortionSizeAccuracy,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@ -639,15 +642,13 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
|||||||
const EdgeInsets.symmetric(vertical: 5.0),
|
const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
AutoCompleteDropdownButton<Accuracy>(
|
||||||
child: AutoCompleteDropdownButton<Accuracy>(
|
|
||||||
controller: _carbsRatioAccuracyController,
|
controller: _carbsRatioAccuracyController,
|
||||||
selectedItem: _carbsRatioAccuracy,
|
selectedItem: _carbsRatioAccuracy,
|
||||||
label: 'Carbs Ratio Accuracy',
|
label: 'Carbs Ratio Accuracy',
|
||||||
items: _carbsRatioAccuracies,
|
items: _carbsRatioAccuracies,
|
||||||
onChanged: updateCarbsRatioAccuracy,
|
onChanged: updateCarbsRatioAccuracy,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
|
@ -1,322 +0,0 @@
|
|||||||
import 'package:diameter/components/detail.dart';
|
|
||||||
import 'package:diameter/utils/dialog_utils.dart';
|
|
||||||
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
|
||||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
|
||||||
import 'package:diameter/models/ingredient.dart';
|
|
||||||
import 'package:diameter/models/meal.dart';
|
|
||||||
import 'package:diameter/models/recipe.dart';
|
|
||||||
import 'package:diameter/models/settings.dart';
|
|
||||||
import 'package:diameter/navigation.dart';
|
|
||||||
import 'package:diameter/screens/meal/meal_detail.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class RecipeDetailScreen extends StatefulWidget {
|
|
||||||
static const String routeName = '/recipe';
|
|
||||||
final int id;
|
|
||||||
|
|
||||||
const RecipeDetailScreen({Key? key, this.id = 0}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_RecipeDetailScreenState createState() => _RecipeDetailScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
|
||||||
Recipe? _recipe;
|
|
||||||
List<Ingredient> _ingredients = [];
|
|
||||||
|
|
||||||
bool _isNew = true;
|
|
||||||
bool _isSaving = false;
|
|
||||||
|
|
||||||
final GlobalKey<FormState> _recipeForm = GlobalKey<FormState>();
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
final _nameController = TextEditingController(text: '');
|
|
||||||
final _notesController = TextEditingController(text: '');
|
|
||||||
|
|
||||||
double _servings = 1;
|
|
||||||
|
|
||||||
final List<TextEditingController> _ingredientControllers = [];
|
|
||||||
|
|
||||||
List<Meal> _meals = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
reload();
|
|
||||||
|
|
||||||
_meals = Meal.getAll();
|
|
||||||
|
|
||||||
if (_recipe != null) {
|
|
||||||
_nameController.text = _recipe!.name;
|
|
||||||
_servings = _recipe!.servings ?? 1;
|
|
||||||
_notesController.text = _recipe!.notes ?? '';
|
|
||||||
|
|
||||||
if (_ingredients.isNotEmpty) {
|
|
||||||
for (Ingredient ingredient in _ingredients) {
|
|
||||||
_ingredientControllers.add(
|
|
||||||
TextEditingController(text: ingredient.ingredient.target?.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void reload({String? message}) {
|
|
||||||
if (widget.id != 0) {
|
|
||||||
setState(() {
|
|
||||||
_recipe = Recipe.get(widget.id);
|
|
||||||
_ingredients = Ingredient.getAllForRecipe(widget.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_isNew = _recipe == null;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
if (message != null) {
|
|
||||||
var snackBar = SnackBar(
|
|
||||||
content: Text(message),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
);
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..removeCurrentSnackBar()
|
|
||||||
..showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAddIngredient() {
|
|
||||||
final newIngredient = Ingredient(amount: 0);
|
|
||||||
setState(() {
|
|
||||||
newIngredient.recipe.target = _recipe;
|
|
||||||
_ingredients.add(newIngredient);
|
|
||||||
_ingredientControllers.add(TextEditingController(text: ''));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleSaveAction({bool close = false}) async {
|
|
||||||
setState(() {
|
|
||||||
_isSaving = true;
|
|
||||||
});
|
|
||||||
if (_recipeForm.currentState!.validate()) {
|
|
||||||
Recipe recipe = Recipe(
|
|
||||||
id: widget.id,
|
|
||||||
name: _nameController.text,
|
|
||||||
servings: _servings,
|
|
||||||
notes: _notesController.text,
|
|
||||||
);
|
|
||||||
Recipe.put(recipe);
|
|
||||||
List<Ingredient> ingredients = _ingredients.map((ingredient) {
|
|
||||||
if (ingredient.id != 0 &&
|
|
||||||
(!ingredient.ingredient.hasValue || ingredient.amount == 0)) {
|
|
||||||
ingredient.deleted = true;
|
|
||||||
}
|
|
||||||
return ingredient;
|
|
||||||
}).toList();
|
|
||||||
ingredients.retainWhere((ingredient) {
|
|
||||||
return ingredient.id != 0 ||
|
|
||||||
(ingredient.amount > 0 && ingredient.ingredient.hasValue);
|
|
||||||
});
|
|
||||||
Ingredient.putMany(ingredients);
|
|
||||||
|
|
||||||
if (close) {
|
|
||||||
Navigator.pop(context, ['${_isNew ? 'New' : ''} Recipe Saved', recipe]);
|
|
||||||
} else {
|
|
||||||
if (_isNew) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => RecipeDetailScreen(id: recipe.id),
|
|
||||||
),
|
|
||||||
).then((result) => Navigator.pop(context, result));
|
|
||||||
} else {
|
|
||||||
reload(message: 'Recipe saved');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_isSaving = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleCancelAction() {
|
|
||||||
if (Settings.get().showConfirmationDialogOnCancel &&
|
|
||||||
((_isNew &&
|
|
||||||
(_nameController.text != '' ||
|
|
||||||
_servings != 1 ||
|
|
||||||
_notesController.text != '')) ||
|
|
||||||
(!_isNew &&
|
|
||||||
(_nameController.text != _recipe!.name ||
|
|
||||||
_servings != _recipe!.servings ||
|
|
||||||
_notesController.text != (_recipe!.notes ?? ''))))) {
|
|
||||||
DialogUtils.showCancelConfirmationDialog(
|
|
||||||
context: context,
|
|
||||||
isNew: _isNew,
|
|
||||||
onSave: handleSaveAction,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(_isNew ? 'New Recipe' : _recipe!.name),
|
|
||||||
),
|
|
||||||
drawer: const Navigation(currentLocation: RecipeDetailScreen.routeName),
|
|
||||||
body: Scrollbar(
|
|
||||||
controller: _scrollController,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: _scrollController,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
FormWrapper(
|
|
||||||
formState: _recipeForm,
|
|
||||||
fields: [
|
|
||||||
TextFormField(
|
|
||||||
controller: _nameController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Name',
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value!.trim().isEmpty) {
|
|
||||||
return 'Empty title';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// NumberFormField(
|
|
||||||
// value: _servings,
|
|
||||||
// label: 'Servings',
|
|
||||||
// suffix: ' portions',
|
|
||||||
// min: 0,
|
|
||||||
// onChanged: (value) {
|
|
||||||
// if (value != null && value >= 0) {
|
|
||||||
// setState(() {
|
|
||||||
// _servings = value.toDouble();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
TextFormField(
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
controller: _notesController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Notes',
|
|
||||||
),
|
|
||||||
minLines: 2,
|
|
||||||
maxLines: 5,
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: onAddIngredient,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'INGREDIENTS',
|
|
||||||
style: Theme.of(context).textTheme.subtitle2,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
IconButton(
|
|
||||||
onPressed: onAddIngredient,
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
!_isNew && _ingredients.isNotEmpty
|
|
||||||
? ListBody(
|
|
||||||
children: _ingredients.map((item) {
|
|
||||||
final ingredient = item.ingredient.target;
|
|
||||||
final index = _ingredients.indexOf(item);
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 10.0, vertical: 5.0),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: AutoCompleteDropdownButton<Meal>(
|
|
||||||
controller: _ingredientControllers[index],
|
|
||||||
selectedItem: ingredient,
|
|
||||||
label: 'Meal Category',
|
|
||||||
items: _meals,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_ingredients[index]
|
|
||||||
.ingredient
|
|
||||||
.target = value;
|
|
||||||
_ingredientControllers[index].text =
|
|
||||||
value?.value ?? '';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
ingredient == null
|
|
||||||
? const MealDetailScreen()
|
|
||||||
: MealDetailScreen(
|
|
||||||
id: ingredient.id),
|
|
||||||
),
|
|
||||||
).then((result) {
|
|
||||||
_ingredients[index].ingredient.target =
|
|
||||||
result?[1];
|
|
||||||
_ingredientControllers[index].text =
|
|
||||||
result?[1].value ?? '';
|
|
||||||
reload(message: result?[0]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: Icon(ingredient == null
|
|
||||||
? Icons.add
|
|
||||||
: Icons.edit),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Padding(
|
|
||||||
// padding: const EdgeInsets.only(top: 10.0),
|
|
||||||
// child: NumberFormField(
|
|
||||||
// controller:
|
|
||||||
// _ingredients[index].amount,
|
|
||||||
// label: 'Amount',
|
|
||||||
// suffix: Settings.nutritionMeasurementSuffix,
|
|
||||||
// min: 0,
|
|
||||||
// onChanged: (value) {
|
|
||||||
// if (value != null && value >= 0) {
|
|
||||||
// setState(() {
|
|
||||||
// _ingredients[index].amount = value.toDouble();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
)
|
|
||||||
: Center(
|
|
||||||
child: Text(_isNew
|
|
||||||
? 'Save the Recipe in order to add ingredients!'
|
|
||||||
: 'You have not added any Ingredients yet!'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: DetailBottomRow(
|
|
||||||
onCancel: handleCancelAction,
|
|
||||||
onAction: _isSaving ? null : handleSaveAction,
|
|
||||||
onMiddleAction: _isSaving ? null : () => handleSaveAction(close: true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,200 +0,0 @@
|
|||||||
import 'package:diameter/utils/dialog_utils.dart';
|
|
||||||
import 'package:diameter/models/ingredient.dart';
|
|
||||||
import 'package:diameter/models/recipe.dart';
|
|
||||||
import 'package:diameter/models/settings.dart';
|
|
||||||
import 'package:diameter/navigation.dart';
|
|
||||||
import 'package:diameter/screens/recipe/recipe_detail.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class RecipeListScreen extends StatefulWidget {
|
|
||||||
static const String routeName = '/recipes';
|
|
||||||
|
|
||||||
const RecipeListScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_RecipeListScreenState createState() => _RecipeListScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RecipeListScreenState extends State<RecipeListScreen> {
|
|
||||||
List<Recipe> _recipes = [];
|
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reload({String? message}) {
|
|
||||||
setState(() {
|
|
||||||
_recipes = Recipe.getAll();
|
|
||||||
});
|
|
||||||
setState(() {
|
|
||||||
if (message != null) {
|
|
||||||
var snackBar = SnackBar(
|
|
||||||
content: Text(message),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
);
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
..removeCurrentSnackBar()
|
|
||||||
..showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDelete(Recipe recipe) {
|
|
||||||
Recipe.remove(recipe.id);
|
|
||||||
reload(message: 'Recipe deleted');
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleDeleteAction(Recipe recipe) async {
|
|
||||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
|
||||||
DialogUtils.showConfirmationDialog(
|
|
||||||
context: context,
|
|
||||||
onConfirm: () => onDelete(recipe),
|
|
||||||
message: 'Are you sure you want to delete this Recipe?',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
onDelete(recipe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(title: const Text('Recipes'), actions: <Widget>[
|
|
||||||
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
|
||||||
]),
|
|
||||||
drawer: const Navigation(currentLocation: RecipeListScreen.routeName),
|
|
||||||
body: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: _recipes.isNotEmpty
|
|
||||||
? Scrollbar(
|
|
||||||
controller: _scrollController,
|
|
||||||
child: ListView.builder(
|
|
||||||
controller: _scrollController,
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
itemCount: _recipes.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final recipe = _recipes[index];
|
|
||||||
final carbsRatio =
|
|
||||||
Ingredient.getCarbsRatioForRecipe(recipe.id);
|
|
||||||
final carbsPerPortion = Recipe.getCarbsPerPortion(recipe.id);
|
|
||||||
return Card(
|
|
||||||
child: ListTile(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
RecipeDetailScreen(id: recipe.id),
|
|
||||||
),
|
|
||||||
).then((result) => reload(message: result?[0]));
|
|
||||||
},
|
|
||||||
title: Text(
|
|
||||||
recipe.name.toUpperCase(),
|
|
||||||
style: Theme.of(context).textTheme.subtitle2,
|
|
||||||
),
|
|
||||||
subtitle: Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(vertical: 10.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(recipe.notes ?? ''),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.center,
|
|
||||||
children:
|
|
||||||
((carbsRatio ?? 0) > 0)
|
|
||||||
? [
|
|
||||||
Text(carbsRatio!.toString()),
|
|
||||||
const Text('% carbs',
|
|
||||||
textScaleFactor: 0.75),
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.center,
|
|
||||||
children:
|
|
||||||
(recipe.servings != null)
|
|
||||||
? [
|
|
||||||
Text(recipe.servings!
|
|
||||||
.toStringAsPrecision(3)),
|
|
||||||
const Text('servings',
|
|
||||||
textScaleFactor: 0.75),
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.center,
|
|
||||||
children:
|
|
||||||
((carbsPerPortion ?? 0) > 0)
|
|
||||||
? [
|
|
||||||
Text(carbsPerPortion!
|
|
||||||
.toStringAsPrecision(3)),
|
|
||||||
Text(
|
|
||||||
'${Settings.nutritionMeasurementSuffix} carbs per serving',
|
|
||||||
textScaleFactor: 0.75),
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => handleDeleteAction(recipe),
|
|
||||||
icon: const Icon(Icons.delete,
|
|
||||||
color: Colors.blue),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const Center(
|
|
||||||
child: Text('You have not created any Recipes yet!'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const RecipeDetailScreen(),
|
|
||||||
),
|
|
||||||
).then((result) => reload(message: result?[0]));
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
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() {
|
void saveSettings() {
|
||||||
Settings.put(Settings(
|
Settings.put(Settings(
|
||||||
id: _settings.id,
|
id: _settings.id,
|
||||||
nutritionMeasurementIndex: nutritionMeasurementLabels
|
nutritionMeasurementIndex: _nutritionMeasurementLabelController.text != '' ? nutritionMeasurementLabels
|
||||||
.indexOf(_nutritionMeasurementLabelController.text),
|
.indexOf(_nutritionMeasurementLabelController.text) : 0,
|
||||||
glucoseMeasurementIndex: glucoseMeasurementLabels
|
glucoseMeasurementIndex: _glucoseMeasurementLabelController.text != '' ? glucoseMeasurementLabels
|
||||||
.indexOf(_glucoseMeasurementLabelController.text),
|
.indexOf(_glucoseMeasurementLabelController.text) : 0,
|
||||||
glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement
|
glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement
|
||||||
? GlucoseDisplayMode.activeOnly.index
|
? GlucoseDisplayMode.activeOnly.index
|
||||||
: _displayBothGlucoseMeasurementsInDetailView &&
|
: _displayBothGlucoseMeasurementsInDetailView &&
|
||||||
|
@ -5,6 +5,11 @@ import 'package:intl/intl.dart';
|
|||||||
final DateTime dummyDate = DateTime(2000);
|
final DateTime dummyDate = DateTime(2000);
|
||||||
|
|
||||||
class DateTimeUtils {
|
class DateTimeUtils {
|
||||||
|
static DateTime today() {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
return DateTime(now.year, now.month, now.day);
|
||||||
|
}
|
||||||
|
|
||||||
static String displayDateTime(DateTime? date, {String fallback = ''}) {
|
static String displayDateTime(DateTime? date, {String fallback = ''}) {
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
return fallback;
|
return fallback;
|
||||||
@ -26,7 +31,7 @@ class DateTimeUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String displayTime(DateTime? date,
|
static String displayTime(DateTime? date,
|
||||||
{String fallback = '', bool? longFormat}) {
|
{String fallback = '', bool longFormat = false}) {
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:diameter/models/settings.dart';
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
// static double roundToDecimalPlaces(double value, int precision) {
|
// static double roundToDecimalPlaces(double value, int precision) {
|
||||||
// double mod = pow(10.0, precision).toDouble();
|
// double mod = pow(10.0, precision).toDouble();
|
||||||
@ -8,15 +10,17 @@ class Utils {
|
|||||||
|
|
||||||
static double roundToMultipleOfBase(double value, double base) {
|
static double roundToMultipleOfBase(double value, double base) {
|
||||||
double result = value;
|
double result = value;
|
||||||
|
if (base != 0) {
|
||||||
double remainder = value % base;
|
double remainder = value % base;
|
||||||
int precision = Utils.getFractionDigitsLength(base);
|
int precision = Utils.getFractionDigitsLength(base);
|
||||||
|
|
||||||
if (remainder != 0) {
|
if (remainder != 0) {
|
||||||
result = Utils.addDoublesWithPrecision(result, -remainder, precision);
|
result = Utils.addDoublesWithPrecision(result, -remainder, precision);
|
||||||
if (remainder > base / 2) {
|
if (remainder > (base / 2)) {
|
||||||
result = Utils.addDoublesWithPrecision(result, base, precision);
|
result = Utils.addDoublesWithPrecision(result, base, precision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -24,9 +28,14 @@ class Utils {
|
|||||||
static double addDoublesWithPrecision(double a, double b, int precision) {
|
static double addDoublesWithPrecision(double a, double b, int precision) {
|
||||||
double mod = pow(10.0, precision).toDouble();
|
double mod = pow(10.0, precision).toDouble();
|
||||||
double difference = (a * mod) + (b * mod);
|
double difference = (a * mod) + (b * mod);
|
||||||
|
|
||||||
|
if (mod != 0) {
|
||||||
return difference.round() / mod;
|
return difference.round() / mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return difference.round().toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
static int getFractionDigitsLength(double value) {
|
static int getFractionDigitsLength(double value) {
|
||||||
final fractionDigits = value.toString().split('.');
|
final fractionDigits = value.toString().split('.');
|
||||||
return fractionDigits[1] == '0' ? 0 : fractionDigits[1].length;
|
return fractionDigits[1] == '0' ? 0 : fractionDigits[1].length;
|
||||||
@ -51,17 +60,33 @@ class Utils {
|
|||||||
return Utils.roundToMultipleOfBase(carbsRatio * portionSize / 100, step);
|
return Utils.roundToMultipleOfBase(carbsRatio * portionSize / 100, step);
|
||||||
}
|
}
|
||||||
|
|
||||||
static double calculateCarbsRatio(
|
static double calculateCarbsRatio(double carbsPerPortion, double portionSize,
|
||||||
double carbsPerPortion, double portionSize, {double step = 0.01}) {
|
{double step = 0.01}) {
|
||||||
return portionSize > 0
|
return portionSize > 0
|
||||||
? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / portionSize, step)
|
? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / portionSize, step)
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static double calculatePortionSize(
|
static double calculatePortionSize(double carbsRatio, double carbsPerPortion,
|
||||||
double carbsRatio, double carbsPerPortion, {double step = 0.01}) {
|
{double step = 0.01}) {
|
||||||
return carbsRatio > 0
|
return carbsRatio > 0
|
||||||
? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / carbsRatio, step)
|
? Utils.roundToMultipleOfBase(carbsPerPortion * 100 / carbsRatio, step)
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String displayGlucose({int? mgPerDl, double? mmolPerL}) {
|
||||||
|
return '${Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? (mgPerDl ?? '') : (mmolPerL ?? '')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String displayGlucoseWithSuffix({int? mgPerDl, double? mmolPerL}) {
|
||||||
|
return '${Utils.displayGlucose(mgPerDl: mgPerDl, mmolPerL: mmolPerL)} ${Settings.glucoseMeasurementSuffix}';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String displayNutritionAmount(double amount,
|
||||||
|
{bool cutExtraDigits = false}) {
|
||||||
|
final value = cutExtraDigits && Utils.getFractionDigitsLength(amount) == 0
|
||||||
|
? amount.toInt().toString()
|
||||||
|
: amount;
|
||||||
|
return '$value${Settings.nutritionMeasurementSuffix}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
166
pubspec.lock
166
pubspec.lock
@ -7,14 +7,21 @@ packages:
|
|||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "31.0.0"
|
version: "36.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.0"
|
version: "3.3.1"
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -29,6 +36,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.2"
|
version: "2.8.2"
|
||||||
|
barcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: barcode
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -42,7 +56,7 @@ packages:
|
|||||||
name: build
|
name: build
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.2.1"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -63,21 +77,21 @@ packages:
|
|||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.6"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.8"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.2.2"
|
version: "7.2.3"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -91,7 +105,7 @@ packages:
|
|||||||
name: built_value
|
name: built_value
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.3"
|
version: "8.1.4"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -106,6 +120,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
charts_common:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charts_common
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.0"
|
||||||
|
charts_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: charts_flutter
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -113,13 +141,6 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
cli_util:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: cli_util
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.5"
|
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -168,7 +189,7 @@ packages:
|
|||||||
name: dart_style
|
name: dart_style
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.2"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -197,6 +218,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
flat_buffers:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flat_buffers
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
flex_color_scheme:
|
flex_color_scheme:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -221,6 +249,11 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -242,13 +275,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.4"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http_multi_server
|
name: http_multi_server
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.2.0"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -256,6 +296,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -339,21 +386,28 @@ packages:
|
|||||||
name: objectbox
|
name: objectbox
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.1"
|
||||||
objectbox_generator:
|
objectbox_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: objectbox_generator
|
name: objectbox_generator
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.1"
|
||||||
objectbox_sync_flutter_libs:
|
objectbox_sync_flutter_libs:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: objectbox_sync_flutter_libs
|
name: objectbox_sync_flutter_libs
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.1"
|
||||||
|
open_file:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: open_file
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -368,55 +422,76 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
|
path_parsing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_parsing
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.9"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.9"
|
version: "2.0.12"
|
||||||
path_provider_ios:
|
path_provider_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_ios
|
name: path_provider_ios
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.8"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.5"
|
||||||
path_provider_macos:
|
path_provider_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_macos
|
name: path_provider_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.5"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.3"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.5"
|
||||||
|
pdf:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: pdf
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.7.3"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.4.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -430,7 +505,7 @@ packages:
|
|||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.1.2"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -438,6 +513,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.0"
|
||||||
|
printing:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: printing
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.7.4"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -451,14 +533,14 @@ packages:
|
|||||||
name: provider
|
name: provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.1"
|
version: "6.0.2"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pub_semver
|
name: pub_semver
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
pubspec_parse:
|
pubspec_parse:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -466,6 +548,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
qr:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: qr
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -491,7 +580,7 @@ packages:
|
|||||||
name: source_gen
|
name: source_gen
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -582,14 +671,21 @@ packages:
|
|||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.4.2"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0+1"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.3.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -598,5 +694,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.14.0 <3.0.0"
|
dart: ">=2.15.0 <3.0.0"
|
||||||
flutter: ">=2.5.0"
|
flutter: ">=2.8.0"
|
||||||
|
@ -11,12 +11,16 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
path_provider: ^2.0.5
|
path_provider: ^2.0.9
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
flex_color_scheme: ^3.0.1
|
flex_color_scheme: ^3.0.1
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
objectbox: ^1.2.0
|
objectbox: ^1.2.0
|
||||||
objectbox_sync_flutter_libs: any
|
objectbox_sync_flutter_libs: any
|
||||||
|
charts_flutter: ^0.12.0
|
||||||
|
printing: ^5.7.2
|
||||||
|
pdf: ^3.7.1
|
||||||
|
open_file: ^3.2.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -25,6 +29,7 @@ dev_dependencies:
|
|||||||
provider: ^6.0.1
|
provider: ^6.0.1
|
||||||
build_runner: ^2.0.0
|
build_runner: ^2.0.0
|
||||||
objectbox_generator: any
|
objectbox_generator: any
|
||||||
|
analyzer: ^3.3.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
17
windows/.gitignore
vendored
Normal file
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