From a379545304def03a3b447bd3796e72ef71e9a601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8F=98=E8=8F=98?= Date: Fri, 26 Aug 2022 14:10:06 +0800 Subject: [PATCH] feat: remove microsoft xbl proxy (#273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 改進:刪除 Microsoft XBL 代理伺服器 --- lib/account/microsoft_account_handler.dart | 144 ++++-------- lib/launcher/apis.dart | 2 - lib/model/account/Account.dart | 8 +- lib/model/account/MicrosoftEntitlements.dart | 136 ----------- lib/model/account/microsoft_entitlements.dart | 49 ++++ lib/screen/ms_oauth_login.dart | 51 ++-- lib/util/util.dart | 12 +- lib/widget/RPMNetworkImage.dart | 51 ++-- lib/widget/dialog/download_java.dart | 1 - pubspec.lock | 2 +- pubspec.yaml | 2 +- test/screen_test.dart | 220 +++++++++--------- 12 files changed, 269 insertions(+), 409 deletions(-) delete mode 100644 lib/model/account/MicrosoftEntitlements.dart create mode 100644 lib/model/account/microsoft_entitlements.dart diff --git a/lib/account/microsoft_account_handler.dart b/lib/account/microsoft_account_handler.dart index dd5cdceed..722ef30a7 100644 --- a/lib/account/microsoft_account_handler.dart +++ b/lib/account/microsoft_account_handler.dart @@ -3,14 +3,11 @@ import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart'; -import 'package:rpmlauncher/launcher/apis.dart'; import 'package:rpmlauncher/model/account/Account.dart'; -import 'package:rpmlauncher/model/account/MicrosoftEntitlements.dart'; +import 'package:rpmlauncher/model/account/microsoft_entitlements.dart'; import 'package:rpmlauncher/util/data.dart'; import 'package:rpmlauncher/util/i18n.dart'; -import 'package:rpmlauncher/util/launcher_info.dart'; import 'package:rpmlauncher/util/logger.dart'; import 'package:rpmlauncher/util/RPMHttpClient.dart'; import 'package:rpmlauncher/widget/rpmtw_design/OkClose.dart'; @@ -28,17 +25,7 @@ enum MicrosoftAccountStatus { minecraftAuthorizeError, checkingGameOwnership, notGameOwnership, - successful, -} - -extension MicrosoftAccountStatusExtension on MicrosoftAccountStatus { - static Account? _accountData; - - void setAccountData(Account account) { - _accountData = account; - } - - Account? getAccountData() => _accountData; + successful; String get stateName { String i18nKey() { @@ -114,8 +101,8 @@ class MSAccountHandler { try { yield MicrosoftAccountStatus.xbl; Map xboxLiveData = await _authorizationXBL(credentials.accessToken); - String xblToken = xboxLiveData["Token"]; - String userHash = xboxLiveData["DisplayClaims"]["xui"][0]["uhs"]; + String xblToken = xboxLiveData['Token']; + String userHash = xboxLiveData['DisplayClaims']['xui'][0]['uhs']; yield MicrosoftAccountStatus.xsts; Response response = await _authorizationXSTS(xblToken, userHash); @@ -126,7 +113,7 @@ class MSAccountHandler { xstsData = response.data; } else if (response.statusCode == 401) { Map data = response.data; - int xError = data["XErr"]; + int xError = data['XErr']; if (xError == 2148916233) { //此微軟帳號沒有Xobx帳號 yield MicrosoftAccountStatus.noneXboxAccount; @@ -146,8 +133,8 @@ class MSAccountHandler { return; } - String xstsToken = xstsData["Token"]; - String xstsUserHash = xstsData["DisplayClaims"]["xui"][0]["uhs"]; + String xstsToken = xstsData['Token']; + String xstsUserHash = xstsData['DisplayClaims']['xui'][0]['uhs']; yield MicrosoftAccountStatus.minecraftAuthorize; Map? minecraftAuthorizeData = @@ -158,7 +145,7 @@ class MSAccountHandler { return; } - String mcAccessToken = minecraftAuthorizeData["access_token"]; + String mcAccessToken = minecraftAuthorizeData['access_token']; yield MicrosoftAccountStatus.checkingGameOwnership; bool canPlayMinecraft = await _checkingGameOwnership(mcAccessToken); @@ -166,11 +153,12 @@ class MSAccountHandler { if (canPlayMinecraft) { Map profileJson = await getProfile(mcAccessToken); - MicrosoftAccountStatus finishState = MicrosoftAccountStatus.successful; - finishState.setAccountData(Account(AccountType.microsoft, mcAccessToken, + final account = Account(AccountType.microsoft, mcAccessToken, profileJson['id'], profileJson['name'], - credentials: credentials)); - yield finishState; + credentials: credentials); + account.save(); + + yield MicrosoftAccountStatus.successful; } else { yield MicrosoftAccountStatus.notGameOwnership; } @@ -181,68 +169,32 @@ class MSAccountHandler { return; } - static Future validate(String accessToken) async { - /* - 驗證微軟帳號的Token是否有效 + /* + Verify the microsoft account is able to play minecraft */ - - var headers = {'Authorization': 'Bearer $accessToken'}; - var request = http.Request('GET', Uri.parse(microsoftProfileAPI)); - request.headers.addAll(headers); - - http.StreamedResponse response = await request.send(); + static Future validate(String accessToken) async { + final Response response = await _httpClient.get( + 'https://api.minecraftservices.com/minecraft/profile', + options: Options(headers: {'Authorization': 'Bearer $accessToken'})); return response.statusCode == 200; } static Future _authorizationXBL(String accessToken) async { - Map result; - - Future proxy() async { - Response response = await _httpClient.get( - "https://rear-end.a102009102009.repl.co/rpmlauncher/api/microsof-auth-xbl?accessToken=$accessToken"); - - if (response.data is Map) { - return response.data; - } else { - return json.decode(response.data.toString()); - } - } - - if (kTestMode) { - result = await proxy(); - } else { - try { - ProcessResult curlResult = await Process.run( - 'curl', - [ - "https://user.auth.xboxlive.com/user/authenticate", - "--location", - "--request", - "POST", - "--header", - "Content-Type: application/json", - "--data-raw", - json.encode({ - "Properties": { - "AuthMethod": "RPS", - "SiteName": "user.auth.xboxlive.com", - "RpsTicket": "d=$accessToken" - }, - "RelyingParty": "http://auth.xboxlive.com", - "TokenType": "JWT" - }), - ], - runInShell: true) - .timeout(const Duration(seconds: 3)); - result = json.decode(curlResult.stdout.toString()); - } catch (e) { - /// 如果使用 curl 超出時間限制或其他未知錯誤則改用代理伺服器 - result = await proxy(); - } - } - - return result; + final Response response = await _httpClient.post( + 'https://user.auth.xboxlive.com/user/authenticate', + data: json.encode({ + 'Properties': { + 'AuthMethod': 'RPS', + 'SiteName': 'user.auth.xboxlive.com', + 'RpsTicket': 'd=$accessToken' + }, + 'RelyingParty': 'http://auth.xboxlive.com', + 'TokenType': 'JWT' + }), + ); + + return response.data; } static Future _authorizationXSTS( @@ -250,14 +202,14 @@ class MSAccountHandler { //Authenticate with XSTS Response response = await _httpClient.post( - "https://xsts.auth.xboxlive.com/xsts/authorize", + 'https://xsts.auth.xboxlive.com/xsts/authorize', data: json.encode({ - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [xblToken] + 'Properties': { + 'SandboxId': 'RETAIL', + 'UserTokens': [xblToken] }, - "RelyingParty": "rp://api.minecraftservices.com/", - "TokenType": "JWT" + 'RelyingParty': 'rp://api.minecraftservices.com/', + 'TokenType': 'JWT' }), options: Options(headers: { 'Content-Type': 'application/json', @@ -272,10 +224,10 @@ class MSAccountHandler { //Authenticate with Minecraft Response response = await _httpClient.post( - "https://api.minecraftservices.com/launcher/login", + 'https://api.minecraftservices.com/launcher/login', data: json.encode({ - "xtoken": "XBL3.0 x=$userHash;$xstsToken", - "platform": "PC_LAUNCHER" + 'xtoken': 'XBL3.0 x=$userHash;$xstsToken', + 'platform': 'PC_LAUNCHER' }), options: Options(headers: { 'Content-Type': 'application/json', @@ -294,10 +246,10 @@ class MSAccountHandler { //Checking Game Ownership Response response = await _httpClient.get( - "https://api.minecraftservices.com/entitlements/license?requestId=${const Uuid().v4()}", + 'https://api.minecraftservices.com/entitlements/license?requestId=${const Uuid().v4()}', options: Options(headers: { 'Authorization': 'Bearer $accessToken', - 'Accept': "application/json" + 'Accept': 'application/json' }, contentType: ContentType.json.mimeType)); if (response.statusCode == 200) { @@ -312,18 +264,18 @@ class MSAccountHandler { static Future getProfile(mcAccessToken) async { Response response = await _httpClient.get( - "https://api.minecraftservices.com/minecraft/profile", + 'https://api.minecraftservices.com/minecraft/profile', options: Options( - headers: {'Authorization': "Bearer $mcAccessToken"}, + headers: {'Authorization': 'Bearer $mcAccessToken'}, responseType: ResponseType.json)); Map data = response.data; - if (data['error'].toString() == "NOT_FOUND") { + if (data['error'].toString() == 'NOT_FOUND') { await showDialog( context: navigator.context, builder: (context) => AlertDialog( title: I18nText.errorInfoText(), - content: I18nText("account.add.microsoft.error.xbox_game_pass"), + content: I18nText('account.add.microsoft.error.xbox_game_pass'), actions: const [OkClose()], )); return data; diff --git a/lib/launcher/apis.dart b/lib/launcher/apis.dart index 5a54b6a8e..eae621ef2 100644 --- a/lib/launcher/apis.dart +++ b/lib/launcher/apis.dart @@ -8,8 +8,6 @@ String forgeMavenUrl = 'https://maven.minecraftforge.net'; String forgeMavenMainUrl = 'https://maven.minecraftforge.net/net/minecraftforge/forge'; String mojangAuthAPI = 'https://authserver.mojang.com'; -String microsoftProfileAPI = - 'https://api.minecraftservices.com/minecraft/profile'; String modrinthAPI = 'https://api.modrinth.com/v2'; String ftbModPackAPI = 'https://api.modpacks.ch/public'; String minecraftNewsRSS = diff --git a/lib/model/account/Account.dart b/lib/model/account/Account.dart index a8e39c271..94910ccc7 100644 --- a/lib/model/account/Account.dart +++ b/lib/model/account/Account.dart @@ -23,11 +23,9 @@ class Account { final Credentials? credentials; Widget get imageWidget { - try { - return RPMNetworkImage(src: "https://crafatar.com/avatars/$uuid?overlay"); - } catch (e) { - return const Icon(Icons.person); - } + return RPMNetworkImage( + src: "https://minotar.net/helm/$uuid", + errorWidget: const Icon(Icons.person)); } Account(this.type, this.accessToken, this.uuid, this.username, diff --git a/lib/model/account/MicrosoftEntitlements.dart b/lib/model/account/MicrosoftEntitlements.dart deleted file mode 100644 index 8711cd718..000000000 --- a/lib/model/account/MicrosoftEntitlements.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; - -class MicrosoftEntitlements { - final List items; - final String signature; - final String keyId; - final String requestId; - - bool get canPlayMinecraft => - items.any((item) => item.name == "product_minecraft") && - items.any((item) => item.name == "game_minecraft"); - - MicrosoftEntitlements({ - required this.items, - required this.signature, - required this.keyId, - required this.requestId, - }); - - MicrosoftEntitlements copyWith({ - List? items, - String? signature, - String? keyId, - String? requestId, - }) { - return MicrosoftEntitlements( - items: items ?? this.items, - signature: signature ?? this.signature, - keyId: keyId ?? this.keyId, - requestId: requestId ?? this.requestId, - ); - } - - Map toMap() { - return { - 'items': items.map((x) => x.toMap()).toList(), - 'signature': signature, - 'keyId': keyId, - 'requestId': requestId, - }; - } - - factory MicrosoftEntitlements.fromMap(Map map) { - return MicrosoftEntitlements( - items: List.from( - map['items']?.map((x) => EntitlementItem.fromMap(x))), - signature: map['signature'], - keyId: map['keyId'], - requestId: map['requestId'], - ); - } - - String toJson() => json.encode(toMap()); - - factory MicrosoftEntitlements.fromJson(String source) => - MicrosoftEntitlements.fromMap(json.decode(source)); - - @override - String toString() { - return 'MicrosoftEntitlements(items: $items, signature: $signature, keyId: $keyId, requestId: $requestId)'; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is MicrosoftEntitlements && - listEquals(other.items, items) && - other.signature == signature && - other.keyId == keyId && - other.requestId == requestId; - } - - @override - int get hashCode { - return items.hashCode ^ - signature.hashCode ^ - keyId.hashCode ^ - requestId.hashCode; - } -} - -class EntitlementItem { - final String name; - final String source; - EntitlementItem({ - required this.name, - required this.source, - }); - - EntitlementItem copyWith({ - String? name, - String? source, - }) { - return EntitlementItem( - name: name ?? this.name, - source: source ?? this.source, - ); - } - - Map toMap() { - return { - 'name': name, - 'source': source, - }; - } - - factory EntitlementItem.fromMap(Map map) { - return EntitlementItem( - name: map['name'], - source: map['source'], - ); - } - - String toJson() => json.encode(toMap()); - - factory EntitlementItem.fromJson(String source) => - EntitlementItem.fromMap(json.decode(source)); - - @override - String toString() => 'Item(name: $name, source: $source)'; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is EntitlementItem && - other.name == name && - other.source == source; - } - - @override - int get hashCode => name.hashCode ^ source.hashCode; -} diff --git a/lib/model/account/microsoft_entitlements.dart b/lib/model/account/microsoft_entitlements.dart new file mode 100644 index 000000000..7f2bdfabf --- /dev/null +++ b/lib/model/account/microsoft_entitlements.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; + +class MicrosoftEntitlements { + final List items; + final String signature; + final String keyId; + final String requestId; + + bool get canPlayMinecraft => + items.any((item) => item.name == 'product_minecraft') && + items.any((item) => item.name == 'game_minecraft'); + + const MicrosoftEntitlements({ + required this.items, + required this.signature, + required this.keyId, + required this.requestId, + }); + + factory MicrosoftEntitlements.fromMap(Map map) { + return MicrosoftEntitlements( + items: List.from( + map['items']?.map((x) => EntitlementItem.fromMap(x))), + signature: map['signature'], + keyId: map['keyId'], + requestId: map['requestId'], + ); + } + + factory MicrosoftEntitlements.fromJson(String source) => + MicrosoftEntitlements.fromMap(json.decode(source)); +} + +class EntitlementItem { + final String name; + final String source; + + const EntitlementItem({ + required this.name, + required this.source, + }); + + factory EntitlementItem.fromMap(Map map) { + return EntitlementItem( + name: map['name'], + source: map['source'], + ); + } +} diff --git a/lib/screen/ms_oauth_login.dart b/lib/screen/ms_oauth_login.dart index 5a8934c1c..2b436392a 100644 --- a/lib/screen/ms_oauth_login.dart +++ b/lib/screen/ms_oauth_login.dart @@ -33,18 +33,10 @@ typedef AuthenticatedBuilder = Widget Function( BuildContext context, oauth2.Client client); class _MSLoginState extends State { - HttpServer? _redirectServer; + late final HttpServer _redirectServer; @override Widget build(BuildContext context) { - Future logIn() async { - await _redirectServer?.close(); - _redirectServer = await HttpServer.bind('127.0.0.1', 5020); - var authenticatedHttpClient = await _getOAuth2Client( - Uri.parse('http://127.0.0.1:5020/rpmlauncher-auth')); - return authenticatedHttpClient; - } - return FutureBuilder( future: microsoftOauthMock?.call() ?? logIn(), builder: (context, AsyncSnapshot snapshot) { @@ -54,11 +46,7 @@ class _MSLoginState extends State { stream: MSAccountHandler.authorization(client.credentials), initialData: MicrosoftAccountStatus.xbl, builder: (context, snapshot) { - MicrosoftAccountStatus status = snapshot.data!; - - if (status == MicrosoftAccountStatus.successful) { - status.getAccountData()!.save(); - } + final MicrosoftAccountStatus status = snapshot.data!; if (status.isError) { return AlertDialog( @@ -68,7 +56,7 @@ class _MSLoginState extends State { ); } else { return AlertDialog( - title: I18nText("account.add.microsoft.state.title"), + title: I18nText('account.add.microsoft.state.title'), content: Text(status.stateName), actions: status == MicrosoftAccountStatus.successful ? [const OkClose()] @@ -80,7 +68,7 @@ class _MSLoginState extends State { return Text(snapshot.error.toString()); } else { return AlertDialog( - title: I18nText("account.add.microsoft.waiting"), + title: I18nText('account.add.microsoft.waiting'), content: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, @@ -99,6 +87,13 @@ class _MSLoginState extends State { }); } + Future logIn() async { + _redirectServer = await HttpServer.bind('127.0.0.1', 5020); + + return await _getOAuth2Client( + Uri.parse('http://127.0.0.1:5020/rpmlauncher-auth')); + } + Future _getOAuth2Client(Uri redirectUrl) async { AuthorizationCodeGrant grant = oauth2.AuthorizationCodeGrant( LauncherInfo.microsoftClientID, //Client ID @@ -109,28 +104,22 @@ class _MSLoginState extends State { Uri authorizationUrl = grant.getAuthorizationUrl(redirectUrl, scopes: ['XboxLive.signin', 'offline_access']); authorizationUrl = Uri.parse( - "${authorizationUrl.toString()}&cobrandid=8058f65d-ce06-4c30-9559-473c9275a65d&prompt=select_account"); - await _redirect(authorizationUrl); - var responseQueryParameters = await _listen(); - var client = - await grant.handleAuthorizationResponse(responseQueryParameters); - return client; - } + '${authorizationUrl.toString()}&cobrandid=8058f65d-ce06-4c30-9559-473c9275a65d&prompt=select_account'); + await Util.openUri(authorizationUrl.toString()); + final responseQueryParameters = await _listenParameters(); - Future _redirect(authorizationUrl) async { - var url = authorizationUrl.toString(); - Util.openUri(url); + return await grant.handleAuthorizationResponse(responseQueryParameters); } - Future> _listen() async { - var request = await _redirectServer!.first; - var params = request.uri.queryParameters; + Future> _listenParameters() async { + final request = await _redirectServer.first; + final params = request.uri.queryParameters; request.response.statusCode = 200; request.response.headers.set('content-type', 'text/plain; charset=utf-8'); request.response.writeln(I18n.format('account.add.microsoft.html')); await request.response.close(); - await _redirectServer!.close(); - _redirectServer = null; + await _redirectServer.close(); + return params; } } diff --git a/lib/util/util.dart b/lib/util/util.dart index 5bc7dc79c..e1dc8a06d 100644 --- a/lib/util/util.dart +++ b/lib/util/util.dart @@ -295,19 +295,13 @@ class Util { if (!isValid) { // The token is expired, so we need to refresh it. try { - Credentials credentials = await account.credentials!.refresh( + final Credentials credentials = await account.credentials!.refresh( identifier: LauncherInfo.microsoftClientID, ); - List statusList = + final List statusList = await MSAccountHandler.authorization(credentials).toList(); - MicrosoftAccountStatus status = statusList - .firstWhere((s) => s == MicrosoftAccountStatus.successful); - - /// Save the new credentials - status.getAccountData()!.save(); - - return true; + return statusList.any((e) => e == MicrosoftAccountStatus.successful); } catch (e) { logger.error( ErrorType.authorization, 'Can\'t refresh the credentials'); diff --git a/lib/widget/RPMNetworkImage.dart b/lib/widget/RPMNetworkImage.dart index 7dbe32f00..263d606e0 100644 --- a/lib/widget/RPMNetworkImage.dart +++ b/lib/widget/RPMNetworkImage.dart @@ -5,29 +5,46 @@ class RPMNetworkImage extends StatelessWidget { final BoxFit? fit; final double? width; final double? height; + final Widget errorWidget; const RPMNetworkImage( - {Key? key, required this.src, this.fit, this.width, this.height}) + {Key? key, + required this.src, + this.fit, + this.width, + this.height, + this.errorWidget = const CircularProgressIndicator()}) : super(key: key); + @override Widget build(BuildContext context) { - return Image.network( - src, - fit: fit, + return SizedBox( width: width, height: height, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) return child; - return Center( - child: CircularProgressIndicator( - value: loadingProgress.expectedTotalBytes != null - ? loadingProgress.cumulativeBytesLoaded.toInt() / - loadingProgress.expectedTotalBytes!.toInt() - : null, - ), - ); - }, - errorBuilder: (context, error, stackTrace) => const SizedBox.shrink(), + child: Builder(builder: (context) { + try { + return Image.network( + src, + fit: fit, + width: width, + height: height, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded.toInt() / + loadingProgress.expectedTotalBytes!.toInt() + : null, + ), + ); + }, + errorBuilder: (context, error, stackTrace) => errorWidget, + ); + } catch (e) { + return errorWidget; + } + }), ); } -} +} \ No newline at end of file diff --git a/lib/widget/dialog/download_java.dart b/lib/widget/dialog/download_java.dart index 3ca3ddfb0..b62f7e9f3 100644 --- a/lib/widget/dialog/download_java.dart +++ b/lib/widget/dialog/download_java.dart @@ -1,6 +1,5 @@ import 'dart:io'; import 'dart:isolate'; -import 'dart:ui'; import 'package:archive/archive_io.dart'; import 'package:flutter/foundation.dart'; diff --git a/pubspec.lock b/pubspec.lock index 55f74045b..e8201c788 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -388,7 +388,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.4" + version: "0.13.5" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5d23ae9d4..f256c2e10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - http: ^0.13.4 + http: ^0.13.5 url_launcher: ^6.1.2 path_provider: ^2.0.10 split_view: ^3.1.0 diff --git a/test/screen_test.dart b/test/screen_test.dart index 9f2338a0e..c92c73f0e 100644 --- a/test/screen_test.dart +++ b/test/screen_test.dart @@ -32,20 +32,20 @@ import 'script/test_helper.dart'; void main() { setUpAll(() => TestHelper.init()); - group("RPMLauncher Screen Test -", () { + group('RPMLauncher Screen Test -', () { testWidgets('Settings Screen', (WidgetTester tester) async { await TestHelper.baseTestWidget(tester, SettingScreen()); - expect(find.text(I18n.format("settings.title")), findsOneWidget); + expect(find.text(I18n.format('settings.title')), findsOneWidget); final Finder appearancePage = - find.text(I18n.format("settings.appearance.title")); + find.text(I18n.format('settings.appearance.title')); await tester.tap(appearancePage); await tester.pumpAndSettle(); expect( - find.text(I18n.format("settings.appearance.theme")), findsOneWidget); + find.text(I18n.format('settings.appearance.theme')), findsOneWidget); }); testWidgets('About Screen', (WidgetTester tester) async { await TestHelper.baseTestWidget(tester, AboutScreen()); @@ -57,7 +57,7 @@ void main() { expect(find.text(LauncherInfo.getUpperCaseName()), findsOneWidget); expect(find.text(LauncherInfo.getFullVersion()), findsOneWidget); - expect(find.text("Powered by Flutter"), findsOneWidget); + expect(find.text('Powered by Flutter'), findsOneWidget); final Finder back = find.byType(BackButton); @@ -91,9 +91,9 @@ void main() { testWidgets('VersionSelection Screen (Client)', (WidgetTester tester) async { rpmHttpClientAdapter = (RequestOptions requestOptions) { - if (requestOptions.method == "GET" && + if (requestOptions.method == 'GET' && requestOptions.uri.toString() == - "$mojangMetaAPI/version_manifest_v2.json") { + '$mojangMetaAPI/version_manifest_v2.json') { return Future.value(Response( requestOptions: requestOptions, data: json.decode(TestData.versionManifest.getFileString()) as T, @@ -104,7 +104,7 @@ void main() { await TestHelper.baseTestWidget( tester, const VersionSelection(side: MinecraftSide.client)); - expect(find.text("1.18.1"), findsOneWidget); + expect(find.text('1.18.1'), findsOneWidget); Finder showSnapshot = find.byType(Checkbox).last; Finder showRelease = find.byType(Checkbox).first; @@ -113,13 +113,13 @@ void main() { await tester.tap(showSnapshot); await tester.pumpAndSettle(); - Finder snapshot = find.text("21w44a"); + Finder snapshot = find.text('21w44a'); await tester.dragUntilVisible( snapshot, find.byType(ListView), const Offset(0.0, -300)); await tester.pumpAndSettle(); - expect(find.text("1.18.1"), findsNothing); + expect(find.text('1.18.1'), findsNothing); expect(snapshot, findsOneWidget); @@ -137,7 +137,7 @@ void main() { await TestHelper.baseTestWidget( tester, const VersionSelection(side: MinecraftSide.server), async: true); - expect(find.text("1.18.1"), findsOneWidget); + expect(find.text('1.18.1'), findsOneWidget); Finder modloader = find.byType(DropdownButton); @@ -152,8 +152,8 @@ void main() { testWidgets('CurseForge ModPack Screen', (WidgetTester tester) async { rpmHttpClientAdapter = (RequestOptions requestOptions) { if (requestOptions.uri.toString() == - "https://api.rpmtw.com:2096/curseforge/?path=v1/mods/search?gameId=432%26classId=4471%26searchFilter=%26sortField=2%26sortOrder=d%E2%80%A6" && - requestOptions.method == "GET") { + 'https://api.rpmtw.com:2096/curseforge/?path=v1/mods/search?gameId=432%26classId=4471%26searchFilter=%26sortField=2%26sortOrder=d%E2%80%A6' && + requestOptions.method == 'GET') { return Future.value(Response( requestOptions: requestOptions, data: (json.decode(TestData.curseforgeModpack.getFileString())) @@ -161,8 +161,8 @@ void main() { statusCode: 200)); } // else if (requestOptions.uri.toString() == - // "$curseForgeModAPI/minecraft/version" && - // requestOptions.method == "GET") { + // '$curseForgeModAPI/minecraft/version' && + // requestOptions.method == 'GET') { // return Future.value(Response( // requestOptions: requestOptions, // data: (json.decode(TestData.curseforgeVersion.getFileString())) @@ -175,7 +175,7 @@ void main() { await TestHelper.baseTestWidget(tester, const CurseForgeModpackPage(), async: true); - final Finder modPack = find.text("RLCraft"); + final Finder modPack = find.text('RLCraft'); await tester.dragUntilVisible( modPack, @@ -190,13 +190,13 @@ void main() { expect( find.text( - "A modpack specially designed to bring an incredibly hardcore and semi-realism challenge revolving around survival, RPG elements, and adventure-like exploration."), + 'A modpack specially designed to bring an incredibly hardcore and semi-realism challenge revolving around survival, RPG elements, and adventure-like exploration.'), findsOneWidget); await tester.sendKeyEvent(LogicalKeyboardKey.escape); await tester.pumpAndSettle(); - final Finder installButton = find.text(I18n.format("gui.install")); + final Finder installButton = find.text(I18n.format('gui.install')); expect(installButton, findsWidgets); await tester.tap(installButton.first); await tester.pumpAndSettle( @@ -206,22 +206,22 @@ void main() { }, skip: true); testWidgets('FTB ModPack Screen', (WidgetTester tester) async { rpmHttpClientAdapter = (RequestOptions requestOptions) { - if (requestOptions.uri.toString() == "$ftbModPackAPI/tag/popular/100" && - requestOptions.method == "GET") { + if (requestOptions.uri.toString() == '$ftbModPackAPI/tag/popular/100' && + requestOptions.method == 'GET') { return Future.value(Response( requestOptions: requestOptions, data: (json.decode(TestData.ftbTags.getFileString())) as T, statusCode: 200)); } else if (requestOptions.uri.toString() == - "$ftbModPackAPI/modpack/popular/installs/FTB/all" && - requestOptions.method == "GET") { + '$ftbModPackAPI/modpack/popular/installs/FTB/all' && + requestOptions.method == 'GET') { return Future.value(Response( requestOptions: requestOptions, data: (json.decode(TestData.ftbModpack.getFileString())) as T, statusCode: 200)); } else if (requestOptions.uri.toString() == - "$ftbModPackAPI/modpack/35" && - requestOptions.method == "GET") { + '$ftbModPackAPI/modpack/35' && + requestOptions.method == 'GET') { return Future.value(Response( requestOptions: requestOptions, data: (json.decode(TestData.ftbModpack35.getFileString())) as T, @@ -232,10 +232,10 @@ void main() { await TestHelper.baseTestWidget(tester, FTBModPack(), async: true); - expect(find.text("FTB Revelation"), findsOneWidget); + expect(find.text('FTB Revelation'), findsOneWidget); expect( find.text( - "Revelation is a general all-purpose modpack with optimal FPS, server performance and stability."), + 'Revelation is a general all-purpose modpack with optimal FPS, server performance and stability.'), findsOneWidget); }); @@ -244,15 +244,15 @@ void main() { tester, const VersionSelection(side: MinecraftSide.client), async: true); - final Finder versionText = find.text("1.17.1"); + final Finder versionText = find.text('1.17.1'); await tester.tap(versionText); await tester.pumpAndSettle(); - final Finder confirm = find.text(I18n.format("gui.confirm")); + final Finder confirm = find.text(I18n.format('gui.confirm')); expect(confirm, findsOneWidget); - expect(find.text(I18n.format("gui.cancel")), findsOneWidget); + expect(find.text(I18n.format('gui.cancel')), findsOneWidget); await tester.tap(confirm); @@ -281,11 +281,11 @@ void main() { expect(find.text('0.00%').evaluate().length, 0); - expect(find.text(I18n.format("launcher.java.install.auto.download.done")), + expect(find.text(I18n.format('launcher.java.install.auto.download.done')), findsOneWidget); if (find - .text(I18n.format("launcher.java.install.auto.download.done")) + .text(I18n.format('launcher.java.install.auto.download.done')) .evaluate() .isNotEmpty) { final Finder close = find.byType(OkClose); @@ -294,31 +294,31 @@ void main() { } }); - testWidgets("Add Mojang Account", (WidgetTester tester) async { + testWidgets('Add Mojang Account', (WidgetTester tester) async { String mockToken = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiU2lvbmdTbmciLCJ0ZXh0IjoiSGVsbG8gUlBNVFcgV29ybGQifQ.Q7VjOWCjl_FI9W4kPlSaYLAUaUqCgfMe5YjnQEtBdTU"; - String mockUUID = "a9b8f8f7-e8e7-4f6d-b8c6-b8c8f8f7e8e7"; - String mockEmail = "RPMTW@email.example"; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiU2lvbmdTbmciLCJ0ZXh0IjoiSGVsbG8gUlBNVFcgV29ybGQifQ.Q7VjOWCjl_FI9W4kPlSaYLAUaUqCgfMe5YjnQEtBdTU'; + String mockUUID = 'a9b8f8f7-e8e7-4f6d-b8c6-b8c8f8f7e8e7'; + String mockEmail = 'RPMTW@email.example'; rpmHttpClientAdapter = (RequestOptions requestOptions) { - if (requestOptions.uri.toString() == "$mojangAuthAPI/authenticate" && - requestOptions.method == "POST") { + if (requestOptions.uri.toString() == '$mojangAuthAPI/authenticate' && + requestOptions.method == 'POST') { return Future.value(Response( requestOptions: requestOptions, data: { - "user": { - "username": mockEmail, - "properties": [ - {"name": "preferredLanguage", "value": "en-us"}, - {"name": "registrationCountry", "value": "country"} + 'user': { + 'username': mockEmail, + 'properties': [ + {'name': 'preferredLanguage', 'value': 'en-us'}, + {'name': 'registrationCountry', 'value': 'country'} ], - "id": mockUUID + 'id': mockUUID }, - "accessToken": mockToken, - "availableProfiles": [ - {"name": "RPMTW", "id": mockUUID} + 'accessToken': mockToken, + 'availableProfiles': [ + {'name': 'RPMTW', 'id': mockUUID} ], - "selectedProfile": {"name": "RPMTW", "id": mockUUID} + 'selectedProfile': {'name': 'RPMTW', 'id': mockUUID} } as T, statusCode: 200)); } @@ -328,9 +328,9 @@ void main() { await TestHelper.baseTestWidget(tester, const MojangAccount()); expect(find.text(I18n.format('account.mojang.title')), findsOneWidget); - await tester.enterText(find.byKey(const Key('mojang_email')), "RPMTW"); + await tester.enterText(find.byKey(const Key('mojang_email')), 'RPMTW'); await tester.enterText( - find.byKey(const Key('mojang_passwd')), "hello_rpmtw_world"); + find.byKey(const Key('mojang_passwd')), 'hello_rpmtw_world'); await tester.pumpAndSettle(); @@ -344,9 +344,9 @@ void main() { expect(showPasswd, findsNothing); expect(find.text(I18n.format('account.passwd.hide')), findsOneWidget); - expect(find.text("hello_rpmtw_world"), findsOneWidget); + expect(find.text('hello_rpmtw_world'), findsOneWidget); - final Finder loginButton = find.text(I18n.format("gui.login")); + final Finder loginButton = find.text(I18n.format('gui.login')); await tester.dragUntilVisible( loginButton, @@ -364,109 +364,109 @@ void main() { expect(find.text(I18n.format('account.add.successful')), findsOneWidget); expect(AccountStorage().getIndex() != -1, true); expect( - AccountStorage().getByUUID("a9b8f8f7-e8e7-4f6d-b8c6-b8c8f8f7e8e7"), - Account(AccountType.mojang, mockToken, mockUUID, "RPMTW", + AccountStorage().getByUUID('a9b8f8f7-e8e7-4f6d-b8c6-b8c8f8f7e8e7'), + Account(AccountType.mojang, mockToken, mockUUID, 'RPMTW', email: mockEmail)); }); - testWidgets("Add Microsoft Account", (WidgetTester tester) async { + testWidgets('Add Microsoft Account', (WidgetTester tester) async { String mockToken = - "eyJhbGciOiJIUzI1NiIsImxhbmciOiJkYXJ0IiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwidGVzdCI6IlJQTVRXIn0.Nd1lXCNoXIqQivebe5Sj4Y7LEt0oSTkbOYIThIZl_II"; + 'eyJhbGciOiJIUzI1NiIsImxhbmciOiJkYXJ0IiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwidGVzdCI6IlJQTVRXIn0.Nd1lXCNoXIqQivebe5Sj4Y7LEt0oSTkbOYIThIZl_II'; String mockRefreshToken = - "M.R3_BAY.-CS1snzEaQsj1AUl6sp1!4UIxuAJEXwSc!BCsAsjahoWGxRgYoCad!ltICMc80mBT33tbHmBpioDPc722coOnNF3nItthH8CL4uSbHaRv4!nzYDmZdtN9QsLAPs24mSsxn*EISkg4vWziNi9GhmXFZ6qqZrwq8pFbCn3CxGPc9QgqdyAh6T9Smkwxxw26duFRKajIBDR86B6Y5jRjE8EiLhCbq9IFZUo9cniQQd2Su20*mRIRPya8pUvrIzADvDIJy1!0Cnff!MVLB0vLvdngKRLErHPmaiMldYEtCTr1*zeg"; + 'M.R3_BAY.-CS1snzEaQsj1AUl6sp1!4UIxuAJEXwSc!BCsAsjahoWGxRgYoCad!ltICMc80mBT33tbHmBpioDPc722coOnNF3nItthH8CL4uSbHaRv4!nzYDmZdtN9QsLAPs24mSsxn*EISkg4vWziNi9GhmXFZ6qqZrwq8pFbCn3CxGPc9QgqdyAh6T9Smkwxxw26duFRKajIBDR86B6Y5jRjE8EiLhCbq9IFZUo9cniQQd2Su20*mRIRPya8pUvrIzADvDIJy1!0Cnff!MVLB0vLvdngKRLErHPmaiMldYEtCTr1*zeg'; - String mockUUID = "896a07c6-7a99-4e4d-9c53-608cfa4fd581"; + String mockUUID = '896a07c6-7a99-4e4d-9c53-608cfa4fd581'; Credentials mockCredentials = Credentials(mockToken, refreshToken: mockRefreshToken, - tokenEndpoint: Uri.parse("https://login.live.com/oauth20_token.srf"), - scopes: ["XboxLive.signin"], - expiration: DateTime.parse("2021-12-04")); + tokenEndpoint: Uri.parse('https://login.live.com/oauth20_token.srf'), + scopes: ['XboxLive.signin'], + expiration: DateTime.parse('2021-12-04')); microsoftOauthMock = () => Future.value(Client(mockCredentials)); rpmHttpClientAdapter = (RequestOptions requestOptions) { if (requestOptions.uri.toString() == - "https://rear-end.a102009102009.repl.co/rpmlauncher/api/microsof-auth-xbl?accessToken=$mockToken" && - requestOptions.method == "GET") { + 'https://user.auth.xboxlive.com/user/authenticate' && + requestOptions.method == 'POST') { return Future.value(Response( requestOptions: requestOptions, data: { - "IssueInstant": "2021-12-04T19:52:08.4463796Z", - "NotAfter": "2032-1-1T19:52:08.4463796Z", - "Token": "xbl_token", - "DisplayClaims": { - "xui": [ - {"uhs": "xbl_user_hash"} + 'IssueInstant': '2021-12-04T19:52:08.4463796Z', + 'NotAfter': '2032-1-1T19:52:08.4463796Z', + 'Token': 'xbl_token', + 'DisplayClaims': { + 'xui': [ + {'uhs': 'xbl_user_hash'} ] } } as T, statusCode: 200)); } else if (requestOptions.uri.toString() == - "https://xsts.auth.xboxlive.com/xsts/authorize" && - requestOptions.method == "POST") { + 'https://xsts.auth.xboxlive.com/xsts/authorize' && + requestOptions.method == 'POST') { return Future.value(Response( requestOptions: requestOptions, data: { - "IssueInstant": "2021-12-04T19:52:08.4463796Z", - "NotAfter": "2032-1-1T19:52:08.4463796Z", - "Token": "xsts_token", - "DisplayClaims": { - "xui": [ - {"uhs": "xsts_user_hash"} + 'IssueInstant': '2021-12-04T19:52:08.4463796Z', + 'NotAfter': '2032-1-1T19:52:08.4463796Z', + 'Token': 'xsts_token', + 'DisplayClaims': { + 'xui': [ + {'uhs': 'xsts_user_hash'} ] } } as T, statusCode: 200)); } else if (requestOptions.uri.toString() == - "https://api.minecraftservices.com/launcher/login" && - requestOptions.method == "POST") { + 'https://api.minecraftservices.com/launcher/login' && + requestOptions.method == 'POST') { return Future.value(Response( requestOptions: requestOptions, data: { - "username": mockUUID, - "roles": [], - "access_token": mockToken, - "token_type": "Bearer", - "expires_in": 86400 + 'username': mockUUID, + 'roles': [], + 'access_token': mockToken, + 'token_type': 'Bearer', + 'expires_in': 86400 } as T, statusCode: 200)); } else if (requestOptions.uri.toString().startsWith( - "https://api.minecraftservices.com/entitlements/license") && - requestOptions.method == "GET") { + 'https://api.minecraftservices.com/entitlements/license') && + requestOptions.method == 'GET') { return Future.value(Response( requestOptions: requestOptions, data: { - "items": [ - {"name": "product_minecraft_bedrock", "source": "PURCHASE"}, - {"name": "game_minecraft_bedrock", "source": "PURCHASE"}, - {"name": "product_minecraft", "source": "MC_PURCHASE"}, - {"name": "game_minecraft", "source": "MC_PURCHASE"} + 'items': [ + {'name': 'product_minecraft_bedrock', 'source': 'PURCHASE'}, + {'name': 'game_minecraft_bedrock', 'source': 'PURCHASE'}, + {'name': 'product_minecraft', 'source': 'MC_PURCHASE'}, + {'name': 'game_minecraft', 'source': 'MC_PURCHASE'} ], - "signature": - "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEiLCJ4NXQiOiJJVXRXd1l0clNfSXpJS0piaTZzNGtWaF9FNXMifQ.ewogICJlbnRpdGxlbWVudHMiIDogWyB7CiAgICAibmFtZSIgOiAicHJvZHVjdF9taW5lY3JhZnRfYmVkcm9jayIsCiAgICAic291cmNlIiA6ICJQVVJDSEFTRSIKICB9LCB7CiAgICAibmFtZSIgOiAiZ2FtZV9taW5lY3JhZnRfYmVkcm9jayIsCiAgICAic291cmNlIiA6ICJQVVJDSEFTRSIKICB9LCB7CiAgICAibmFtZSIgOiAicHJvZHVjdF9taW5lY3JhZnQiLAogICAgInNvdXJjZSIgOiAiTUNfUFVSQ0hBU0UiCiAgfSwgewogICAgIm5hbWUiIDogImdhbWVfbWluZWNyYWZ0IiwKICAgICJzb3VyY2UiIDogIk1DX1BVUkNIQVNFIgogIH0gXSwKICAic2lnbmVySWQiIDogIjI1MzU0NTczMDk1Nzg4NjQiLAogICJuYmYiIDogMTYzODU4NjQ1MywKICAicmVxdWVzdElkIiA6ICJjOGEwOWUyNS05ZjI1LTQwYmMtYTNiZi0zMzdkY2U4MGQ1NDIiLAogICJyaWRjciIgOiAiZGZjMDUwMTVhZTIwM2IyNiIsCiAgImV4cCIgOiAxNjM4NzU5NDMzLAogICJpYXQiIDogMTYzODU4NjYzMywKICAicGxhdGZvcm0iIDogIlBDX0xBVU5DSEVSIgp9.EA51R3SsPpcN9GLwX_T1g7hJ0Z0vcvUSOZb9c-4vBliY3EfvgH7y3hcUzPLu40kazkmE2hsRuG-TmgWYIdSqmprZZd390r4tCDtmo4wXqGrZ1OUDK3wdQLSBU0F2LLc2wqYTj0e1aehlYhHe3FfCSWP90gsmm__IoBgkKaMJkDT7R_7dqQCvwARvzwuN9XoFzakKuKRb1Lz7vMnstWCXqtwCeaZhOUs12A0mZvce4721Www3OVneRURf35wADV4cGNCzO91AqVzHjshLk0HehPMjzaO-gRAw_TiDxAQm2Md48Cf08OlNMdHzppMt04vg4FZh_HlqzIFhgi2L2Drq4uTHS_8SS4y1Zou10PPser0AmX5Uz3V_OaipRVgd4BQ0xnx4Q4DZkgVX0gh-FbBQ4X307-RGjl4AvnCG6yyx6tsctKeIrsmPSwcJYzGWxAk3A6VDgrXnvtMkw9bDNHrVzgwAl54BhvdxFRJl5knal9rc0-WVesf3wUc-h2lKO7vLz_e9lBhwf_4zFirkvrwjr_67mMp-a498GnvCuznTf633C4ygN-RTvaX51tgnR6PUwjdMlsqqTk4VsFAr3Ljl-rc526-EEQ6GPRk6tXZyUSfYMiL98J9Btc9rYNbDeJlNM2rM3Zd_okqyD1_xtPYjjuvwogxxM7t69oi9hM_v8Wc", - "keyId": "1", - "requestId": "c8a09e25-9f25-40bc-a3bf-337dce80d542" + 'signature': + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEiLCJ4NXQiOiJJVXRXd1l0clNfSXpJS0piaTZzNGtWaF9FNXMifQ.ewogICJlbnRpdGxlbWVudHMiIDogWyB7CiAgICAibmFtZSIgOiAicHJvZHVjdF9taW5lY3JhZnRfYmVkcm9jayIsCiAgICAic291cmNlIiA6ICJQVVJDSEFTRSIKICB9LCB7CiAgICAibmFtZSIgOiAiZ2FtZV9taW5lY3JhZnRfYmVkcm9jayIsCiAgICAic291cmNlIiA6ICJQVVJDSEFTRSIKICB9LCB7CiAgICAibmFtZSIgOiAicHJvZHVjdF9taW5lY3JhZnQiLAogICAgInNvdXJjZSIgOiAiTUNfUFVSQ0hBU0UiCiAgfSwgewogICAgIm5hbWUiIDogImdhbWVfbWluZWNyYWZ0IiwKICAgICJzb3VyY2UiIDogIk1DX1BVUkNIQVNFIgogIH0gXSwKICAic2lnbmVySWQiIDogIjI1MzU0NTczMDk1Nzg4NjQiLAogICJuYmYiIDogMTYzODU4NjQ1MywKICAicmVxdWVzdElkIiA6ICJjOGEwOWUyNS05ZjI1LTQwYmMtYTNiZi0zMzdkY2U4MGQ1NDIiLAogICJyaWRjciIgOiAiZGZjMDUwMTVhZTIwM2IyNiIsCiAgImV4cCIgOiAxNjM4NzU5NDMzLAogICJpYXQiIDogMTYzODU4NjYzMywKICAicGxhdGZvcm0iIDogIlBDX0xBVU5DSEVSIgp9.EA51R3SsPpcN9GLwX_T1g7hJ0Z0vcvUSOZb9c-4vBliY3EfvgH7y3hcUzPLu40kazkmE2hsRuG-TmgWYIdSqmprZZd390r4tCDtmo4wXqGrZ1OUDK3wdQLSBU0F2LLc2wqYTj0e1aehlYhHe3FfCSWP90gsmm__IoBgkKaMJkDT7R_7dqQCvwARvzwuN9XoFzakKuKRb1Lz7vMnstWCXqtwCeaZhOUs12A0mZvce4721Www3OVneRURf35wADV4cGNCzO91AqVzHjshLk0HehPMjzaO-gRAw_TiDxAQm2Md48Cf08OlNMdHzppMt04vg4FZh_HlqzIFhgi2L2Drq4uTHS_8SS4y1Zou10PPser0AmX5Uz3V_OaipRVgd4BQ0xnx4Q4DZkgVX0gh-FbBQ4X307-RGjl4AvnCG6yyx6tsctKeIrsmPSwcJYzGWxAk3A6VDgrXnvtMkw9bDNHrVzgwAl54BhvdxFRJl5knal9rc0-WVesf3wUc-h2lKO7vLz_e9lBhwf_4zFirkvrwjr_67mMp-a498GnvCuznTf633C4ygN-RTvaX51tgnR6PUwjdMlsqqTk4VsFAr3Ljl-rc526-EEQ6GPRk6tXZyUSfYMiL98J9Btc9rYNbDeJlNM2rM3Zd_okqyD1_xtPYjjuvwogxxM7t69oi9hM_v8Wc', + 'keyId': '1', + 'requestId': 'c8a09e25-9f25-40bc-a3bf-337dce80d542' } as T, statusCode: 200)); } else if (requestOptions.uri.toString() == - "https://api.minecraftservices.com/minecraft/profile" && - requestOptions.method == "GET") { + 'https://api.minecraftservices.com/minecraft/profile' && + requestOptions.method == 'GET') { return Future.value(Response( requestOptions: requestOptions, data: { - "id": mockUUID, - "name": "RPMTW", - "skins": [ + 'id': mockUUID, + 'name': 'RPMTW', + 'skins': [ { - "id": "6a6e65e5-76dd-4c3c-a625-162924514568", - "state": "ACTIVE", - "url": - "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b", - "variant": "CLASSIC", - "alias": "STEVE" + 'id': '6a6e65e5-76dd-4c3c-a625-162924514568', + 'state': 'ACTIVE', + 'url': + 'http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b', + 'variant': 'CLASSIC', + 'alias': 'STEVE' } ], - "capes": [] + 'capes': [] } as T, statusCode: 200)); } @@ -499,9 +499,9 @@ void main() { await tester.pumpAndSettle(); rpmHttpClientAdapter = (RequestOptions requestOptions) { - if (requestOptions.method == "GET" && + if (requestOptions.method == 'GET' && requestOptions.uri.toString() == - "$mojangMetaAPI/version_manifest_v2.json") { + '$mojangMetaAPI/version_manifest_v2.json') { return Future.value(Response( requestOptions: requestOptions, data: json.decode(TestData.versionManifest.getFileString()) as T, @@ -514,7 +514,7 @@ void main() { await tester.pumpAndSettle(); }); testWidgets( - "Instance Independent Setting", + 'Instance Independent Setting', (WidgetTester tester) async { InstanceConfig config = InstanceConfig.unknown(); @@ -523,7 +523,7 @@ void main() { Material( child: InstanceIndependentSetting(instanceConfig: config))); - expect(find.text(I18n.format("gui.default")), findsOneWidget); + expect(find.text(I18n.format('gui.default')), findsOneWidget); }, ); });