You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
8.1 KiB
Dart
278 lines
8.1 KiB
Dart
// ignore_for_file: implementation_imports
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:async/src/result/result.dart' as result;
|
|
import 'package:collection/collection.dart';
|
|
import 'package:get_storage/get_storage.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:matrix/matrix.dart';
|
|
|
|
import 'package:fluffychat/l10n/l10n.dart';
|
|
import 'package:fluffychat/pangea/common/constants/local.key.dart';
|
|
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
|
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
|
import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart';
|
|
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
|
import 'package:fluffychat/widgets/matrix.dart';
|
|
import '../../bot/widgets/bot_face_svg.dart';
|
|
import '../../common/controllers/base_controller.dart';
|
|
|
|
class ClassController extends BaseController {
|
|
late PangeaController _pangeaController;
|
|
static final GetStorage _classStorage = GetStorage('class_storage');
|
|
|
|
ClassController(PangeaController pangeaController) : super() {
|
|
_pangeaController = pangeaController;
|
|
}
|
|
|
|
Future<void> cacheSpaceCode(String code) async {
|
|
if (code.isEmpty) return;
|
|
await _classStorage.write(
|
|
PLocalKey.cachedClassCodeToJoin,
|
|
code,
|
|
);
|
|
}
|
|
|
|
String? justInputtedCode() {
|
|
return _classStorage.read(PLocalKey.justInputtedCode);
|
|
}
|
|
|
|
String? get cachedClassCode {
|
|
return _classStorage.read(PLocalKey.cachedClassCodeToJoin);
|
|
}
|
|
|
|
String? get cachedAlias {
|
|
return _classStorage.read(PLocalKey.cachedAliasToJoin);
|
|
}
|
|
|
|
Future<void> joinCachedSpaceCode(BuildContext context) async {
|
|
final String? classCode = cachedClassCode;
|
|
final String? alias = cachedAlias;
|
|
|
|
if (classCode != null) {
|
|
await joinClasswithCode(
|
|
context,
|
|
classCode,
|
|
);
|
|
|
|
await _classStorage.remove(
|
|
PLocalKey.cachedClassCodeToJoin,
|
|
);
|
|
} else if (alias != null) {
|
|
await joinCachedRoomAlias(alias, context);
|
|
await _classStorage.remove(PLocalKey.cachedAliasToJoin);
|
|
}
|
|
}
|
|
|
|
Future<void> joinCachedRoomAlias(
|
|
String alias,
|
|
BuildContext context,
|
|
) async {
|
|
if (alias.isEmpty) {
|
|
context.go("/rooms");
|
|
return;
|
|
}
|
|
|
|
final client = Matrix.of(context).client;
|
|
if (!client.isLogged()) {
|
|
await _classStorage.write(PLocalKey.cachedAliasToJoin, alias);
|
|
context.go("/home");
|
|
return;
|
|
}
|
|
|
|
Room? room = client.getRoomByAlias(alias) ?? client.getRoomById(alias);
|
|
if (room != null) {
|
|
room.isSpace
|
|
? context.go("/rooms?spaceId=${room.id}")
|
|
: context.go("/rooms/${room.id}");
|
|
return;
|
|
}
|
|
|
|
final roomID = await client.joinRoom(alias);
|
|
room = client.getRoomById(roomID);
|
|
if (room == null) {
|
|
await client.waitForRoomInSync(roomID);
|
|
room = client.getRoomById(roomID);
|
|
if (room == null) {
|
|
context.go("/rooms");
|
|
return;
|
|
}
|
|
}
|
|
|
|
room.isSpace
|
|
? context.go("/rooms?spaceId=${room.id}")
|
|
: context.go("/rooms/${room.id}");
|
|
}
|
|
|
|
Future<result.Result<String?>> joinClasswithCode(
|
|
BuildContext context,
|
|
String classCode, {
|
|
String? notFoundError,
|
|
}) async {
|
|
final client = Matrix.of(context).client;
|
|
final spaceID = await showFutureLoadingDialog<String?>(
|
|
context: context,
|
|
future: () async {
|
|
await _classStorage.write(
|
|
PLocalKey.justInputtedCode,
|
|
classCode,
|
|
);
|
|
|
|
final knockResponse = await client.httpClient.post(
|
|
Uri.parse(
|
|
'${client.homeserver}/_synapse/client/pangea/v1/knock_with_code',
|
|
),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ${client.accessToken}',
|
|
},
|
|
body: jsonEncode({'access_code': classCode}),
|
|
);
|
|
|
|
if (knockResponse.statusCode == 429) {
|
|
return "429";
|
|
}
|
|
if (knockResponse.statusCode != 200) {
|
|
throw notFoundError ?? L10n.of(context).unableToFindClass;
|
|
}
|
|
|
|
final knockResult = jsonDecode(knockResponse.body);
|
|
final foundClasses = List<String>.from(knockResult['rooms']);
|
|
final alreadyJoined = List<String>.from(knockResult['already_joined']);
|
|
|
|
final bool inFoundClass = foundClasses.isNotEmpty &&
|
|
_pangeaController.matrixState.client.rooms.any(
|
|
(room) => room.id == foundClasses.first,
|
|
);
|
|
|
|
if (alreadyJoined.isNotEmpty || inFoundClass) {
|
|
final room = client.getRoomById(alreadyJoined.first);
|
|
if (!(room?.isSpace ?? true)) {
|
|
context.go("/rooms/${alreadyJoined.first}");
|
|
} else {
|
|
context.go("/rooms?spaceId=${alreadyJoined.first}");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
if (foundClasses.isEmpty) {
|
|
throw notFoundError ?? L10n.of(context).unableToFindClass;
|
|
}
|
|
|
|
final chosenClassId = foundClasses.first;
|
|
return chosenClassId;
|
|
},
|
|
);
|
|
|
|
if (spaceID.isError || spaceID.result == null) {
|
|
return spaceID;
|
|
}
|
|
|
|
if (spaceID.result == "429") {
|
|
await _showTooManyRequestsPopup(context);
|
|
return result.Result.error(
|
|
Exception(L10n.of(context).tooManyRequestsWarning),
|
|
StackTrace.current,
|
|
);
|
|
}
|
|
|
|
try {
|
|
await client.joinRoomById(spaceID.result!);
|
|
|
|
Room? room = client.getRoomById(spaceID.result!);
|
|
if (room == null) {
|
|
await _pangeaController.matrixState.client.waitForRoomInSync(
|
|
spaceID.result!,
|
|
join: true,
|
|
);
|
|
room = client.getRoomById(spaceID.result!);
|
|
if (room == null) return spaceID;
|
|
}
|
|
|
|
GoogleAnalytics.joinClass(classCode);
|
|
|
|
if (room.membership != Membership.join) {
|
|
await room.client.waitForRoomInSync(room.id, join: true);
|
|
}
|
|
|
|
// Sometimes, the invite event comes through after the join event and
|
|
// replaces it, so membership gets out of sync. In this case,
|
|
// load the true value from the server.
|
|
// Related github issue: https://github.com/pangeachat/client/issues/2098
|
|
if (room.membership !=
|
|
room
|
|
.getParticipants()
|
|
.firstWhereOrNull((u) => u.id == room?.client.userID)
|
|
?.membership) {
|
|
await room.requestParticipants();
|
|
}
|
|
|
|
if (room.isSpace) {
|
|
context.go("/rooms?spaceId=${room.id}");
|
|
} else {
|
|
context.go("/rooms/${room.id}");
|
|
}
|
|
return spaceID;
|
|
} catch (e, s) {
|
|
ErrorHandler.logError(
|
|
e: e,
|
|
s: s,
|
|
data: {
|
|
"classCode": classCode,
|
|
},
|
|
);
|
|
return result.Result.error(e, s);
|
|
}
|
|
}
|
|
|
|
Future<void> _showTooManyRequestsPopup(BuildContext context) async {
|
|
await showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12.0),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const BotFace(
|
|
width: 100,
|
|
expression: BotExpression.idle,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
// "Are you like me?",
|
|
L10n.of(context).areYouLikeMe,
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
// "Too many attempts made. Please try again in 5 minutes.",
|
|
L10n.of(context).tryAgainLater,
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: const Text("Close"),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|