Token management, Factory reset, UX/UI Improvements
This commit is contained in:
+261
-97
@@ -1,11 +1,15 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:liteauthconfig/models/appconfig.dart';
|
||||
import 'package:liteauthconfig/l10n/app_localizations.dart';
|
||||
import 'package:liteauthconfig/models/device_status.dart';
|
||||
import 'package:liteauthconfig/models/logentry.dart';
|
||||
import 'package:liteauthconfig/pages/reconfigure_network.dart';
|
||||
import 'package:liteauthconfig/pages/tokeninfo.dart';
|
||||
import 'package:liteauthconfig/utils/network.dart';
|
||||
|
||||
class DeviceInfoPage extends StatefulWidget {
|
||||
@@ -17,13 +21,18 @@ class DeviceInfoPage extends StatefulWidget {
|
||||
State<StatefulWidget> createState() => _DeviceInfoPageState();
|
||||
}
|
||||
|
||||
enum TokenState { requesting, exists, error }
|
||||
|
||||
class _DeviceInfoPageState extends State<DeviceInfoPage> {
|
||||
late Dio httpClient;
|
||||
late Device device;
|
||||
DeviceStatus? deviceStatus;
|
||||
String statusMessage = "";
|
||||
bool online = false;
|
||||
bool loading = false;
|
||||
Timer? refreshTimer;
|
||||
late Timer logPollTimer;
|
||||
List<LogEntry> logEntries = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -31,40 +40,67 @@ class _DeviceInfoPageState extends State<DeviceInfoPage> {
|
||||
|
||||
refreshDevice();
|
||||
checkStatus();
|
||||
logPollTimer = Timer.periodic(
|
||||
Duration(seconds: 15),
|
||||
(timer) => queryLogs(),
|
||||
);
|
||||
}
|
||||
|
||||
void refreshDevice() {
|
||||
device = AppConfig().devices[widget.deviceIndex];
|
||||
}
|
||||
|
||||
Future checkToken() async {
|
||||
var appLocal = AppLocalizations.of(context)!;
|
||||
var resp = await httpClient.get("https://liteauth.local/api/getToken");
|
||||
|
||||
if (resp.statusCode != 200) {
|
||||
statusMessage = appLocal.tokenForbidden;
|
||||
return;
|
||||
}
|
||||
|
||||
device.token = resp.data;
|
||||
final config = AppConfig();
|
||||
config.devices[widget.deviceIndex] = device;
|
||||
await config.save();
|
||||
}
|
||||
|
||||
Future checkStatus() async {
|
||||
statusMessage = "";
|
||||
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
var client = await createEspHttpClient();
|
||||
httpClient = await createEspHttpClient(token: device.token);
|
||||
|
||||
try {
|
||||
var resp = await client.get(
|
||||
Uri.parse("https://liteauth.local/api/status"),
|
||||
);
|
||||
deviceStatus = DeviceStatus.fromJson(jsonDecode(resp.body));
|
||||
online = resp.statusCode >= 200 && resp.statusCode < 300;
|
||||
var resp = await httpClient.get("https://liteauth.local/api/status");
|
||||
deviceStatus = DeviceStatus.fromJson(resp.data);
|
||||
online = resp.statusCode! >= 200 && resp.statusCode! < 300;
|
||||
} catch (e) {
|
||||
statusMessage = e.toString();
|
||||
online = false;
|
||||
refreshTimer ??= Timer.periodic(const Duration(seconds: 5), (
|
||||
refreshTimer ??= Timer.periodic(const Duration(seconds: 10), (
|
||||
timer,
|
||||
) async {
|
||||
if (loading) return;
|
||||
await checkStatus();
|
||||
if (online) {
|
||||
statusMessage = "";
|
||||
timer.cancel();
|
||||
refreshTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (online && device.token.isEmpty) {
|
||||
try {
|
||||
await checkToken();
|
||||
} catch (e) {
|
||||
statusMessage = e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
loading = false;
|
||||
@@ -75,28 +111,96 @@ class _DeviceInfoPageState extends State<DeviceInfoPage> {
|
||||
void factoryReset() async {
|
||||
var appLocal = AppLocalizations.of(context)!;
|
||||
|
||||
var reset = await showDialog<bool>(context: context, builder: (ctx) => AlertDialog.adaptive(
|
||||
title: Text(appLocal.factoryReset),
|
||||
content: Text(appLocal.resetConfirm),
|
||||
actions: [
|
||||
TextButton(onPressed: () {
|
||||
Navigator.of(ctx).pop(false);
|
||||
}, child: Text(appLocal.cancel)),
|
||||
TextButton(onPressed: () {
|
||||
Navigator.of(ctx).pop(true);
|
||||
}, child: Text(appLocal.reset)),
|
||||
],
|
||||
)) ?? false;
|
||||
var reset =
|
||||
await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog.adaptive(
|
||||
title: Text(appLocal.factoryReset),
|
||||
content: Text(appLocal.resetConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop(false);
|
||||
},
|
||||
child: Text(appLocal.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop(true);
|
||||
},
|
||||
child: Text(appLocal.reset),
|
||||
),
|
||||
],
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
|
||||
if (!reset) return;
|
||||
|
||||
var client = await createEspHttpClient();
|
||||
client.get(Uri.parse("https://liteauth.local/api/factoryReset"));
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
final resp = await httpClient.get(
|
||||
"https://liteauth.local/api/factoryReset",
|
||||
);
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
|
||||
if (resp.statusCode != 200 && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(appLocal.resetFailedCode(resp.statusCode!))),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(appLocal.resetSuccessful)));
|
||||
}
|
||||
|
||||
device.token = "";
|
||||
var config = AppConfig();
|
||||
config.devices[widget.deviceIndex] = device;
|
||||
await config.save();
|
||||
reconfigureNetwork();
|
||||
}
|
||||
|
||||
void queryLogs() async {
|
||||
if (!online) return;
|
||||
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
var resp = await httpClient.get("https://liteauth.local/api/logs");
|
||||
print(DateTime.now().toIso8601String());
|
||||
logEntries = (resp.data as List<dynamic>).reversed
|
||||
.map((data) => LogEntry.fromJson(data))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
refreshTimer?.cancel();
|
||||
logPollTimer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -115,81 +219,7 @@ class _DeviceInfoPageState extends State<DeviceInfoPage> {
|
||||
)
|
||||
: null,
|
||||
actions: [
|
||||
MenuAnchor(
|
||||
builder: (context, controller, child) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
icon: Icon(Icons.adaptive.more),
|
||||
);
|
||||
},
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.refresh),
|
||||
onPressed: checkStatus,
|
||||
child: Text(appLocal.refresh),
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.network_wifi),
|
||||
child: Text(appLocal.reconfigureNetwork),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ReconfigureNetworkPage(widget.deviceIndex),
|
||||
),
|
||||
).then((result) {
|
||||
if (result) {
|
||||
refreshDevice();
|
||||
checkStatus();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.history),
|
||||
child: Text(appLocal.factoryReset),
|
||||
onPressed: factoryReset,
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.delete),
|
||||
child: Text(appLocal.delete),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog.adaptive(
|
||||
title: Text(appLocal.deletion),
|
||||
content: Text(appLocal.deviceDeleteConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(appLocal.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
AppConfig()
|
||||
..devices.removeAt(widget.deviceIndex)
|
||||
..save();
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(appLocal.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(onPressed: moreActions, icon: Icon(Icons.adaptive.more)),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
@@ -251,6 +281,25 @@ class _DeviceInfoPageState extends State<DeviceInfoPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: logEntries.length,
|
||||
itemBuilder: (ctx, idx) {
|
||||
var entry = logEntries[idx];
|
||||
return Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(
|
||||
// DateFormat.yMd().add_jms().format(
|
||||
// entry.time.toLocal(),
|
||||
// ),
|
||||
entry.time.toIso8601String(),
|
||||
),
|
||||
Text(entry.uid),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -259,4 +308,119 @@ class _DeviceInfoPageState extends State<DeviceInfoPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void reconfigureNetwork() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ReconfigureNetworkPage(widget.deviceIndex),
|
||||
),
|
||||
).then((result) {
|
||||
if (result == true) {
|
||||
refreshDevice();
|
||||
checkStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void moreActions() {
|
||||
final appTheme = Theme.of(context);
|
||||
final appLocal = AppLocalizations.of(context)!;
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (ctx) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||
child: Text(
|
||||
appLocal.additionalActions,
|
||||
style: appTheme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.refresh),
|
||||
onTap: () {
|
||||
if (online) {
|
||||
queryLogs();
|
||||
} else {
|
||||
checkStatus();
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
title: Text(appLocal.refresh),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.key),
|
||||
onTap: () {
|
||||
Navigator.of(ctx).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
TokenInfo(widget.deviceIndex, device.token),
|
||||
),
|
||||
);
|
||||
},
|
||||
title: Text(appLocal.manageToken),
|
||||
),
|
||||
Visibility(
|
||||
visible: Platform.isAndroid || Platform.isIOS,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.network_wifi),
|
||||
title: Text(appLocal.reconfigureNetwork),
|
||||
onTap: reconfigureNetwork,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
|
||||
child: Text(
|
||||
appLocal.dangerousActions,
|
||||
style: appTheme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.history),
|
||||
title: Text(appLocal.factoryReset),
|
||||
onTap: factoryReset,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete),
|
||||
title: Text(appLocal.delete),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog.adaptive(
|
||||
title: Text(appLocal.deletion),
|
||||
content: Text(appLocal.deviceDeleteConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(appLocal.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
AppConfig()
|
||||
..devices.removeAt(widget.deviceIndex)
|
||||
..save();
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(appLocal.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user