feat: added datepicker, better refresh, and lots of fixes

This commit is contained in:
2024-03-17 21:17:09 +01:00
parent 48476f6fe4
commit fa129f17fb
6 changed files with 319 additions and 326 deletions

View File

@ -2,21 +2,23 @@ import 'dart:convert';
import 'package:anyhow/base.dart';
import 'package:bloc/bloc.dart';
import 'package:duration_picker/duration_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:ot_viewer_app/owntracks_api.dart';
import 'package:ot_viewer_app/util.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsState {
SettingsState({
this.url = '',
this.username = '',
this.password = '',
this.usersToDevices = const {},
this.activeDevices = const {},
});
SettingsState(
{this.url = '',
this.username = '',
this.password = '',
this.usersToDevices = const {},
this.activeDevices = const {},
this.historyTime = const Duration(days: 1)});
final String url;
final String username;
@ -24,14 +26,12 @@ class SettingsState {
final Map<String, List<String>> usersToDevices;
final Set<(String, String)> activeDevices;
// Copy constructor
SettingsState.copy(SettingsState source)
: url = source.url,
username = source.username,
password = source.password,
usersToDevices = Map.from(source.usersToDevices)
.map((key, value) => MapEntry(key, List.from(value))),
activeDevices = Set.from(source.activeDevices);
final Duration historyTime;
@override
String toString() {
return 'SettingsState{url: $url, username: $username, password: $password, usersToDevices: $usersToDevices, activeDevices: $activeDevices, historyTime: $historyTime}';
}
@override
bool operator ==(Object other) =>
@ -42,7 +42,8 @@ class SettingsState {
username == other.username &&
password == other.password &&
usersToDevices == other.usersToDevices &&
activeDevices == other.activeDevices;
activeDevices == other.activeDevices &&
historyTime == other.historyTime;
@override
int get hashCode =>
@ -50,49 +51,10 @@ class SettingsState {
username.hashCode ^
password.hashCode ^
usersToDevices.hashCode ^
activeDevices.hashCode;
@override
String toString() {
return 'SettingsState{url: $url, username: $username, password: $password, usersToDevices: $usersToDevices, activeDevices: $activeDevices}';
}
activeDevices.hashCode ^
historyTime.hashCode;
}
/*
Future<void> loadFromSharedPrefs() async {
final sp = await SharedPreferences.getInstance();
final url = sp.getString('url') ?? '';
final username = sp.getString('username') ?? '';
final password = sp.getString('password') ?? '';
// Decode the JSON string and then manually convert to the expected type
final usersToDevicesJson =
jsonDecode(sp.getString('usersToDevices') ?? '{}')
as Map<String, dynamic>;
final Map<String, List<String>> usersToDevices =
usersToDevicesJson.map((key, value) {
// Ensure the value is cast to a List<String>
final List<String> list = List<String>.from(value);
return MapEntry(key, list);
});
// Decode the JSON string for activeDevices. Adjust this part as needed based on your actual data structure
final activeDevicesJson =
jsonDecode(sp.getString('activeDevices') ?? '[]') as List;
final List<(String, String)> activeDevices = List.from(activeDevicesJson);
emit(SettingsState(
url: url,
username: username,
password: password,
usersToDevices: usersToDevices,
activeDevices: activeDevices,
));
}
*/
class SettingsCubit extends HydratedCubit<SettingsState> {
SettingsCubit() : super(SettingsState());
@ -134,26 +96,26 @@ 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) {
if (e.length != 2) {
return null;
} else {
return (e[0], e[1]);
}
}).where((element) => element != null)));
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) {
if (e.length != 2) {
return null;
} else {
return (e[0], e[1]);
}
}).where((element) => element != null)),
historyTime: Duration(hours: (json['historyTime'] ?? '24') as int),
);
}
@override
@ -165,6 +127,7 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
'password': state.password,
'usersToDevices': state.usersToDevices,
'activeDevices': state.activeDevices.map((e) => [e.$1, e.$2]).toList(),
'historyTime': state.historyTime.inHours,
};
}
@ -174,6 +137,7 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
String? password,
Map<String, List<String>>? usersToDevices,
Set<(String, String)>? activeDevices,
Duration? historyTime,
}) async {
final currentState = state;
@ -184,6 +148,7 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
password: password ?? currentState.password,
usersToDevices: usersToDevices ?? currentState.usersToDevices,
activeDevices: activeDevices ?? currentState.activeDevices,
historyTime: historyTime ?? currentState.historyTime,
));
}
}
@ -228,8 +193,7 @@ 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,
@ -238,12 +202,10 @@ 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),
@ -254,8 +216,7 @@ 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),
@ -268,8 +229,7 @@ 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(
@ -278,8 +238,7 @@ 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
@ -287,8 +246,7 @@ 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(),
@ -300,37 +258,63 @@ class _SettingsPageState extends State<SettingsPage> {
username: _usernameController.text,
password: _passwordController.text,
);
context.read<SettingsCubit>().fetchDevices();
},
icon: Icon(Icons.save),
label: Text('Save'))
icon: const Icon(Icons.save),
label: const Text('Save'))
],
),
]),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
final settingsCubit = context.read<SettingsCubit>();
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!')));
}
} else {
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!')));
}
}
}
},
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(
@ -349,26 +333,20 @@ 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"),