new pipeline
Some checks failed
Release / meta (push) Successful in 2s
Release / android-build (push) Failing after 15s
Release / linux-build (push) Failing after 34s
Release / windows-build (push) Has been cancelled
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled
Some checks failed
Release / meta (push) Successful in 2s
Release / android-build (push) Failing after 15s
Release / linux-build (push) Failing after 34s
Release / windows-build (push) Has been cancelled
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled
This commit is contained in:
@@ -10,10 +10,10 @@ 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_CHANNEL: "stable"
|
||||||
BUILD_WINDOWS: "false" # set to "true" when you have a Windows runner available
|
BUILD_WINDOWS: "false" # set to "true" when you actually want Windows builds
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
meta:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
base_version: ${{ steps.meta.outputs.base }}
|
base_version: ${{ steps.meta.outputs.base }}
|
||||||
@@ -21,7 +21,21 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install OS deps (Android + Linux desktop)
|
- name: Determine version
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
RAW_VERSION=$(awk '/^version:/{print $2}' pubspec.yaml)
|
||||||
|
BASE_VERSION=${RAW_VERSION%%+*}
|
||||||
|
echo "base=${BASE_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
android-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: meta
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install OS deps (Android)
|
||||||
run: |
|
run: |
|
||||||
if command -v sudo >/dev/null 2>&1; then
|
if command -v sudo >/dev/null 2>&1; then
|
||||||
SUDO="sudo"
|
SUDO="sudo"
|
||||||
@@ -29,7 +43,7 @@ jobs:
|
|||||||
SUDO=""
|
SUDO=""
|
||||||
fi
|
fi
|
||||||
$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 liblzma-dev curl
|
$SUDO apt-get install -y unzip xz-utils zip libstdc++6 liblzma-dev curl
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
@@ -43,8 +57,11 @@ jobs:
|
|||||||
curl -fsSL https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -o /tmp/cli-tools.zip
|
curl -fsSL https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -o /tmp/cli-tools.zip
|
||||||
unzip -q /tmp/cli-tools.zip -d "$ANDROID_SDK_ROOT"/cmdline-tools
|
unzip -q /tmp/cli-tools.zip -d "$ANDROID_SDK_ROOT"/cmdline-tools
|
||||||
mv "$ANDROID_SDK_ROOT"/cmdline-tools/cmdline-tools "$ANDROID_SDK_ROOT"/cmdline-tools/latest
|
mv "$ANDROID_SDK_ROOT"/cmdline-tools/cmdline-tools "$ANDROID_SDK_ROOT"/cmdline-tools/latest
|
||||||
yes | "$ANDROID_SDK_ROOT"/cmdline-tools/latest/bin/sdkmanager --licenses
|
|
||||||
yes | "$ANDROID_SDK_ROOT"/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-33" "build-tools;33.0.2"
|
yes | "$ANDROID_SDK_ROOT"/cmdline-tools/latest/bin/sdkmanager --sdk_root="$ANDROID_SDK_ROOT" --licenses
|
||||||
|
yes | "$ANDROID_SDK_ROOT"/cmdline-tools/latest/bin/sdkmanager --sdk_root="$ANDROID_SDK_ROOT" \
|
||||||
|
"platform-tools" "platforms;android-33" "build-tools;33.0.2"
|
||||||
|
|
||||||
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
|
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
|
||||||
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"
|
||||||
@@ -57,46 +74,34 @@ jobs:
|
|||||||
- name: Flutter dependencies
|
- name: Flutter dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
- name: Enable Linux desktop
|
|
||||||
run: flutter config --enable-linux-desktop
|
|
||||||
|
|
||||||
- name: Determine version
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
RAW_VERSION=$(awk '/^version:/{print $2}' pubspec.yaml)
|
|
||||||
BASE_VERSION=${RAW_VERSION%%+*}
|
|
||||||
echo "base=${BASE_VERSION}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Build APK (release)
|
- name: Build APK (release)
|
||||||
run: |
|
run: |
|
||||||
flutter build apk --release
|
flutter build apk --release
|
||||||
cp build/app/outputs/flutter-apk/app-release.apk app-release.apk
|
cp build/app/outputs/flutter-apk/app-release.apk app-release.apk
|
||||||
|
|
||||||
- name: Build Linux binary (release)
|
- name: Upload Android APK artifact
|
||||||
run: |
|
|
||||||
flutter build linux --release
|
|
||||||
tar -C build/linux/x64/release/bundle -czf app-linux-x64.tar.gz .
|
|
||||||
|
|
||||||
- name: Upload APK artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: apk
|
name: android-apk
|
||||||
path: app-release.apk
|
path: app-release.apk
|
||||||
|
|
||||||
- name: Upload Linux artifact
|
linux-build:
|
||||||
uses: actions/upload-artifact@v4
|
runs-on: ubuntu-latest
|
||||||
with:
|
needs: meta
|
||||||
name: linux
|
|
||||||
path: app-linux-x64.tar.gz
|
|
||||||
|
|
||||||
windows-build:
|
|
||||||
if: env.BUILD_WINDOWS == 'true'
|
|
||||||
runs-on: windows-latest
|
|
||||||
needs: build
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install OS deps (Linux desktop)
|
||||||
|
run: |
|
||||||
|
if command -v sudo >/dev/null 2>&1; then
|
||||||
|
SUDO="sudo"
|
||||||
|
else
|
||||||
|
SUDO=""
|
||||||
|
fi
|
||||||
|
$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 liblzma-dev curl
|
||||||
|
|
||||||
- name: Setup Flutter
|
- name: Setup Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -105,48 +110,88 @@ jobs:
|
|||||||
- name: Flutter dependencies
|
- name: Flutter dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Enable Linux desktop
|
||||||
|
run: flutter config --enable-linux-desktop
|
||||||
|
|
||||||
|
- name: Build Linux binary (release)
|
||||||
|
run: |
|
||||||
|
flutter build linux --release
|
||||||
|
tar -C build/linux/x64/release/bundle -czf app-linux-x64.tar.gz .
|
||||||
|
|
||||||
|
- name: Upload Linux artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: linux-bundle
|
||||||
|
path: app-linux-x64.tar.gz
|
||||||
|
|
||||||
|
windows-build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: meta
|
||||||
|
# Job always runs; individual steps are gated so release jobs can still depend on it.
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
if: env.BUILD_WINDOWS == 'true'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
if: env.BUILD_WINDOWS == 'true'
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: ${{ env.FLUTTER_CHANNEL }}
|
||||||
|
|
||||||
|
- name: Flutter dependencies
|
||||||
|
if: env.BUILD_WINDOWS == 'true'
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
- name: Enable Windows desktop
|
- name: Enable Windows desktop
|
||||||
|
if: env.BUILD_WINDOWS == 'true'
|
||||||
run: flutter config --enable-windows-desktop
|
run: flutter config --enable-windows-desktop
|
||||||
|
|
||||||
- name: Build Windows binary (release)
|
- name: Build Windows binary (release)
|
||||||
|
if: env.BUILD_WINDOWS == 'true'
|
||||||
run: |
|
run: |
|
||||||
flutter build windows --release
|
flutter build windows --release
|
||||||
powershell -Command "Compress-Archive -Path build/windows/x64/runner/Release/* -DestinationPath app-windows-x64.zip"
|
powershell -Command "Compress-Archive -Path build/windows/x64/runner/Release/* -DestinationPath app-windows-x64.zip"
|
||||||
|
|
||||||
- name: Upload Windows artifact
|
- name: Upload Windows artifact
|
||||||
|
if: env.BUILD_WINDOWS == 'true'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows
|
name: windows-zip
|
||||||
path: app-windows-x64.zip
|
path: app-windows-x64.zip
|
||||||
|
|
||||||
release-dev:
|
release-dev:
|
||||||
if: github.ref_name == 'dev'
|
if: github.ref_name == 'dev'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build]
|
needs:
|
||||||
|
- meta
|
||||||
|
- android-build
|
||||||
|
- linux-build
|
||||||
|
- windows-build
|
||||||
steps:
|
steps:
|
||||||
- name: Download APK
|
- name: Download Android APK
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: apk
|
name: android-apk
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Download Linux bundle
|
- name: Download Linux bundle
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux
|
name: linux-bundle
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Download Windows bundle (optional)
|
- name: Download Windows bundle (optional)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows
|
name: windows-zip
|
||||||
path: artifacts
|
path: artifacts
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
|
|
||||||
- name: Prepare artifacts and tag
|
- name: Prepare artefacts and tag
|
||||||
id: bundle
|
id: bundle
|
||||||
run: |
|
run: |
|
||||||
BASE="${{ needs.build.outputs.base_version }}"
|
BASE="${{ needs.meta.outputs.base_version }}"
|
||||||
TAG="v${BASE}-dev"
|
TAG="v${BASE}-dev"
|
||||||
|
|
||||||
mv artifacts/app-release.apk "artifacts/app-${BASE}-dev.apk"
|
mv artifacts/app-release.apk "artifacts/app-${BASE}-dev.apk"
|
||||||
@@ -180,31 +225,35 @@ jobs:
|
|||||||
release-master:
|
release-master:
|
||||||
if: github.ref_name == 'master'
|
if: github.ref_name == 'master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build]
|
needs:
|
||||||
|
- meta
|
||||||
|
- android-build
|
||||||
|
- linux-build
|
||||||
|
- windows-build
|
||||||
steps:
|
steps:
|
||||||
- name: Download APK
|
- name: Download Android APK
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: apk
|
name: android-apk
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Download Linux bundle
|
- name: Download Linux bundle
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux
|
name: linux-bundle
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Download Windows bundle (optional)
|
- name: Download Windows bundle (optional)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows
|
name: windows-zip
|
||||||
path: artifacts
|
path: artifacts
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
|
|
||||||
- name: Prepare artifacts and tag
|
- name: Prepare artefacts and tag
|
||||||
id: bundle
|
id: bundle
|
||||||
run: |
|
run: |
|
||||||
BASE="${{ needs.build.outputs.base_version }}"
|
BASE="${{ needs.meta.outputs.base_version }}"
|
||||||
TAG="v${BASE}"
|
TAG="v${BASE}"
|
||||||
|
|
||||||
mv artifacts/app-release.apk "artifacts/app-${BASE}.apk"
|
mv artifacts/app-release.apk "artifacts/app-${BASE}.apk"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:mileograph_flutter/components/calculator/calculator.dart';
|
import 'package:mileograph_flutter/components/calculator/calculator.dart';
|
||||||
@@ -25,21 +27,19 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
final _mileageController = TextEditingController();
|
final _mileageController = TextEditingController();
|
||||||
final _networkController = TextEditingController();
|
final _networkController = TextEditingController();
|
||||||
bool _submitting = false;
|
bool _submitting = false;
|
||||||
bool _initialised = false;
|
|
||||||
bool _useManualMileage = false;
|
bool _useManualMileage = false;
|
||||||
RouteResult? _routeResult;
|
RouteResult? _routeResult;
|
||||||
final List<_TractionItem> _tractionItems = [_TractionItem.marker()];
|
final List<_TractionItem> _tractionItems = [_TractionItem.marker()];
|
||||||
int? _selectedTripId;
|
int? _selectedTripId;
|
||||||
bool _tripsRequested = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
Future.microtask(() {
|
||||||
if (!_tripsRequested) {
|
if (!mounted) return;
|
||||||
_tripsRequested = true;
|
final data = context.read<DataService>();
|
||||||
context.read<DataService>().fetchTrips();
|
data.fetchClassList();
|
||||||
}
|
data.fetchTrips();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,21 +140,6 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
if (!_initialised) {
|
|
||||||
_initialised = true;
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
context.read<DataService>().fetchClassList();
|
|
||||||
if (!_tripsRequested) {
|
|
||||||
_tripsRequested = true;
|
|
||||||
context.read<DataService>().fetchTrips();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _openCalculator() async {
|
Future<void> _openCalculator() async {
|
||||||
final result = await Navigator.of(context).push<RouteResult>(
|
final result = await Navigator.of(context).push<RouteResult>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -304,6 +289,9 @@ class _NewEntryPageState extends State<NewEntryPage> {
|
|||||||
};
|
};
|
||||||
await api.post('/add', body);
|
await api.post('/add', body);
|
||||||
}
|
}
|
||||||
|
if (mounted) {
|
||||||
|
context.read<DataService>().refreshLegs();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
|||||||
@@ -150,11 +150,12 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Fleet',
|
Text('Fleet', style: Theme.of(context).textTheme.labelMedium),
|
||||||
style: Theme.of(context).textTheme.labelMedium),
|
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text('Traction',
|
Text(
|
||||||
style: Theme.of(context).textTheme.headlineSmall),
|
'Traction',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -174,8 +175,10 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text('Filters',
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleMedium),
|
'Filters',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _clearFilters,
|
onPressed: _clearFilters,
|
||||||
child: const Text('Clear'),
|
child: const Text('Clear'),
|
||||||
@@ -199,24 +202,30 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
(c) => c.toLowerCase().contains(query),
|
(c) => c.toLowerCase().contains(query),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
initialValue:
|
initialValue: TextEditingValue(
|
||||||
TextEditingValue(text: _classController.text),
|
text: _classController.text,
|
||||||
fieldViewBuilder: (context, controller, focusNode,
|
),
|
||||||
onFieldSubmitted) {
|
fieldViewBuilder:
|
||||||
controller.value = _classController.value;
|
(
|
||||||
return TextField(
|
context,
|
||||||
controller: controller,
|
controller,
|
||||||
focusNode: focusNode,
|
focusNode,
|
||||||
decoration: const InputDecoration(
|
onFieldSubmitted,
|
||||||
labelText: 'Class',
|
) {
|
||||||
border: OutlineInputBorder(),
|
controller.value = _classController.value;
|
||||||
),
|
return TextField(
|
||||||
onChanged: (val) {
|
controller: controller,
|
||||||
_classController.text = val;
|
focusNode: focusNode,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Class',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (val) {
|
||||||
|
_classController.text = val;
|
||||||
|
},
|
||||||
|
onSubmitted: (_) => _refreshTraction(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onSubmitted: (_) => _refreshTraction(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onSelected: (String selection) {
|
onSelected: (String selection) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedClass = selection;
|
_selectedClass = selection;
|
||||||
@@ -249,7 +258,7 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
FilterChip(
|
FilterChip(
|
||||||
label: const Text('Had only'),
|
label: const Text('Had first'),
|
||||||
selected: _hadOnly,
|
selected: _hadOnly,
|
||||||
onSelected: (v) {
|
onSelected: (v) {
|
||||||
setState(() => _hadOnly = v);
|
setState(() => _hadOnly = v);
|
||||||
@@ -258,13 +267,18 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => setState(
|
onPressed: () => setState(
|
||||||
() => _showAdvancedFilters = !_showAdvancedFilters),
|
() => _showAdvancedFilters = !_showAdvancedFilters,
|
||||||
icon: Icon(_showAdvancedFilters
|
),
|
||||||
? Icons.expand_less
|
icon: Icon(
|
||||||
: Icons.expand_more),
|
_showAdvancedFilters
|
||||||
label: Text(_showAdvancedFilters
|
? Icons.expand_less
|
||||||
? 'Hide filters'
|
: Icons.expand_more,
|
||||||
: 'More filters'),
|
),
|
||||||
|
label: Text(
|
||||||
|
_showAdvancedFilters
|
||||||
|
? 'Hide filters'
|
||||||
|
: 'More filters',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: _refreshTraction,
|
onPressed: _refreshTraction,
|
||||||
@@ -398,10 +412,9 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'No traction found',
|
'No traction found',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
.textTheme
|
fontWeight: FontWeight.w700,
|
||||||
.titleMedium
|
),
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
const Text('Try relaxing the filters or sync again.'),
|
const Text('Try relaxing the filters or sync again.'),
|
||||||
@@ -449,8 +462,10 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
label: const Text('Back'),
|
label: const Text('Back'),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -484,25 +499,24 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${loco.locoClass} ${loco.number}',
|
'${loco.locoClass} ${loco.number}',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
.textTheme
|
fontWeight: FontWeight.w700,
|
||||||
.titleMedium
|
),
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
|
||||||
),
|
),
|
||||||
if ((loco.name ?? '').isNotEmpty)
|
if ((loco.name ?? '').isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
loco.name ?? '',
|
loco.name ?? '',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
.textTheme
|
fontStyle: FontStyle.italic,
|
||||||
.bodyMedium
|
),
|
||||||
?.copyWith(fontStyle: FontStyle.italic),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Chip(
|
Chip(
|
||||||
label: Text(status),
|
label: Text(status),
|
||||||
backgroundColor:
|
backgroundColor: Theme.of(
|
||||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
context,
|
||||||
|
).colorScheme.surfaceContainerHighest,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -529,9 +543,11 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: Icon(isSelected
|
icon: Icon(
|
||||||
? Icons.remove_circle_outline
|
isSelected
|
||||||
: Icons.add_circle_outline),
|
? Icons.remove_circle_outline
|
||||||
|
: Icons.add_circle_outline,
|
||||||
|
),
|
||||||
label: Text(isSelected ? 'Remove' : 'Add to entry'),
|
label: Text(isSelected ? 'Remove' : 'Add to entry'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -551,17 +567,9 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
value: (loco.trips ?? loco.journeys ?? 0).toString(),
|
value: (loco.trips ?? loco.journeys ?? 0).toString(),
|
||||||
),
|
),
|
||||||
if (operatorName.isNotEmpty)
|
if (operatorName.isNotEmpty)
|
||||||
_statPill(
|
_statPill(context, label: 'Operator', value: operatorName),
|
||||||
context,
|
|
||||||
label: 'Operator',
|
|
||||||
value: operatorName,
|
|
||||||
),
|
|
||||||
if (domain.isNotEmpty)
|
if (domain.isNotEmpty)
|
||||||
_statPill(
|
_statPill(context, label: 'Domain', value: domain),
|
||||||
context,
|
|
||||||
label: 'Domain',
|
|
||||||
value: domain,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -570,8 +578,11 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _statPill(BuildContext context,
|
Widget _statPill(
|
||||||
{required String label, required String value}) {
|
BuildContext context, {
|
||||||
|
required String label,
|
||||||
|
required String value,
|
||||||
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -581,16 +592,12 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text('$label: ', style: Theme.of(context).textTheme.labelSmall),
|
||||||
'$label: ',
|
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: Theme.of(context)
|
style: Theme.of(
|
||||||
.textTheme
|
context,
|
||||||
.labelSmall
|
).textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w700),
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -621,10 +628,9 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'${loco.locoClass} ${loco.number}',
|
'${loco.locoClass} ${loco.number}',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
.textTheme
|
fontWeight: FontWeight.w700,
|
||||||
.titleLarge
|
),
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -647,10 +653,11 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
_detailRow('Owner', loco.owner ?? ''),
|
_detailRow('Owner', loco.owner ?? ''),
|
||||||
_detailRow('Livery', loco.livery ?? ''),
|
_detailRow('Livery', loco.livery ?? ''),
|
||||||
_detailRow('Location', loco.location ?? ''),
|
_detailRow('Location', loco.location ?? ''),
|
||||||
|
_detailRow('Mileage', _formatNumber(loco.mileage ?? 0)),
|
||||||
_detailRow(
|
_detailRow(
|
||||||
'Mileage', _formatNumber(loco.mileage ?? 0)),
|
'Trips',
|
||||||
_detailRow('Trips',
|
(loco.trips ?? loco.journeys ?? 0).toString(),
|
||||||
(loco.trips ?? loco.journeys ?? 0).toString()),
|
),
|
||||||
_detailRow('EVN', loco.evn ?? ''),
|
_detailRow('EVN', loco.evn ?? ''),
|
||||||
if (loco.notes != null && loco.notes!.isNotEmpty)
|
if (loco.notes != null && loco.notes!.isNotEmpty)
|
||||||
_detailRow('Notes', loco.notes!),
|
_detailRow('Notes', loco.notes!),
|
||||||
@@ -676,17 +683,13 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
width: 110,
|
width: 110,
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
style: Theme.of(context)
|
style: Theme.of(
|
||||||
.textTheme
|
context,
|
||||||
.labelMedium
|
).textTheme.labelMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||||
?.copyWith(fontWeight: FontWeight.w600),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(value, style: Theme.of(context).textTheme.bodyMedium),
|
||||||
value,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,11 +5,29 @@ import 'package:flutter/scheduler.dart';
|
|||||||
import 'package:mileograph_flutter/objects/objects.dart';
|
import 'package:mileograph_flutter/objects/objects.dart';
|
||||||
import 'package:mileograph_flutter/services/apiService.dart'; // assumes you've moved HomepageStats + submodels to a separate file
|
import 'package:mileograph_flutter/services/apiService.dart'; // assumes you've moved HomepageStats + submodels to a separate file
|
||||||
|
|
||||||
|
class _LegFetchOptions {
|
||||||
|
final int limit;
|
||||||
|
final String sortBy;
|
||||||
|
final int sortDirection;
|
||||||
|
final String? dateRangeStart;
|
||||||
|
final String? dateRangeEnd;
|
||||||
|
|
||||||
|
const _LegFetchOptions({
|
||||||
|
this.limit = 100,
|
||||||
|
this.sortBy = 'date',
|
||||||
|
this.sortDirection = 0,
|
||||||
|
this.dateRangeStart,
|
||||||
|
this.dateRangeEnd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class DataService extends ChangeNotifier {
|
class DataService extends ChangeNotifier {
|
||||||
final ApiService api;
|
final ApiService api;
|
||||||
|
|
||||||
DataService({required this.api});
|
DataService({required this.api});
|
||||||
|
|
||||||
|
_LegFetchOptions _lastLegsFetch = const _LegFetchOptions();
|
||||||
|
|
||||||
// Homepage Data
|
// Homepage Data
|
||||||
HomepageStats? _homepageStats;
|
HomepageStats? _homepageStats;
|
||||||
HomepageStats? get homepageStats => _homepageStats;
|
HomepageStats? get homepageStats => _homepageStats;
|
||||||
@@ -89,6 +107,15 @@ class DataService extends ChangeNotifier {
|
|||||||
bool append = false,
|
bool append = false,
|
||||||
}) async {
|
}) async {
|
||||||
_isLegsLoading = true;
|
_isLegsLoading = true;
|
||||||
|
if (!append) {
|
||||||
|
_lastLegsFetch = _LegFetchOptions(
|
||||||
|
limit: limit,
|
||||||
|
sortBy: sortBy,
|
||||||
|
sortDirection: sortDirection,
|
||||||
|
dateRangeStart: dateRangeStart,
|
||||||
|
dateRangeEnd: dateRangeEnd,
|
||||||
|
);
|
||||||
|
}
|
||||||
final buffer = StringBuffer(
|
final buffer = StringBuffer(
|
||||||
'?sort_direction=$sortDirection&sort_by=$sortBy&offset=$offset&limit=$limit');
|
'?sort_direction=$sortDirection&sort_by=$sortBy&offset=$offset&limit=$limit');
|
||||||
if (dateRangeStart != null && dateRangeStart.isNotEmpty) {
|
if (dateRangeStart != null && dateRangeStart.isNotEmpty) {
|
||||||
@@ -117,6 +144,16 @@ class DataService extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> refreshLegs() {
|
||||||
|
return fetchLegs(
|
||||||
|
limit: _lastLegsFetch.limit,
|
||||||
|
sortBy: _lastLegsFetch.sortBy,
|
||||||
|
sortDirection: _lastLegsFetch.sortDirection,
|
||||||
|
dateRangeStart: _lastLegsFetch.dateRangeStart,
|
||||||
|
dateRangeEnd: _lastLegsFetch.dateRangeEnd,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> fetchHadTraction({int offset = 0, int limit = 100}) async {
|
Future<void> fetchHadTraction({int offset = 0, int limit = 100}) async {
|
||||||
await fetchTraction(
|
await fetchTraction(
|
||||||
hadOnly: true,
|
hadOnly: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user