mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-08-23 21:46:15 +00:00
Adding OpenHaystack Mobile app
Co-Authored-By: Lukas Burg <lukas.burg@hemalu.de>
This commit is contained in:
committed by
Alexander Heinrich
co-authored by
Lukas Burg
parent
b65a6e6be0
commit
3d593a006c
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/plugin_api.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:openhaystack_mobile/accessory/accessory_list.dart';
|
||||
import 'package:openhaystack_mobile/accessory/accessory_registry.dart';
|
||||
import 'package:openhaystack_mobile/location/location_model.dart';
|
||||
import 'package:openhaystack_mobile/map/map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class AccessoryMapListVertical extends StatefulWidget {
|
||||
final AsyncCallback loadLocationUpdates;
|
||||
|
||||
/// Displays a map view and the accessory list in a vertical alignment.
|
||||
const AccessoryMapListVertical({
|
||||
Key? key,
|
||||
required this.loadLocationUpdates,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AccessoryMapListVertical> createState() => _AccessoryMapListVerticalState();
|
||||
}
|
||||
|
||||
class _AccessoryMapListVerticalState extends State<AccessoryMapListVertical> {
|
||||
final MapController _mapController = MapController();
|
||||
|
||||
void _centerPoint(LatLng point) {
|
||||
_mapController.fitBounds(
|
||||
LatLngBounds(point),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer2<AccessoryRegistry, LocationModel>(
|
||||
builder: (BuildContext context, AccessoryRegistry accessoryRegistry, LocationModel locationModel, Widget? child) {
|
||||
return Column(
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AccessoryMap(
|
||||
mapController: _mapController,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: AccessoryList(
|
||||
loadLocationUpdates: widget.loadLocationUpdates,
|
||||
centerOnPoint: _centerPoint,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:openhaystack_mobile/accessory/accessory_list.dart';
|
||||
import 'package:openhaystack_mobile/accessory/accessory_registry.dart';
|
||||
import 'package:openhaystack_mobile/location/location_model.dart';
|
||||
import 'package:openhaystack_mobile/map/map.dart';
|
||||
import 'package:openhaystack_mobile/preferences/preferences_page.dart';
|
||||
import 'package:openhaystack_mobile/preferences/user_preferences_model.dart';
|
||||
|
||||
class DashboardDesktop extends StatefulWidget {
|
||||
|
||||
/// Displays the layout for the desktop view of the app.
|
||||
///
|
||||
/// The layout is optimized for horizontally aligned larger screens
|
||||
/// on desktop devices.
|
||||
const DashboardDesktop({ Key? key }) : super(key: key);
|
||||
|
||||
@override
|
||||
_DashboardDesktopState createState() => _DashboardDesktopState();
|
||||
}
|
||||
|
||||
class _DashboardDesktopState extends State<DashboardDesktop> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Initialize models and preferences
|
||||
var userPreferences = Provider.of<UserPreferences>(context, listen: false);
|
||||
var locationModel = Provider.of<LocationModel>(context, listen: false);
|
||||
var locationPreferenceKnown = userPreferences.locationPreferenceKnown ?? false;
|
||||
var locationAccessWanted = userPreferences.locationAccessWanted ?? false;
|
||||
if (!locationPreferenceKnown || locationAccessWanted) {
|
||||
locationModel.requestLocationUpdates();
|
||||
}
|
||||
|
||||
loadLocationUpdates();
|
||||
}
|
||||
|
||||
/// Fetch locaiton updates for all accessories.
|
||||
Future<void> loadLocationUpdates() async {
|
||||
var accessoryRegistry = Provider.of<AccessoryRegistry>(context, listen: false);
|
||||
await accessoryRegistry.loadLocationReports();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 400,
|
||||
child: Column(
|
||||
children: [
|
||||
AppBar(
|
||||
title: const Text('OpenHaystack'),
|
||||
leading: IconButton(
|
||||
onPressed: () { /* reload */ },
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const PreferencesPage()),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.settings),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(5),
|
||||
child: Text('My Accessories')
|
||||
),
|
||||
Expanded(
|
||||
child: AccessoryList(
|
||||
loadLocationUpdates: loadLocationUpdates,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Expanded(
|
||||
child: AccessoryMap(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:openhaystack_mobile/accessory/accessory_registry.dart';
|
||||
import 'package:openhaystack_mobile/dashboard/accessory_map_list_vert.dart';
|
||||
import 'package:openhaystack_mobile/item_management/item_management.dart';
|
||||
import 'package:openhaystack_mobile/item_management/new_item_action.dart';
|
||||
import 'package:openhaystack_mobile/location/location_model.dart';
|
||||
import 'package:openhaystack_mobile/preferences/preferences_page.dart';
|
||||
import 'package:openhaystack_mobile/preferences/user_preferences_model.dart';
|
||||
|
||||
class DashboardMobile extends StatefulWidget {
|
||||
|
||||
/// Displays the layout for the mobile view of the app.
|
||||
///
|
||||
/// The layout is optimized for a vertically aligned small screens.
|
||||
/// The functionality is structured in a bottom tab bar for easy access
|
||||
/// on mobile devices.
|
||||
const DashboardMobile({ Key? key }) : super(key: key);
|
||||
|
||||
@override
|
||||
_DashboardMobileState createState() => _DashboardMobileState();
|
||||
}
|
||||
|
||||
class _DashboardMobileState extends State<DashboardMobile> {
|
||||
|
||||
/// A list of the tabs displayed in the bottom tab bar.
|
||||
late final List<Map<String, dynamic>> _tabs = [
|
||||
{
|
||||
'title': 'My Accessories',
|
||||
'body': (ctx) => AccessoryMapListVertical(
|
||||
loadLocationUpdates: loadLocationUpdates,
|
||||
),
|
||||
'icon': Icons.place,
|
||||
'label': 'Map',
|
||||
},
|
||||
{
|
||||
'title': 'My Accessories',
|
||||
'body': (ctx) => const KeyManagement(),
|
||||
'icon': Icons.style,
|
||||
'label': 'Accessories',
|
||||
'actionButton': (ctx) => const NewKeyAction(),
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Initialize models and preferences
|
||||
var userPreferences = Provider.of<UserPreferences>(context, listen: false);
|
||||
var locationModel = Provider.of<LocationModel>(context, listen: false);
|
||||
var locationPreferenceKnown = userPreferences.locationPreferenceKnown ?? false;
|
||||
var locationAccessWanted = userPreferences.locationAccessWanted ?? false;
|
||||
if (!locationPreferenceKnown || locationAccessWanted) {
|
||||
locationModel.requestLocationUpdates();
|
||||
}
|
||||
|
||||
// Load new location reports on app start
|
||||
loadLocationUpdates();
|
||||
}
|
||||
|
||||
/// Fetch locaiton updates for all accessories.
|
||||
Future<void> loadLocationUpdates() async {
|
||||
var accessoryRegistry = Provider.of<AccessoryRegistry>(context, listen: false);
|
||||
try {
|
||||
await accessoryRegistry.loadLocationReports();
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
content: Text(
|
||||
'Could not find location reports. Try again later.',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The selected tab index.
|
||||
int _selectedIndex = 0;
|
||||
/// Updates the currently displayed tab to [index].
|
||||
void _onItemTapped(int index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('My Accessories'),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const PreferencesPage()),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.settings),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _tabs[_selectedIndex]['body'](context),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: _tabs.map((tab) => BottomNavigationBarItem(
|
||||
icon: Icon(tab['icon']),
|
||||
label: tab['label'],
|
||||
)).toList(),
|
||||
currentIndex: _selectedIndex,
|
||||
selectedItemColor: Theme.of(context).indicatorColor,
|
||||
onTap: _onItemTapped,
|
||||
),
|
||||
floatingActionButton: _tabs[_selectedIndex]['actionButton']?.call(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user