Fix notification toggle and add check interval selector

This commit is contained in:
Nutcake 2023-05-04 13:13:24 +02:00
parent 22deb64bce
commit 28fe3fc3c0
9 changed files with 77 additions and 31 deletions

View file

@ -22,6 +22,7 @@ class ApiClient {
final AuthenticationData _authenticationData;
AuthenticationData get authenticationData => _authenticationData;
String get userId => _authenticationData.userId;
bool get isAuthenticated => _authenticationData.isAuthenticated;

View file

@ -1,12 +1,14 @@
import 'dart:convert';
import 'dart:io';
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:contacts_plus_plus/clients/api_client.dart';
import 'package:contacts_plus_plus/config.dart';
import 'package:contacts_plus_plus/models/message.dart';
import 'package:logging/logging.dart';
import 'package:workmanager/workmanager.dart';
enum EventType {
unknown,
@ -31,10 +33,12 @@ class NeosHub {
static const String eofChar = "";
static const String _negotiationPacket = "{\"protocol\":\"json\", \"version\":1}$eofChar";
static const List<int> _reconnectTimeoutsSeconds = [0, 5, 10, 20, 60];
static const String taskName = "periodic-unread-check";
final ApiClient _apiClient;
final Map<String, MessageCache> _messageCache = {};
final Map<String, Function> _updateListeners = {};
final Logger _logger = Logger("NeosHub");
final Workmanager _workmanager = Workmanager();
WebSocket? _wsChannel;
bool _isConnecting = false;
@ -58,13 +62,27 @@ class NeosHub {
return cache;
}
Future<void> checkUnreads() async {
final unreads = await MessageApi.getUserMessages(_apiClient, unreadOnly: true);
static Future<void> backgroundCheckUnreads(Map<String, dynamic>? inputData) async {
if (inputData == null) return;
final auth = AuthenticationData.fromMap(inputData);
final unreads = await MessageApi.getUserMessages(ApiClient(authenticationData: auth), unreadOnly: true);
for (var message in unreads) {
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) {
_logger.warning("Neos Hub connection died with error '$error', reconnecting...");
start();

View file

@ -18,6 +18,7 @@ class SettingsClient {
_currentSettings = Settings.fromMap(jsonDecode(data));
} catch (_) {
_storage.delete(key: _settingsKey);
rethrow;
}
}

View file

@ -1,16 +1,21 @@
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/widgets/friends_list.dart';
import 'package:contacts_plus_plus/widgets/login_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_phoenix/flutter_phoenix.dart';
import 'package:logging/logging.dart';
import 'package:workmanager/workmanager.dart';
import 'clients/api_client.dart';
import 'models/authentication_data.dart';
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));
WidgetsFlutterBinding.ensureInitialized();
final settingsClient = SettingsClient();
@ -18,6 +23,17 @@ void main() async {
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 {
const ContactsPlusPlus({required this.settingsClient, super.key});

View file

@ -24,4 +24,12 @@ class AuthenticationData {
Map<String, String> get authorizationHeader => {
"Authorization": "neos $userId:$token"
};
Map<String, dynamic> toMap() {
return {
"userId": userId,
"token": token,
"secretMachineId": secretMachineId,
};
}
}

View file

@ -1,3 +1,5 @@
import 'dart:convert';
class SettingsEntry<T> {
final T? value;
final T deflt;
@ -6,7 +8,7 @@ class SettingsEntry<T> {
factory SettingsEntry.fromMap(Map map) {
return SettingsEntry<T>(
value: map["value"] as T,
value: jsonDecode(map["value"]) as T?,
deflt: map["default"],
);
}

View file

@ -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/settings_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart' as fln;
class FriendsList extends StatefulWidget {
const FriendsList({super.key});

View file

@ -104,7 +104,7 @@ class _LoginScreenState extends State<LoginScreen> {
final requestResult = await notificationManager.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestPermission();
await settingsClient.changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: requestResult));
await settingsClient.changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: requestResult == null ? null : !requestResult));
},
child: const Text("Yes"),
)

View file

@ -1,10 +1,19 @@
import 'package:contacts_plus_plus/clients/api_client.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:workmanager/workmanager.dart';
class SettingsPage extends StatelessWidget {
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
Widget build(BuildContext context) {
@ -23,33 +32,25 @@ class SettingsPage extends StatelessWidget {
children: [
const ListSectionHeader(name: "Notifications"),
BooleanSettingsTile(
title: "Send Notifications",
initialState: sClient.currentSettings.notificationsDenied.valueOrDefault,
onChanged: (value) async => await sClient.changeSettings(sClient.currentSettings.copyWith(notificationsDenied: value)),
title: "Enable Notifications",
initialState: !sClient.currentSettings.notificationsDenied.valueOrDefault,
onChanged: (value) async => await sClient.changeSettings(sClient.currentSettings.copyWith(notificationsDenied: !value)),
),
ListTile(
trailing: const Icon(Icons.logout),
title: const Text("Sign out"),
trailing: StatefulBuilder(
builder: (context, setState) {
return DropdownButton<int>(
items: _intervalSelections.keys.map((e) => DropdownMenuItem<int>(value: e, child: Text("${_intervalSelections[e]}"))).toList(),
value: sClient.currentSettings.unreadCheckIntervalMinutes.valueOrDefault,
onChanged: (int? value) async {
await sClient.changeSettings(sClient.currentSettings.copyWith(unreadCheckIntervalMinutes: value));
setState(() {});
},
);
}
),
title: const Text("Check Interval"),
onTap: () {
showDialog(
context: context,
builder: (context) =>
AlertDialog(
title: Text("Are you sure you want to sign out?", style: Theme
.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"),
),
],
),
);
},
),
const ListSectionHeader(name: "Other"),