resolve merge conflicts
This commit is contained in:
commit
80695728d0
15 changed files with 209 additions and 99 deletions
|
|
@ -29,8 +29,10 @@ class StateMessage extends StatelessWidget {
|
|||
// event.calcLocalizedBodyFallback(
|
||||
// MatrixLocals(L10n.of(context)),
|
||||
// ),
|
||||
event.type == EventTypes.RoomMember &&
|
||||
event.roomMemberChangeType == RoomMemberChangeType.leave
|
||||
(event.type == EventTypes.RoomMember) &&
|
||||
(event.roomMemberChangeType ==
|
||||
RoomMemberChangeType.leave) &&
|
||||
(event.stateKey == event.room.client.userID)
|
||||
? L10n.of(context).youLeftTheChat
|
||||
: event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
|
|
|
|||
|
|
@ -50,7 +50,13 @@ class AnalyticsDownloadDialogState extends State<AnalyticsDownloadDialog> {
|
|||
}
|
||||
|
||||
void _setDownloadType(DownloadType type) {
|
||||
if (mounted) setState(() => _downloadType = type);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_downloadType = type;
|
||||
_downloaded = false;
|
||||
_error = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _downloadAnalytics() async {
|
||||
|
|
@ -427,7 +433,8 @@ class AnalyticsDownloadDialogState extends State<AnalyticsDownloadDialog> {
|
|||
padding: const EdgeInsets.all(8.0),
|
||||
child: SegmentedButton<DownloadType>(
|
||||
selected: {_downloadType},
|
||||
onSelectionChanged: (c) => _setDownloadType(c.first),
|
||||
onSelectionChanged:
|
||||
_downloading ? null : (c) => _setDownloadType(c.first),
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: DownloadType.csv,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import 'package:fluffychat/pangea/practice_activities/message_activity_request.d
|
|||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_generation_repo.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
|
||||
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/message_practice/practice_record_controller.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -200,6 +201,15 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
}
|
||||
}
|
||||
|
||||
void _playAudio() {
|
||||
if (activityTarget.value == null) return;
|
||||
if (widget.type != ConstructTypeEnum.vocab) return;
|
||||
TtsController.tryToSpeak(
|
||||
activityTarget.value!.tokens.first.vocabConstructID.lemma,
|
||||
langCode: MatrixState.pangeaController.userController.userL2!.langCode,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveSession() async {
|
||||
if (_sessionLoader.isLoaded) {
|
||||
await AnalyticsPracticeSessionRepo.update(
|
||||
|
|
@ -276,7 +286,10 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
} else {
|
||||
activityState.value = const AsyncState.loading();
|
||||
final nextActivityCompleter = _queue.removeFirst();
|
||||
|
||||
activityTarget.value = nextActivityCompleter.key;
|
||||
_playAudio();
|
||||
|
||||
final activity = await nextActivityCompleter.value.future;
|
||||
activityState.value = AsyncState.loaded(activity);
|
||||
}
|
||||
|
|
@ -295,12 +308,14 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
|
||||
try {
|
||||
activityState.value = const AsyncState.loading();
|
||||
|
||||
final req = requests.first;
|
||||
|
||||
activityTarget.value = req.target;
|
||||
_playAudio();
|
||||
|
||||
final res = await _fetchActivity(req);
|
||||
if (!mounted) return;
|
||||
|
||||
activityTarget.value = req.target;
|
||||
activityState.value = AsyncState.loaded(res);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
|
|
@ -401,6 +416,8 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
await _saveSession();
|
||||
if (!activity.multipleChoiceContent.isCorrect(choiceContent)) return;
|
||||
|
||||
_playAudio();
|
||||
|
||||
// Display the fact that the choice was correct before loading the next activity
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_page.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_session_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/choice_cards/audio_choice_card.dart';
|
||||
|
|
@ -15,9 +16,11 @@ import 'package:fluffychat/pangea/common/utils/async_state.dart';
|
|||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
|
||||
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class AnalyticsPracticeView extends StatelessWidget {
|
||||
final AnalyticsPracticeState controller;
|
||||
|
|
@ -68,26 +71,28 @@ class AnalyticsPracticeView extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
body: MaxWidthBody(
|
||||
withScrolling: false,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 24.0,
|
||||
),
|
||||
showBorder: false,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.sessionState,
|
||||
builder: (context, state, __) {
|
||||
return switch (state) {
|
||||
AsyncError<AnalyticsPracticeSessionModel>(:final error) =>
|
||||
ErrorIndicator(message: error.toString()),
|
||||
AsyncLoaded<AnalyticsPracticeSessionModel>(:final value) =>
|
||||
value.isComplete
|
||||
? CompletedActivitySessionView(state.value, controller)
|
||||
: _AnalyticsActivityView(controller),
|
||||
_ => loading,
|
||||
};
|
||||
},
|
||||
child: MaxWidthBody(
|
||||
withScrolling: false,
|
||||
showBorder: false,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.sessionState,
|
||||
builder: (context, state, __) {
|
||||
return switch (state) {
|
||||
AsyncError<AnalyticsPracticeSessionModel>(:final error) =>
|
||||
ErrorIndicator(message: error.toString()),
|
||||
AsyncLoaded<AnalyticsPracticeSessionModel>(:final value) =>
|
||||
value.isComplete
|
||||
? CompletedActivitySessionView(state.value, controller)
|
||||
: _AnalyticsActivityView(controller),
|
||||
_ => loading,
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -122,13 +127,29 @@ class _AnalyticsActivityView extends StatelessWidget {
|
|||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.activityTarget,
|
||||
builder: (context, target, __) => target != null
|
||||
? Text(
|
||||
target.promptText(context),
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
? Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
Text(
|
||||
target.promptText(context),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (controller.widget.type ==
|
||||
ConstructTypeEnum.vocab)
|
||||
PhoneticTranscriptionWidget(
|
||||
text:
|
||||
target.tokens.first.vocabConstructID.lemma,
|
||||
textLanguage: MatrixState
|
||||
.pangeaController.userController.userL2!,
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ class _GameChoiceCardState extends State<GameChoiceCard>
|
|||
animation: _scaleAnim,
|
||||
builder: (context, _) {
|
||||
final scale = _scaleAnim.value;
|
||||
final showAlt = scale < 0.1 && widget.altChild != null;
|
||||
final showContent = scale > 0.05;
|
||||
|
||||
return Transform.scale(
|
||||
|
|
@ -113,7 +112,7 @@ class _GameChoiceCardState extends State<GameChoiceCard>
|
|||
: (hovered ? hoverColor : Colors.transparent),
|
||||
child: Opacity(
|
||||
opacity: showContent ? 1 : 0,
|
||||
child: showAlt ? widget.altChild! : widget.child,
|
||||
child: _revealed ? widget.altChild! : widget.child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ class BotChatSettingsDialogState extends State<BotChatSettingsDialog> {
|
|||
initialLevel: _selectedLevel,
|
||||
onChanged: _setLevel,
|
||||
enabled: !widget.room.isActivitySession,
|
||||
width: 300,
|
||||
),
|
||||
DropdownButtonFormField2<String>(
|
||||
customButton: _selectedVoice != null
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ class SpaceDetailsContent extends StatelessWidget {
|
|||
],
|
||||
Flexible(
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
spacing: isColumnMode ? 12.0 : 6.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
|
@ -311,7 +311,7 @@ class SpaceDetailsContent extends StatelessWidget {
|
|||
: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (isColumnMode && room.coursePlan != null)
|
||||
if (room.coursePlan != null)
|
||||
CourseInfoChips(
|
||||
room.coursePlan!.uuid,
|
||||
fontSize: 12.0,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/dropdown_text_button.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_level_type_enum.dart';
|
||||
|
|
@ -14,6 +13,7 @@ class LanguageLevelDropdown extends StatelessWidget {
|
|||
final FormFieldValidator<Object>? validator;
|
||||
final bool enabled;
|
||||
final Color? backgroundColor;
|
||||
final double? width;
|
||||
|
||||
const LanguageLevelDropdown({
|
||||
super.key,
|
||||
|
|
@ -22,6 +22,7 @@ class LanguageLevelDropdown extends StatelessWidget {
|
|||
this.validator,
|
||||
this.enabled = true,
|
||||
this.backgroundColor,
|
||||
this.width,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -33,12 +34,12 @@ class LanguageLevelDropdown extends StatelessWidget {
|
|||
LanguageLevelTypeEnum.values.contains(initialLevel)
|
||||
? CustomDropdownTextButton(text: initialLevel!.title(context))
|
||||
: null,
|
||||
menuItemStyleData: MenuItemStyleData(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
height: FluffyThemes.isColumnMode(context) ? 100.0 : 150.0,
|
||||
height: 100.0,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.cefrLevelLabel,
|
||||
|
|
@ -51,6 +52,7 @@ class LanguageLevelDropdown extends StatelessWidget {
|
|||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
width: width,
|
||||
),
|
||||
items:
|
||||
LanguageLevelTypeEnum.values.map((LanguageLevelTypeEnum levelOption) {
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ class ChoreoConstants {
|
|||
static const int msBeforeIGCStart = 10000;
|
||||
static const int maxLength = 1000;
|
||||
static const String inputTransformTargetKey = 'input_text_field';
|
||||
static const int defaultErrorBackoffSeconds = 5;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_mode_enum.dart';
|
||||
|
|
@ -45,6 +47,11 @@ class Choreographer extends ChangeNotifier {
|
|||
String? _lastChecked;
|
||||
ChoreoModeEnum _choreoMode = ChoreoModeEnum.igc;
|
||||
|
||||
DateTime? _lastIgcError;
|
||||
DateTime? _lastTokensError;
|
||||
int _igcErrorBackoff = ChoreoConstants.defaultErrorBackoffSeconds;
|
||||
int _tokenErrorBackoff = ChoreoConstants.defaultErrorBackoffSeconds;
|
||||
|
||||
StreamSubscription? _languageSub;
|
||||
StreamSubscription? _settingsUpdateSub;
|
||||
StreamSubscription? _acceptedContinuanceSub;
|
||||
|
|
@ -68,6 +75,12 @@ class Choreographer extends ChangeNotifier {
|
|||
openMatches: [],
|
||||
);
|
||||
|
||||
bool _backoffRequest(DateTime? error, int backoffSeconds) {
|
||||
if (error == null) return false;
|
||||
final secondsSinceError = DateTime.now().difference(error).inSeconds;
|
||||
return secondsSinceError <= backoffSeconds;
|
||||
}
|
||||
|
||||
void _initialize() {
|
||||
textController = PangeaTextController(choreographer: this);
|
||||
textController.addListener(_onChange);
|
||||
|
|
@ -82,7 +95,14 @@ class Choreographer extends ChangeNotifier {
|
|||
itController.editing.addListener(_onSubmitSourceTextEdits);
|
||||
|
||||
igcController = IgcController(
|
||||
(e) => errorService.setErrorAndLock(ChoreoError(raw: e)),
|
||||
(e) {
|
||||
errorService.setErrorAndLock(ChoreoError(raw: e));
|
||||
_lastIgcError = DateTime.now();
|
||||
_igcErrorBackoff *= 2;
|
||||
},
|
||||
() {
|
||||
_igcErrorBackoff = ChoreoConstants.defaultErrorBackoffSeconds;
|
||||
},
|
||||
);
|
||||
|
||||
_languageSub ??= MatrixState
|
||||
|
|
@ -233,7 +253,8 @@ class Choreographer extends ChangeNotifier {
|
|||
!ToolSetting.interactiveTranslator.enabled) ||
|
||||
(!ToolSetting.autoIGC.enabled &&
|
||||
!manual &&
|
||||
_choreoMode != ChoreoModeEnum.it)) {
|
||||
_choreoMode != ChoreoModeEnum.it) ||
|
||||
_backoffRequest(_lastIgcError, _igcErrorBackoff)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -275,7 +296,9 @@ class Choreographer extends ChangeNotifier {
|
|||
MatrixState.pangeaController.userController.userL2?.langCode;
|
||||
final l1LangCode =
|
||||
MatrixState.pangeaController.userController.userL1?.langCode;
|
||||
if (l1LangCode != null && l2LangCode != null) {
|
||||
if (l1LangCode != null &&
|
||||
l2LangCode != null &&
|
||||
!_backoffRequest(_lastTokensError, _tokenErrorBackoff)) {
|
||||
final res = await TokensRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
TokensRequestModel(
|
||||
|
|
@ -283,7 +306,21 @@ class Choreographer extends ChangeNotifier {
|
|||
senderL1: l1LangCode,
|
||||
senderL2: l2LangCode,
|
||||
),
|
||||
).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
return Result.error("Token request timed out");
|
||||
},
|
||||
);
|
||||
|
||||
if (res.isError) {
|
||||
_lastTokensError = DateTime.now();
|
||||
_tokenErrorBackoff *= 2;
|
||||
} else {
|
||||
// reset backoff on success
|
||||
_tokenErrorBackoff = ChoreoConstants.defaultErrorBackoffSeconds;
|
||||
}
|
||||
|
||||
tokensResp = res.isValue ? res.result : null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
|
||||
class IgcController {
|
||||
final Function(Object) onError;
|
||||
IgcController(this.onError);
|
||||
final VoidCallback onFetch;
|
||||
IgcController(this.onError, this.onFetch);
|
||||
|
||||
bool _isFetching = false;
|
||||
String? _currentText;
|
||||
|
|
@ -321,6 +322,8 @@ class IgcController {
|
|||
onError(res.asError!);
|
||||
clear();
|
||||
return;
|
||||
} else {
|
||||
onFetch();
|
||||
}
|
||||
|
||||
if (!_isFetching) return;
|
||||
|
|
|
|||
|
|
@ -122,7 +122,9 @@ class PangeaController {
|
|||
}
|
||||
|
||||
Future<void> _clearCache({List<String> exclude = const []}) async {
|
||||
final List<Future<void>> futures = [];
|
||||
final List<Future<void>> futures = [
|
||||
matrixState.store.setString(SettingKeys.fontSizeFactor, ''),
|
||||
];
|
||||
for (final key in _storageKeys) {
|
||||
if (exclude.contains(key)) continue;
|
||||
futures.add(GetStorage(key).erase());
|
||||
|
|
@ -140,6 +142,7 @@ class PangeaController {
|
|||
);
|
||||
}
|
||||
|
||||
AppConfig.fontSizeFactor = 1.0;
|
||||
await Future.wait(futures);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,12 +169,12 @@ class SelectedCourseController extends State<SelectedCourse>
|
|||
: await client.joinRoom(widget.roomChunk!.roomId);
|
||||
|
||||
Room? room = client.getRoomById(roomId);
|
||||
if (!knock && room == null) {
|
||||
await client.waitForRoomInSync(roomId);
|
||||
if (!knock && room?.membership != Membership.join) {
|
||||
await client.waitForRoomInSync(roomId, join: true);
|
||||
room = client.getRoomById(roomId);
|
||||
}
|
||||
|
||||
if (knock && room == null) {
|
||||
if (knock) {
|
||||
Navigator.of(context).pop();
|
||||
await showOkAlertDialog(
|
||||
context: context,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/space_analytics/space_analytics_requested_dialog.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
class AnalyticsRequestIndicator extends StatefulWidget {
|
||||
|
|
@ -26,51 +27,68 @@ class AnalyticsRequestIndicator extends StatefulWidget {
|
|||
|
||||
class AnalyticsRequestIndicatorState extends State<AnalyticsRequestIndicator> {
|
||||
AnalyticsRequestIndicatorState();
|
||||
|
||||
final Map<User, List<Room>> _knockingAdmins = {};
|
||||
StreamSubscription? _analyticsRoomSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchKnockingAdmins();
|
||||
_init();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant AnalyticsRequestIndicator oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.room.id != widget.room.id) {
|
||||
_fetchKnockingAdmins();
|
||||
_init();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchKnockingAdmins() async {
|
||||
setState(() => _knockingAdmins.clear());
|
||||
@override
|
||||
void dispose() {
|
||||
_analyticsRoomSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
final admins = (await widget.room.requestParticipants(
|
||||
[Membership.join, Membership.invite, Membership.knock],
|
||||
false,
|
||||
true,
|
||||
))
|
||||
.where((u) => u.powerLevel >= 100);
|
||||
Future<void> _init() async {
|
||||
final analyticsRooms = widget.room.client.allMyAnalyticsRooms;
|
||||
final futures = analyticsRooms.map(
|
||||
(r) => r.requestParticipants(
|
||||
[Membership.join, Membership.invite, Membership.knock],
|
||||
false,
|
||||
true,
|
||||
),
|
||||
);
|
||||
await Future.wait(futures);
|
||||
|
||||
final analyicsRoomIds = analyticsRooms.map((r) => r.id).toSet();
|
||||
_analyticsRoomSub?.cancel();
|
||||
_analyticsRoomSub = widget.room.client.onRoomState.stream
|
||||
.where(
|
||||
(event) =>
|
||||
analyicsRoomIds.contains(event.roomId) &&
|
||||
event.state.type == EventTypes.RoomMember,
|
||||
)
|
||||
.rateLimit(const Duration(seconds: 1))
|
||||
.listen((_) => setState(() {}));
|
||||
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Map<User, List<Room>> get _knockingAdmins {
|
||||
final Map<User, List<Room>> knockingAdmins = {};
|
||||
for (final analyticsRoom in widget.room.client.allMyAnalyticsRooms) {
|
||||
final knocking = await analyticsRoom.requestParticipants(
|
||||
[Membership.knock],
|
||||
);
|
||||
final knockingSpace =
|
||||
knocking.where((u) => u.content['reason'] == widget.room.id).toList();
|
||||
if (knockingSpace.isEmpty) continue;
|
||||
final knocking = analyticsRoom
|
||||
.getParticipants([Membership.knock])
|
||||
.where((u) => u.content['reason'] == widget.room.id)
|
||||
.toList();
|
||||
|
||||
for (final admin in admins) {
|
||||
if (knockingSpace.any((u) => u.id == admin.id)) {
|
||||
_knockingAdmins.putIfAbsent(admin, () => []).add(analyticsRoom);
|
||||
}
|
||||
if (knocking.isEmpty) continue;
|
||||
for (final admin in knocking) {
|
||||
knockingAdmins.putIfAbsent(admin, () => []).add(analyticsRoom);
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
return knockingAdmins;
|
||||
}
|
||||
|
||||
Future<void> _onTap(BuildContext context) async {
|
||||
|
|
@ -109,8 +127,6 @@ class AnalyticsRequestIndicatorState extends State<AnalyticsRequestIndicator> {
|
|||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (mounted) _fetchKnockingAdmins();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -31,40 +31,38 @@ class SpaceNavigationColumn extends StatefulWidget {
|
|||
}
|
||||
|
||||
class SpaceNavigationColumnState extends State<SpaceNavigationColumn> {
|
||||
bool _hovered = false;
|
||||
bool _expanded = false;
|
||||
Timer? _debounceTimer;
|
||||
Timer? _timer;
|
||||
|
||||
void _onHoverUpdate(bool hovered) {
|
||||
if (hovered == _hovered) return;
|
||||
_hovered = hovered;
|
||||
_cancelTimer();
|
||||
|
||||
if (hovered) {
|
||||
_timer = Timer(const Duration(milliseconds: 200), () {
|
||||
if (_hovered && mounted) {
|
||||
setState(() => _expanded = true);
|
||||
}
|
||||
_cancelTimer();
|
||||
});
|
||||
} else {
|
||||
setState(() => _expanded = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _cancelTimer() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = null;
|
||||
_cancelTimer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _expand() {
|
||||
if (_debounceTimer?.isActive == true) return;
|
||||
if (!_expanded) {
|
||||
setState(() => _expanded = true);
|
||||
}
|
||||
}
|
||||
|
||||
void _collapse() {
|
||||
if (_expanded) {
|
||||
setState(() {
|
||||
_expanded = false;
|
||||
_debounce();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _debounce() {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
|
@ -115,7 +113,7 @@ class SpaceNavigationColumnState extends State<SpaceNavigationColumn> {
|
|||
HoverBuilder(
|
||||
builder: (context, hovered) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
hovered ? _expand() : _collapse();
|
||||
_onHoverUpdate(hovered);
|
||||
});
|
||||
|
||||
return Row(
|
||||
|
|
@ -128,7 +126,10 @@ class SpaceNavigationColumnState extends State<SpaceNavigationColumn> {
|
|||
? navRailWidth + navRailExtraWidth
|
||||
: navRailWidth,
|
||||
expanded: _expanded,
|
||||
collapse: _collapse,
|
||||
collapse: () {
|
||||
_cancelTimer();
|
||||
setState(() => _expanded = false);
|
||||
},
|
||||
),
|
||||
Container(
|
||||
width: 1,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue