Skip to content

Commit ae05aa7

Browse files
authored
Merge pull request #1463 from lichess-org/playban
Playban message
2 parents d54db63 + 9c0024a commit ae05aa7

20 files changed

+605
-218
lines changed

lib/src/app.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:lichess_mobile/l10n/l10n.dart';
77
import 'package:lichess_mobile/src/app_links.dart';
88
import 'package:lichess_mobile/src/constants.dart';
99
import 'package:lichess_mobile/src/model/account/account_repository.dart';
10+
import 'package:lichess_mobile/src/model/account/account_service.dart';
1011
import 'package:lichess_mobile/src/model/challenge/challenge_service.dart';
1112
import 'package:lichess_mobile/src/model/common/preloaded_data.dart';
1213
import 'package:lichess_mobile/src/model/correspondence/correspondence_service.dart';
@@ -78,6 +79,8 @@ class _AppState extends ConsumerState<Application> {
7879
// Start services
7980
ref.read(notificationServiceProvider).start();
8081
ref.read(challengeServiceProvider).start();
82+
ref.read(accountServiceProvider).start();
83+
ref.read(correspondenceServiceProvider).start();
8184

8285
// Listen for connectivity changes and perform actions accordingly.
8386
ref.listenManual(connectivityChangesProvider, (prev, current) async {

lib/src/model/account/account_repository.dart

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,34 +54,17 @@ Future<IList<OngoingGame>> ongoingGames(Ref ref) async {
5454
);
5555
}
5656

57-
@Riverpod(keepAlive: true)
58-
AccountService accountService(Ref ref) {
59-
return AccountService(ref);
60-
}
61-
62-
class AccountService {
63-
const AccountService(this._ref);
64-
65-
final Ref _ref;
66-
67-
Future<void> setGameBookmark(GameId id, {required bool bookmark}) async {
68-
final session = _ref.read(authSessionProvider);
69-
if (session == null) return;
70-
71-
await _ref.withClient((client) => AccountRepository(client).bookmark(id, bookmark: bookmark));
72-
73-
_ref.invalidate(accountProvider);
74-
}
75-
}
76-
7757
class AccountRepository {
7858
AccountRepository(this.client);
7959

8060
final LichessClient client;
8161
final Logger _log = Logger('AccountRepository');
8262

8363
Future<User> getProfile() {
84-
return client.readJson(Uri(path: '/api/account'), mapper: User.fromServerJson);
64+
return client.readJson(
65+
Uri(path: '/api/account', queryParameters: {'playban': '1'}),
66+
mapper: User.fromServerJson,
67+
);
8568
}
8669

8770
Future<void> saveProfile(Map<String, String> profile) async {
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter/material.dart' show Navigator, Text, showAdaptiveDialog;
4+
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
5+
import 'package:flutter_riverpod/flutter_riverpod.dart';
6+
import 'package:lichess_mobile/src/binding.dart' show LichessBinding;
7+
import 'package:lichess_mobile/src/model/account/account_repository.dart';
8+
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
9+
import 'package:lichess_mobile/src/model/common/id.dart';
10+
import 'package:lichess_mobile/src/model/notifications/notification_service.dart';
11+
import 'package:lichess_mobile/src/model/notifications/notifications.dart'
12+
show LocalNotification, PlaybanNotification;
13+
import 'package:lichess_mobile/src/model/user/user.dart' show TemporaryBan, User;
14+
import 'package:lichess_mobile/src/navigation.dart' show currentNavigatorKeyProvider;
15+
import 'package:lichess_mobile/src/network/http.dart';
16+
import 'package:lichess_mobile/src/view/play/playban.dart';
17+
import 'package:lichess_mobile/src/widgets/platform_alert_dialog.dart';
18+
import 'package:riverpod_annotation/riverpod_annotation.dart';
19+
20+
part 'account_service.g.dart';
21+
22+
@Riverpod(keepAlive: true)
23+
AccountService accountService(Ref ref) {
24+
final service = AccountService(ref);
25+
ref.onDispose(() {
26+
service.dispose();
27+
});
28+
return service;
29+
}
30+
31+
class AccountService {
32+
AccountService(this._ref);
33+
34+
ProviderSubscription<AsyncValue<User?>>? _accountProviderSubscription;
35+
StreamSubscription<(NotificationResponse, LocalNotification)>? _notificationResponseSubscription;
36+
37+
final Ref _ref;
38+
39+
static const _storageKey = 'account.playban_notification_date';
40+
41+
void start() {
42+
final prefs = LichessBinding.instance.sharedPreferences;
43+
44+
_accountProviderSubscription = _ref.listen(accountProvider, (_, account) {
45+
final playban = account.valueOrNull?.playban;
46+
final storedDate = prefs.getString(_storageKey);
47+
final lastPlaybanNotificationDate = storedDate != null ? DateTime.parse(storedDate) : null;
48+
49+
if (playban != null && lastPlaybanNotificationDate != playban.date) {
50+
_savePlaybanNotificationDate(playban.date);
51+
_ref.read(notificationServiceProvider).show(PlaybanNotification(playban));
52+
} else if (playban == null && lastPlaybanNotificationDate != null) {
53+
_ref
54+
.read(notificationServiceProvider)
55+
.cancel(lastPlaybanNotificationDate.toIso8601String().hashCode);
56+
_clearPlaybanNotificationDate();
57+
}
58+
});
59+
60+
_notificationResponseSubscription = NotificationService.responseStream.listen((data) {
61+
final (_, notification) = data;
62+
switch (notification) {
63+
case PlaybanNotification(:final playban):
64+
_onPlaybanNotificationResponse(playban);
65+
case _:
66+
break;
67+
}
68+
});
69+
}
70+
71+
void _savePlaybanNotificationDate(DateTime date) {
72+
LichessBinding.instance.sharedPreferences.setString(_storageKey, date.toIso8601String());
73+
}
74+
75+
void _clearPlaybanNotificationDate() {
76+
LichessBinding.instance.sharedPreferences.remove(_storageKey);
77+
}
78+
79+
void dispose() {
80+
_accountProviderSubscription?.close();
81+
_notificationResponseSubscription?.cancel();
82+
}
83+
84+
Future<void> _onPlaybanNotificationResponse(TemporaryBan playban) async {
85+
final context = _ref.read(currentNavigatorKeyProvider).currentContext;
86+
if (context == null || !context.mounted) return;
87+
88+
return showAdaptiveDialog(
89+
context: context,
90+
barrierDismissible: true,
91+
builder: (context) {
92+
return PlatformAlertDialog(
93+
content: PlaybanMessage(playban: playban, centerText: true),
94+
actions: [
95+
PlatformDialogAction(
96+
child: const Text('OK'),
97+
onPressed: () {
98+
Navigator.of(context).pop();
99+
},
100+
),
101+
],
102+
);
103+
},
104+
);
105+
}
106+
107+
Future<void> setGameBookmark(GameId id, {required bool bookmark}) async {
108+
final session = _ref.read(authSessionProvider);
109+
if (session == null) return;
110+
111+
await _ref.withClient((client) => AccountRepository(client).bookmark(id, bookmark: bookmark));
112+
113+
_ref.invalidate(accountProvider);
114+
}
115+
}

lib/src/model/challenge/challenge_service.dart

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
44
import 'package:deep_pick/deep_pick.dart';
55
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
66
import 'package:flutter/widgets.dart';
7+
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
78
import 'package:flutter_riverpod/flutter_riverpod.dart';
89
import 'package:lichess_mobile/src/model/challenge/challenge.dart';
910
import 'package:lichess_mobile/src/model/challenge/challenge_repository.dart';
@@ -38,6 +39,7 @@ class ChallengeService {
3839
ChallengesList? _previous;
3940

4041
StreamSubscription<ChallengesList>? _socketSubscription;
42+
StreamSubscription<(NotificationResponse, LocalNotification)>? _notificationResponseSubscription;
4143

4244
/// The stream of challenge events that are received from the server.
4345
static Stream<ChallengesList> get stream =>
@@ -49,9 +51,19 @@ class ChallengeService {
4951
return (inward: inward.lock, outward: outward.lock);
5052
}).whereNotNull();
5153

52-
/// Start listening to challenge events from the server.
54+
/// Start listening to events.
5355
void start() {
5456
_socketSubscription = stream.listen(_onSocketEvent);
57+
58+
_notificationResponseSubscription = NotificationService.responseStream.listen((data) {
59+
final (response, notification) = data;
60+
switch (notification) {
61+
case ChallengeNotification(:final challenge):
62+
_onNotificationResponse(response.actionId, challenge);
63+
case _:
64+
break;
65+
}
66+
});
5567
}
5668

5769
void _onSocketEvent(ChallengesList current) {
@@ -90,10 +102,11 @@ class ChallengeService {
90102
/// Stop listening to challenge events from the server.
91103
void dispose() {
92104
_socketSubscription?.cancel();
105+
_notificationResponseSubscription?.cancel();
93106
}
94107

95108
/// Handle a local notification response when the app is in the foreground.
96-
Future<void> onNotificationResponse(String? actionid, Challenge challenge) async {
109+
Future<void> _onNotificationResponse(String? actionid, Challenge challenge) async {
97110
final challengeId = challenge.id;
98111

99112
switch (actionid) {

lib/src/model/correspondence/correspondence_service.dart

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import 'package:lichess_mobile/src/model/correspondence/offline_correspondence_g
1616
import 'package:lichess_mobile/src/model/game/game_repository.dart';
1717
import 'package:lichess_mobile/src/model/game/game_socket_events.dart';
1818
import 'package:lichess_mobile/src/model/game/playable_game.dart';
19+
import 'package:lichess_mobile/src/model/notifications/notification_service.dart';
20+
import 'package:lichess_mobile/src/model/notifications/notifications.dart';
1921
import 'package:lichess_mobile/src/navigation.dart';
2022
import 'package:lichess_mobile/src/network/http.dart';
2123
import 'package:lichess_mobile/src/network/socket.dart';
@@ -27,7 +29,9 @@ part 'correspondence_service.g.dart';
2729

2830
@Riverpod(keepAlive: true)
2931
CorrespondenceService correspondenceService(Ref ref) {
30-
return CorrespondenceService(Logger('CorrespondenceService'), ref: ref);
32+
final service = CorrespondenceService(Logger('CorrespondenceService'), ref: ref);
33+
ref.onDispose(() => service.dispose());
34+
return service;
3135
}
3236

3337
/// Services that manages correspondence games.
@@ -37,8 +41,41 @@ class CorrespondenceService {
3741
final Ref ref;
3842
final Logger _log;
3943

44+
StreamSubscription<ParsedLocalNotification>? _notificationResponseSubscription;
45+
StreamSubscription<ReceivedFcmMessage>? _fcmSubscription;
46+
47+
void start() {
48+
_fcmSubscription = NotificationService.fcmMessageStream.listen((data) {
49+
final (message: fcmMessage, fromBackground: fromBackground) = data;
50+
switch (fcmMessage) {
51+
case CorresGameUpdateFcmMessage(fullId: final fullId, game: final game):
52+
if (game != null) {
53+
_onServerUpdateEvent(fullId, game, fromBackground: fromBackground);
54+
}
55+
56+
case _:
57+
break;
58+
}
59+
});
60+
61+
_notificationResponseSubscription = NotificationService.responseStream.listen((data) {
62+
final (_, notification) = data;
63+
switch (notification) {
64+
case CorresGameUpdateNotification(:final fullId):
65+
_onNotificationResponse(fullId);
66+
case _:
67+
break;
68+
}
69+
});
70+
}
71+
72+
void dispose() {
73+
_fcmSubscription?.cancel();
74+
_notificationResponseSubscription?.cancel();
75+
}
76+
4077
/// Handles a notification response that caused the app to open.
41-
Future<void> onNotificationResponse(GameFullId fullId) async {
78+
Future<void> _onNotificationResponse(GameFullId fullId) async {
4279
final context = ref.read(currentNavigatorKeyProvider).currentContext;
4380
if (context == null || !context.mounted) return;
4481

@@ -185,7 +222,7 @@ class CorrespondenceService {
185222
}
186223

187224
/// Handles a game update event from the server.
188-
Future<void> onServerUpdateEvent(
225+
Future<void> _onServerUpdateEvent(
189226
GameFullId fullId,
190227
PlayableGame game, {
191228
required bool fromBackground,

lib/src/model/game/game_controller.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
1111
import 'package:freezed_annotation/freezed_annotation.dart';
1212
import 'package:lichess_mobile/src/model/account/account_preferences.dart';
1313
import 'package:lichess_mobile/src/model/account/account_repository.dart';
14+
import 'package:lichess_mobile/src/model/account/account_service.dart';
1415
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
1516
import 'package:lichess_mobile/src/model/clock/chess_clock.dart';
1617
import 'package:lichess_mobile/src/model/common/chess.dart';

0 commit comments

Comments
 (0)