pretty broken, icons missing, login not working. browser opens though and cams still appear on map

This commit is contained in:
stopflock
2025-07-19 14:11:02 -05:00
parent d3d1e4a7b2
commit 56518bab28
11 changed files with 689 additions and 154 deletions

View File

@@ -2,8 +2,13 @@
A minimal Flutter scaffold for mapping and tagging Flockstyle ALPR cameras in OpenStreetMap.
## Platform setup notes
# NOTE:
Forks should register for their own oauth2 client id from OSM: https://www.openstreetmap.org/oauth2/applications
These are hardcoded in lib/services/auth_service.dart for each app.
If you discover a bug that causes bad behavior w/rt OSM API, you might want to register a new one for the patched version to distinguish them. You can also then delete the old version from OSM to prevent new people from using the old version.
## Platform setup notes
### iOS
Add location permission strings to `ios/Runner/Info.plist`:
```xml

View File

@@ -1,45 +1,52 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
// Flutter plugin *must* be applied last.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.flock_map_app"
compileSdk = flutter.compileSdkVersion
// ndkVersion = flutter.ndkVersion
// Matches current stable Flutter (compileSdk 34 as of July 2025)
compileSdk = 35
// NDK only needed if you build native plugins; keep your pinned version
ndkVersion = "27.0.12077973"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
// Application ID (package name)
applicationId = "com.example.flock_map_app"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
// ────────────────────────────────────────────────────────────
// oauth2_client 4.x & flutter_web_auth_2 5.x require minSdk 23
// ────────────────────────────────────────────────────────────
minSdk = 23
targetSdk = 34
// Flutter tool injects these during `flutter build`
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
// Using debug signing so `flutter run --release` works outofbox.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
// Path up to the Flutter project directory
source = "../.."
}

View File

@@ -1,43 +1,67 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Location permissions for bluedot positioning -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application
android:label="flock_map_app"
android:name="${applicationName}"
android:label="flock_map_app"
android:icon="@mipmap/ic_launcher">
<!-- Main Flutter activity -->
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:hardwareAccelerated="true"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<!-- The theme behind the splash while Flutter initializes -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<!-- Launcher intent -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- OPTIONAL: handle flockmap://auth redirect right here
(kept for backwardcompat) -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="flockmap" android:host="auth"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<!-- flutter_web_auth_2 callback activity (V2 embedding) -->
<activity
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter android:label="flutter_web_auth_2">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!-- flockmap://auth -->
<data android:scheme="flockmap" android:host="auth"/>
</intent-filter>
</activity>
<!-- Flutter plugin registration flag -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<!-- Required so ProcessTextPlugin can query other apps -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
@@ -45,3 +69,4 @@
</intent>
</queries>
</manifest>

View File

@@ -47,5 +47,23 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<!-- OAuth2 redirect handler -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>None</string>
<key>CFBundleURLSchemes</key>
<array>
<string>flockmap</string>
</array>
</dict>
</array>
<!-- (Optional) allow opening the system browser and returning -->
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
</dict>
</plist>

View File

@@ -1,50 +1,77 @@
import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'models/camera_profile.dart';
import 'models/pending_upload.dart';
import 'services/auth_service.dart';
import 'services/uploader.dart';
// ------------------ AddCameraSession ------------------
class AddCameraSession {
AddCameraSession({required this.profile, this.directionDegrees = 0});
CameraProfile profile;
double directionDegrees;
LatLng? target;
}
// ------------------ AppState ------------------
class AppState extends ChangeNotifier {
AppState() {
_profiles = [CameraProfile.alpr()];
_enabled = {..._profiles}; // all enabled by default
_init();
}
// ---------- Auth ----------
bool _loggedIn = false;
bool get isLoggedIn => _loggedIn;
void setLoggedIn(bool v) {
_loggedIn = v;
notifyListeners();
}
final _auth = AuthService();
String? _username;
// ---------- Profiles & toggles ----------
late final List<CameraProfile> _profiles;
late final Set<CameraProfile> _enabled;
List<CameraProfile> get profiles => List.unmodifiable(_profiles);
bool isEnabled(CameraProfile p) => _enabled.contains(p);
late final List<CameraProfile> _profiles = [CameraProfile.alpr()];
final Set<CameraProfile> _enabled = {};
void toggleProfile(CameraProfile p, bool enable) {
enable ? _enabled.add(p) : _enabled.remove(p);
notifyListeners();
}
List<CameraProfile> get enabledProfiles => _profiles
.where((p) => _enabled.contains(p))
.toList(growable: false);
// ---------- Add-camera session ----------
AddCameraSession? _session;
AddCameraSession? get session => _session;
final List<PendingUpload> _queue = [];
Timer? _uploadTimer;
bool get isLoggedIn => _username != null;
String get username => _username ?? '';
// ---------- Init ----------
Future<void> _init() async {
_enabled.addAll(_profiles);
await _loadQueue();
if (await _auth.isLoggedIn()) {
_username = await _auth.login();
}
_startUploader();
notifyListeners();
}
// ---------- Auth ----------
Future<void> login() async {
_username = await _auth.login();
notifyListeners();
}
Future<void> logout() async {
await _auth.logout();
_username = null;
notifyListeners();
}
// ---------- Profiles ----------
List<CameraProfile> get profiles => List.unmodifiable(_profiles);
bool isEnabled(CameraProfile p) => _enabled.contains(p);
List<CameraProfile> get enabledProfiles =>
_profiles.where(isEnabled).toList(growable: false);
void toggleProfile(CameraProfile p, bool e) {
e ? _enabled.add(p) : _enabled.remove(p);
notifyListeners();
}
// ---------- Addcamera session ----------
void startAddSession() {
_session = AddCameraSession(profile: enabledProfiles.first);
notifyListeners();
@@ -59,7 +86,6 @@ class AppState extends ChangeNotifier {
if (directionDeg != null) _session!.directionDegrees = directionDeg;
if (profile != null) _session!.profile = profile;
if (target != null) _session!.target = target;
notifyListeners();
}
void cancelSession() {
@@ -67,10 +93,6 @@ class AppState extends ChangeNotifier {
notifyListeners();
}
// ---------- Pending uploads ----------
final List<PendingUpload> _queue = [];
List<PendingUpload> get queue => List.unmodifiable(_queue);
void commitSession() {
if (_session?.target == null) return;
_queue.add(
@@ -80,8 +102,62 @@ class AppState extends ChangeNotifier {
profile: _session!.profile,
),
);
_saveQueue();
_session = null;
notifyListeners();
}
// ---------- Queue persistence ----------
Future<void> _saveQueue() async {
final prefs = await SharedPreferences.getInstance();
final jsonList = _queue.map((e) => e.toJson()).toList();
await prefs.setString('queue', jsonEncode(jsonList));
}
Future<void> _loadQueue() async {
final prefs = await SharedPreferences.getInstance();
final jsonStr = prefs.getString('queue');
if (jsonStr == null) return;
final list = jsonDecode(jsonStr) as List<dynamic>;
_queue
..clear()
..addAll(list.map((e) => PendingUpload.fromJson(e)));
}
// ---------- Uploader ----------
void _startUploader() {
_uploadTimer?.cancel();
// No uploads without auth or queue.
if (_queue.isEmpty) return;
_uploadTimer = Timer.periodic(const Duration(seconds: 10), (t) async {
if (_queue.isEmpty) return;
final access = await _auth.getAccessToken();
if (access == null) return; // not logged in
final item = _queue.first;
final up = Uploader(access, () {
_queue.remove(item);
_saveQueue();
notifyListeners();
});
final ok = await up.upload(item);
if (!ok) {
item.attempts++;
if (item.attempts >= 3) {
// give up until next launch
_uploadTimer?.cancel();
} else {
await Future.delayed(const Duration(seconds: 20));
}
}
});
}
// ---------- Exposed getters ----------
int get pendingCount => _queue.length;
}

View File

@@ -5,12 +5,28 @@ class PendingUpload {
final LatLng coord;
final double direction;
final CameraProfile profile;
final DateTime queuedAt;
int attempts;
PendingUpload({
required this.coord,
required this.direction,
required this.profile,
}) : queuedAt = DateTime.now();
this.attempts = 0,
});
Map<String, dynamic> toJson() => {
'lat': coord.latitude,
'lon': coord.longitude,
'dir': direction,
'profile': profile.name,
'attempts': attempts,
};
factory PendingUpload.fromJson(Map<String, dynamic> j) => PendingUpload(
coord: LatLng(j['lat'], j['lon']),
direction: j['dir'],
profile: CameraProfile.alpr(), // only builtin for now
attempts: j['attempts'] ?? 0,
);
}

View File

@@ -15,11 +15,30 @@ class SettingsScreen extends StatelessWidget {
body: ListView(
padding: const EdgeInsets.all(16),
children: [
SwitchListTile(
title: const Text('Logged in to OSM (OAuth coming soon)'),
value: appState.isLoggedIn,
onChanged: null, // disabled for now
ListTile(
leading: Icon(
appState.isLoggedIn ? Icons.person : Icons.login,
color: appState.isLoggedIn ? Colors.green : null,
),
title: Text(appState.isLoggedIn
? 'Loggedin as ${appState.username}'
: 'Login to OpenStreetMap'),
onTap: () async {
if (appState.isLoggedIn) {
await appState.logout();
} else {
await appState.login();
}
},
),
if (appState.isLoggedIn)
ListTile(
leading: const Icon(Icons.cloud_upload),
title: const Text('Test upload'),
onTap: () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Upload will run soon...')),
),
),
const Divider(),
const Text('Camera Profiles',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
@@ -30,6 +49,11 @@ class SettingsScreen extends StatelessWidget {
onChanged: (v) => appState.toggleProfile(p, v),
),
),
const Divider(),
ListTile(
leading: const Icon(Icons.sync),
title: Text('Pending uploads: ${appState.pendingCount}'),
),
],
),
);

View File

@@ -0,0 +1,76 @@
import 'dart:convert';
import 'package:oauth2_client/oauth2_client.dart';
import 'package:oauth2_client/oauth2_helper.dart';
import 'package:http/http.dart' as http;
/// Handles OAuth2 PKCE login to OpenStreetMap and exposes
/// the stored access token & display name.
///
/// ─ Requirements ─
/// • Register an OAuth app at
/// https://www.openstreetmap.org/oauth2/applications
/// Redirect URI: flockmap://auth
/// • Put that client ID below (replace 'flockmap').
class AuthService {
static const _clientId = 'flockmap'; // ← replace with your ID
static const _redirect = 'flockmap://auth';
late final OAuth2Helper _helper;
String? _displayName; // cached after login
String? get displayName => _displayName;
AuthService() {
final client = OAuth2Client(
authorizeUrl: 'https://www.openstreetmap.org/oauth2/authorize',
tokenUrl: 'https://www.openstreetmap.org/oauth2/token',
redirectUri: _redirect,
customUriScheme: 'flockmap', // matches redirect scheme
);
_helper = OAuth2Helper(
client,
clientId: _clientId,
scopes: ['write_api'],
enablePKCE: true, // PKCE flow
// No custom token store needed: oauth2_client will
// autouse flutter_secure_storage when present.
);
}
/* ───────── Public helpers ───────── */
/// Returns `true` if a nonexpired token is stored.
Future<bool> isLoggedIn() async =>
(await _helper.getTokenFromStorage())?.isExpired() == false;
/// Launches browser login if necessary; caches display name.
Future<String?> login() async {
final token = await _helper.getToken();
if (token?.accessToken == null) return null;
_displayName = await _fetchUsername(token!.accessToken!);
return _displayName;
}
Future<void> logout() async {
await _helper.removeAllTokens();
_displayName = null;
}
/// Safely fetch current access token (or null).
Future<String?> getAccessToken() async =>
(await _helper.getTokenFromStorage())?.accessToken;
/* ───────── Internal ───────── */
Future<String?> _fetchUsername(String accessToken) async {
final resp = await http.get(
Uri.parse('https://api.openstreetmap.org/api/0.6/user/details.json'),
headers: {'Authorization': 'Bearer $accessToken'},
);
if (resp.statusCode != 200) return null;
return jsonDecode(resp.body)['user']?['display_name'];
}
}

View File

@@ -0,0 +1,66 @@
import 'dart:async';
import 'package:http/http.dart' as http;
import '../models/pending_upload.dart';
class Uploader {
Uploader(this.accessToken, this.onSuccess);
final String accessToken;
final void Function() onSuccess;
Future<bool> upload(PendingUpload p) async {
try {
// 1. open changeset
final csXml = '''
<osm>
<changeset>
<tag k="created_by" v="FlockMap 0.5"/>
<tag k="comment" v="Add surveillance camera"/>
</changeset>
</osm>''';
final csResp = await _post('/api/0.6/changeset/create', csXml);
if (csResp.statusCode != 200) return false;
final csId = csResp.body;
// 2. create node
final nodeXml = '''
<osm>
<node changeset="$csId" lat="${p.coord.latitude}" lon="${p.coord.longitude}">
<tag k="man_made" v="surveillance"/>
<tag k="surveillance:type" v="ALPR"/>
<tag k="camera:type" v="fixed"/>
<tag k="direction" v="${p.direction.round()}"/>
</node>
</osm>''';
final nodeResp = await _put('/api/0.6/node/create', nodeXml);
if (nodeResp.statusCode != 200) return false;
// 3. close changeset
await _put('/api/0.6/changeset/$csId/close', '');
onSuccess();
return true;
} catch (_) {
return false;
}
}
Future<http.Response> _post(String path, String body) => http.post(
Uri.https('api.openstreetmap.org', path),
headers: _headers,
body: body,
);
Future<http.Response> _put(String path, String body) => http.put(
Uri.https('api.openstreetmap.org', path),
headers: _headers,
body: body,
);
Map<String, String> get _headers => {
'Authorization': 'Bearer $accessToken',
'Content-Type': 'text/xml',
};
}

View File

@@ -9,14 +9,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
@@ -49,14 +41,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.6"
fake_async:
desktop_webview_window:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
name: desktop_webview_window
sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
version: "0.2.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
@@ -78,11 +86,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.2.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: f7eceb0bc6f4fd0441e29d43cab9ac2a1c5ffd7ea7b64075136b718c46954874
url: "https://pub.dev"
source: hosted
version: "10.0.0-beta.4"
flutter_secure_storage_darwin:
dependency: transitive
description:
name: flutter_secure_storage_darwin
sha256: f226f2a572bed96bc6542198ebaec227150786e34311d455a7e2d3d06d951845
url: "https://pub.dev"
source: hosted
version: "0.1.0"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: "9b4b73127e857cd3117d43a70fa3dddadb6e0b253be62e6a6ab85caa0742182c"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: "4c3f233e739545c6cb09286eeec1cc4744138372b985113acc904f7263bef517"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: ff32af20f70a8d0e59b2938fc92de35b54a74671041c814275afd80e27df9f21
url: "https://pub.dev"
source: hosted
version: "4.0.0"
flutter_web_auth_2:
dependency: "direct main"
description:
name: flutter_web_auth_2
sha256: "2483d1fd3c45fe1262446e8d5f5490f01b864f2e7868ffe05b4727e263cc0182"
url: "https://pub.dev"
source: hosted
version: "5.0.0-alpha.3"
flutter_web_auth_2_platform_interface:
dependency: transitive
description:
name: flutter_web_auth_2_platform_interface
sha256: "45927587ebb2364cd273675ec95f6f67b81725754b416cef2b65cdc63fd3e853"
url: "https://pub.dev"
source: hosted
version: "5.0.0-alpha.0"
flutter_web_plugins:
dependency: transitive
description: flutter
@@ -168,30 +235,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lists:
dependency: transitive
description:
@@ -208,14 +251,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
@@ -248,6 +283,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
oauth2_client:
dependency: "direct main"
description:
name: oauth2_client
sha256: d6a146049f36ef2da32bdc7a7a9e5671a0e66ea596d8f70a26de4cddfcab4d2e
url: "https://pub.dev"
source: hosted
version: "4.2.0"
path:
dependency: transitive
description:
@@ -256,6 +299,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@@ -288,6 +387,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.5"
random_string:
dependency: transitive
description:
name: random_string
sha256: "03b52435aae8cbdd1056cf91bfc5bf845e9706724dd35ae2e99fa14a1ef79d02"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
@@ -309,22 +472,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
@@ -341,14 +488,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
typed_data:
dependency: transitive
description:
@@ -365,6 +504,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.1"
url_launcher:
dependency: transitive
description:
name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
url: "https://pub.dev"
source: hosted
version: "6.3.16"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
url: "https://pub.dev"
source: hosted
version: "6.3.3"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
uuid:
dependency: transitive
description:
@@ -381,14 +584,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
web:
dependency: transitive
description:
@@ -397,6 +592,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
url: "https://pub.dev"
source: hosted
version: "5.14.0"
window_to_front:
dependency: transitive
description:
name: window_to_front
sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee"
url: "https://pub.dev"
source: hosted
version: "0.0.3"
wkt_parser:
dependency: transitive
description:
@@ -405,6 +616,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
sdks:
dart: ">=3.7.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.27.0"

View File

@@ -1,25 +1,28 @@
name: flock_map_app
description: Simple OSM cameramapping client
publish_to: "none"
version: 0.2.1
version: 0.5.0
environment:
sdk: ">=3.3.0 <4.0.0"
sdk: ">=3.5.0 <4.0.0" # oauth2_client 4.x needs Dart 3.5+
dependencies:
flutter:
sdk: flutter
# UI & Map
provider: ^6.1.2
flutter_map: ^6.1.0
flutter_map: ^6.2.1
latlong2: ^0.9.0
geolocator: ^10.1.0
http: ^1.2.1
dev_dependencies:
flutter_test:
sdk: flutter
# Auth, storage, prefs
oauth2_client: ^4.2.0
flutter_web_auth_2: 5.0.0-alpha.3
flutter_secure_storage: 10.0.0-beta.4
flutter:
uses-material-design: true
# Persistence
shared_preferences: ^2.2.2