Revert "base timer off of game state event"
This commit is contained in:
parent
c263e7b872
commit
77fb4bcf4e
9 changed files with 131 additions and 266 deletions
|
|
@ -15,12 +15,10 @@ import 'package:fluffychat/pages/chat/event_info_dialog.dart';
|
||||||
import 'package:fluffychat/pages/chat/recording_dialog.dart';
|
import 'package:fluffychat/pages/chat/recording_dialog.dart';
|
||||||
import 'package:fluffychat/pages/chat_details/chat_details.dart';
|
import 'package:fluffychat/pages/chat_details/chat_details.dart';
|
||||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
|
||||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||||
import 'package:fluffychat/pangea/models/choreo_record.dart';
|
import 'package:fluffychat/pangea/models/choreo_record.dart';
|
||||||
import 'package:fluffychat/pangea/models/game_state_model.dart';
|
|
||||||
import 'package:fluffychat/pangea/models/representation_content_model.dart';
|
import 'package:fluffychat/pangea/models/representation_content_model.dart';
|
||||||
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
|
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
|
||||||
import 'package:fluffychat/pangea/pages/games/story_game/round_model.dart';
|
import 'package:fluffychat/pangea/pages/games/story_game/round_model.dart';
|
||||||
|
|
@ -28,6 +26,7 @@ import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||||
import 'package:fluffychat/pangea/utils/report_message.dart';
|
import 'package:fluffychat/pangea/utils/report_message.dart';
|
||||||
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
|
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
|
||||||
|
import 'package:fluffychat/pangea/widgets/chat/round_timer.dart';
|
||||||
import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart';
|
import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart';
|
||||||
import 'package:fluffychat/utils/error_reporter.dart';
|
import 'package:fluffychat/utils/error_reporter.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
||||||
|
|
@ -116,9 +115,17 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
// #Pangea
|
// #Pangea
|
||||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||||
late Choreographer choreographer = Choreographer(pangeaController, this);
|
late Choreographer choreographer = Choreographer(pangeaController, this);
|
||||||
|
final GlobalKey<RoundTimerState> roundTimerStateKey =
|
||||||
|
GlobalKey<RoundTimerState>();
|
||||||
|
RoundTimer? timer;
|
||||||
|
|
||||||
/// Model of the current story game round
|
final List<GameRoundModel> gameRounds = [];
|
||||||
GameRoundModel? currentRound;
|
|
||||||
|
List<String> get completedRoundEventIds => gameRounds
|
||||||
|
.where((round) => round.isCompleted)
|
||||||
|
.map((round) => round.userMessageIDs)
|
||||||
|
.expand((x) => x)
|
||||||
|
.toList();
|
||||||
// Pangea#
|
// Pangea#
|
||||||
|
|
||||||
Room get room => sendingClient.getRoomById(roomId) ?? widget.room;
|
Room get room => sendingClient.getRoomById(roomId) ?? widget.room;
|
||||||
|
|
@ -301,22 +308,12 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
|
|
||||||
// #Pangea
|
// #Pangea
|
||||||
/// Recursive function that sets the current round, waits for it to
|
void addRound() {
|
||||||
/// finish, sets it, etc. until the chat view is no longer mounted.
|
debugPrint("ADDING A ROUND. Rounds so far: ${gameRounds.length}");
|
||||||
void setRound() {
|
final newRound = GameRoundModel(controller: this, timer: timer!);
|
||||||
currentRound?.dispose();
|
gameRounds.add(newRound);
|
||||||
currentRound = GameRoundModel(room: room);
|
newRound.roundCompleter.future.then((_) {
|
||||||
room.client.onRoomState.stream.firstWhere((update) {
|
if (mounted) addRound();
|
||||||
if (update.roomId != roomId) return false;
|
|
||||||
if (update.state is! Event) return false;
|
|
||||||
if ((update.state as Event).type != PangeaEventTypes.storyGame) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final game = GameModel.fromJson((update.state as Event).content);
|
|
||||||
return game.previousRoundEndTime != null;
|
|
||||||
}).then((_) {
|
|
||||||
if (mounted) setRound();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Pangea#
|
// Pangea#
|
||||||
|
|
@ -336,7 +333,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
sendingClient = Matrix.of(context).client;
|
sendingClient = Matrix.of(context).client;
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
// #Pangea
|
// #Pangea
|
||||||
setRound();
|
timer = RoundTimer(key: roundTimerStateKey);
|
||||||
|
addRound();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Future.delayed(const Duration(seconds: 1), () async {
|
Future.delayed(const Duration(seconds: 1), () async {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
@ -423,7 +421,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
List<Event> get visibleEvents =>
|
List<Event> get visibleEvents =>
|
||||||
timeline?.events
|
timeline?.events
|
||||||
.where(
|
.where(
|
||||||
(x) => x.isVisibleInGui,
|
(x) =>
|
||||||
|
x.isVisibleInGui && !completedRoundEventIds.contains(x.eventId),
|
||||||
)
|
)
|
||||||
.toList() ??
|
.toList() ??
|
||||||
<Event>[];
|
<Event>[];
|
||||||
|
|
@ -561,7 +560,6 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
//#Pangea
|
//#Pangea
|
||||||
choreographer.stateListener.close();
|
choreographer.stateListener.close();
|
||||||
choreographer.dispose();
|
choreographer.dispose();
|
||||||
currentRound?.dispose();
|
|
||||||
//Pangea#
|
//Pangea#
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import 'package:fluffychat/pages/chat/typing_indicators.dart';
|
||||||
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
|
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
|
||||||
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
|
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
|
||||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
|
||||||
import 'package:fluffychat/pangea/widgets/chat/locked_chat_message.dart';
|
import 'package:fluffychat/pangea/widgets/chat/locked_chat_message.dart';
|
||||||
import 'package:fluffychat/utils/account_config.dart';
|
import 'package:fluffychat/utils/account_config.dart';
|
||||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
||||||
|
|
@ -33,13 +32,7 @@ class ChatEventList extends StatelessWidget {
|
||||||
event.isVisibleInGui
|
event.isVisibleInGui
|
||||||
// #Pangea
|
// #Pangea
|
||||||
&&
|
&&
|
||||||
// In story game, hide messages sent by non-bot users in previous round
|
!controller.completedRoundEventIds.contains(event.eventId)
|
||||||
(event.type != EventTypes.Message ||
|
|
||||||
event.senderId == BotName.byEnvironment ||
|
|
||||||
controller.currentRound?.previousRoundEnd == null ||
|
|
||||||
event.originServerTs.isAfter(
|
|
||||||
controller.currentRound!.previousRoundEnd!,
|
|
||||||
))
|
|
||||||
// Pangea#
|
// Pangea#
|
||||||
,
|
,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
|
||||||
import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
||||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||||
import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart';
|
import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart';
|
||||||
import 'package:fluffychat/pangea/widgets/chat/round_timer.dart';
|
|
||||||
import 'package:fluffychat/utils/account_config.dart';
|
import 'package:fluffychat/utils/account_config.dart';
|
||||||
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
||||||
import 'package:fluffychat/widgets/connection_status_header.dart';
|
import 'package:fluffychat/widgets/connection_status_header.dart';
|
||||||
|
|
@ -120,7 +119,7 @@ class ChatView extends StatelessWidget {
|
||||||
// #Pangea
|
// #Pangea
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
RoundTimer(controller: controller),
|
controller.timer ?? const SizedBox(),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 10,
|
width: 10,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
class GameConstants {
|
|
||||||
static const int timerMaxSeconds = 120;
|
|
||||||
}
|
|
||||||
|
|
@ -119,8 +119,4 @@ class ModelKey {
|
||||||
|
|
||||||
static const String prevEventId = "prev_event_id";
|
static const String prevEventId = "prev_event_id";
|
||||||
static const String prevLastUpdated = "prev_last_updated";
|
static const String prevLastUpdated = "prev_last_updated";
|
||||||
|
|
||||||
static const String gameState = "game_state";
|
|
||||||
static const String currentRoundStartTime = "start_time";
|
|
||||||
static const String previousRoundEndTime = "message_visible_from";
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,4 @@ class PangeaEventTypes {
|
||||||
/// A record of completion of an activity. There
|
/// A record of completion of an activity. There
|
||||||
/// can be one per user per activity.
|
/// can be one per user per activity.
|
||||||
static const activityRecord = "pangea.activity_completion";
|
static const activityRecord = "pangea.activity_completion";
|
||||||
|
|
||||||
static const storyGame = "p.game.story";
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
|
||||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
|
||||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:matrix/matrix_api_lite/generated/model.dart';
|
|
||||||
|
|
||||||
class GameModel {
|
|
||||||
DateTime? currentRoundStartTime;
|
|
||||||
DateTime? previousRoundEndTime;
|
|
||||||
|
|
||||||
GameModel({
|
|
||||||
this.currentRoundStartTime,
|
|
||||||
this.previousRoundEndTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory GameModel.fromJson(json) {
|
|
||||||
return GameModel(
|
|
||||||
currentRoundStartTime: json[ModelKey.currentRoundStartTime] != null
|
|
||||||
? DateTime.parse(json[ModelKey.currentRoundStartTime])
|
|
||||||
: null,
|
|
||||||
previousRoundEndTime: json[ModelKey.previousRoundEndTime] != null
|
|
||||||
? DateTime.parse(json[ModelKey.previousRoundEndTime])
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final data = <String, dynamic>{};
|
|
||||||
try {
|
|
||||||
data[ModelKey.currentRoundStartTime] =
|
|
||||||
currentRoundStartTime?.toIso8601String();
|
|
||||||
data[ModelKey.previousRoundEndTime] =
|
|
||||||
previousRoundEndTime?.toIso8601String();
|
|
||||||
return data;
|
|
||||||
} catch (e, s) {
|
|
||||||
debugger(when: kDebugMode);
|
|
||||||
ErrorHandler.logError(e: e, s: s);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StateEvent get toStateEvent => StateEvent(
|
|
||||||
content: toJson(),
|
|
||||||
type: PangeaEventTypes.storyGame,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +1,62 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fluffychat/pangea/constants/game_constants.dart';
|
import 'package:fluffychat/pages/chat/chat.dart';
|
||||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
|
||||||
import 'package:fluffychat/pangea/extensions/sync_update_extension.dart';
|
import 'package:fluffychat/pangea/extensions/sync_update_extension.dart';
|
||||||
import 'package:fluffychat/pangea/models/game_state_model.dart';
|
|
||||||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||||
|
import 'package:fluffychat/pangea/widgets/chat/round_timer.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
/// A model of a game round. Manages the round's state and duration.
|
enum RoundState { notStarted, inProgress, completed }
|
||||||
|
|
||||||
class GameRoundModel {
|
class GameRoundModel {
|
||||||
final Duration roundDuration = const Duration(
|
static const int timerMaxSeconds = 180;
|
||||||
seconds: GameConstants.timerMaxSeconds,
|
final String adminName = BotName.byEnvironment;
|
||||||
);
|
|
||||||
|
|
||||||
final Room room;
|
final ChatController controller;
|
||||||
|
final Completer<void> roundCompleter = Completer<void>();
|
||||||
// All the below state variables are used for sending and managing
|
|
||||||
// round start and end times. Once the bot starts doing that, they should be removed.
|
|
||||||
late DateTime createdAt;
|
late DateTime createdAt;
|
||||||
Timer? timer;
|
RoundTimer timer;
|
||||||
|
DateTime? startTime;
|
||||||
|
DateTime? endTime;
|
||||||
|
RoundState state = RoundState.notStarted;
|
||||||
StreamSubscription? syncSubscription;
|
StreamSubscription? syncSubscription;
|
||||||
final List<String> userMessageIDs = [];
|
final List<String> userMessageIDs = [];
|
||||||
final List<String> botMessageIDs = [];
|
final List<String> botMessageIDs = [];
|
||||||
|
|
||||||
GameRoundModel({
|
GameRoundModel({
|
||||||
required this.room,
|
required this.controller,
|
||||||
|
required this.timer,
|
||||||
}) {
|
}) {
|
||||||
createdAt = DateTime.now();
|
createdAt = DateTime.now();
|
||||||
|
syncSubscription ??= client.onSync.stream.listen(_handleSync);
|
||||||
// if, on creation, the current round is already ongoing,
|
|
||||||
// start the timer (or reset it if the round went over)
|
|
||||||
if (currentRoundStart != null) {
|
|
||||||
final currentRoundDuration = DateTime.now().difference(
|
|
||||||
currentRoundStart!,
|
|
||||||
);
|
|
||||||
final roundFinished = currentRoundDuration > roundDuration;
|
|
||||||
|
|
||||||
if (roundFinished) {
|
|
||||||
endRound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// listen to syncs for new bot messages to start and stop rounds
|
|
||||||
syncSubscription ??= room.client.onSync.stream.listen(_handleSync);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GameModel get gameState => GameModel.fromJson(
|
|
||||||
room.getState(PangeaEventTypes.storyGame)?.content ?? {},
|
|
||||||
);
|
|
||||||
|
|
||||||
DateTime? get currentRoundStart => gameState.currentRoundStartTime;
|
|
||||||
DateTime? get previousRoundEnd => gameState.previousRoundEndTime;
|
|
||||||
|
|
||||||
void _handleSync(SyncUpdate update) {
|
void _handleSync(SyncUpdate update) {
|
||||||
final newMessages = update
|
final newMessages = update
|
||||||
.messages(room)
|
.messages(controller.room)
|
||||||
.where((msg) => msg.originServerTs.isAfter(createdAt))
|
.where((msg) => msg.originServerTs.isAfter(createdAt))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final botMessages = newMessages
|
final botMessages =
|
||||||
.where((msg) => msg.senderId == BotName.byEnvironment)
|
newMessages.where((msg) => msg.senderId == adminName).toList();
|
||||||
.toList();
|
final userMessages =
|
||||||
final userMessages = newMessages
|
newMessages.where((msg) => msg.senderId != adminName).toList();
|
||||||
.where((msg) => msg.senderId != BotName.byEnvironment)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final hasNewBotMessage = botMessages.any(
|
final hasNewBotMessage = botMessages.any(
|
||||||
(msg) => !botMessageIDs.contains(msg.eventId),
|
(msg) => !botMessageIDs.contains(msg.eventId),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasNewBotMessage) {
|
if (hasNewBotMessage) {
|
||||||
if (currentRoundStart == null) {
|
if (state == RoundState.notStarted) {
|
||||||
startRound();
|
startRound();
|
||||||
} else {
|
} else if (state == RoundState.inProgress) {
|
||||||
endRound();
|
endRound();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentRoundStart != null) {
|
if (state == RoundState.inProgress) {
|
||||||
for (final message in botMessages) {
|
for (final message in botMessages) {
|
||||||
if (!botMessageIDs.contains(message.eventId)) {
|
if (!botMessageIDs.contains(message.eventId)) {
|
||||||
botMessageIDs.add(message.eventId);
|
botMessageIDs.add(message.eventId);
|
||||||
|
|
@ -93,53 +71,32 @@ class GameRoundModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the start and end times of the current and previous rounds.
|
Client get client => controller.pangeaController.matrixState.client;
|
||||||
Future<void> setRoundTimes({
|
|
||||||
DateTime? currentRoundStart,
|
|
||||||
DateTime? previousRoundEnd,
|
|
||||||
}) async {
|
|
||||||
final game = GameModel.fromJson(
|
|
||||||
room.getState(PangeaEventTypes.storyGame)?.content ?? {},
|
|
||||||
);
|
|
||||||
|
|
||||||
game.currentRoundStartTime = currentRoundStart;
|
bool get isCompleted => roundCompleter.isCompleted;
|
||||||
game.previousRoundEndTime = previousRoundEnd;
|
|
||||||
|
|
||||||
await room.client.setRoomStateWithKey(
|
|
||||||
room.id,
|
|
||||||
PangeaEventTypes.storyGame,
|
|
||||||
'',
|
|
||||||
game.toJson(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a new round.
|
|
||||||
void startRound() {
|
void startRound() {
|
||||||
setRoundTimes(
|
debugPrint("starting round");
|
||||||
currentRoundStart: DateTime.now(),
|
state = RoundState.inProgress;
|
||||||
previousRoundEnd: null,
|
startTime = DateTime.now();
|
||||||
).then((_) => timer = Timer(roundDuration, endRound));
|
controller.roundTimerStateKey.currentState?.resetTimer(
|
||||||
|
roundLength: timerMaxSeconds,
|
||||||
|
);
|
||||||
|
controller.roundTimerStateKey.currentState?.startTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// End and cleanup after the current round.
|
|
||||||
void endRound() {
|
void endRound() {
|
||||||
syncSubscription?.cancel();
|
debugPrint(
|
||||||
syncSubscription = null;
|
"ending round, user message IDs: $userMessageIDs, bot message IDs: $botMessageIDs",
|
||||||
|
|
||||||
timer?.cancel();
|
|
||||||
timer = null;
|
|
||||||
|
|
||||||
setRoundTimes(
|
|
||||||
currentRoundStart: null,
|
|
||||||
previousRoundEnd: DateTime.now(),
|
|
||||||
);
|
);
|
||||||
|
endTime = DateTime.now();
|
||||||
|
state = RoundState.completed;
|
||||||
|
controller.roundTimerStateKey.currentState?.resetTimer();
|
||||||
|
syncSubscription?.cancel();
|
||||||
|
roundCompleter.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
syncSubscription?.cancel();
|
syncSubscription?.cancel();
|
||||||
syncSubscription = null;
|
|
||||||
|
|
||||||
timer?.cancel();
|
|
||||||
timer = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,17 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fluffychat/pages/chat/chat.dart';
|
|
||||||
import 'package:fluffychat/pangea/constants/game_constants.dart';
|
|
||||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
|
||||||
import 'package:fluffychat/pangea/models/game_state_model.dart';
|
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
|
||||||
|
|
||||||
/// Create a timer that counts down to the given time
|
/// Create a timer that counts down to the given time
|
||||||
/// Default duration is 180 seconds
|
/// Default duration is 180 seconds
|
||||||
class RoundTimer extends StatefulWidget {
|
class RoundTimer extends StatefulWidget {
|
||||||
final ChatController controller;
|
final int timerMaxSeconds;
|
||||||
|
final Duration roundDuration;
|
||||||
|
|
||||||
const RoundTimer({
|
const RoundTimer({
|
||||||
super.key,
|
super.key,
|
||||||
required this.controller,
|
this.timerMaxSeconds = 180,
|
||||||
|
this.roundDuration = const Duration(seconds: 1),
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -23,112 +20,90 @@ class RoundTimer extends StatefulWidget {
|
||||||
|
|
||||||
class RoundTimerState extends State<RoundTimer> {
|
class RoundTimerState extends State<RoundTimer> {
|
||||||
int currentSeconds = 0;
|
int currentSeconds = 0;
|
||||||
Timer? timer;
|
Timer? _timer;
|
||||||
StreamSubscription? stateSubscription;
|
bool isTiming = false;
|
||||||
|
Duration? duration;
|
||||||
|
int timerMaxSeconds = 180;
|
||||||
|
|
||||||
|
void resetTimer({Duration? roundDuration, int? roundLength}) {
|
||||||
|
if (_timer != null) {
|
||||||
|
_timer!.cancel();
|
||||||
|
isTiming = false;
|
||||||
|
}
|
||||||
|
if (roundDuration != null) {
|
||||||
|
duration = roundDuration;
|
||||||
|
}
|
||||||
|
if (roundLength != null) {
|
||||||
|
timerMaxSeconds = roundLength;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
currentSeconds = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int get remainingTime => timerMaxSeconds - currentSeconds;
|
||||||
|
|
||||||
|
String get timerText =>
|
||||||
|
'${(remainingTime ~/ 60).toString().padLeft(2, '0')}: ${(remainingTime % 60).toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
startTimer() {
|
||||||
|
_timer = Timer.periodic(duration ?? widget.roundDuration, (timer) {
|
||||||
|
setState(() {
|
||||||
|
currentSeconds++;
|
||||||
|
if (currentSeconds >= timerMaxSeconds) timer.cancel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setState(() {
|
||||||
|
isTiming = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTimer() {
|
||||||
|
if (_timer != null) {
|
||||||
|
_timer!.cancel();
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isTiming = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
duration = widget.roundDuration;
|
||||||
|
timerMaxSeconds = widget.timerMaxSeconds;
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
final roundStartTime = widget.controller.currentRound?.currentRoundStart;
|
|
||||||
if (roundStartTime != null) {
|
|
||||||
final roundDuration = DateTime.now().difference(roundStartTime).inSeconds;
|
|
||||||
if (roundDuration > GameConstants.timerMaxSeconds) return;
|
|
||||||
|
|
||||||
currentSeconds = roundDuration;
|
|
||||||
timer = Timer.periodic(const Duration(seconds: 1), (Timer t) {
|
|
||||||
currentSeconds++;
|
|
||||||
if (currentSeconds >= GameConstants.timerMaxSeconds) {
|
|
||||||
t.cancel();
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stateSubscription = Matrix.of(context)
|
|
||||||
.client
|
|
||||||
.onRoomState
|
|
||||||
.stream
|
|
||||||
.where(isRoundUpdate)
|
|
||||||
.listen(onRoundUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isRoundUpdate(update) {
|
|
||||||
return update.roomId == widget.controller.room.id &&
|
|
||||||
update.state is Event &&
|
|
||||||
(update.state as Event).type == PangeaEventTypes.storyGame;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onRoundUpdate(update) {
|
|
||||||
final GameModel gameState = GameModel.fromJson(
|
|
||||||
(update.state as Event).content,
|
|
||||||
);
|
|
||||||
final startTime = gameState.currentRoundStartTime;
|
|
||||||
final endTime = gameState.previousRoundEndTime;
|
|
||||||
|
|
||||||
if (startTime == null && endTime == null) return;
|
|
||||||
timer?.cancel();
|
|
||||||
timer = null;
|
|
||||||
|
|
||||||
// if this update is the start of a round
|
|
||||||
if (startTime != null) {
|
|
||||||
timer = Timer.periodic(const Duration(seconds: 1), (Timer t) {
|
|
||||||
currentSeconds++;
|
|
||||||
if (currentSeconds >= GameConstants.timerMaxSeconds) {
|
|
||||||
t.cancel();
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this update is the end of a round
|
|
||||||
currentSeconds = 0;
|
|
||||||
setState(() {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
if (_timer != null) {
|
||||||
|
_timer!.cancel();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
||||||
stateSubscription?.cancel();
|
|
||||||
stateSubscription = null;
|
|
||||||
|
|
||||||
timer?.cancel();
|
|
||||||
timer = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int get remainingTime => GameConstants.timerMaxSeconds - currentSeconds;
|
|
||||||
|
|
||||||
String get timerText =>
|
|
||||||
'${(remainingTime ~/ 60).toString().padLeft(2, '0')}: ${(remainingTime % 60).toString().padLeft(2, '0')}';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
color: const Color.fromARGB(255, 126, 22, 14),
|
color: const Color.fromARGB(255, 126, 22, 14),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(5),
|
padding: const EdgeInsets.all(
|
||||||
|
5,
|
||||||
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(timerText),
|
Text(timerText),
|
||||||
const Row(
|
// Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
// children: [
|
||||||
// IconButton(
|
// IconButton(
|
||||||
// onPressed: widget.currentRound.timer == null
|
// onPressed: isTiming ? stopTimeout : startTimeout,
|
||||||
// ? widget.currentRound.startRound
|
// icon: Icon(isTiming ? Icons.pause_circle : Icons.play_circle),
|
||||||
// : null,
|
// ),
|
||||||
// icon: Icon(
|
// ],
|
||||||
// widget.currentRound.timer != null
|
// ),
|
||||||
// ? Icons.pause_circle
|
|
||||||
// : Icons.play_circle,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue