Compare commits

...

6 Commits

62 changed files with 840 additions and 509 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#161616</color>
</resources>

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip

View File

@ -19,8 +19,8 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
id "com.android.application" version "8.2.1" apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
}
include ":app"

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="1024"
height="1024"
version="1.1"
id="svg7"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs7" />
<path
fill="#b7c8d6"
d="M 574.95327,727.1786 C 621.0786,715.75383 835.07211,783.96642 779.2208,873.56 753.19461,915.31393 687.82887,937.7341 641.46397,948.12902 539.77668,970.92877 374.95759,967.86413 283.52211,909.4802 247.56146,886.51865 220.19118,850.20332 247.55524,809.35168 c 34.15296,-50.9852 139.25338,-77.39721 200.9633,-82.17308 16.45266,21.21609 32.90222,41.92815 46.42712,65.17905 9.0384,11.05452 14.33698,24.61988 21.44634,36.91893 3.59047,-14.15028 6.89157,-27.4543 14.02894,-40.32583 6.59911,-15.67172 31.29678,-51.67592 44.53233,-61.77215 z m -44.53233,61.77215 c -7.13737,12.87153 -10.43847,26.17555 -14.02894,40.32583 -7.10936,-12.29905 -12.40794,-25.86441 -21.44634,-36.91893 -46.20933,2.92776 -77.39409,3.08643 -122.53002,23.51846 -61.76593,27.95833 -16.41221,52.7369 22.76554,65.33463 122.60158,39.42354 314.21225,-2.73796 285.29252,-47.16762 -24.82522,-38.14168 -109.29445,-34.96191 -150.05276,-45.09237 z"
id="path3"
style="stroke-width:3.11132" />
<path
fill="#027ffe"
transform="scale(2)"
d="m 235.675,384.386 c -6.071,-8.749 -13.779,-16.557 -20.711,-24.63 C 168.07,305.142 104.976,237.782 120.407,159.992 135.235,85.2497 207.377,35.7622 282.458,50.6084 c 63.998,13.8489 112.285,71.0996 113.364,136.7236 0.083,5.046 0.013,9.975 -0.739,14.978 -7.577,42.713 -50.295,103.765 -77.901,134.902 l -22.552,25.306 c -5.632,6.234 -12.80121,14.12315 -18.318,21.868 -4.81587,6.76086 -10.69492,16.31408 -12.81592,21.35108 -2.294,4.137 -4.85208,6.91592 -6.00608,11.46392 -2.285,-3.953 -3.988,-8.313 -6.893,-11.866 -4.347,-7.473 -9.634,-14.13 -14.922,-20.949 z"
id="path4"
style="fill:#4597f8;fill-opacity:1" />
<path
fill="#52ACFE"
transform="scale(2 2)"
d="M282.458 50.6084C346.456 64.4573 394.743 121.708 395.822 187.332C395.905 192.378 395.835 197.307 395.083 202.31C378.697 198.446 375.872 208.32 370.74 220.35C366.858 229.451 355.055 230.996 348.85 238.106C342.551 245.323 343.463 255.186 334.255 262.846C322.012 273.029 304.694 266.057 294.733 276.125C283.672 287.304 290.198 314.317 269.643 320.654C264.281 322.308 258.111 322.151 252.541 322.448C236.791 323.286 212.298 325.925 202.257 310.378C198.04 303.851 196.839 295.817 192.871 289.041C178.521 264.535 147.156 248.575 182.159 223.626C183.084 225.775 184.471 227.927 185.792 229.857C212.135 268.364 266.655 278.129 304.921 251.596C367.421 208.261 346.235 110.701 271.109 97.64C224.454 89.529 180.312 120.162 172.214 166.552C154.456 159.067 150.053 137.169 160.782 121.346C165.967 113.7 174.109 108.737 180.081 101.725C188.344 92.0217 187.917 85.7948 200.64 76.7024C216.305 65.5079 230.106 68.6899 247.472 71.4804C267.529 74.7033 279.146 71.6985 282.458 50.6084Z"
id="path5"
style="fill:#abc837" />
<path
fill="#fefefe"
d="m 499.38888,158.09052 c 169.94731,-17.304 286.44329,191.90753 176.47575,330.09677 -38.01608,47.76886 -88.34709,72.34323 -148.18818,79.02768 C 410.52174,570.99751 308.95484,489.2586 305.64728,369.20545 302.7003,262.18489 390.43718,161.04097 499.38888,158.09052 Z"
id="path7"
style="stroke-width:3.46704" />
<path
fill="#4597f8"
d="m 630.84754,363.86526 h 38.974 c 0.914,66.906 -46.618,129.634 -110.476,148.672 -44.854,13.372 -85.778,4.048 -125.45,-17.534 -21.554,21.806 -25.888,18.45 -35.968,-11.444 -2.446,-7.256 -5.034,-14.466 -7.58,-21.686 -4.438,-13.116 -23.726,-53.626 -3.83,-56.454 5.176,-0.736 79.526,20.744 88.836,23.898 24.54,8.308 -1.816,26.818 -11.444,36.208 14.018,9.73 33.972,13.398 50.73,13.596 62.824,0.738 115.902,-52.644 116.208,-115.256 z"
id="path3-3"
style="stroke-width:2" />
<path
fill="#4597f8"
d="m 509.17154,206.72526 c 33.336,-3.75 69.722,9.532 95.826,30.124 22.13,-21.988 25.908,-0.458 31.504,17.824 l 8.312,26.882 c 3.6,11.856 8.638,24.144 10.514,36.372 -6.396,16.988 -21.274,8.69 -34.398,5.246 l -32.236,-8.3 c -15.444,-3.938 -38.742,-3.352 -38.216,-21.924 5.026,-9.302 18.538,-20.17 26.15,-28.236 -20.252,-13.184 -45.382,-20.118 -69.546,-18.07 l -0.426,0.002 c -62.178,1.046 -106.582,58.824 -105.796,119.038 h -38.944 l -0.022,-0.728 c -2.226,-82.754 63.468,-156.254 147.278,-158.23 z"
id="path4-6"
style="stroke-width:2" />
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,23 @@
# flutter pub run flutter_launcher_icons
flutter_launcher_icons:
image_path: "assets/icons/otui-viewer-icon.png"
android: true
adaptive_icon_background: "#161616"
adaptive_icon_foreground: "assets/icons/otui-viewer-icon.png"
min_sdk_android: 21 # android min sdk min:16, default 21
ios: true
remove_alpha_ios: true
web:
generate: true
background_color: "#000000"
theme_color: "#000000"
windows:
generate: true
icon_size: 48 # min:48, max:256, default: 48
macos:
generate: true

View File

@ -427,7 +427,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -484,7 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";

View File

@ -1,122 +1 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:ot_viewer_app/global_location_store.dart';
import 'package:ot_viewer_app/refresh_cubit.dart';
import 'package:ot_viewer_app/settings_page.dart';
import 'package:path_provider/path_provider.dart';
import 'map_page.dart';
@ -20,8 +21,22 @@ Future<void> main() async {
GetIt.I
.registerSingleton<GlobalLocationStoreCubit>(GlobalLocationStoreCubit());
GetIt.I.registerSingleton<RefreshCubit>(RefreshCubit());
runApp(MyApp());
runApp(
MultiBlocProvider(
providers: [
BlocProvider(
lazy: false,
create: (BuildContext context) => SettingsCubit(),
),
BlocProvider(
create: (BuildContext context) => GetIt.I.get<RefreshCubit>(),
),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@ -31,7 +46,17 @@ class MyApp extends StatelessWidget {
title: 'OwnTracks Data Viewer',
theme: ThemeData.dark(),
home: Scaffold(
appBar: AppBar(title: const Text('OwnTrakcs Data Viewer')),
appBar: AppBar(
title: const Text('OwnTrakcs Data Viewer'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () async {
context.read<RefreshCubit>().triggerRefresh();
},
),
],
),
body: const MainPage(),
),
);
@ -45,43 +70,72 @@ class MainPage extends StatefulWidget {
createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
int _currentIndex = 0;
final List<Widget> _pages = [
const MapPage(),
const SettingsPage(), // Assume this is your settings page widget
const SettingsPage(),
];
// Variable to keep track of the last lifecycle state
AppLifecycleState? _lastLifecycleState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void _onItemTapped(int index) {
setState(() {
_currentIndex = index;
});
}
// Listen for app lifecycle changes
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// Check if the app is transitioning from paused to resumed
print('DEBUG: lifecycle state changed from $_lastLifecycleState to $state');
if (_lastLifecycleState == AppLifecycleState.paused &&
(state == AppLifecycleState.resumed ||
state == AppLifecycleState.inactive ||
state == AppLifecycleState.detached ||
state == AppLifecycleState.hidden)) {
print('DEBUG: app is unpaused, triggering refresh');
// Trigger the refresh when the app is "unpaused"
context.read<RefreshCubit>().triggerRefresh();
}
// Update the last lifecycle state
_lastLifecycleState = state;
}
@override
Widget build(BuildContext context) {
return BlocProvider(
lazy: false,
create: (BuildContext context) => SettingsCubit(),
child: Scaffold(
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.map),
label: 'Map',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
currentIndex: _currentIndex,
onTap: _onItemTapped,
),
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.map),
label: 'Map',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
currentIndex: _currentIndex,
onTap: _onItemTapped,
),
);
}

View File

