resolve merge conflicts
This commit is contained in:
commit
2b21329266
10 changed files with 145 additions and 32 deletions
|
|
@ -85,6 +85,7 @@ class AnalyticsDataService {
|
|||
void dispose() {
|
||||
_syncController?.dispose();
|
||||
updateDispatcher.dispose();
|
||||
updateService.dispose();
|
||||
_closeDatabase();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart';
|
||||
import 'package:fluffychat/pangea/analytics_data/analytics_update_dispatcher.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
|
|
@ -52,6 +53,13 @@ class AnalyticsSyncController {
|
|||
|
||||
if (constructEvents.isEmpty) return;
|
||||
await dataService.updateServerAnalytics(constructEvents);
|
||||
|
||||
// Server updates do not usually need to update the UI, since usually they are only
|
||||
// transfering local data to the server. However, if a user if using multiple devices,
|
||||
// we do need to update the UI when new data comes from the server.
|
||||
dataService.updateDispatcher.sendConstructAnalyticsUpdate(
|
||||
AnalyticsUpdate([]),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> waitForSync(String analyticsRoomId) async {
|
||||
|
|
|
|||
|
|
@ -23,9 +23,19 @@ class AnalyticsUpdateService {
|
|||
|
||||
final AnalyticsDataService dataService;
|
||||
|
||||
AnalyticsUpdateService(this.dataService);
|
||||
AnalyticsUpdateService(this.dataService) {
|
||||
_periodicTimer = Timer.periodic(
|
||||
const Duration(minutes: 5),
|
||||
(_) => sendLocalAnalyticsToAnalyticsRoom(),
|
||||
);
|
||||
}
|
||||
|
||||
Completer<void>? _updateCompleter;
|
||||
Timer? _periodicTimer;
|
||||
|
||||
void dispose() {
|
||||
_periodicTimer?.cancel();
|
||||
}
|
||||
|
||||
LanguageModel? get _l2 => MatrixState.pangeaController.userController.userL2;
|
||||
|
||||
|
|
@ -50,8 +60,9 @@ class AnalyticsUpdateService {
|
|||
|
||||
Future<void> addAnalytics(
|
||||
String? targetID,
|
||||
List<OneConstructUse> newConstructs,
|
||||
) async {
|
||||
List<OneConstructUse> newConstructs, {
|
||||
bool forceUpdate = false,
|
||||
}) async {
|
||||
await dataService.updateDispatcher.sendConstructAnalyticsUpdate(
|
||||
AnalyticsUpdate(
|
||||
newConstructs,
|
||||
|
|
@ -63,7 +74,9 @@ class AnalyticsUpdateService {
|
|||
final lastUpdated = await dataService.getLastUpdatedAnalytics();
|
||||
final difference = DateTime.now().difference(lastUpdated ?? DateTime.now());
|
||||
|
||||
if (localConstructCount > _maxMessagesCached || difference.inMinutes > 10) {
|
||||
if (forceUpdate ||
|
||||
localConstructCount > _maxMessagesCached ||
|
||||
difference.inMinutes > 10) {
|
||||
sendLocalAnalyticsToAnalyticsRoom();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:diacritic/diacritic.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
|
|
@ -106,7 +107,11 @@ class ConstructAnalyticsViewState extends State<ConstructAnalyticsView> {
|
|||
|
||||
vocab = data.values.toList();
|
||||
vocab!.sort(
|
||||
(a, b) => a.lemma.toLowerCase().compareTo(b.lemma.toLowerCase()),
|
||||
(a, b) {
|
||||
final normalizedA = removeDiacritics(a.lemma).toLowerCase();
|
||||
final normalizedB = removeDiacritics(b.lemma).toLowerCase();
|
||||
return normalizedA.compareTo(normalizedB);
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
if (mounted) setState(() {});
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class VocabAnalyticsListTile extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.topCenter,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
height: (maxWidth - padding * 2) * 0.4,
|
||||
child: ShrinkableText(
|
||||
|
|
|
|||
|
|
@ -253,7 +253,11 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
setState(() {});
|
||||
|
||||
final bonus = _sessionLoader.value!.state.allBonusUses;
|
||||
await _analyticsService.updateService.addAnalytics(null, bonus);
|
||||
await _analyticsService.updateService.addAnalytics(
|
||||
null,
|
||||
bonus,
|
||||
forceUpdate: true,
|
||||
);
|
||||
await _saveSession();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,11 +91,17 @@ class AnalyticsPracticeSessionRepo {
|
|||
return dateA.compareTo(dateB);
|
||||
});
|
||||
|
||||
return constructs
|
||||
.where((construct) => construct.lemma.isNotEmpty)
|
||||
.take(AnalyticsPracticeConstants.practiceGroupSize)
|
||||
.map((construct) => construct.id)
|
||||
.toList();
|
||||
final Set<String> seemLemmas = {};
|
||||
final targets = <ConstructIdentifier>[];
|
||||
for (final construct in constructs) {
|
||||
if (seemLemmas.contains(construct.lemma)) continue;
|
||||
seemLemmas.add(construct.lemma);
|
||||
targets.add(construct.id);
|
||||
if (targets.length >= AnalyticsPracticeConstants.practiceGroupSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
static Future<Map<PangeaToken, MorphFeaturesEnum>> _fetchMorphs() async {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:fluffychat/pangea/course_plans/courses/course_plan_model.dart';
|
|||
import 'package:fluffychat/pangea/course_plans/courses/course_plan_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/spaces/client_spaces_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
enum SelectedCourseMode { launch, addToSpace, join }
|
||||
|
|
@ -74,7 +75,9 @@ class SelectedCourseController extends State<SelectedCourse>
|
|||
case SelectedCourseMode.addToSpace:
|
||||
return L10n.of(context).addCoursePlan;
|
||||
case SelectedCourseMode.join:
|
||||
return L10n.of(context).joinWithClassCode;
|
||||
return widget.roomChunk?.joinRule == JoinRules.knock.name
|
||||
? L10n.of(context).knock
|
||||
: L10n.of(context).join;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,16 +155,36 @@ class SelectedCourseController extends State<SelectedCourse>
|
|||
}
|
||||
|
||||
final client = Matrix.of(context).client;
|
||||
final roomId = await client.joinRoom(
|
||||
widget.roomChunk!.roomId,
|
||||
);
|
||||
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room == null || room.membership != Membership.join) {
|
||||
await client.waitForRoomInSync(roomId, join: true);
|
||||
final r = client.getRoomById(widget.roomChunk!.roomId);
|
||||
if (r != null && r.membership == Membership.join) {
|
||||
if (mounted) {
|
||||
context.go("/rooms/spaces/${r.id}/details");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.getRoomById(roomId) == null) {
|
||||
final knock = widget.roomChunk!.joinRule == JoinRules.knock.name;
|
||||
final roomId = widget.roomChunk != null && knock
|
||||
? await client.knockRoom(widget.roomChunk!.roomId)
|
||||
: await client.joinRoom(widget.roomChunk!.roomId);
|
||||
|
||||
Room? room = client.getRoomById(roomId);
|
||||
if (!knock && room == null) {
|
||||
await client.waitForRoomInSync(roomId);
|
||||
room = client.getRoomById(roomId);
|
||||
}
|
||||
|
||||
if (knock && room == null) {
|
||||
Navigator.of(context).pop();
|
||||
await showOkAlertDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).youHaveKnocked,
|
||||
message: L10n.of(context).pleaseWaitUntilInvited,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (room == null) {
|
||||
throw Exception("Failed to join room");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -283,7 +283,8 @@ class SelectedCourseView extends StatelessWidget {
|
|||
),
|
||||
child: Row(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.map_outlined),
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import 'package:fluffychat/pangea/course_plans/courses/get_localized_courses_req
|
|||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/spaces/public_course_extension.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class PublicCoursesPage extends StatefulWidget {
|
||||
|
|
@ -52,10 +53,10 @@ class PublicCoursesPageState extends State<PublicCoursesPage> {
|
|||
_loadCourses();
|
||||
}
|
||||
|
||||
void setTargetLanguageFilter(LanguageModel? language, {bool reload = true}) {
|
||||
void setTargetLanguageFilter(LanguageModel? language) {
|
||||
if (targetLanguageFilter?.langCodeShort == language?.langCodeShort) return;
|
||||
setState(() => targetLanguageFilter = language);
|
||||
if (reload) _loadCourses();
|
||||
_loadCourses();
|
||||
}
|
||||
|
||||
List<PublicCoursesChunk> get filteredCourses {
|
||||
|
|
@ -67,8 +68,7 @@ class PublicCoursesPageState extends State<PublicCoursesPage> {
|
|||
r.id == c.room.roomId &&
|
||||
r.membership == Membership.join,
|
||||
) &&
|
||||
coursePlans.containsKey(c.courseId) &&
|
||||
c.room.joinRule == 'public',
|
||||
coursePlans.containsKey(c.courseId),
|
||||
)
|
||||
.toList();
|
||||
|
||||
|
|
@ -83,16 +83,20 @@ class PublicCoursesPageState extends State<PublicCoursesPage> {
|
|||
).toList();
|
||||
}
|
||||
|
||||
// sort by join rule, with knock rooms at the end
|
||||
filtered.sort((a, b) {
|
||||
final aKnock = a.room.joinRule == JoinRules.knock.name;
|
||||
final bKnock = b.room.joinRule == JoinRules.knock.name;
|
||||
if (aKnock && !bKnock) return 1;
|
||||
if (!aKnock && bKnock) return -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
Future<void> _loadCourses() async {
|
||||
Future<void> _loadPublicSpaces() async {
|
||||
try {
|
||||
setState(() {
|
||||
loading = true;
|
||||
error = null;
|
||||
});
|
||||
|
||||
final resp = await Matrix.of(context).client.requestPublicCourses(
|
||||
since: nextBatch,
|
||||
);
|
||||
|
|
@ -114,6 +118,21 @@ class PublicCoursesPageState extends State<PublicCoursesPage> {
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadCourses() async {
|
||||
setState(() {
|
||||
loading = true;
|
||||
error = null;
|
||||
});
|
||||
|
||||
await _loadPublicSpaces();
|
||||
|
||||
int timesLoaded = 0;
|
||||
while (error == null && timesLoaded < 5 && nextBatch != null) {
|
||||
await _loadPublicSpaces();
|
||||
timesLoaded++;
|
||||
}
|
||||
|
||||
try {
|
||||
final resp = await CoursePlansRepo.search(
|
||||
|
|
@ -333,6 +352,39 @@ class PublicCoursesPageState extends State<PublicCoursesPage> {
|
|||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12.0),
|
||||
HoverBuilder(
|
||||
builder: (context, hovered) =>
|
||||
ElevatedButton(
|
||||
onPressed: () => context.go(
|
||||
'/${widget.route}/course/public/$courseId',
|
||||
extra: roomChunk,
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme
|
||||
.colorScheme.primaryContainer
|
||||
.withAlpha(hovered ? 255 : 200),
|
||||
foregroundColor: theme
|
||||
.colorScheme.onPrimaryContainer,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
roomChunk.joinRule ==
|
||||
JoinRules.knock.name
|
||||
? L10n.of(context).knock
|
||||
: L10n.of(context).join,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue