new entry panel fixes
Some checks failed
Release / android-build (push) Blocked by required conditions
Release / linux-build (push) Blocked by required conditions
Release / meta (push) Successful in 2s
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled

This commit is contained in:
2025-12-14 12:51:20 +00:00
parent 924c23a401
commit 13cd3cdf14
2 changed files with 128 additions and 36 deletions

View File

@@ -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 '*'

View File

@@ -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;
} }
} }