@ -13,6 +13,7 @@ import 'package:get_it/get_it.dart';
import 'package:intl/intl.dart';
import 'package:latlong2/latlong.dart';
import 'package:ot_viewer_app/global_location_store.dart';
import 'package:ot_viewer_app/refresh_cubit.dart';
import 'package:ot_viewer_app/settings_page.dart';
import 'package:ot_viewer_app/user_path_bloc.dart';
import 'package:ot_viewer_app/util.dart';
@ -34,6 +35,8 @@ class MapPage extends StatefulWidget {
}
class _MapPageState extends State<MapPage> {
final MapController _mapController = MapController();
@override
void initState() {
super.initState();
@ -48,40 +51,58 @@ class _MapPageState extends State<MapPage> {
final settingsCubit = context.read<SettingsCubit>();
cubit.subscribe(settingsCubit.state);
settingsCubit.stream.listen((settings) => cubit.subscribe(settings));
final refreshCubit = context.read<RefreshCubit>();
refreshCubit.stream.listen((_) => cubit.reconnect());
return cubit;
},
child: FutureBuilder<String>(
future: getPath(),
builder: (something, tempPath) => BlocBuilder<SettingsCubit, SettingsState>(
builder: (something, tempPath) =>
BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
if (tempPath.data == null) {
return const Center(child: Text('Loading Map...'));
}
return FlutterMap(
options: const MapOptions(
initialCenter: LatLng(48.3285, 9.8942),
initialZoom: 13.0,
return Stack(children: [
FlutterMap(
mapController: _mapController,
options: const MapOptions(
initialCenter: LatLng(48.3285, 9.8942),
initialZoom: 13.0,
),
children: [
TileLayer(
urlTemplate:
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
tileProvider: CachedTileProvider(
maxStale: const Duration(days: 30),
store: HiveCacheStore(tempPath.data!,
hiveBoxName: 'HiveCacheStore')),
),
...state.activeDevices.map((id) =>
UserPath(key: ValueKey(id), device: id, settings: state)),
const MapCompass.cupertino(
rotationDuration: Duration(milliseconds: 600)),
// CurrentLocationLayer(), TODO: add permission
RichAttributionWidget(
attributions: [
TextSourceAttribution(
'OpenStreetMap contributors',
onTap: () =>
(Uri.parse('https://openstreetmap.org/copyright')),
),
],
),
],
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
tileProvider: CachedTileProvider(
maxStale: const Duration(days: 30),
store: HiveCacheStore(tempPath.data!, hiveBoxName: 'HiveCacheStore')),
Positioned(
bottom: 16,
left: 16,
child: TrackerSelector(
mapController: _mapController,
),
...state.activeDevices.map((id) => UserPath(key: ValueKey(id), device: id, settings: state)),
const MapCompass.cupertino(rotationDuration: Duration(milliseconds: 600)),
// CurrentLocationLayer(), TODO: add permission
RichAttributionWidget(
attributions: [
TextSourceAttribution(
'OpenStreetMap contributors',
onTap: () => (Uri.parse('https://openstreetmap.org/copyright')),
),
],
),
],
);
),
]);
},
),
),
@ -110,20 +131,34 @@ class _UserPathState extends State<UserPath> {
// ONLY WORKS because gets rebuilt every time with settings update
return bloc;
},
child: BlocListener<LocationSubscribeCubit, LocationUpdateState>(
listenWhen: (prevState, state) => state is LocationUpdateReceived,
listener: (context, state) {
UserPathBloc userPathBloc = context.read<UserPathBloc>();
if (state case LocationUpdateReceived(:final position, :final deviceId)) {
if (userPathBloc.deviceId == state.deviceId) {
userPathBloc.add(UserPathLiveSubscriptionUpdate(position));
}
}
},
child: MultiBlocListener(
listeners: [
BlocListener<LocationSubscribeCubit, LocationUpdateState>(
listenWhen: (prevState, state) => state is LocationUpdateReceived,
listener: (context, state) {
UserPathBloc userPathBloc = context.read<UserPathBloc>();
if (state
case LocationUpdateReceived(
:final position,
:final deviceId
)) {
if (userPathBloc.deviceId == state.deviceId) {
userPathBloc.add(UserPathLiveSubscriptionUpdate(position));
}
}
}),
BlocListener<RefreshCubit, DateTime>(
listener: (context, state) {
context.read<UserPathBloc>().add(UserPathFullUpdate());
},
)
],
child: BlocBuilder<UserPathBloc, UserPathState>(
builder: (context, state) {
// TODO: change once smarter rebuilds are ready
context.read<UserPathBloc>().add(UserPathLoginDataChanged(widget.settings));
context
.read<UserPathBloc>()
.add(UserPathLoginDataChanged(widget.settings));
print("rebuild");
final _istate = state as MainUserPathState;
@ -139,7 +174,8 @@ class _UserPathState extends State<UserPath> {
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () => showUserLocationModalBottomSheet(context, widget.device),
onTap: () => showUserLocationModalBottomSheet(
context, widget.device),
child: Container(
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.85),
@ -170,7 +206,8 @@ class _UserPathState extends State<UserPath> {
List<List<LatLng>> segments = [];
List<Color> colors = [];
if (state.initialPoints.isNotEmpty) {
final allPoints = state.initialPoints.map((e) => LatLng(e.lat, e.lon)).toList();
final allPoints =
state.initialPoints.map((e) => LatLng(e.lat, e.lon)).toList();
final segmentCount = math.min(100, allPoints.length);
final pointsPerSegment = (allPoints.length / segmentCount).ceil();
@ -193,8 +230,9 @@ class _UserPathState extends State<UserPath> {
Polyline(
points: segments[i],
strokeWidth: 4.0,
color: colors[i]
.withOpacity((math.pow(i, 2) / math.pow(segments.length, 2)) * 0.7 + 0.3), // Fading effect
color: colors[i].withOpacity(
(math.pow(i, 2) / math.pow(segments.length, 2)) * 0.7 +
0.3), // Fading effect
),
);
}
@ -217,13 +255,17 @@ class _UserPathState extends State<UserPath> {
...polylines
],
),
PolylineLayer(key: ValueKey('${widget.device}_liveLines'), polylines: [
Polyline(
points: state.livePoints.map((e) => LatLng(e.lat, e.lon)).toList(),
strokeWidth: 4.0,
color: Colors.blue.shade200,
),
]),
PolylineLayer(
key: ValueKey('${widget.device}_liveLines'),
polylines: [
Polyline(
points: state.livePoints
.map((e) => LatLng(e.lat, e.lon))
.toList(),
strokeWidth: 4.0,
color: Colors.blue.shade200,
),
]),
MarkerLayer(markers: markers)
],
);
@ -244,13 +286,15 @@ showUserLocationModalBottomSheet(BuildContext context, (String, String) user) {
height: 500,
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
),
padding: const EdgeInsets.all(32),
child: StreamBuilder<UserPathState>(
stream: context.read<UserPathBloc>().stream,
builder: (sheetContext, state) {
final istate = state.data as MainUserPathState? ?? context.read<UserPathBloc>().state as MainUserPathState;
final istate = state.data as MainUserPathState? ??
context.read<UserPathBloc>().state as MainUserPathState;
if (istate.livePoints.isEmpty) {
return Text("Couldn't find ${user.$1}:${user.$2}'s Location");
}
@ -275,12 +319,15 @@ showUserLocationModalBottomSheet(BuildContext context, (String, String) user) {
width: double.infinity,
child: Column(
children: [
Text("(${curLocation.lat.toStringAsFixed(4)}, ${curLocation.lon.toStringAsFixed(4)})"),
Text(DateFormat('dd.MM.yyyy - kk:mm:ss').format(curLocation.timestamp)),
Text(
"(${curLocation.lat.toStringAsFixed(4)}, ${curLocation.lon.toStringAsFixed(4)})"),
Text(DateFormat('dd.MM.yyyy - kk:mm:ss')
.format(curLocation.timestamp)),
StreamBuilder(
stream: Stream.periodic(const Duration(seconds: 1)),
builder: (context, _) {
return Text("${formatDuration(DateTime.now().difference(curLocation.timestamp))} ago");
return Text(
"${formatDuration(DateTime.now().difference(curLocation.timestamp))} ago");
},
),
],
@ -291,7 +338,8 @@ showUserLocationModalBottomSheet(BuildContext context, (String, String) user) {
),
Expanded(
// Ensure GridView.builder is within an Expanded widget
child: BlocBuilder<GlobalLocationStoreCubit, GlobalLocationStoreState>(
child: BlocBuilder<GlobalLocationStoreCubit,
GlobalLocationStoreState>(
bloc: GetIt.I.get<GlobalLocationStoreCubit>(),
builder: (sheetContext, state) {
// get map camera
@ -299,22 +347,32 @@ showUserLocationModalBottomSheet(BuildContext context, (String, String) user) {
final mapRotation = mapController.camera.rotation;
List<MapEntry<(String, String), Point>> locations = state.locations
List<MapEntry<(String, String), Point>> locations = state
.locations
.remove(user) // remove this
.entries // get entries into a list, and sort alphabetically
.toList()
..sort((a, b) => "${a.key.$1}:${a.key.$2}".compareTo("${b.key.$1}:${b.key.$2}"));
..sort((a, b) => "${a.key.$1}:${a.key.$2}"
.compareTo("${b.key.$1}:${b.key.$2}"));
if (locations.isEmpty) {
return const SizedBox();
}
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1, crossAxisSpacing: 4.0, mainAxisSpacing: 4.0, mainAxisExtent: 100),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0,
mainAxisExtent: 100),
itemCount: locations.length,
itemBuilder: (BuildContext context, int index) {
// calculate distance and bearing
double distance = distanceBetween(curLocation.lat, curLocation.lon,
locations[index].value.lat, locations[index].value.lon, "meters");
double distance = distanceBetween(
curLocation.lat,
curLocation.lon,
locations[index].value.lat,
locations[index].value.lon,
"meters");
double bearing = (bearingBetween(
curLocation.lat,
@ -333,7 +391,8 @@ showUserLocationModalBottomSheet(BuildContext context, (String, String) user) {
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black,
border: Border.all(color: Colors.pink, width: 2),
border:
Border.all(color: Colors.pink, width: 2),
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.all(16),
@ -343,7 +402,8 @@ showUserLocationModalBottomSheet(BuildContext context, (String, String) user) {
children: [
Text(
"${locations[index].key.$1}:${locations[index].key.$2}",
style: const TextStyle(fontWeight: FontWeight.bold),
style: const TextStyle(
fontWeight: FontWeight.bold),
),
const Spacer(),
Text(formatDistance(distance ~/ 1)),
@ -359,11 +419,13 @@ showUserLocationModalBottomSheet(BuildContext context, (String, String) user) {
Row(
children: [
StreamBuilder(
stream: Stream.periodic(const Duration(seconds: 1)),
stream: Stream.periodic(
const Duration(seconds: 1)),
builder: (context, _) {
return Text(
"${formatDuration(DateTime.now().difference(locations[index].value.timestamp))} ago",
style: const TextStyle(fontSize: 12));
style: const TextStyle(
fontSize: 12));
},
),
const Spacer(),
@ -386,3 +448,81 @@ showUserLocationModalBottomSheet(BuildContext context, (String, String) user) {
},
);
}
class TrackerSelector extends StatefulWidget {
final MapController mapController;
const TrackerSelector({super.key, required this.mapController});
@override
State<TrackerSelector> createState() => _TrackerSelectorState();
}
class _TrackerSelectorState extends State<TrackerSelector> {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: _isExpanded ? MediaQuery.of(context).size.width - 32 : 48,
height: 48,
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(24),
),
child: Row(
children: [
IconButton(
icon: Icon(_isExpanded ? Icons.close : Icons.people),
onPressed: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
),
if (_isExpanded)
Expanded(
child: BlocBuilder<GlobalLocationStoreCubit,
GlobalLocationStoreState>(
bloc: GetIt.I.get<GlobalLocationStoreCubit>(),
builder: (context, state) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.locations.length,
itemBuilder: (context, index) {
final entry = state.locations.entries.elementAt(index);
return GestureDetector(
onTap: () => widget.mapController
.moveAndRotate(entry.value.asLatLng, 15, 0),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.location_history,
color: Colors.white,
size: 24,
),
Text(
"${entry.key.$1}:${entry.key.$2}",
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
],
),
),
);
},
);
},
),
),
],
),
);
}
}

9
lib/refresh_cubit.dart Normal file
View File

@ -0,0 +1,9 @@
import 'package:flutter_bloc/flutter_bloc.dart';
class RefreshCubit extends Cubit<DateTime> {
RefreshCubit() : super(DateTime.now());
void triggerRefresh() {
emit(DateTime.now());
}
}

View File

@ -96,18 +96,20 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
@override
SettingsState? fromJson(Map<String, dynamic> json) {
// print("fromjson $json");
final usersToDevicesMap = (json['usersToDevices'] as Map<String, dynamic>?)?.map((key, value) {
final List<String> list = List<String>.from(value as List);
return MapEntry(key, list);
}) ??
{};
final usersToDevicesMap =
(json['usersToDevices'] as Map<String, dynamic>?)?.map((key, value) {
final List<String> list = List<String>.from(value as List);
return MapEntry(key, list);
}) ??
{};
return SettingsState(
url: (json['url'] ?? '') as String,
username: (json['username'] ?? '') as String,
password: (json['password'] ?? '') as String,
usersToDevices: usersToDevicesMap,
activeDevices: Set<(String, String)>.from(List<List<String>>.from(json['activeDevices']).map((e) {
activeDevices: Set<(String, String)>.from(
List<List<String>>.from(json['activeDevices']).map((e) {
if (e.length != 2) {
return null;
} else {
@ -193,7 +195,8 @@ class _SettingsPageState extends State<SettingsPage> {
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.redAccent.withOpacity(0.8), width: 2),
border: Border.all(
color: Colors.redAccent.withOpacity(0.8), width: 2),
borderRadius: BorderRadius.circular(10)),
child: Column(
mainAxisSize: MainAxisSize.min,
@ -202,10 +205,12 @@ class _SettingsPageState extends State<SettingsPage> {
TextField(
controller: _urlController,
decoration: const InputDecoration(
labelText: 'URL (e.g. https://your-owntracks.host.com)',
labelText:
'URL (e.g. https://your-owntracks.host.com)',
border: OutlineInputBorder(),
// Adds a border to the TextField
prefixIcon: Icon(Icons.account_tree), // Adds an icon to the left
prefixIcon: Icon(
Icons.account_tree), // Adds an icon to the left
),
),
const SizedBox(height: 10),
@ -216,7 +221,8 @@ class _SettingsPageState extends State<SettingsPage> {
labelText: 'Username',
border: OutlineInputBorder(),
// Adds a border to the TextField
prefixIcon: Icon(Icons.person), // Adds an icon to the left
prefixIcon:
Icon(Icons.person), // Adds an icon to the left
),
),
const SizedBox(height: 10),
@ -229,7 +235,8 @@ class _SettingsPageState extends State<SettingsPage> {
labelText: 'Password',
border: OutlineInputBorder(),
// Adds a border to the TextField
prefixIcon: Icon(Icons.lock), // Adds an icon to the left
prefixIcon:
Icon(Icons.lock), // Adds an icon to the left
),
),
const SizedBox(
@ -238,7 +245,8 @@ class _SettingsPageState extends State<SettingsPage> {
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.warning, color: Colors.red, size: 16),
const Icon(Icons.warning,
color: Colors.red, size: 16),
// Small red exclamation mark icon
const SizedBox(width: 4),
// Space between icon and text
@ -246,7 +254,8 @@ class _SettingsPageState extends State<SettingsPage> {
'Password saved locally',
style: TextStyle(
color: Colors.red,
fontSize: 12, // Small font size for the warning text
fontSize:
12, // Small font size for the warning text
),
),
const Spacer(),
@ -274,47 +283,56 @@ class _SettingsPageState extends State<SettingsPage> {
var resultingDuration = await showDurationPicker(
context: context,
initialTime: state.historyTime,
snapToMins: 60,
baseUnit: BaseUnit.hour,
);
if (resultingDuration != null) {
if (resultingDuration.inHours == 0) {
if (context.mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('Please pick a duration of 1h or more!')));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Please pick a duration of 1h or more!')));
}
} else {
settingsCubit.updateSettings(historyTime: resultingDuration);
settingsCubit.updateSettings(
historyTime: resultingDuration);
}
if (resultingDuration.inDays > 4) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Warning:\nLong durations might cause slowdowns and high data usage!')));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Warning:\nLong durations might cause slowdowns and high data usage!')));
}
}
}
},
child: Text('Set Time to Load Points (current: ${formatDuration(state.historyTime)})')),
child: Text(
'Set Time to Load Points (current: ${formatDuration(state.historyTime)})')),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.deepPurple.withOpacity(0.8), width: 2),
border: Border.all(
color: Colors.deepPurple.withOpacity(0.8), width: 2),
borderRadius: BorderRadius.circular(10)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: List<Widget>.from([
Center(
child: ElevatedButton.icon(
onPressed: () => context.read<SettingsCubit>().fetchDevices(),
onPressed: () => context
.read<SettingsCubit>()
.fetchDevices(),
icon: const Icon(Icons.refresh),
label: const Text('Refresh Devices'),
),
),
const SizedBox(height: 16),
]) +
List<Widget>.from(state.usersToDevices.entries.map((entry) {
List<Widget>.from(
state.usersToDevices.entries.map((entry) {
return Column(
children: [
Row(
@ -333,20 +351,26 @@ class _SettingsPageState extends State<SettingsPage> {
const Divider(),
Container(
padding: const EdgeInsets.only(left: 8),
child: Column(children: List<Widget>.from(entry.value.map((device) {
child: Column(children: List<Widget>.from(
entry.value.map((device) {
return Row(
children: [
Checkbox(
value: state.activeDevices.contains((entry.key, device)),
value: state.activeDevices
.contains((entry.key, device)),
onChanged: (newVal) {
var cubit = context.read<SettingsCubit>();
Set<(String, String)> newSet = Set.from(state.activeDevices);
var cubit =
context.read<SettingsCubit>();
Set<(String, String)> newSet =
Set.from(state.activeDevices);
if (newVal ?? false) {
newSet.add((entry.key, device));
} else {
newSet.remove((entry.key, device));
newSet.remove(
(entry.key, device));
}
cubit.updateSettings(activeDevices: newSet);
cubit.updateSettings(
activeDevices: newSet);
}),
const SizedBox(width: 8),
Text("${entry.key}:$device"),

View File

@ -1,6 +1,6 @@
// web_socket_cubit.dart
import 'dart:async';
import 'dart:convert';
import 'package:anyhow/anyhow.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ot_viewer_app/owntracks_api.dart';
import 'package:ot_viewer_app/settings_page.dart';
@ -24,86 +24,125 @@ class LocationUpdateReceived extends LocationUpdateState {
class LocationSubscribeCubit extends Cubit<LocationUpdateState> {
Option<WebSocketClient> _wsClient = None;
Option<Completer<void>> _connectionCompleter = None;
String url = '';
String username = '';
String pass = '';
LocationSubscribeCubit() : super(LocationUpdateUnconnected());
// TODO: handle ongoing connection attempt by canceling? the loop? or smth
subscribe(SettingsState settings) async {
// check if resubscribe is necessary (different URL)
if (settings.url == url && settings.username == username && settings.password == pass) {
if (settings.url == url &&
settings.username == username &&
settings.password == pass) {
return;
} else {
url = settings.url;
username = settings.username;
pass = settings.password;
}
await _wsConnectionEstablish();
}
reconnect() async {
print("reconnecting...");
if (_wsClient.isSome()) {
await _wsClient.unwrap().close();
_wsClient = None;
} else {
print("not connected, not reconnecting");
return;
}
await _wsConnectionEstablish();
}
Future<void> _wsConnectionEstablish() async {
// If there's an ongoing connection attempt, wait for it
if (_connectionCompleter.isSome()) {
await _connectionCompleter.unwrap().future;
return;
}
// Start new connection attempt
_connectionCompleter = Some(Completer<void>());
if (_wsClient.isSome()) {
await _wsClient.unwrap().close();
_wsClient = None;
}
var ws = await OwntracksApi(baseUrl: url, username: username, pass: pass)
.createWebSocketConnection(
wsPath: 'last',
onMessage: (msg) {
if (msg is String) {
if (msg == 'LAST') {
return;
}
try {
final Map<String, dynamic> map = jsonDecode(msg);
if (map['_type'] == 'location') {
// filter points (only the ones for this device pls!)
final topic = (map['topic'] as String?)?.split('/');
if (topic == null || topic.length < 3) {
// couldn't reconstruct ID, bail
return;
}
// build device_id
final deviceId = (topic[1], topic[2]);
print(map);
// build point
final p = Point(
lat: map['lat'] as double,
lon: map['lon'] as double,
timestamp: DateTime.fromMillisecondsSinceEpoch((map['tst'] as int) * 1000));
print(p);
emit(LocationUpdateReceived(p, deviceId));
Result<WebSocketClient> ws = bail('Not done yet');
while (!ws.isOk()) {
ws = await OwntracksApi(baseUrl: url, username: username, pass: pass)
.createWebSocketConnection(
wsPath: 'last',
onMessage: (msg) {
if (msg is String) {
if (msg == 'LAST') {
return;
}
try {
final Map<String, dynamic> map = jsonDecode(msg);
if (map['_type'] == 'location') {
// filter points (only the ones for this device pls!)
final topic = (map['topic'] as String?)?.split('/');
if (topic == null || topic.length < 3) {
// couldn't reconstruct ID, bail
return;
}
// build device_id
final deviceId = (topic[1], topic[2]);
print(map);
// build point
final p = Point(
lat: map['lat'] as double,
lon: map['lon'] as double,
timestamp: DateTime.fromMillisecondsSinceEpoch(
(map['tst'] as int) * 1000));
print(p);
emit(LocationUpdateReceived(p, deviceId));
}
} catch (e) {
print('BUG: Couldn\'t parse WS message: $msg ($e)');
}
} catch (e) {
print('BUG: Couldn\'t parse WS message: $msg ($e)');
}
}
},
onStateChange: (sc) {
switch (sc) {
case WebSocketClientState$Open(:final url):
_wsClient.map((wsc) => wsc.add('LAST'));
emit(LocationUpdateConnected());
break;
default:
emit(LocationUpdateUnconnected());
break;
}
print(sc);
},
);
_wsClient = ws.expect("Estabilshing Websocket Conenction failed").toOption();
},
onStateChange: (sc) {
switch (sc) {
case WebSocketClientState$Open(:final url):
_wsClient.map((wsc) => wsc.add('LAST'));
emit(LocationUpdateConnected());
break;
default:
emit(LocationUpdateUnconnected());
break;
}
print(sc);
},
);
if (!ws.isOk()) {
print(
'Failed to connect to WebSocket: ${ws.unwrapErr()}\n, retrying in 1s');
await Future.delayed(const Duration(seconds: 1));
}
}
_wsClient = Some(ws.expect("Estabilshing Websocket Connection failed"));
_connectionCompleter.unwrap().complete();
_connectionCompleter = None;
}
@override
@ -113,7 +152,9 @@ class LocationSubscribeCubit extends Cubit<LocationUpdateState> {
@override
Future<void> close() async {
await _wsClient.toFutureOption().map((conn) => conn.close());
await _wsClient
.toFutureOption()
.map<Future<void>>((conn) async => await conn.close());
return super.close();
}
}

View File

@ -1,68 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
"info": {
"version": 1,
"author": "xcode"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
"images": [
{
"size": "16x16",
"idiom": "mac",
"filename": "app_icon_16.png",
"scale": "1x"
},
{
"size": "16x16",
"idiom": "mac",
"filename": "app_icon_32.png",
"scale": "2x"
},
{
"size": "32x32",
"idiom": "mac",
"filename": "app_icon_32.png",
"scale": "1x"
},
{
"size": "32x32",
"idiom": "mac",
"filename": "app_icon_64.png",
"scale": "2x"
},
{
"size": "128x128",
"idiom": "mac",
"filename": "app_icon_128.png",
"scale": "1x"
},
{
"size": "128x128",
"idiom": "mac",
"filename": "app_icon_256.png",
"scale": "2x"
},
{
"size": "256x256",
"idiom": "mac",
"filename": "app_icon_256.png",
"scale": "1x"
},
{
"size": "256x256",
"idiom": "mac",
"filename": "app_icon_512.png",
"scale": "2x"
},
{
"size": "512x512",
"idiom": "mac",
"filename": "app_icon_512.png",
"scale": "1x"
},
{
"size": "512x512",
"idiom": "mac",
"filename": "app_icon_1024.png",
"scale": "2x"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -5,90 +5,130 @@ packages:
dependency: "direct main"
description:
name: anyhow
sha256: "8fcd8a16ab1dadcae0d77b880c4cda85367876ef9da6ca7fd68ce6ce51679bbe"
sha256: "05e046d1f2cea2fb72e713429dbb8b02764c92a1df35a5b9220a62d41fa106fc"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.3.2"
archive:
dependency: transitive
description:
name: archive
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
url: "https://pub.dev"
source: hosted
version: "4.0.7"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.dev"
source: hosted
version: "2.11.0"
version: "2.12.0"
bloc:
dependency: "direct main"
description:
name: bloc
sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.3"
version: "8.1.4"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.6"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.6"
version: "1.0.8"
dart_earcut:
dependency: transitive
description:
name: dart_earcut
sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b
url: "https://pub.dev"
source: hosted
version: "1.2.0"
dio:
dependency: transitive
description:
name: dio
sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8"
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
url: "https://pub.dev"
source: hosted
version: "5.4.1"
version: "5.8.0+1"
dio_cache_interceptor:
dependency: transitive
description:
name: dio_cache_interceptor
sha256: fb7905c0d12075d8786a6b63bffd64ae062d053f682cfaf28d145a2686507308
sha256: "1346705a2057c265014d7696e3e2318b560bfb00b484dac7f9b01e2ceaebb07d"
url: "https://pub.dev"
source: hosted
version: "3.5.0"
version: "3.5.1"
dio_cache_interceptor_hive_store:
dependency: "direct main"
description:
@ -97,54 +137,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.2"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
duration_picker:
dependency: "direct main"
description:
name: duration_picker
sha256: "052b34dac04c29f3849bb3817a26c5aebe9e5f0697c3a374be87db2b84d75753"
sha256: e505a749c93f3218aa4194d339e5d5480d927df23a81f075b5282511f6ac11ab
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.2.0"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.2"
fast_immutable_collections:
dependency: "direct main"
description:
name: fast_immutable_collections
sha256: "06c2aee88473a55fe36d6dc89f498b42b24a6c09a001536628257fe1a81bf514"
sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c
url: "https://pub.dev"
source: hosted
version: "10.1.2"
version: "10.2.4"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.0"
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
@ -154,58 +202,66 @@ packages:
dependency: "direct main"
description:
name: flutter_bloc
sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1"
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev"
source: hosted
version: "8.1.4"
version: "8.1.6"
flutter_dotenv:
dependency: "direct main"
description:
name: flutter_dotenv
sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b
url: "https://pub.dev"
source: hosted
version: "5.1.0"
version: "5.2.1"
flutter_launcher_icons:
dependency: "direct main"
description:
name: flutter_launcher_icons
sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c
url: "https://pub.dev"
source: hosted
version: "0.14.3"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: cda8d72135b697f519287258b5294a57ce2f2a5ebf234f0e406aad4dc14c9399
sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
version: "7.0.2"
flutter_map_cache:
dependency: "direct main"
description:
name: flutter_map_cache
sha256: "5539033bbfbc0a663f3a038f223a36b472974d6613ce8f84fe7762eeff38aa5a"
sha256: "5b30c9b0d36315a22f4ee070737104a6017e7ff990e8addc8128ba81786e03ef"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
version: "1.5.2"
flutter_map_compass:
dependency: "direct main"
description:
name: flutter_map_compass
sha256: f904bdfa3f0aa008ed57abb1154197318ab4524a2cc6fda6888133aa70f2415f
sha256: "5b03c135c4ff5e8d184551c918d30d403cffa246fcdc43604cfd600e9c4939fb"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.1.1"
flutter_map_math:
dependency: "direct main"
description:
name: flutter_map_math
sha256: "4ac7e68a0862cb22a49484e5ae5c8ed01794fd3d58e85362cf3c3cf2c1b8cf2e"
sha256: "213600df9559e8cc3352d41ee5d229d949c28bd691feffaf50d9deaf69339d6d"
url: "https://pub.dev"
source: hosted
version: "0.1.7"
version: "0.1.9"
flutter_test:
dependency: "direct dev"
description: flutter
@ -220,10 +276,10 @@ packages:
dependency: "direct main"
description:
name: get_it
sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7
sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1
url: "https://pub.dev"
source: hosted
version: "7.6.7"
version: "7.7.0"
hive:
dependency: transitive
description:
@ -236,26 +292,34 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.4.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.1.2"
hydrated_bloc:
dependency: "direct main"
description:
name: hydrated_bloc
sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d"
sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c
url: "https://pub.dev"
source: hosted
version: "9.1.4"
version: "9.1.5"
image:
dependency: transitive
description:
name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
url: "https://pub.dev"
source: hosted
version: "4.5.4"
intl:
dependency: "direct main"
description:
@ -264,38 +328,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.19.0"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
latlong2:
dependency: "direct main"
description:
name: latlong2
sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b"
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
url: "https://pub.dev"
source: hosted
version: "0.9.0"
version: "0.9.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.dev"
source: hosted
version: "10.0.0"
version: "10.0.8"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.1"
lints:
dependency: transitive
description:
@ -316,34 +388,34 @@ packages:
dependency: transitive
description:
name: logger
sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770
sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.5.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.16.0"
mgrs_dart:
dependency: transitive
description:
@ -364,34 +436,34 @@ packages:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.9.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
path_provider_linux:
dependency: transitive
description:
@ -412,18 +484,26 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
platform:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@ -440,6 +520,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
posix:
dependency: transitive
description:
name: posix
sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62
url: "https://pub.dev"
source: hosted
version: "6.0.2"
proj4dart:
dependency: transitive
description:
@ -452,87 +540,95 @@ packages:
dependency: transitive
description:
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev"
source: hosted
version: "6.1.2"
version: "6.1.5"
rust:
dependency: transitive
description:
name: rust
sha256: a5331e469c289e104b2e58f8760ba59bde704aad1ee292a4c0ccc72c683f6135
url: "https://pub.dev"
source: hosted
version: "1.3.7"
rust_core:
dependency: "direct main"
description:
name: rust_core
sha256: "39154cfa64fd4c14b23addd2f8786567b5270c51dd4b6220670348c65254b3ed"
sha256: "348366d285245d64577bfb4506d5cdd56975b38af20fc85ecd468f0bc9ba9822"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.5.6"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.10.1"
sprintf:
dependency: transitive
description:
@ -545,58 +641,58 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.4.1"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
version: "3.3.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.6.1"
version: "0.7.4"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.4.0"
unicode:
dependency: transitive
description:
@ -609,10 +705,10 @@ packages:
dependency: transitive
description:
name: uuid
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.3.3"
version: "4.5.1"
vector_math:
dependency: transitive
description:
@ -625,26 +721,18 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
url: "https://pub.dev"
source: hosted
version: "13.0.0"
version: "14.3.1"
web:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
win32:
dependency: transitive
description:
name: win32
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev"
source: hosted
version: "5.3.0"
version: "1.1.1"
wkt_parser:
dependency: transitive
description:
@ -665,10 +753,26 @@ packages:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0"
dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.0"

View File

@ -35,7 +35,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6
flutter_map: ^6.1.0
flutter_map: ^7.0.2
http: ^1.2.1
flutter_dotenv: ^5.1.0
shared_preferences: ^2.2.2
@ -56,6 +56,7 @@ dependencies:
intl: ^0.19.0
get_it: ^7.6.7
duration_picker: ^1.1.1
flutter_launcher_icons: ^0.14.1
dev_dependencies:
flutter_test:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -3,8 +3,8 @@
"short_name": "ot_viewer_app",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"background_color": "#000000",
"theme_color": "#000000",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
@ -32,4 +32,4 @@
"purpose": "maskable"
}
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB