-
-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Preset messages allow players (including anonymous non-logged in user…
…s) to send preset messages such as 'good luck' and 'have fun' on game start and 'gg' on game end. This functionality mirrors that currently on the website. Limitations: * No internationalisation support (this is also the case on the website and would be a separate piece of work requiring updates to both the phone app and website to make the messages appear in both users' languages.) * Whether a preset has already been set can't be synced up with the website / other devices (I don't think this is a big deal :P.) * State forgotten when closing the app and returning to game Message Bubble Bug Fix Prior to this PR, the message bubble functionality didn't take into account that anonymous players can send these preset messages and the bubble 'else' case makes it look like it was the phone player who sent these even if it was the other player. I added a change to expose the "colour" field (`c`) in the websocket message. State Persistence The state is written to sqlite so it is retained when games are opened again after the app is closed (or the game is moved away from - I don't think this is possible at the moment but it perhaps could be in the future to navigate between multiple correspondence games if it isn't possible already.) If you think this is heavy handed let me know and I could explore just retaining it for the lifetime of the game widget. Being a flutter noob, I'd have to work out how to pipe that state together as the chat widget state is built from scratch each time the chat button is clicked and creates a 'router change event'. I thought putting it in SQLlite was probably fine because the chat widget does similar for new / read messages, etc. Scenarios Tested * Chat bubbles still work as expected after adding in the 'look at the anonymous player's colour if they're not logged in' change. * Clicking the preset buttons makes them disappear. * Pressing 2 buttons on either game start or end makes all the bubbles disappear. * The chat buttons move from the 'start' buttons to 'end' buttons when the game has ended in less than 4 moves * The 'start' chat buttons disappear after 4 moves have been played. * The 'end' buttons appear if the user clicks that chat after the game ends * The end buttons appear if the user is on the chat while the game ends * The start buttons disappear if the user clicks the chat after the 4th move * The start buttons disappear if the user is on the chat when the 4th move has been played
- Loading branch information
Showing
6 changed files
with
234 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import 'package:freezed_annotation/freezed_annotation.dart'; | ||
import 'package:lichess_mobile/src/model/common/id.dart'; | ||
import 'package:lichess_mobile/src/model/game/chat_controller.dart'; | ||
import 'package:lichess_mobile/src/model/game/game_controller.dart'; | ||
import 'package:lichess_mobile/src/model/game/message_presets.dart'; | ||
import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||
|
||
part 'chat_presets_controller.freezed.dart'; | ||
part 'chat_presets_controller.g.dart'; | ||
|
||
@riverpod | ||
class ChatPresetsController extends _$ChatPresetsController { | ||
late GameFullId _gameId; | ||
|
||
static const Map<PresetMessageGroup, List<PresetMessage>> _presetMessages = { | ||
PresetMessageGroup.start: [ | ||
(label: 'HI', value: 'Hello'), | ||
(label: 'GL', value: 'Good luck'), | ||
(label: 'HF', value: 'Have fun!'), | ||
(label: 'U2', value: 'You too!'), | ||
], | ||
PresetMessageGroup.end: [ | ||
(label: 'GG', value: 'Good game'), | ||
(label: 'WP', value: 'Well played'), | ||
(label: 'TY', value: 'Thank you'), | ||
(label: 'GTG', value: "I've got to go"), | ||
(label: 'BYE', value: 'Bye!'), | ||
], | ||
}; | ||
|
||
@override | ||
Future<ChatPresetsState> build(GameFullId id) async { | ||
_gameId = id; | ||
|
||
final gameState = ref.read(gameControllerProvider(_gameId)).value; | ||
|
||
ref.listen(gameControllerProvider(_gameId), _handleGameStateChange); | ||
|
||
if (gameState != null) { | ||
final presetMessageGroup = PresetMessageGroup.fromGame(gameState.game); | ||
|
||
const List<PresetMessage> alreadySaid = []; | ||
|
||
final initialState = ChatPresetsState( | ||
presets: _presetMessages, | ||
gameId: _gameId, | ||
alreadySaid: alreadySaid, | ||
currentPresetMessageGroup: presetMessageGroup, | ||
); | ||
|
||
return initialState; | ||
} else { | ||
return ChatPresetsState( | ||
presets: _presetMessages, | ||
gameId: _gameId, | ||
alreadySaid: [], | ||
currentPresetMessageGroup: null, | ||
); | ||
} | ||
} | ||
|
||
void _handleGameStateChange( | ||
AsyncValue<GameState>? previousGame, | ||
AsyncValue<GameState> currentGame, | ||
) { | ||
final newGameState = currentGame.value; | ||
|
||
if (newGameState != null) { | ||
final newMessageGroup = PresetMessageGroup.fromGame(newGameState.game); | ||
|
||
final currentMessageGroup = state.value?.currentPresetMessageGroup; | ||
|
||
if (newMessageGroup != currentMessageGroup) { | ||
state = state.whenData((s) { | ||
final newState = s.copyWith( | ||
currentPresetMessageGroup: newMessageGroup, | ||
alreadySaid: [], | ||
); | ||
|
||
return newState; | ||
}); | ||
} | ||
} | ||
} | ||
|
||
void sendPreset(PresetMessage message) { | ||
final chatController = ref.read(chatControllerProvider(gameId).notifier); | ||
chatController.sendMessage(message.value); | ||
|
||
state = state.whenData((s) { | ||
final state = s.copyWith(alreadySaid: [...s.alreadySaid, message]); | ||
return state; | ||
}); | ||
} | ||
} | ||
|
||
@freezed | ||
class ChatPresetsState with _$ChatPresetsState { | ||
const ChatPresetsState._(); | ||
|
||
const factory ChatPresetsState({ | ||
required Map<PresetMessageGroup, List<PresetMessage>> presets, | ||
required GameFullId gameId, | ||
required List<PresetMessage> alreadySaid, | ||
required PresetMessageGroup? currentPresetMessageGroup, | ||
}) = _ChatPresetsState; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import 'package:lichess_mobile/src/model/game/game_status.dart'; | ||
import 'package:lichess_mobile/src/model/game/playable_game.dart'; | ||
|
||
enum PresetMessageGroup { | ||
start, | ||
end; | ||
|
||
static PresetMessageGroup? fromString(String groupName) { | ||
if (groupName == 'start') { | ||
return start; | ||
} else if (groupName == 'end') { | ||
return end; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
static PresetMessageGroup? fromGame(PlayableGame game) { | ||
if (game.status.value <= GameStatus.mate.value && game.steps.length < 4) { | ||
return start; | ||
} else if (game.status.value >= GameStatus.mate.value) { | ||
return end; | ||
} else { | ||
return null; | ||
} | ||
} | ||
} | ||
|
||
typedef PresetMessage = ({String label, String value}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import 'package:flutter/cupertino.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
import 'package:lichess_mobile/src/model/common/id.dart'; | ||
import 'package:lichess_mobile/src/model/game/message_presets.dart'; | ||
import 'package:lichess_mobile/src/widgets/buttons.dart'; | ||
|
||
class PresetMessages extends ConsumerWidget { | ||
final GameFullId gameId; | ||
final List<PresetMessage> alreadySaid; | ||
final PresetMessageGroup? presetMessageGroup; | ||
final Map<PresetMessageGroup, List<PresetMessage>> presetMessages; | ||
final void Function(PresetMessage presetMessage) sendChatPreset; | ||
|
||
const PresetMessages({ | ||
required this.gameId, | ||
required this.alreadySaid, | ||
required this.presetMessageGroup, | ||
required this.presetMessages, | ||
required this.sendChatPreset, | ||
}); | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
final messages = presetMessages[presetMessageGroup] ?? []; | ||
|
||
if (messages.isEmpty || alreadySaid.length >= 2) { | ||
return const SizedBox.shrink(); | ||
} | ||
|
||
final notAlreadySaid = | ||
messages.where((message) => !alreadySaid.contains(message)); | ||
|
||
return Row( | ||
children: notAlreadySaid | ||
.map((preset) => _renderPresetMessageButton(preset, ref)) | ||
.toList(), | ||
); | ||
} | ||
|
||
Widget _renderPresetMessageButton(PresetMessage preset, WidgetRef ref) { | ||
return Padding( | ||
padding: const EdgeInsets.all(4.0), | ||
child: SecondaryButton( | ||
semanticsLabel: preset.label, | ||
onPressed: () { | ||
sendChatPreset(preset); | ||
}, | ||
child: Text( | ||
preset.label, | ||
textAlign: TextAlign.center, | ||
), | ||
), | ||
); | ||
} | ||
} |