diff --git a/lib/colors.dart b/lib/colors.dart new file mode 100644 index 0000000..03a3737 --- /dev/null +++ b/lib/colors.dart @@ -0,0 +1,4 @@ +import 'package:flutter/material.dart'; + +final Color primaryColour = Color.fromARGB(255, 53, 150, 243); +final Color tabBackgroundColour = Colors.grey[200]!; diff --git a/lib/fbp_advanced.dart b/lib/fbp_advanced.dart index 50499e3..8542b95 100644 --- a/lib/fbp_advanced.dart +++ b/lib/fbp_advanced.dart @@ -26,6 +26,7 @@ import 'fbp_results.dart'; import 'fire.dart'; import 'fire_widgets.dart'; import 'basic_input.dart'; +import 'colors.dart'; import 'global.dart'; import 'ignition_type.dart'; import 'coordinate_picker.dart'; @@ -338,320 +339,374 @@ class AdvancedFireBehaviourPredictionFormState fontWeight: FontWeight.bold, ); // Build a Form widget using the _formKey created above. - return Column( - children: [ - Form( - key: _formKey, - child: Column( - children: [ - Card( - margin: const EdgeInsets.symmetric(vertical: 8.0), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - // Date - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Row( - children: [ - Expanded( - child: DatePicker( - onChanged: (DateTime? value) { - _onDateChanged(value); - }, - initialValue: _currentDate, - ), + return DefaultTabController( + length: 2, + initialIndex: 0, + child: Column( + children: [ + Container( + color: tabBackgroundColour, + child: TabBar( + indicatorSize: TabBarIndicatorSize.tab, + indicatorColor: primaryColour, + labelColor: primaryColour, + unselectedLabelColor: Colors.black, + labelPadding: const EdgeInsets.symmetric(vertical: 8), + tabs: [ + Tab( + child: Text( + 'Inputs', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + Tab( + child: Text( + 'Results', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + Expanded( + child: TabBarView( + children: [ + SingleChildScrollView( + key: const PageStorageKey('inputs_scroll'), + child: Form( + key: _formKey, + child: Column( + children: [ + Card( + margin: const EdgeInsets.symmetric(vertical: 8.0), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + // Date + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + children: [ + Expanded( + child: DatePicker( + onChanged: (DateTime? value) { + _onDateChanged(value); + }, + initialValue: _currentDate, + ), + ), + ], + ), + ), + // Ignition type + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text( + "Ignition Type:", + style: TextStyle(fontSize: fontSize), + ), + const SizedBox(width: 16), + RadioGroup( + groupValue: _ignitionType, + onChanged: (IgnitionType? value) { + if (value != null) + _onIgnitionTypeChanged(value); + }, + child: Row( + children: IgnitionType.values.map(( + type, + ) { + final label = + type == IgnitionType.point + ? 'Point' + : 'Line'; + return InkWell( + onTap: () { + _onIgnitionTypeChanged(type); + }, + borderRadius: BorderRadius.circular( + 8, + ), + child: Padding( + padding: + const EdgeInsets.symmetric( + horizontal: 8, + ), + child: Row( + children: [ + Radio( + value: type, + fillColor: + WidgetStateProperty.all( + Colors.black, + ), + ), + Text(label), + ], + ), + ), + ); + }).toList(), + ), + ), + ], + ), + // Time elapsed + Row( + children: [ + makeInputLabel( + 'Time elapsed', + '${_minutes.toInt()}', + ' minutes', + textStyle, + textStyleBold, + ), + Expanded( + flex: sliderFlex, + child: FancySliderWidget( + value: _minutes, + min: 0, + max: 120, + divisions: 12, + activeColor: intensityClassColour, + label: '${_minutes.toInt()} minutes', + onChanged: (value) { + _onTChanged(value.roundToDouble()); + }, + ), + ), + ], + ), + // Coordinate picker + CoordinatePicker( + coordinate: _basicInput!.coordinate, + onChanged: (coordinate) { + _basicInput!.coordinate = coordinate; + _onBasicInputChanged(_basicInput!); + }, + ), + ], ), - ], - ), - ), - // Ignition type - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - "Ignition Type:", - style: TextStyle(fontSize: fontSize), ), - const SizedBox(width: 16), - RadioGroup( - groupValue: _ignitionType, - onChanged: (IgnitionType? value) { - if (value != null) _onIgnitionTypeChanged(value); - }, - child: Row( - children: IgnitionType.values.map((type) { - final label = type == IgnitionType.point - ? 'Point' - : 'Line'; - return InkWell( - onTap: () { - _onIgnitionTypeChanged(type); - }, - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, + ), + + Card( + margin: const EdgeInsets.symmetric(vertical: 8.0), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + // Fuel type dropdown + Row( + children: [ + Expanded( + child: FuelTypePresetDropdown( + onChanged: (FuelTypePreset? value) { + _onPresetChanged(value); + }, + initialValue: _fuelTypePreset!, + ), ), - child: Row( - children: [ - Radio( - value: type, - fillColor: WidgetStateProperty.all( - Colors.black, - ), + ], + ), + // Modifiers + if (isGrassFuelType(_fuelTypePreset!.code)) ...[ + Row( + children: [ + // GFL field + makeInputLabel( + 'GFL', + _gfl.toStringAsFixed(2), + ' kg/\u33A1', + textStyle, + textStyleBold, + ), + Expanded( + flex: sliderFlex, + child: FancySliderWidget( + value: _gfl, + min: minGFL, + max: maxGFL, + divisions: ((maxGFL - minGFL) / 0.05) + .round(), + activeColor: intensityClassColour, + label: + 'Grass Fuel Load: ${_gfl.toStringAsFixed(2)} kg/\u33A1', + onChanged: (value) { + _onGFLChanged(value); + }, ), - Text(label), - ], - ), + ), + ], ), - ); - }).toList(), - ), - ), - ], - ), - // Time elapsed - Row( - children: [ - makeInputLabel( - 'Time elapsed', - '${_minutes.toInt()}', - ' minutes', - textStyle, - textStyleBold, - ), - Expanded( - flex: sliderFlex, - child: FancySliderWidget( - value: _minutes, - min: 0, - max: 120, - divisions: 12, - activeColor: intensityClassColour, - label: '${_minutes.toInt()} minutes', - onChanged: (value) { - _onTChanged(value.roundToDouble()); - }, - ), - ), - ], - ), - // Coordinate picker - CoordinatePicker( - coordinate: _basicInput!.coordinate, - onChanged: (coordinate) { - _basicInput!.coordinate = coordinate; - _onBasicInputChanged(_basicInput!); - }, - ), - ], - ), - ), - ), - - Card( - margin: const EdgeInsets.symmetric(vertical: 8.0), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - // Fuel type dropdown - Row( - children: [ - Expanded( - child: FuelTypePresetDropdown( - onChanged: (FuelTypePreset? value) { - _onPresetChanged(value); - }, - initialValue: _fuelTypePreset!, + Row( + children: [ + makeInputLabel( + 'Curing', + '${_basicInput!.cc.toInt()}', + '%', + textStyle, + textStyleBold, + ), + Expanded( + flex: sliderFlex, + child: FancySliderWidget( + value: _basicInput!.cc, + min: 0, + max: 100, + divisions: 20, + activeColor: intensityClassColour, + label: '${_basicInput!.cc.toInt()}%', + onChanged: (value) { + _onCCChanged(value.roundToDouble()); + }, + ), + ), + ], + ), + ], + if (canAdjustDeadFir(_fuelTypePreset!.code)) + Row( + children: [ + makeInputLabel( + 'Dead Balsam', + (_pdf ?? 0).toStringAsFixed(0), + '%', + textStyle, + textStyleBold, + ), + Expanded( + flex: sliderFlex, + child: FancySliderWidget( + value: _pdf ?? 0, + min: 0, + max: 100, + divisions: 100, + activeColor: intensityClassColour, + label: + '${(_pdf ?? 0).toStringAsFixed(0)}%', + onChanged: (value) { + _onPDFChanged( + value.roundToDouble(), + ); + }, + ), + ), + ], + ), + if (canAdjustConifer(_fuelTypePreset!.code)) + Row( + children: [ + makeInputLabel( + 'Conifer', + (_pc ?? 0).toStringAsFixed(0), + '%', + textStyle, + textStyleBold, + ), + Expanded( + flex: sliderFlex, + child: FancySliderWidget( + value: _pc ?? 0, + min: 0, + max: 100, + divisions: 100, + activeColor: intensityClassColour, + label: + '${(_pc ?? 0).toStringAsFixed(0)}%', + onChanged: (value) { + _onPCChanged(value.roundToDouble()); + }, + ), + ), + ], + ), + if (canAdjustCBH(_fuelTypePreset!.code)) + Row( + children: [ + makeInputLabel( + 'CBH', + _cbh?.toStringAsFixed(1) ?? '0.0', + ' m', + textStyle, + textStyleBold, + ), + Expanded( + flex: sliderFlex, + child: FancySliderWidget( + value: _cbh ?? 1, + min: 1, + max: 15, + divisions: 14, + activeColor: intensityClassColour, + label: _cbh!.toStringAsFixed(0), + onChanged: (value) { + _onCBHChanged( + value.roundToDouble(), + ); + }, + ), + ), + ], + ), + ], ), ), - ], - ), - // Modifiers - if (isGrassFuelType(_fuelTypePreset!.code)) ...[ - Row( - children: [ - // GFL field - makeInputLabel( - 'GFL', - _gfl.toStringAsFixed(2), - ' kg/\u33A1', - textStyle, - textStyleBold, - ), - Expanded( - flex: sliderFlex, - child: FancySliderWidget( - value: _gfl, - min: minGFL, - max: maxGFL, - divisions: ((maxGFL - minGFL) / 0.05).round(), - activeColor: intensityClassColour, - label: - 'Grass Fuel Load: ${_gfl.toStringAsFixed(2)} kg/\u33A1', - onChanged: (value) { - _onGFLChanged(value); - }, - ), - ), - ], ), - Row( - children: [ - makeInputLabel( - 'Curing', - '${_basicInput!.cc.toInt()}', - '%', - textStyle, - textStyleBold, - ), - Expanded( - flex: sliderFlex, - child: FancySliderWidget( - value: _basicInput!.cc, - min: 0, - max: 100, - divisions: 20, - activeColor: intensityClassColour, - label: '${_basicInput!.cc.toInt()}%', - onChanged: (value) { - _onCCChanged(value.roundToDouble()); - }, - ), + + Card( + margin: const EdgeInsets.symmetric(vertical: 8.0), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: BasicInputWidget( + basicInput: _basicInput!, + prediction: prediction, + fuelTypePreset: _fuelTypePreset!, + onChanged: (BasicInput basicInput) { + _onBasicInputChanged(basicInput); + }, + ), + ), + ], + ), + ], ), - ], + ), ), ], - if (canAdjustDeadFir(_fuelTypePreset!.code)) - Row( - children: [ - makeInputLabel( - 'Dead Balsam', - (_pdf ?? 0).toStringAsFixed(0), - '%', - textStyle, - textStyleBold, - ), - Expanded( - flex: sliderFlex, - child: FancySliderWidget( - value: _pdf ?? 0, - min: 0, - max: 100, - divisions: 100, - activeColor: intensityClassColour, - label: '${(_pdf ?? 0).toStringAsFixed(0)}%', - onChanged: (value) { - _onPDFChanged(value.roundToDouble()); - }, - ), - ), - ], - ), - if (canAdjustConifer(_fuelTypePreset!.code)) - Row( - children: [ - makeInputLabel( - 'Conifer', - (_pc ?? 0).toStringAsFixed(0), - '%', - textStyle, - textStyleBold, - ), - Expanded( - flex: sliderFlex, - child: FancySliderWidget( - value: _pc ?? 0, - min: 0, - max: 100, - divisions: 100, - activeColor: intensityClassColour, - label: '${(_pc ?? 0).toStringAsFixed(0)}%', - onChanged: (value) { - _onPCChanged(value.roundToDouble()); - }, - ), - ), - ], - ), - if (canAdjustCBH(_fuelTypePreset!.code)) - Row( - children: [ - makeInputLabel( - 'CBH', - _cbh?.toStringAsFixed(1) ?? '0.0', - ' m', - textStyle, - textStyleBold, - ), - Expanded( - flex: sliderFlex, - child: FancySliderWidget( - value: _cbh ?? 1, - min: 1, - max: 15, - divisions: 14, - activeColor: intensityClassColour, - label: _cbh!.toStringAsFixed(0), - onChanged: (value) { - _onCBHChanged(value.roundToDouble()); - }, - ), - ), - ], - ), - ], + ), ), ), - ), - - Card( - margin: const EdgeInsets.symmetric(vertical: 8.0), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: BasicInputWidget( - basicInput: _basicInput!, - prediction: prediction, - fuelTypePreset: _fuelTypePreset!, - onChanged: (BasicInput basicInput) { - _onBasicInputChanged(basicInput); - }, - ), + SingleChildScrollView( + key: const PageStorageKey('results_scroll'), + child: (prediction == null) + ? buildError(errorMessage) + : Padding( + padding: const EdgeInsets.only(top: 2.0), + child: ResultsStateWidget( + prediction: prediction, + minutes: _minutes, + fireSize: fireSize, + surfaceFlameLength: surfaceFlameLength, + input: input, + intensityClass: intensityClass, + intensityClassColour: intensityClassColour, + intensityClassTextColor: intensityClassTextColour, ), - ], - ), - ], - ), + ), ), - ), - ], + ], + ), ), - ), - - (prediction == null) - ? buildError(errorMessage) - : Padding( - padding: const EdgeInsets.only(top: 2.0), - child: ResultsStateWidget( - prediction: prediction, - minutes: _minutes, - fireSize: fireSize, - surfaceFlameLength: surfaceFlameLength, - input: input, - intensityClass: intensityClass, - intensityClassColour: intensityClassColour, - intensityClassTextColor: intensityClassTextColour, - ), - ), - ], + ], + ), ); } } diff --git a/lib/fbp_results.dart b/lib/fbp_results.dart index b6d2e45..5a17575 100644 --- a/lib/fbp_results.dart +++ b/lib/fbp_results.dart @@ -103,7 +103,7 @@ abstract class Group { } class SecondaryFireBehaviourGroup extends Group { - SecondaryFireBehaviourGroup({required super.heading}); + SecondaryFireBehaviourGroup({required super.heading, super.isExpanded}); @override Widget buildBody( @@ -330,7 +330,10 @@ List generateGroups() { heading: 'Basic Fire Behaviour Outputs', isExpanded: true, ), - SecondaryFireBehaviourGroup(heading: 'Advanced Fire Behaviour Outputs'), + SecondaryFireBehaviourGroup( + heading: 'Advanced Fire Behaviour Outputs', + isExpanded: true, + ), ]; return groups; } diff --git a/lib/main.dart b/lib/main.dart index 0b663fb..46c4bba 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,6 +23,7 @@ import 'fbp_advanced.dart'; import 'package:flutter/material.dart'; import 'cffdrs/fire_behaviour_prediction.dart'; import 'fmc.dart'; +import 'colors.dart'; void main() => runApp(const MyApp()); @@ -114,11 +115,9 @@ class HomePageState extends State { child: Column(children: [AboutPage()]), ); case (Section.advanced): - return const Center( - child: SingleChildScrollView( - padding: EdgeInsets.only(left: edgeInset, right: edgeInset), - child: Column(children: [AdvancedFireBehaviourPredictionForm()]), - ), + return Container( + padding: EdgeInsets.only(left: edgeInset, right: edgeInset), + child: const AdvancedFireBehaviourPredictionForm(), ); case (Section.fwi): return Container( @@ -146,7 +145,7 @@ class HomePageState extends State { } return Scaffold( appBar: AppBar( - backgroundColor: Color.fromARGB(255, 53, 150, 243), + backgroundColor: primaryColour, foregroundColor: Color.fromARGB(255, 255, 255, 255), title: Text(_getSectionText()), ),