Fix notification toggle and add check interval selector
This commit is contained in:
parent
22deb64bce
commit
28fe3fc3c0
9 changed files with 77 additions and 31 deletions
|
@ -22,6 +22,7 @@ class ApiClient {
|
||||||
|
|
||||||
final AuthenticationData _authenticationData;
|
final AuthenticationData _authenticationData;
|
||||||
|
|
||||||
|
AuthenticationData get authenticationData => _authenticationData;
|
||||||
String get userId => _authenticationData.userId;
|
String get userId => _authenticationData.userId;
|
||||||
bool get isAuthenticated => _authenticationData.isAuthenticated;
|
bool get isAuthenticated => _authenticationData.isAuthenticated;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:contacts_plus_plus/apis/message_api.dart';
|
import 'package:contacts_plus_plus/apis/message_api.dart';
|
||||||
|
import 'package:contacts_plus_plus/models/authentication_data.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||||
import 'package:contacts_plus_plus/config.dart';
|
import 'package:contacts_plus_plus/config.dart';
|
||||||
import 'package:contacts_plus_plus/models/message.dart';
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:workmanager/workmanager.dart';
|
||||||
|
|
||||||
enum EventType {
|
enum EventType {
|
||||||
unknown,
|
unknown,
|
||||||
|
@ -31,10 +33,12 @@ class NeosHub {
|
||||||
static const String eofChar = "";
|
static const String eofChar = "";
|
||||||
static const String _negotiationPacket = "{\"protocol\":\"json\", \"version\":1}$eofChar";
|
static const String _negotiationPacket = "{\"protocol\":\"json\", \"version\":1}$eofChar";
|
||||||
static const List<int> _reconnectTimeoutsSeconds = [0, 5, 10, 20, 60];
|
static const List<int> _reconnectTimeoutsSeconds = [0, 5, 10, 20, 60];
|
||||||
|
static const String taskName = "periodic-unread-check";
|
||||||
final ApiClient _apiClient;
|
final ApiClient _apiClient;
|
||||||
final Map<String, MessageCache> _messageCache = {};
|
final Map<String, MessageCache> _messageCache = {};
|
||||||
final Map<String, Function> _updateListeners = {};
|
final Map<String, Function> _updateListeners = {};
|
||||||
final Logger _logger = Logger("NeosHub");
|
final Logger _logger = Logger("NeosHub");
|
||||||
|
final Workmanager _workmanager = Workmanager();
|
||||||
WebSocket? _wsChannel;
|
WebSocket? _wsChannel;
|
||||||
bool _isConnecting = false;
|
bool _isConnecting = false;
|
||||||
|
|
||||||
|
@ -58,13 +62,27 @@ class NeosHub {
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkUnreads() async {
|
static Future<void> backgroundCheckUnreads(Map<String, dynamic>? inputData) async {
|
||||||
final unreads = await MessageApi.getUserMessages(_apiClient, unreadOnly: true);
|
if (inputData == null) return;
|
||||||
|
final auth = AuthenticationData.fromMap(inputData);
|
||||||
|
final unreads = await MessageApi.getUserMessages(ApiClient(authenticationData: auth), unreadOnly: true);
|
||||||
for (var message in unreads) {
|
for (var message in unreads) {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _updateNotificationTask(int minuteInterval) async {
|
||||||
|
final auth = _apiClient.authenticationData;
|
||||||
|
if (!auth.isAuthenticated) throw "Unauthenticated";
|
||||||
|
await _workmanager.cancelByUniqueName(taskName);
|
||||||
|
_workmanager.registerPeriodicTask(
|
||||||
|
taskName,
|
||||||
|
taskName,
|
||||||
|
frequency: Duration(minutes: minuteInterval),
|
||||||
|
inputData: auth.toMap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _onDisconnected(error) {
|
void _onDisconnected(error) {
|
||||||
_logger.warning("Neos Hub connection died with error '$error', reconnecting...");
|
_logger.warning("Neos Hub connection died with error '$error', reconnecting...");
|
||||||
start();
|
start();
|
||||||
|
|
|
@ -18,6 +18,7 @@ class SettingsClient {
|
||||||
_currentSettings = Settings.fromMap(jsonDecode(data));
|
_currentSettings = Settings.fromMap(jsonDecode(data));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
_storage.delete(key: _settingsKey);
|
_storage.delete(key: _settingsKey);
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:contacts_plus_plus/clients/neos_hub.dart';
|
||||||
import 'package:contacts_plus_plus/clients/settings_client.dart';
|
import 'package:contacts_plus_plus/clients/settings_client.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/friends_list.dart';
|
import 'package:contacts_plus_plus/widgets/friends_list.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/login_screen.dart';
|
import 'package:contacts_plus_plus/widgets/login_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'clients/api_client.dart';
|
import 'clients/api_client.dart';
|
||||||
import 'models/authentication_data.dart';
|
import 'models/authentication_data.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
|
await Workmanager().initialize(
|
||||||
|
callbackDispatcher, // The top level function, aka callbackDispatcher
|
||||||
|
isInDebugMode: true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
|
||||||
|
);
|
||||||
Logger.root.onRecord.listen((event) => log(event.message, name: event.loggerName));
|
Logger.root.onRecord.listen((event) => log(event.message, name: event.loggerName));
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
final settingsClient = SettingsClient();
|
final settingsClient = SettingsClient();
|
||||||
|
@ -18,6 +23,17 @@ void main() async {
|
||||||
runApp(Phoenix(child: ContactsPlusPlus(settingsClient: settingsClient,)));
|
runApp(Phoenix(child: ContactsPlusPlus(settingsClient: settingsClient,)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+
|
||||||
|
void callbackDispatcher() {
|
||||||
|
Workmanager().executeTask((String task, Map<String, dynamic>? inputData) async {
|
||||||
|
debugPrint("Native called background task: $task"); //simpleTask will be emitted here.
|
||||||
|
if (task == NeosHub.taskName) {
|
||||||
|
final unreads = NeosHub.backgroundCheckUnreads(inputData);
|
||||||
|
}
|
||||||
|
return Future.value(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class ContactsPlusPlus extends StatefulWidget {
|
class ContactsPlusPlus extends StatefulWidget {
|
||||||
const ContactsPlusPlus({required this.settingsClient, super.key});
|
const ContactsPlusPlus({required this.settingsClient, super.key});
|
||||||
|
|
||||||
|
|
|
@ -24,4 +24,12 @@ class AuthenticationData {
|
||||||
Map<String, String> get authorizationHeader => {
|
Map<String, String> get authorizationHeader => {
|
||||||
"Authorization": "neos $userId:$token"
|
"Authorization": "neos $userId:$token"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
"userId": userId,
|
||||||
|
"token": token,
|
||||||
|
"secretMachineId": secretMachineId,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
class SettingsEntry<T> {
|
class SettingsEntry<T> {
|
||||||
final T? value;
|
final T? value;
|
||||||
final T deflt;
|
final T deflt;
|
||||||
|
@ -6,7 +8,7 @@ class SettingsEntry<T> {
|
||||||
|
|
||||||
factory SettingsEntry.fromMap(Map map) {
|
factory SettingsEntry.fromMap(Map map) {
|
||||||
return SettingsEntry<T>(
|
return SettingsEntry<T>(
|
||||||
value: map["value"] as T,
|
value: jsonDecode(map["value"]) as T?,
|
||||||
deflt: map["default"],
|
deflt: map["default"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import 'package:contacts_plus_plus/widgets/expanding_input_fab.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/friend_list_tile.dart';
|
import 'package:contacts_plus_plus/widgets/friend_list_tile.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/settings_page.dart';
|
import 'package:contacts_plus_plus/widgets/settings_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart' as fln;
|
|
||||||
|
|
||||||
class FriendsList extends StatefulWidget {
|
class FriendsList extends StatefulWidget {
|
||||||
const FriendsList({super.key});
|
const FriendsList({super.key});
|
||||||
|
|
|
@ -104,7 +104,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
final requestResult = await notificationManager.resolvePlatformSpecificImplementation<
|
final requestResult = await notificationManager.resolvePlatformSpecificImplementation<
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
?.requestPermission();
|
?.requestPermission();
|
||||||
await settingsClient.changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: requestResult));
|
await settingsClient.changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: requestResult == null ? null : !requestResult));
|
||||||
},
|
},
|
||||||
child: const Text("Yes"),
|
child: const Text("Yes"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
import 'package:contacts_plus_plus/clients/api_client.dart';
|
import 'package:contacts_plus_plus/clients/api_client.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:workmanager/workmanager.dart';
|
||||||
|
|
||||||
class SettingsPage extends StatelessWidget {
|
class SettingsPage extends StatelessWidget {
|
||||||
const SettingsPage({super.key});
|
const SettingsPage({super.key});
|
||||||
|
static const Map<int, String> _intervalSelections = {
|
||||||
|
5: "5 Minutes",
|
||||||
|
15: "15 Minutes",
|
||||||
|
30: "30 Minutes",
|
||||||
|
60: "1 Hour",
|
||||||
|
120: "2 Hours",
|
||||||
|
300: "6 Hours",
|
||||||
|
600: "12 Hours",
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -23,33 +32,25 @@ class SettingsPage extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
const ListSectionHeader(name: "Notifications"),
|
const ListSectionHeader(name: "Notifications"),
|
||||||
BooleanSettingsTile(
|
BooleanSettingsTile(
|
||||||
title: "Send Notifications",
|
title: "Enable Notifications",
|
||||||
initialState: sClient.currentSettings.notificationsDenied.valueOrDefault,
|
initialState: !sClient.currentSettings.notificationsDenied.valueOrDefault,
|
||||||
onChanged: (value) async => await sClient.changeSettings(sClient.currentSettings.copyWith(notificationsDenied: value)),
|
onChanged: (value) async => await sClient.changeSettings(sClient.currentSettings.copyWith(notificationsDenied: !value)),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
trailing: const Icon(Icons.logout),
|
trailing: StatefulBuilder(
|
||||||
title: const Text("Sign out"),
|
builder: (context, setState) {
|
||||||
onTap: () {
|
return DropdownButton<int>(
|
||||||
showDialog(
|
items: _intervalSelections.keys.map((e) => DropdownMenuItem<int>(value: e, child: Text("${_intervalSelections[e]}"))).toList(),
|
||||||
context: context,
|
value: sClient.currentSettings.unreadCheckIntervalMinutes.valueOrDefault,
|
||||||
builder: (context) =>
|
onChanged: (int? value) async {
|
||||||
AlertDialog(
|
await sClient.changeSettings(sClient.currentSettings.copyWith(unreadCheckIntervalMinutes: value));
|
||||||
title: Text("Are you sure you want to sign out?", style: Theme
|
setState(() {});
|
||||||
.of(context)
|
|
||||||
.textTheme
|
|
||||||
.titleLarge,),
|
|
||||||
actions: [
|
|
||||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("No")),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await ClientHolder.of(context).apiClient.logout(context);
|
|
||||||
},
|
},
|
||||||
child: const Text("Yes"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
title: const Text("Check Interval"),
|
||||||
|
onTap: () {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const ListSectionHeader(name: "Other"),
|
const ListSectionHeader(name: "Other"),
|
||||||
|
|
Loading…
Reference in a new issue