From 235b9f3a3657c0686c6ef8ccb97da7e95c4a9145 Mon Sep 17 00:00:00 2001 From: Tricked <72335827+SkyBlockDev@users.noreply.github.com> Date: Sun, 27 Feb 2022 23:49:02 +0100 Subject: [PATCH] feat: rewrite stuff --- lib/config.dart | 11 +- lib/main.dart | 4 - lib/src/models/auth.dart | 238 --------------- lib/src/models/models.dart | 8 + lib/src/models/models.g.dart | 556 ++++++++++++++++++++++++++++++++++ lib/src/screens/launcher.dart | 141 +++++++++ lib/src/screens/login.dart | 103 ------- lib/src/screens/mod.dart | 1 - lib/src/screens/settings.dart | 17 -- lib/src/screens/updater.dart | 22 +- lib/src/screens/version.dart | 3 +- lib/src/utils.dart | 42 ++- pubspec.lock | 36 +-- pubspec.yaml | 45 +-- 14 files changed, 763 insertions(+), 464 deletions(-) delete mode 100644 lib/src/models/auth.dart create mode 100644 lib/src/screens/launcher.dart delete mode 100644 lib/src/screens/login.dart diff --git a/lib/config.dart b/lib/config.dart index 6f67d3e..2cf129d 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -47,7 +47,7 @@ class Config { static Future initDb() async { isar = await Isar.open( - schemas: [InstalledModSchema], + schemas: [InstalledModSchema, VersionSchema], name: "data", directory: Config.appDir, inspector: true, @@ -80,13 +80,4 @@ class Config { static bool get icons { return _icons; } - - static set newMenu(bool v) { - Config.preferences?.setBool("new_menu", v); - _newMenu = v; - } - - static bool get newMenu { - return _newMenu; - } } diff --git a/lib/main.dart b/lib/main.dart index 448fd9b..1ea894e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tmodinstaller/config.dart'; import 'package:tmodinstaller/src/models/models.dart'; -import 'package:tmodinstaller/src/screens/login.dart'; import 'package:tmodinstaller/src/screens/modlist.dart'; import 'package:tmodinstaller/src/screens/settings.dart'; import 'package:tmodinstaller/src/screens/updater.dart'; @@ -29,7 +28,6 @@ import 'package:url_launcher/link.dart'; import 'package:url_strategy/url_strategy.dart'; import 'package:window_manager/window_manager.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:simple_auth_flutter/simple_auth_flutter.dart'; void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); @@ -129,7 +127,6 @@ class _TModInstallerPageState extends State { @override void initState() { super.initState(); - SimpleAuthFlutter.init(context); } int index = 0; @@ -216,7 +213,6 @@ class _TModInstallerPageState extends State { @override Widget build(BuildContext context) { - return LoginStatefulWidget(); final appTheme = context.watch(); final List lastversions = [ "1.8.9", diff --git a/lib/src/models/auth.dart b/lib/src/models/auth.dart deleted file mode 100644 index 54ff43b..0000000 --- a/lib/src/models/auth.dart +++ /dev/null @@ -1,238 +0,0 @@ -// THIS CODE IS LICENSED UNDER GPL-3 -// PORT OF https://github.com/ResetPower/Epherome/blob/main/src/core/auth.ts license: GPL-3 - -import 'dart:convert'; - -import 'package:http/http.dart' as http; -import 'package:simple_auth/simple_auth.dart' as simpleAuth; -import '../../config.dart'; - -enum Reason { - //"The account is not satisfied with this action" - satisfied -} - -class MSAuthError extends Error { - final Reason reason; - MSAuthError(this.reason); -} - -class TAzure extends simpleAuth.AzureADApi implements showAuthenticator { - TAzure(String identifier, String clientId, String tokenUrl, String resource, - String authorizationUrl, String redirectUrl) - : super(identifier, clientId, tokenUrl, resource, authorizationUrl, - redirectUrl); -} - -class MSAuth { - static const MICROSOFT_CLIENT_ID = "e2d21d17-35c3-46e6-9afd-f475b0f08c46"; - static const AZURE_TENNANT = "f8cdef31-a31e-4b4a-93e4-5f571e91255a"; - static final simpleAuth.AzureADApi azureApi = simpleAuth.AzureADApi( - "azure", - MICROSOFT_CLIENT_ID, - "https://login.microsoftonline.com/$AZURE_TENNANT/oauth2/authorize", - "https://login.microsoftonline.com/$AZURE_TENNANT/oauth2/token", - "https://management.azure.com/", - "redirecturl"); - - static Future authCode2AuthToken(String code) async { - var map = {}; - map["code"] = code; - map["client_id"] = MICROSOFT_CLIENT_ID; - map["grant_type"] = "authorization_code"; - map["redirect_uri"] = "https://login.live.com/oauth20_desktop.srf"; - map["scope"] = "service::user.auth.xboxlive.com::MBI_SSL"; - - try { - var r = await http.post( - Uri.parse("https://login.live.com/oauth20_token.srf"), - headers: {"Content-Type": "application/x-www-form-urlencoded"}, - body: map); - - return json.decode(r.body); - } catch (e) { - return {"err": true}; - } - } - - static Future authToken2XBLToken(String token) async { - var map = { - "Properties": { - "AuthMethod": "RPS", - "SiteName": "user.auth.xboxlive.com", - "RpsTicket": token, - }, - "RelyingParty": "http://auth.xboxlive.com", - "TokenType": "JWT", - }; - - try { - var r = await http.post( - Uri.parse("https://user.auth.xboxlive.com/user/authenticate"), - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - }, - body: map); - - return json.decode(r.body); - } catch (e) { - return {"err": true}; - } - } - - static Future XBLToken2XSTSToken(String token) async { - var map = { - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [token], - }, - "RelyingParty": "rp://api.minecraftservices.com/", - "TokenType": "JWT", - }; - - try { - var r = await http.post( - Uri.parse("https://xsts.auth.xboxlive.com/xsts/authorize"), - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - }, - body: map); - - return json.decode(r.body); - } catch (e) { - return {"err": true}; - } - } - - static XSTSToken2MinecraftToken(String token, String uhs) async { - var map = { - "identityToken": "XBL3.0 x=${uhs};${token}", - }; - - try { - var r = await http.post( - Uri.parse( - "https://api.minecraftservices.com/authentication/login_with_xbox"), - headers: { - "Content-Type": "application/json", - }, - body: map); - - return json.decode(r.body); - } catch (e) { - return {"err": true}; - } - } - - static checkMinecraftOwnership(String token) async { - try { - var r = await http.get( - Uri.parse("https://api.minecraftservices.com/entitlements/mcstore"), - headers: { - "Content-Type": "application/json", - "Authorization": "Bearer ${token}", - }, - ); - - return json.decode(r.body); - } catch (e) { - return {"err": true}; - } - } - - static getMicrosoftMinecraftProfile(String token) async { - try { - var r = await http.get( - Uri.parse("https://api.minecraftservices.com/minecraft/profile"), - headers: { - "Content-Type": "application/json", - "Authorization": "Bearer ${token}", - }, - ); - - return json.decode(r.body); - } catch (e) { - return {"err": true}; - } - } - - static bool validateMicrosoft(String token) { - final payload = utf8.decode(base64.decode(token.split(".")[0])); - final params = json.decode(payload); - return (DateTime.now().millisecondsSinceEpoch / 1000).floor() < - params["exp"]; - } - - static Future refreshMicrosoft(Map account) async { - try { - if (!account["refreshToken"] || account["mode"] != "microsoft") { - throw MSAuthError(Reason.satisfied); - } - var r = await http.post( - Uri.parse("https://user.auth.xboxlive.com/user/authenticate"), - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - }, - body: { - "client_id": MICROSOFT_CLIENT_ID, - "refresh_token": account["refreshToken"], - "grant_type": "refresh_token", - "redirect_uri": "https://login.live.com/oauth20_desktop.srf", - }); - var params = json.decode(r.body); - var mcTokenResult = - await authToken2MinecraftTokenDirectly(params["access_token"]); - var mcToken = mcTokenResult["token"]; - if (mcTokenResult["err"] || !mcToken) { - return false; - } - // updateAccountToken(account, mcToken, params["refresh_token"]); - Config.preferences?.setString("account", json.encode(account)); - Config.preferences?.setString("mcToken", json.encode(mcToken)); - Config.preferences?.setString("refresh_token", params["refresh_token"]); - return true; - } catch (e) { - return false; - } - } - - static Future authToken2MinecraftTokenDirectly(String authToken) async { - var unsuccessfulWith = (String msg) { - return { - "err": msg, - }; - }; - // Authorization Token -> XBL Token - var XBLTokenResult = await authToken2XBLToken(authToken); - var XBLToken = XBLTokenResult["Token"]; - if (XBLTokenResult["error"] || !XBLToken) { - // unable to get xbl token - return unsuccessfulWith( - "Unable to get XBL token at microsoft authenticating"); - } - - // XBL Token -> XSTS Token - var XSTSTokenResult = await XBLToken2XSTSToken(XBLToken); - var XSTSToken = XSTSTokenResult["Token"]; - var XSTSTokenUhs = XSTSTokenResult["DisplayClaims"]?["xui"][0]["uhs"]; - if (XSTSTokenResult["error"] || !XSTSToken || !XSTSTokenUhs) { - // unable to get xsts token - return unsuccessfulWith( - "Unable to XSTS auth token at microsoft authenticating"); - } - - // XSTS Token -> Minecraft Token - var minecraftTokenResult = - await XSTSToken2MinecraftToken(XSTSToken, XSTSTokenUhs); - var minecraftToken = minecraftTokenResult.access_token; - if (minecraftTokenResult.error || !minecraftToken) { - // unable to get minecraft token - return unsuccessfulWith("Unable to get Minecraft token"); - } - - return {"token": minecraftToken}; - } -} diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 1581b38..94144f0 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -83,3 +83,11 @@ class InstalledMod { late String repo; late String mcv; } + +@Collection() +class Version { + @Id() + int? id; + late String moddir; + late String version; +} diff --git a/lib/src/models/models.g.dart b/lib/src/models/models.g.dart index 33cf6cc..b1334c7 100644 --- a/lib/src/models/models.g.dart +++ b/lib/src/models/models.g.dart @@ -1290,6 +1290,562 @@ extension InstalledModQueryProperty } } +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast + +extension GetVersionCollection on Isar { + IsarCollection get versions { + return getCollection('Version'); + } +} + +final VersionSchema = CollectionSchema( + name: 'Version', + schema: + '{"name":"Version","idName":"id","properties":[{"name":"moddir","type":"String"},{"name":"version","type":"String"}],"indexes":[],"links":[]}', + nativeAdapter: const _VersionNativeAdapter(), + webAdapter: const _VersionWebAdapter(), + idName: 'id', + propertyIds: {'moddir': 0, 'version': 1}, + listProperties: {}, + indexIds: {}, + indexTypes: {}, + linkIds: {}, + backlinkIds: {}, + linkedCollections: [], + getId: (obj) { + if (obj.id == Isar.autoIncrement) { + return null; + } else { + return obj.id; + } + }, + setId: (obj, id) => obj.id = id, + getLinks: (obj) => [], + version: 2, +); + +class _VersionWebAdapter extends IsarWebTypeAdapter { + const _VersionWebAdapter(); + + @override + Object serialize(IsarCollection collection, Version object) { + final jsObj = IsarNative.newJsObject(); + IsarNative.jsObjectSet(jsObj, 'id', object.id); + IsarNative.jsObjectSet(jsObj, 'moddir', object.moddir); + IsarNative.jsObjectSet(jsObj, 'version', object.version); + return jsObj; + } + + @override + Version deserialize(IsarCollection collection, dynamic jsObj) { + final object = Version(); + object.id = IsarNative.jsObjectGet(jsObj, 'id'); + object.moddir = IsarNative.jsObjectGet(jsObj, 'moddir') ?? ''; + object.version = IsarNative.jsObjectGet(jsObj, 'version') ?? ''; + return object; + } + + @override + P deserializeProperty

(Object jsObj, String propertyName) { + switch (propertyName) { + case 'id': + return (IsarNative.jsObjectGet(jsObj, 'id')) as P; + case 'moddir': + return (IsarNative.jsObjectGet(jsObj, 'moddir') ?? '') as P; + case 'version': + return (IsarNative.jsObjectGet(jsObj, 'version') ?? '') as P; + default: + throw 'Illegal propertyName'; + } + } + + @override + void attachLinks(Isar isar, int id, Version object) {} +} + +class _VersionNativeAdapter extends IsarNativeTypeAdapter { + const _VersionNativeAdapter(); + + @override + void serialize(IsarCollection collection, IsarRawObject rawObj, + Version object, int staticSize, List offsets, AdapterAlloc alloc) { + var dynamicSize = 0; + final value0 = object.moddir; + final _moddir = IsarBinaryWriter.utf8Encoder.convert(value0); + dynamicSize += (_moddir.length) as int; + final value1 = object.version; + final _version = IsarBinaryWriter.utf8Encoder.convert(value1); + dynamicSize += (_version.length) as int; + final size = staticSize + dynamicSize; + + rawObj.buffer = alloc(size); + rawObj.buffer_length = size; + final buffer = IsarNative.bufAsBytes(rawObj.buffer, size); + final writer = IsarBinaryWriter(buffer, staticSize); + writer.writeBytes(offsets[0], _moddir); + writer.writeBytes(offsets[1], _version); + } + + @override + Version deserialize(IsarCollection collection, int id, + IsarBinaryReader reader, List offsets) { + final object = Version(); + object.id = id; + object.moddir = reader.readString(offsets[0]); + object.version = reader.readString(offsets[1]); + return object; + } + + @override + P deserializeProperty

( + int id, IsarBinaryReader reader, int propertyIndex, int offset) { + switch (propertyIndex) { + case -1: + return id as P; + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + default: + throw 'Illegal propertyIndex'; + } + } + + @override + void attachLinks(Isar isar, int id, Version object) {} +} + +extension VersionQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return addWhereClauseInternal(const WhereClause(indexName: null)); + } +} + +extension VersionQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(int? id) { + return addWhereClauseInternal(WhereClause( + indexName: null, + lower: [id], + includeLower: true, + upper: [id], + includeUpper: true, + )); + } + + QueryBuilder idNotEqualTo(int? id) { + if (whereSortInternal == Sort.asc) { + return addWhereClauseInternal(WhereClause( + indexName: null, + upper: [id], + includeUpper: false, + )).addWhereClauseInternal(WhereClause( + indexName: null, + lower: [id], + includeLower: false, + )); + } else { + return addWhereClauseInternal(WhereClause( + indexName: null, + lower: [id], + includeLower: false, + )).addWhereClauseInternal(WhereClause( + indexName: null, + upper: [id], + includeUpper: false, + )); + } + } + + QueryBuilder idGreaterThan( + int? id, { + bool include = false, + }) { + return addWhereClauseInternal(WhereClause( + indexName: null, + lower: [id], + includeLower: include, + )); + } + + QueryBuilder idLessThan( + int? id, { + bool include = false, + }) { + return addWhereClauseInternal(WhereClause( + indexName: null, + upper: [id], + includeUpper: include, + )); + } + + QueryBuilder idBetween( + int? lowerId, + int? upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return addWhereClauseInternal(WhereClause( + indexName: null, + lower: [lowerId], + includeLower: includeLower, + upper: [upperId], + includeUpper: includeUpper, + )); + } +} + +extension VersionQueryFilter + on QueryBuilder { + QueryBuilder idIsNull() { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.isNull, + property: 'id', + value: null, + )); + } + + QueryBuilder idEqualTo(int? value) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.eq, + property: 'id', + value: value, + )); + } + + QueryBuilder idGreaterThan( + int? value, { + bool include = false, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.gt, + include: include, + property: 'id', + value: value, + )); + } + + QueryBuilder idLessThan( + int? value, { + bool include = false, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.lt, + include: include, + property: 'id', + value: value, + )); + } + + QueryBuilder idBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return addFilterConditionInternal(FilterCondition.between( + property: 'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + } + + QueryBuilder moddirEqualTo( + String value, { + bool caseSensitive = true, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.eq, + property: 'moddir', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder moddirGreaterThan( + String value, { + bool caseSensitive = true, + bool include = false, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.gt, + include: include, + property: 'moddir', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder moddirLessThan( + String value, { + bool caseSensitive = true, + bool include = false, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.lt, + include: include, + property: 'moddir', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder moddirBetween( + String lower, + String upper, { + bool caseSensitive = true, + bool includeLower = true, + bool includeUpper = true, + }) { + return addFilterConditionInternal(FilterCondition.between( + property: 'moddir', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder moddirStartsWith( + String value, { + bool caseSensitive = true, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.startsWith, + property: 'moddir', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder moddirEndsWith( + String value, { + bool caseSensitive = true, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.endsWith, + property: 'moddir', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder moddirContains( + String value, + {bool caseSensitive = true}) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.contains, + property: 'moddir', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder moddirMatches( + String pattern, + {bool caseSensitive = true}) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.matches, + property: 'moddir', + value: pattern, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder versionEqualTo( + String value, { + bool caseSensitive = true, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.eq, + property: 'version', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder versionGreaterThan( + String value, { + bool caseSensitive = true, + bool include = false, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.gt, + include: include, + property: 'version', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder versionLessThan( + String value, { + bool caseSensitive = true, + bool include = false, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.lt, + include: include, + property: 'version', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder versionBetween( + String lower, + String upper, { + bool caseSensitive = true, + bool includeLower = true, + bool includeUpper = true, + }) { + return addFilterConditionInternal(FilterCondition.between( + property: 'version', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder versionStartsWith( + String value, { + bool caseSensitive = true, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.startsWith, + property: 'version', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder versionEndsWith( + String value, { + bool caseSensitive = true, + }) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.endsWith, + property: 'version', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder versionContains( + String value, + {bool caseSensitive = true}) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.contains, + property: 'version', + value: value, + caseSensitive: caseSensitive, + )); + } + + QueryBuilder versionMatches( + String pattern, + {bool caseSensitive = true}) { + return addFilterConditionInternal(FilterCondition( + type: ConditionType.matches, + property: 'version', + value: pattern, + caseSensitive: caseSensitive, + )); + } +} + +extension VersionQueryLinks + on QueryBuilder {} + +extension VersionQueryWhereSortBy on QueryBuilder { + QueryBuilder sortById() { + return addSortByInternal('id', Sort.asc); + } + + QueryBuilder sortByIdDesc() { + return addSortByInternal('id', Sort.desc); + } + + QueryBuilder sortByModdir() { + return addSortByInternal('moddir', Sort.asc); + } + + QueryBuilder sortByModdirDesc() { + return addSortByInternal('moddir', Sort.desc); + } + + QueryBuilder sortByVersion() { + return addSortByInternal('version', Sort.asc); + } + + QueryBuilder sortByVersionDesc() { + return addSortByInternal('version', Sort.desc); + } +} + +extension VersionQueryWhereSortThenBy + on QueryBuilder { + QueryBuilder thenById() { + return addSortByInternal('id', Sort.asc); + } + + QueryBuilder thenByIdDesc() { + return addSortByInternal('id', Sort.desc); + } + + QueryBuilder thenByModdir() { + return addSortByInternal('moddir', Sort.asc); + } + + QueryBuilder thenByModdirDesc() { + return addSortByInternal('moddir', Sort.desc); + } + + QueryBuilder thenByVersion() { + return addSortByInternal('version', Sort.asc); + } + + QueryBuilder thenByVersionDesc() { + return addSortByInternal('version', Sort.desc); + } +} + +extension VersionQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctById() { + return addDistinctByInternal('id'); + } + + QueryBuilder distinctByModdir( + {bool caseSensitive = true}) { + return addDistinctByInternal('moddir', caseSensitive: caseSensitive); + } + + QueryBuilder distinctByVersion( + {bool caseSensitive = true}) { + return addDistinctByInternal('version', caseSensitive: caseSensitive); + } +} + +extension VersionQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return addPropertyNameInternal('id'); + } + + QueryBuilder moddirProperty() { + return addPropertyNameInternal('moddir'); + } + + QueryBuilder versionProperty() { + return addPropertyNameInternal('version'); + } +} + // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** diff --git a/lib/src/screens/launcher.dart b/lib/src/screens/launcher.dart new file mode 100644 index 0000000..28623a3 --- /dev/null +++ b/lib/src/screens/launcher.dart @@ -0,0 +1,141 @@ +// TMOD Installer (c) by Tricked-dev +// +// TMOD Installer is licensed under a +// Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License. +// +// You should have received a copy of the license along with this +// work. If not, see . + +import 'package:flutter/foundation.dart'; +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/material.dart' as flutter; +import 'package:flutter_acrylic/flutter_acrylic.dart' as flutter_acrylic; +import 'package:provider/provider.dart'; +import 'package:tmodinstaller/config.dart'; +import 'package:tmodinstaller/src/models/models.dart'; +import 'package:tmodinstaller/src/utils.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'dart:io'; +import '../../theme.dart'; +import 'package:isar/isar.dart'; +import 'package:collection/collection.dart'; +import 'package:path/path.dart'; + +class Launcher extends StatefulWidget { + const Launcher({Key? key, this.controller, required this.mcv}) + : super(key: key); + final String mcv; + + final ScrollController? controller; + @override + _LauncherState createState() => _LauncherState(); +} + +class _LauncherState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + String _modfolder = ""; + String _newmodfolder = ""; + @override + Widget build(BuildContext context) { + var versions = Config.isar.versions.where().findAllSync(); + var version = + versions.firstWhereOrNull((element) => element.version == widget.mcv); + _modfolder = getModFolder(widget.mcv); + + const spacer = SizedBox(height: 10.0); + const biggerSpacer = SizedBox(height: 40.0); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Refresh mod folder", + style: FluentTheme.of(context).typography.subtitle), + spacer, + flutter.SelectableText( + "This will empty the current mod folder and install all mods from this version that you installed", + ), + FilledButton( + child: Text("Click here!"), + onPressed: () async { + await Directory(_modfolder).delete(recursive: true); + await Directory(_modfolder).create(); + var files = + Directory("${Config.appDir}/modlists/${widget.mcv}/") + .listSync(); + for (var element in files) { + await File(element.path) + .copy("$_modfolder/${basename(element.path)}"); + // element.("$_modfolder/${basename(element.path)}"); + } + ; + }), + biggerSpacer, + Text("Mod folder", + style: FluentTheme.of(context).typography.subtitle), + spacer, + flutter.SelectableText( + "Current directory $_modfolder", + ), + spacer, + TextBox( + placeholder: 'Change ', + onEditingComplete: () {}, + onChanged: (v) { + _newmodfolder = v; + }, + suffix: IconButton( + icon: const Icon(FluentIcons.add_to), + onPressed: () async { + var fold = _newmodfolder + .replaceFirst( + "~", Platform.environment['HOME'] ?? "NO_HOME") + .replaceFirst("%APPDATA%", + Platform.environment['APPDATA'] ?? "NO_APPDATA"); + var r = await Directory(fold).exists(); + if (r) { + final data = Version() + ..version = widget.mcv + ..moddir = fold; + if (version?.id != null) { + data.id = version?.id; + } + await Config.isar.writeTxn((isar) async { + data.id = await isar.versions.put(data); + }); + setState(() {}); + } else { + showDialog( + context: context, + builder: (BuildContext context) => _invaliddir(context), + ); + } + }, + ), + ), + ]); + } + + Widget _invaliddir(BuildContext context) { + return ContentDialog( + title: Text("Directory does not exist"), + content: Text("beep boop"), + actions: [ + FilledButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Close'), + ), + ], + ); + } +} diff --git a/lib/src/screens/login.dart b/lib/src/screens/login.dart deleted file mode 100644 index a352f21..0000000 --- a/lib/src/screens/login.dart +++ /dev/null @@ -1,103 +0,0 @@ -// TMOD Installer (c) by Tricked-dev -// -// TMOD Installer is licensed under a -// Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License. -// -// You should have received a copy of the license along with this -// work. If not, see . - -import 'package:fluent_ui/fluent_ui.dart'; -import 'package:tmodinstaller/src/models/auth.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:social_login_buttons/social_login_buttons.dart'; - -class LoginStatefulWidget extends StatefulWidget { - const LoginStatefulWidget({Key? key}) : super(key: key); - - @override - State createState() => _LoginStatefulWidgetState(); -} - -class _LoginStatefulWidgetState extends State { - TextEditingController nameController = TextEditingController(); - TextEditingController passwordController = TextEditingController(); - //Source https://www.tutorialkart.com/flutter/flutter-login-screen/ - @override - Widget build(BuildContext context) { - return Center( - child: SocialLoginButton( - buttonType: SocialLoginButtonType.microsoft, - onPressed: () async { - var success = await MSAuth.azureApi.authenticate(); - print(success?.toJson()); - // showMessage("Logged in success: $success"); - }, - ), - ); - // return Padding( - // padding: const EdgeInsets.all(10), - // child: ListView( - // children: [ - // Container( - // alignment: Alignment.center, - // padding: const EdgeInsets.all(10), - // child: const Text( - // 'TMod Installer', - // style: TextStyle( - // color: Color(0xff60cfbc), - // fontWeight: FontWeight.w500, - // fontSize: 30), - // )), - // Container( - // alignment: Alignment.center, - // padding: const EdgeInsets.all(10), - // child: const Text( - // 'Log into Microsoft Account', - // style: TextStyle(fontSize: 20), - // )), - // Container( - // padding: const EdgeInsets.all(10), - // child: TextBox( - // controller: nameController, - // placeholder: "Username", - // // decoration: const InputDecoration( - // // border: OutlineInputBorder(), - // // labelText: 'User Name', - // // ), - // ), - // ), - // Container( - // padding: const EdgeInsets.fromLTRB(10, 10, 10, 0), - // child: TextBox( - // obscureText: true, - // controller: passwordController, - // placeholder: "Password", - // // decoration: const InputDecoration( - // // border: OutlineInputBorder(), - // // labelText: 'Password', - // // ), - // ), - // ), - // TextButton( - // onPressed: () async { - // await launch("https://www.minecraft.net/en-us/password/forgot"); - // //forgot password screen - // }, - // child: const Text( - // 'Forgot Password', - // ), - // ), - // Container( - // // height: 50, - // padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), - // child: OutlinedButton( - // child: const Text('Microsoft Login'), - // onPressed: () { - // print(nameController.text); - // print(passwordController.text); - // }, - // )), - // ], - // )); - } -} diff --git a/lib/src/screens/mod.dart b/lib/src/screens/mod.dart index 23f69a5..39d1439 100644 --- a/lib/src/screens/mod.dart +++ b/lib/src/screens/mod.dart @@ -16,7 +16,6 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:markdown/markdown.dart' as md; import 'dart:convert'; -import 'package:filesystem_picker/filesystem_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/material.dart' as flutter; diff --git a/lib/src/screens/settings.dart b/lib/src/screens/settings.dart index 0145790..a0bdae7 100644 --- a/lib/src/screens/settings.dart +++ b/lib/src/screens/settings.dart @@ -6,7 +6,6 @@ // You should have received a copy of the license along with this // work. If not, see . -import 'package:filesystem_picker/filesystem_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/material.dart' as flutter; @@ -160,7 +159,6 @@ class _SettingsState extends State { ), ], ), - biggerSpacer, Text("Mod folder", style: FluentTheme.of(context).typography.subtitle), flutter.SelectableText( @@ -224,21 +222,6 @@ class _SettingsState extends State { spacer, const Text( "TMod Installer created by Tricked-dev licensed under: Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License."), - - // OutlinedButton( - // child: Text("Change folder!"), - // onPressed: () async { - // String? path = await FilesystemPicker.open( - // title: 'Save to folder', - // context: context, - // rootDirectory: Directory("/"), - // fsType: FilesystemType.folder, - // pickText: 'Save file to this folder', - // folderIconColor: Colors.teal, - // ); - // print(path); - // }, - // ) ], ); } diff --git a/lib/src/screens/updater.dart b/lib/src/screens/updater.dart index c5ad000..6e6d750 100644 --- a/lib/src/screens/updater.dart +++ b/lib/src/screens/updater.dart @@ -7,7 +7,6 @@ // work. If not, see . import 'dart:convert'; -import 'package:filesystem_picker/filesystem_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/material.dart' as flutter; @@ -21,6 +20,7 @@ import 'package:path/path.dart'; import '../../theme.dart'; import 'package:http/http.dart' as http; import 'package:collection/collection.dart'; +import 'package:isar/isar.dart'; class Updater extends StatefulWidget { const Updater({Key? key, this.controller, required this.version}) @@ -51,20 +51,16 @@ class _UpdaterState extends State { .where((element) => element.mcv == widget.version) .toList(); return Column(children: [ - Center( - child: OutlinedButton(onPressed: () {}, child: Text("Update all")), - ), + // Center( + // child: OutlinedButton(onPressed: () {}, child: Text("Update all")), + // ), Column(children: [ ...files.map((mod) { final style = FluentTheme.of(context); - // Mod? foundMod; - // DownloadMod? current; - // DownloadMod? update; currentMods.forEach((element) { print(element.mcv); }); - print(currentMods); var data = currentMods .firstWhereOrNull((x) => x.filename == basename(mod.path)); @@ -80,9 +76,7 @@ class _UpdaterState extends State { element.filename == basename(mod.path) && element.mcversions.contains(widget.version)) ?? update; - print(update?.filename); - print(current?.filename); - print(data?.filename); + return HoverButton( autofocus: true, builder: ((p0, state) { @@ -173,12 +167,16 @@ class _UpdaterState extends State { OutlinedButton( child: Text("Delete"), onPressed: () async { - await mod.delete(); if (data != null) { + await UtilMod( + basename(mod.path), widget.version) + .delete(); await Config.isar.writeTxn((isar) async { await Config.isar.installedMods .delete(data.id!); }); + } else { + await mod.delete(); } setState(() {}); }) diff --git a/lib/src/screens/version.dart b/lib/src/screens/version.dart index 05bec19..10be524 100644 --- a/lib/src/screens/version.dart +++ b/lib/src/screens/version.dart @@ -19,6 +19,7 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' as flutter; import 'package:tmodinstaller/config.dart'; +import 'package:tmodinstaller/src/screens/launcher.dart'; import 'package:tmodinstaller/src/screens/mod.dart'; import 'package:tmodinstaller/src/screens/modlist.dart'; import 'package:tmodinstaller/src/screens/updater.dart'; @@ -116,7 +117,7 @@ class _VersionPage extends State { ? ModListsPage(mods: widget.mods, version: widget.version) : index == 2 ? Updater(version: widget.version) - : Text("TODO") + : Launcher(mcv: widget.version) ]), ), ), diff --git a/lib/src/utils.dart b/lib/src/utils.dart index e925131..4eae8a3 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -9,11 +9,14 @@ import 'dart:convert'; import 'dart:io'; import 'dart:developer'; +import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:fluent_ui/fluent_ui.dart'; +import 'package:isar/isar.dart'; import 'package:tmodinstaller/config.dart'; import 'models/models.dart'; +import 'package:collection/collection.dart'; List mods = []; @@ -28,12 +31,41 @@ Map defaultDirectories = { TargetPlatform.linux: "${Platform.environment['HOME']}/.config/tmodinstaller", }; +class UtilMod { + final String name; + final String mcv; + + UtilMod(this.name, this.mcv); + + write(Uint8List bytes) async { + try { + await Future.wait([ + File("${Config.appDir}/modlists/${mcv}/${name}").writeAsBytes(bytes), + File("${getModFolder(mcv)}/${name}").writeAsBytes(bytes) + ]); + } catch (e) { + print(e); + } + } + + delete() async { + try { + await Future.wait([ + File("${Config.appDir}/modlists/${mcv}/${name}").delete(), + File("${getModFolder(mcv)}/${name}").delete() + ]); + } catch (e) { + print(e); + } + } +} + Future installMod(Mod mod, DownloadMod version, String mcv) async { final response = await http.get(Uri.parse(version.url)); //TODO: Hashing! await Directory("${Config.appDir}/modlists/${mcv}/").create(recursive: true); - await File("${Config.appDir}/modlists/${mcv}/${version.filename}") - .writeAsBytes(response.bodyBytes); + + UtilMod(version.filename, mcv).write(response.bodyBytes); final data = InstalledMod() ..modId = mod.id @@ -47,3 +79,9 @@ Future installMod(Mod mod, DownloadMod version, String mcv) async { data.id = await isar.installedMods.put(data); }); } + +String getModFolder(String mcv) { + var versions = Config.isar.versions.where().findAllSync(); + var version = versions.firstWhereOrNull((element) => element.version == mcv); + return version?.moddir ?? Config.directory; +} diff --git a/pubspec.lock b/pubspec.lock index 03ca31e..d4118b2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -176,13 +176,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.2" - filesystem_picker: - dependency: "direct main" - description: - name: filesystem_picker - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" fixnum: dependency: transitive description: @@ -411,7 +404,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_drawing: dependency: transitive description: @@ -615,32 +608,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" - simple_auth: - dependency: "direct main" - description: - name: simple_auth - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - simple_auth_flutter: - dependency: "direct main" - description: - name: simple_auth_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.11" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" - social_login_buttons: - dependency: "direct main" - description: - name: social_login_buttons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6+2" source_gen: dependency: transitive description: @@ -661,7 +633,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -780,7 +752,7 @@ packages: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.0.9" url_launcher_windows: dependency: transitive description: @@ -829,7 +801,7 @@ packages: name: window_manager url: "https://pub.dartlang.org" source: hosted - version: "0.1.7" + version: "0.1.8" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9780aa6..e485dc1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,72 +42,29 @@ dependencies: url_strategy: ^0.2.0 window_manager: ^0.1.6 shared_preferences: ^2.0.8 - filesystem_picker: ^2.0.0 flutter_svg: ^1.0.3 args: ^2.3.0 flutter_markdown: ^0.6.9 path_provider: ^2.0.9 isar: 2.2.1 isar_flutter_libs: 2.2.1 # contains the binarie - social_login_buttons: ^1.0.6+2 - simple_auth: ^2.0.10 - simple_auth_flutter: ^2.0.11 dev_dependencies: - # flutter_test: - # sdk: flutter build_runner: ^2.0.0 json_serializable: ^6.0.0 dependency_validator: ^3.0.0 isar_generator: 2.2.1 - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^1.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec -# The following section is specific to Flutter. flutter: assets: - assets/Logo.svg - assets/tmodinstaller.png - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages + uses-material-design: true - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages