6 Commits

Author SHA1 Message Date
irene 64b80eecc9 Merge branch 'ci/gps-speedo-workflow' into master
CI / Analyze & Build (push) Failing after 11s
2026-08-27 13:17:43 +09:00
irene 43f1e4ecb2 Merge branch 'feat/gps-update-interval' into master 2026-08-27 13:17:43 +09:00
irene 449e345e3e ci: add CI workflow (analyze + release APK)
- Modelled on iperf3-gui: analyze job on flutter-android runner.
- flutter analyze + flutter build apk --release.
- Uploads release APK as artifact.
2026-08-27 13:17:43 +09:00
irene c2bcd685cf feat: add 4Hz GPS update interval
- Switch LocationSettings to AndroidSettings with intervalDuration: 250ms.
- 250ms = 4Hz; higher rate is only achievable when GNSS is available (not indoors).
2026-08-27 13:17:15 +09:00
irene 54cec166b4 fix: add SettingsProvider to SpeedometerViewModel and fix ProviderNotFoundException
- SpeedometerViewModel now requires both LocationRepository and SettingsProvider
- SpeedometerView uses SettingsProvider.currentTheme instead of non-existent Theme provider
- main.dart: SettingsProvider created before SpeedometerViewModel in provider hierarchy
- Restores proper DI wiring after layered architecture refactor
2026-08-23 23:26:03 +09:00
mikkeli fff81719b1 Merge pull request 'refactor: layered architecture (core/data/ui separation)' (#1) from refactor/flutter-architecture into master
Reviewed-on: #1
2026-08-23 11:25:54 +00:00
5 changed files with 58 additions and 22 deletions
+41
View File
@@ -0,0 +1,41 @@
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
# Target the self-hosted Flutter/Android runner at the android-stuff org.
# See tool/setup_ci_runner.sh for how that runner is provisioned.
#
# The runner is host-mode so the same JDK 17 + Android SDK + Flutter
# toolchain is used as the verified release — nothing installs per-run.
jobs:
analyze:
name: Analyze & Build
runs-on: flutter-android
steps:
- uses: actions/checkout@v4
- name: Toolchain
run: |
flutter --version
echo "ANDROID_HOME=${ANDROID_HOME:?not set on this runner}"
- name: Get dependencies
run: flutter pub get
- name: Analyze
run: flutter analyze
- name: Build release APK
run: flutter build apk --release
- uses: actions/upload-artifact@v4
with:
name: app-release-apk
path: build/app/outputs/flutter-apk/app-release.apk
if-no-files-found: error
+2 -1
View File
@@ -15,9 +15,10 @@ class GpsService {
LocationAccuracy accuracy = LocationAccuracy.high, LocationAccuracy accuracy = LocationAccuracy.high,
}) { }) {
return Geolocator.getPositionStream( return Geolocator.getPositionStream(
locationSettings: LocationSettings( locationSettings: AndroidSettings(
accuracy: accuracy, accuracy: accuracy,
distanceFilter: 0, distanceFilter: 0,
intervalDuration: Duration(milliseconds: 250),
), ),
); );
} }
+9 -8
View File
@@ -33,19 +33,20 @@ void main() async {
}, },
), ),
// UI layer — ViewModels subscribe to repositories // UI layer — ViewModels subscribe to repositories
ChangeNotifierProvider<SpeedometerViewModel>(
create: (context) => SpeedometerViewModel(
context.read<LocationRepository>(),
),
),
ChangeNotifierProvider<OverlayViewModel>(
create: (_) => OverlayViewModel(),
),
ChangeNotifierProvider<SettingsProvider>( ChangeNotifierProvider<SettingsProvider>(
create: (context) => SettingsProvider( create: (context) => SettingsProvider(
context.read<SharedPreferences>(), context.read<SharedPreferences>(),
), ),
), ),
ChangeNotifierProvider<SpeedometerViewModel>(
create: (context) => SpeedometerViewModel(
context.read<LocationRepository>(),
context.read<SettingsProvider>(),
),
),
ChangeNotifierProvider<OverlayViewModel>(
create: (_) => OverlayViewModel(),
),
], ],
child: const SpeedoApp(), child: const SpeedoApp(),
), ),
@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
import '../../../core/constants/overlay_constants.dart'; import '../../../core/constants/overlay_constants.dart';
import '../../../core/theme/color_themes.dart'; import '../../../core/theme/color_themes.dart';
import '../../shared/speed_display.dart'; import '../../shared/speed_display.dart';
import '../../../providers/settings_provider.dart';
import 'speedometer_view_model.dart'; import 'speedometer_view_model.dart';
/// Thin UI view for the speedometer — renders speed and compass. /// Thin UI view for the speedometer — renders speed and compass.
@@ -19,9 +20,8 @@ class SpeedometerView extends StatelessWidget {
selector: (_, vm) => vm.displaySpeed, selector: (_, vm) => vm.displaySpeed,
builder: (context, displaySpeed, _) { builder: (context, displaySpeed, _) {
final vm = context.watch<SpeedometerViewModel>(); final vm = context.watch<SpeedometerViewModel>();
final theme = context.select<Theme, ColorTheme>( final settings = context.read<SettingsProvider>();
(theme) => ColorThemes.themes[0], final theme = settings.currentTheme;
);
// Staleness — UI-layer concern // Staleness — UI-layer concern
final age = DateTime.now().difference(vm.displayData.timestamp); final age = DateTime.now().difference(vm.displayData.timestamp);
@@ -1,5 +1,4 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import '../../../core/constants/speed_unit.dart'; import '../../../core/constants/speed_unit.dart';
import '../../../data/models/location_data.dart'; import '../../../data/models/location_data.dart';
import '../../../data/repositories/location_repository.dart'; import '../../../data/repositories/location_repository.dart';
@@ -11,22 +10,16 @@ import '../../../providers/settings_provider.dart';
/// the current display state to the view. No data-layer logic here. /// the current display state to the view. No data-layer logic here.
class SpeedometerViewModel extends ChangeNotifier { class SpeedometerViewModel extends ChangeNotifier {
final LocationRepository _repository; final LocationRepository _repository;
SettingsProvider? _settings; final SettingsProvider _settings;
SpeedUnit get displayUnit => _settings?.currentUnit ?? SpeedUnit.kmh; SpeedUnit get displayUnit => _settings.currentUnit;
LocationData get displayData => _repository.currentData; LocationData get displayData => _repository.currentData;
String get displaySpeedText => _repository.currentData.displaySpeed; String get displaySpeedText => _repository.currentData.displaySpeed;
String get displayHeading => _repository.currentData.displayHeading; String get displayHeading => _repository.currentData.displayHeading;
bool get isSpeedValid => _repository.currentData.isSpeedValid; bool get isSpeedValid => _repository.currentData.isSpeedValid;
bool get isHeadingValid => _repository.currentData.isHeadingValid; bool get isHeadingValid => _repository.currentData.isHeadingValid;
SpeedometerViewModel(this._repository); SpeedometerViewModel(this._repository, this._settings);
/// Call when the ViewModel is inserted into the provider tree so it
/// can listen to SettingsProvider and repository state.
void attach(BuildContext context) {
_settings = context.read<SettingsProvider>();
}
double get displaySpeed { double get displaySpeed {
return displayUnit.convert(_repository.currentData.speed); return displayUnit.convert(_repository.currentData.speed);