Add new builtin profiles, better handline of initialization, bump version

This commit is contained in:
stopflock
2025-10-24 16:23:09 -05:00
parent 2a7004e5a2
commit c8ae925dc1
6 changed files with 78 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:latlong2/latlong.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'models/node_profile.dart';
import 'models/operator_profile.dart';
@@ -13,6 +14,8 @@ import 'services/offline_area_service.dart';
import 'services/node_cache.dart';
import 'services/tile_preview_service.dart';
import 'services/changelog_service.dart';
import 'services/operator_profile_service.dart';
import 'services/profile_service.dart';
import 'widgets/camera_provider_with_cache.dart';
import 'state/auth_state.dart';
import 'state/navigation_state.dart';
@@ -168,8 +171,26 @@ class AppState extends ChangeNotifier {
// Attempt to fetch missing tile type preview tiles (fails silently)
_fetchMissingTilePreviews();
await _operatorProfileState.init();
await _profileState.init();
// Check if we should add default profiles (first launch OR no profiles of each type exist)
final prefs = await SharedPreferences.getInstance();
const firstLaunchKey = 'profiles_defaults_initialized';
final isFirstLaunch = !(prefs.getBool(firstLaunchKey) ?? false);
// Load existing profiles to check each type independently
final existingOperatorProfiles = await OperatorProfileService().load();
final existingNodeProfiles = await ProfileService().load();
final shouldAddOperatorDefaults = isFirstLaunch || existingOperatorProfiles.isEmpty;
final shouldAddNodeDefaults = isFirstLaunch || existingNodeProfiles.isEmpty;
await _operatorProfileState.init(addDefaults: shouldAddOperatorDefaults);
await _profileState.init(addDefaults: shouldAddNodeDefaults);
// Mark defaults as initialized if this was first launch
if (isFirstLaunch) {
await prefs.setBool(firstLaunchKey, true);
}
await _suspectedLocationState.init(offlineMode: _settingsState.offlineMode);
await _uploadQueueState.init();
await _authState.init(_settingsState.uploadMode);

View File

@@ -13,6 +13,28 @@ class OperatorProfile {
required this.tags,
});
/// Built-in default: Lowe's operator profile
factory OperatorProfile.lowes() => OperatorProfile(
id: 'builtin-lowes',
name: "Lowe's",
tags: const {
'operator': "Lowe's",
'operator:wikidata': 'Q1373493',
'operator:type': 'private',
},
);
/// Built-in default: The Home Depot operator profile
factory OperatorProfile.homeDepot() => OperatorProfile(
id: 'builtin-home-depot',
name: 'The Home Depot',
tags: const {
'operator': 'The Home Depot',
'operator:wikidata': 'Q864407',
'operator:type': 'private',
},
);
OperatorProfile copyWith({
String? id,
String? name,

View File

@@ -20,7 +20,6 @@ class ProfileService {
// MUST convert to List before jsonEncode; the previous MappedIterable
// caused "Converting object to an encodable object failed".
final encodable = profiles
.where((p) => !p.builtin)
.map((p) => p.toJson())
.toList(); // <- crucial

View File

@@ -8,8 +8,19 @@ class OperatorProfileState extends ChangeNotifier {
List<OperatorProfile> get profiles => List.unmodifiable(_profiles);
Future<void> init() async {
Future<void> init({bool addDefaults = false}) async {
_profiles.addAll(await OperatorProfileService().load());
// Add default operator profiles if this is first launch
if (addDefaults) {
final defaults = [
OperatorProfile.lowes(),
OperatorProfile.homeDepot(),
];
_profiles.addAll(defaults);
await OperatorProfileService().save(_profiles);
}
}
void addOrUpdateProfile(OperatorProfile p) {

View File

@@ -17,19 +17,28 @@ class ProfileState extends ChangeNotifier {
_profiles.where(isEnabled).toList(growable: false);
// Initialize profiles from built-in and custom sources
Future<void> init() async {
// Initialize profiles: built-in + custom
_profiles.add(NodeProfile.genericAlpr());
_profiles.add(NodeProfile.flock());
_profiles.add(NodeProfile.motorola());
_profiles.add(NodeProfile.genetec());
_profiles.add(NodeProfile.leonardo());
_profiles.add(NodeProfile.neology());
_profiles.add(NodeProfile.genericGunshotDetector());
_profiles.add(NodeProfile.shotspotter());
_profiles.add(NodeProfile.flockRaven());
Future<void> init({bool addDefaults = false}) async {
// Load custom profiles from storage
_profiles.addAll(await ProfileService().load());
// Add built-in profiles if this is first launch
if (addDefaults) {
final builtinProfiles = [
NodeProfile.genericAlpr(),
NodeProfile.flock(),
NodeProfile.motorola(),
NodeProfile.genetec(),
NodeProfile.leonardo(),
NodeProfile.neology(),
NodeProfile.genericGunshotDetector(),
NodeProfile.shotspotter(),
NodeProfile.flockRaven(),
];
_profiles.addAll(builtinProfiles);
await ProfileService().save(_profiles);
}
// Load enabled profile IDs from prefs
final prefs = await SharedPreferences.getInstance();
final enabledIds = prefs.getStringList(_enabledPrefsKey);

View File

@@ -1,7 +1,7 @@
name: deflockapp
description: Map public surveillance infrastructure with OpenStreetMap
publish_to: "none"
version: 1.2.7+6 # The thing after the + is the version code, incremented with each release
version: 1.2.8+7 # The thing after the + is the version code, incremented with each release
environment:
sdk: ">=3.5.0 <4.0.0" # oauth2_client 4.x needs Dart 3.5+