new entry panel fixes
Some checks failed
Some checks failed
This commit is contained in:
@@ -9,7 +9,7 @@ on:
|
|||||||
env:
|
env:
|
||||||
JAVA_VERSION: "17"
|
JAVA_VERSION: "17"
|
||||||
ANDROID_SDK_ROOT: "${{ github.workspace }}/android-sdk"
|
ANDROID_SDK_ROOT: "${{ github.workspace }}/android-sdk"
|
||||||
FLUTTER_CHANNEL: "stable"
|
FLUTTER_VERSION: "3.22.2"
|
||||||
BUILD_WINDOWS: "false" # set to "true" when you actually want Windows builds
|
BUILD_WINDOWS: "false" # set to "true" when you actually want Windows builds
|
||||||
GITEA_BASE_URL: https://git.tgj.services
|
GITEA_BASE_URL: https://git.tgj.services
|
||||||
|
|
||||||
@@ -70,11 +70,17 @@ jobs:
|
|||||||
echo "$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_PATH"
|
echo "$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_PATH"
|
||||||
echo "$ANDROID_SDK_ROOT/build-tools/33.0.2" >> "$GITHUB_PATH"
|
echo "$ANDROID_SDK_ROOT/build-tools/33.0.2" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
- name: Setup Flutter
|
- name: Install Flutter SDK
|
||||||
uses: subosito/flutter-action@v2
|
run: |
|
||||||
with:
|
set -euo pipefail
|
||||||
channel: ${{ env.FLUTTER_CHANNEL }}
|
FLUTTER_HOME="$HOME/flutter"
|
||||||
cache: true
|
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
|
||||||
|
rm -rf "$FLUTTER_HOME"
|
||||||
|
curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
|
||||||
|
tar -C "$HOME" -xf /tmp/flutter.tar.xz
|
||||||
|
fi
|
||||||
|
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
|
||||||
|
"$FLUTTER_HOME/bin/flutter" --version
|
||||||
|
|
||||||
- name: Allow all git directories (CI)
|
- name: Allow all git directories (CI)
|
||||||
run: git config --global --add safe.directory '*'
|
run: git config --global --add safe.directory '*'
|
||||||
@@ -175,11 +181,17 @@ jobs:
|
|||||||
$SUDO apt-get update
|
$SUDO apt-get update
|
||||||
$SUDO apt-get install -y unzip xz-utils zip libstdc++6 libglu1-mesa clang cmake ninja-build pkg-config libgtk-3-dev libsecret-1-dev liblzma-dev curl jq
|
$SUDO apt-get install -y unzip xz-utils zip libstdc++6 libglu1-mesa clang cmake ninja-build pkg-config libgtk-3-dev libsecret-1-dev liblzma-dev curl jq
|
||||||
|
|
||||||
- name: Setup Flutter
|
- name: Install Flutter SDK
|
||||||
uses: subosito/flutter-action@v2
|
run: |
|
||||||
with:
|
set -euo pipefail
|
||||||
channel: ${{ env.FLUTTER_CHANNEL }}
|
FLUTTER_HOME="$HOME/flutter"
|
||||||
cache: true
|
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
|
||||||
|
rm -rf "$FLUTTER_HOME"
|
||||||
|
curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
|
||||||
|
tar -C "$HOME" -xf /tmp/flutter.tar.xz
|
||||||
|
fi
|
||||||
|
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
|
||||||
|
"$FLUTTER_HOME/bin/flutter" --version
|
||||||
|
|
||||||
- name: Allow all git directories (CI)
|
- name: Allow all git directories (CI)
|
||||||
run: git config --global --add safe.directory '*'
|
run: git config --global --add safe.directory '*'
|
||||||
|
|||||||
@@ -283,14 +283,53 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> _validateRequiredFields() async {
|
||||||
|
final missing = <String>[];
|
||||||
|
|
||||||
|
if (_useManualMileage) {
|
||||||
|
if (_startController.text.trim().isEmpty) missing.add('From');
|
||||||
|
if (_endController.text.trim().isEmpty) missing.add('To');
|
||||||
|
final mileageText = _mileageController.text.trim();
|
||||||
|
if (double.tryParse(mileageText) == null) {
|
||||||
|
missing.add('Mileage');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_routeResult == null || _routeResult!.calculatedRoute.isEmpty) {
|
||||||
|
missing.add('Route');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_networkController.text.trim().isEmpty) {
|
||||||
|
missing.add('Network');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing.isEmpty) return true;
|
||||||
|
if (!mounted) return false;
|
||||||
|
|
||||||
|
final fieldList = missing.join(', ');
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
title: const Text('Required field missing'),
|
||||||
|
content: Text(
|
||||||
|
missing.length == 1
|
||||||
|
? 'Please fill the following field: $fieldList.'
|
||||||
|
: 'Please fill the following fields: $fieldList.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _submit() async {
|
Future<void> _submit() async {
|
||||||
if (!_formKey.currentState!.validate()) return;
|
if (!_formKey.currentState!.validate()) return;
|
||||||
if (!_useManualMileage && _routeResult == null) {
|
if (!await _validateRequiredFields()) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Please calculate mileage first')),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() => _submitting = true);
|
setState(() => _submitting = true);
|
||||||
final api = context.read<ApiService>();
|
final api = context.read<ApiService>();
|
||||||
final routeStations = _routeResult?.calculatedRoute ?? [];
|
final routeStations = _routeResult?.calculatedRoute ?? [];
|
||||||
@@ -524,6 +563,10 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final twoCol = !isMobile && constraints.maxWidth > 1000;
|
final twoCol = !isMobile && constraints.maxWidth > 1000;
|
||||||
|
final tractionEmpty = _tractionItems.length == 1;
|
||||||
|
final mileageEmpty = !_useManualMileage && _routeResult == null;
|
||||||
|
final balancePanels = twoCol && tractionEmpty && mileageEmpty;
|
||||||
|
final balancedHeight = balancePanels ? 165.0 : null;
|
||||||
|
|
||||||
final detailPanel = _section('Details', [
|
final detailPanel = _section('Details', [
|
||||||
_buildTripSelector(context),
|
_buildTripSelector(context),
|
||||||
@@ -614,11 +657,27 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildTractionList(),
|
_buildTractionList(),
|
||||||
]);
|
], minHeight: balancedHeight);
|
||||||
|
|
||||||
final mileagePanel = _section(
|
final mileagePanel = _section(
|
||||||
'Mileage',
|
'Mileage',
|
||||||
[
|
[
|
||||||
|
if (!_useManualMileage)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
minimumSize: const Size(0, 32),
|
||||||
|
),
|
||||||
|
onPressed: _openCalculator,
|
||||||
|
icon: const Icon(Icons.calculate, size: 18),
|
||||||
|
label: const Text('Open mileage calculator'),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (_useManualMileage)
|
if (_useManualMileage)
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _mileageController,
|
controller: _mileageController,
|
||||||
@@ -637,14 +696,12 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'${_routeResult!.distance.toStringAsFixed(2)} mi',
|
'${_routeResult!.distance.toStringAsFixed(2)} mi',
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
if (!_useManualMileage)
|
else
|
||||||
Align(
|
const Padding(
|
||||||
alignment: Alignment.centerLeft,
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
child: ElevatedButton.icon(
|
child: Text(
|
||||||
onPressed: _openCalculator,
|
'No route selected. Use the calculator to add a route.',
|
||||||
icon: const Icon(Icons.calculate),
|
|
||||||
label: const Text('Open mileage calculator'),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -656,6 +713,7 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
_saveDraft();
|
_saveDraft();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
minHeight: balancedHeight,
|
||||||
);
|
);
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
@@ -663,6 +721,22 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
minimumSize: const Size(0, 36),
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
onPressed: _submitting
|
||||||
|
? null
|
||||||
|
: () => _resetFormState(clearDraft: true),
|
||||||
|
icon: const Icon(Icons.clear, size: 16),
|
||||||
|
label: const Text('Clear form'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
detailPanel,
|
detailPanel,
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
twoCol
|
twoCol
|
||||||
@@ -682,14 +756,6 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: _submitting
|
|
||||||
? null
|
|
||||||
: () => _resetFormState(clearDraft: true),
|
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
label: const Text('Clear form'),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: _submitting ? null : _submit,
|
onPressed: _submitting ? null : _submit,
|
||||||
icon: _submitting
|
icon: _submitting
|
||||||
@@ -791,8 +857,13 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _section(String title, List<Widget> children, {Widget? trailing}) {
|
Widget _section(
|
||||||
return Card(
|
String title,
|
||||||
|
List<Widget> children, {
|
||||||
|
Widget? trailing,
|
||||||
|
double? minHeight,
|
||||||
|
}) {
|
||||||
|
Widget card = Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -821,6 +892,15 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (minHeight != null) {
|
||||||
|
card = ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minHeight: minHeight),
|
||||||
|
child: card,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return card;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user