mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-19 01:14:40 +08:00
Update
This commit is contained in:
@@ -1,19 +1,75 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
import 'api_result.dart';
|
||||
import 'status.dart';
|
||||
import 'token.dart';
|
||||
import 'user.dart';
|
||||
|
||||
part 'client.g.dart';
|
||||
|
||||
@RestApi(parser: Parser.FlutterCompute)
|
||||
final _pbkdf2a = Pbkdf2(
|
||||
macAlgorithm: Hmac.sha512(),
|
||||
iterations: 210000,
|
||||
bits: 512,
|
||||
);
|
||||
|
||||
final _pbkdf2b = Pbkdf2(
|
||||
macAlgorithm: Hmac.sha512(),
|
||||
iterations: 1000,
|
||||
bits: 512,
|
||||
);
|
||||
|
||||
const _utf8Encoder = Utf8Encoder();
|
||||
final _salt = _utf8Encoder.convert("eh-downloader-salt");
|
||||
|
||||
@RestApi()
|
||||
abstract class EHApi {
|
||||
factory EHApi(Dio dio, {String baseUrl}) = _EHApi;
|
||||
|
||||
@PUT('/user')
|
||||
Future<ApiResult<int>> createUser(
|
||||
@Query("name") String name, @Query("password") String password,
|
||||
{@Query("is_admin") bool? isAdmin,
|
||||
@Query("permissions") int? permissions});
|
||||
@GET('/user')
|
||||
Future<ApiResult<BUser>> getUser();
|
||||
@GET('/user')
|
||||
Future<ApiResult<BUser>> getUserById(@Query("id") int id);
|
||||
@GET('/user')
|
||||
Future<ApiResult<BUser>> getUserByUsername(
|
||||
@Query("username") String username);
|
||||
Future<ApiResult<BUser>> getUser(
|
||||
{@Query("id") int? id, @Query("username") String? username});
|
||||
|
||||
@GET('/status')
|
||||
Future<ApiResult<ServerStatus>> getStatus();
|
||||
|
||||
@PUT('/token')
|
||||
Future<ApiResult<Token>> _createToken(
|
||||
{@Query("username") String username,
|
||||
@Query("password") String password,
|
||||
@Query("t") int t,
|
||||
@Query("set_cookie") bool? setCookie,
|
||||
@Query("http_only") bool? httpOnly,
|
||||
@Query("secure") bool? secure});
|
||||
Future<ApiResult<Token>> createToken(
|
||||
{required String username,
|
||||
required String password,
|
||||
bool? setCookie,
|
||||
bool? httpOnly,
|
||||
bool? secure}) async {
|
||||
int t = DateTime.now().millisecondsSinceEpoch;
|
||||
final p =
|
||||
await _pbkdf2a.deriveKeyFromPassword(password: password, nonce: _salt);
|
||||
final p2 = await _pbkdf2b.deriveKey(
|
||||
secretKey: p, nonce: _utf8Encoder.convert(t.toString()));
|
||||
final p3 = base64Encode(await p2.extractBytes());
|
||||
return await _createToken(
|
||||
username: username,
|
||||
password: p3,
|
||||
t: t,
|
||||
setCookie: setCookie,
|
||||
httpOnly: httpOnly,
|
||||
secure: secure);
|
||||
}
|
||||
@DELETE('/token')
|
||||
Future<ApiResult<bool>> deleteToken({@Query("token") String? token});
|
||||
@GET('/token')
|
||||
Future<ApiResult<Token>> getToken({@Query("token") String? token});
|
||||
}
|
||||
|
||||
@@ -19,20 +19,98 @@ class _EHApi implements EHApi {
|
||||
String? baseUrl;
|
||||
|
||||
@override
|
||||
Future<ApiResult<BUser>> getUser() async {
|
||||
Future<ApiResult<int>> createUser(
|
||||
String name,
|
||||
String password, {
|
||||
bool? isAdmin,
|
||||
int? permissions,
|
||||
}) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{
|
||||
r'name': name,
|
||||
r'password': password,
|
||||
r'is_admin': isAdmin,
|
||||
r'permissions': permissions,
|
||||
};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result = await _dio
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<int>>(Options(
|
||||
method: 'PUT',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/user',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = ApiResult<int>.fromJson(
|
||||
_result.data!,
|
||||
(json) => json as int,
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ApiResult<BUser>> getUser({
|
||||
int? id,
|
||||
String? username,
|
||||
}) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{
|
||||
r'id': id,
|
||||
r'username': username,
|
||||
};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result = await _dio
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<BUser>>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/user',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = ApiResult<BUser>.fromJson(
|
||||
_result.data!,
|
||||
(json) => BUser.fromJson(json as Map<String, dynamic>),
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ApiResult<ServerStatus>> getStatus() async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result = await _dio
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<BUser>>(Options(
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(
|
||||
_setStreamType<ApiResult<ServerStatus>>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/user',
|
||||
'/status',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
@@ -41,25 +119,43 @@ class _EHApi implements EHApi {
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = await compute(deserializeApiResult<BUser>, _result.data!);
|
||||
final value = ApiResult<ServerStatus>.fromJson(
|
||||
_result.data!,
|
||||
(json) => ServerStatus.fromJson(json as Map<String, dynamic>),
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ApiResult<BUser>> getUserById(int id) async {
|
||||
Future<ApiResult<Token>> _createToken({
|
||||
required String username,
|
||||
required String password,
|
||||
required int t,
|
||||
bool? setCookie,
|
||||
bool? httpOnly,
|
||||
bool? secure,
|
||||
}) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{r'id': id};
|
||||
final queryParameters = <String, dynamic>{
|
||||
r'username': username,
|
||||
r'password': password,
|
||||
r't': t,
|
||||
r'set_cookie': setCookie,
|
||||
r'http_only': httpOnly,
|
||||
r'secure': secure,
|
||||
};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result = await _dio
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<BUser>>(Options(
|
||||
method: 'GET',
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<Token>>(Options(
|
||||
method: 'PUT',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/user',
|
||||
'/token',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
@@ -68,25 +164,29 @@ class _EHApi implements EHApi {
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = await compute(deserializeApiResult<BUser>, _result.data!);
|
||||
final value = ApiResult<Token>.fromJson(
|
||||
_result.data!,
|
||||
(json) => Token.fromJson(json as Map<String, dynamic>),
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ApiResult<BUser>> getUserByUsername(String username) async {
|
||||
Future<ApiResult<bool>> deleteToken({String? token}) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{r'username': username};
|
||||
final queryParameters = <String, dynamic>{r'token': token};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result = await _dio
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<BUser>>(Options(
|
||||
method: 'GET',
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<bool>>(Options(
|
||||
method: 'DELETE',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/user',
|
||||
'/token',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
@@ -95,7 +195,41 @@ class _EHApi implements EHApi {
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = await compute(deserializeApiResult<BUser>, _result.data!);
|
||||
final value = ApiResult<bool>.fromJson(
|
||||
_result.data!,
|
||||
(json) => json as bool,
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ApiResult<Token>> getToken({String? token}) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{r'token': token};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result = await _dio
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<Token>>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/token',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = ApiResult<Token>.fromJson(
|
||||
_result.data!,
|
||||
(json) => Token.fromJson(json as Map<String, dynamic>),
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
37
lib/api/status.dart
Normal file
37
lib/api/status.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'status.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class MeilisearchInfo {
|
||||
const MeilisearchInfo({
|
||||
required this.host,
|
||||
required this.key,
|
||||
});
|
||||
final String host;
|
||||
final String key;
|
||||
factory MeilisearchInfo.fromJson(Map<String, dynamic> json) => _$MeilisearchInfoFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$MeilisearchInfoToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServerStatus {
|
||||
const ServerStatus({
|
||||
required this.ffmpegApiEnabled,
|
||||
required this.ffmpegBinaryEnabled,
|
||||
required this.meilisearchEnabled,
|
||||
this.meilisearch,
|
||||
required this.noUser,
|
||||
});
|
||||
@JsonKey(name: 'ffmpeg_api_enabled')
|
||||
final bool ffmpegApiEnabled;
|
||||
@JsonKey(name: 'ffmpeg_binary_enabled')
|
||||
final bool ffmpegBinaryEnabled;
|
||||
@JsonKey(name: 'meilisearch_enabled')
|
||||
final bool meilisearchEnabled;
|
||||
final MeilisearchInfo? meilisearch;
|
||||
@JsonKey(name: 'no_user')
|
||||
final bool noUser;
|
||||
factory ServerStatus.fromJson(Map<String, dynamic> json) => _$ServerStatusFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$ServerStatusToJson(this);
|
||||
}
|
||||
39
lib/api/status.g.dart
Normal file
39
lib/api/status.g.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'status.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
MeilisearchInfo _$MeilisearchInfoFromJson(Map<String, dynamic> json) =>
|
||||
MeilisearchInfo(
|
||||
host: json['host'] as String,
|
||||
key: json['key'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$MeilisearchInfoToJson(MeilisearchInfo instance) =>
|
||||
<String, dynamic>{
|
||||
'host': instance.host,
|
||||
'key': instance.key,
|
||||
};
|
||||
|
||||
ServerStatus _$ServerStatusFromJson(Map<String, dynamic> json) => ServerStatus(
|
||||
ffmpegApiEnabled: json['ffmpeg_api_enabled'] as bool,
|
||||
ffmpegBinaryEnabled: json['ffmpeg_binary_enabled'] as bool,
|
||||
meilisearchEnabled: json['meilisearch_enabled'] as bool,
|
||||
meilisearch: json['meilisearch'] == null
|
||||
? null
|
||||
: MeilisearchInfo.fromJson(
|
||||
json['meilisearch'] as Map<String, dynamic>),
|
||||
noUser: json['no_user'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServerStatusToJson(ServerStatus instance) =>
|
||||
<String, dynamic>{
|
||||
'ffmpeg_api_enabled': instance.ffmpegApiEnabled,
|
||||
'ffmpeg_binary_enabled': instance.ffmpegBinaryEnabled,
|
||||
'meilisearch_enabled': instance.meilisearchEnabled,
|
||||
'meilisearch': instance.meilisearch,
|
||||
'no_user': instance.noUser,
|
||||
};
|
||||
27
lib/api/token.dart
Normal file
27
lib/api/token.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'token.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class Token {
|
||||
const Token ({
|
||||
required this.id,
|
||||
required this.uid,
|
||||
required this.token,
|
||||
required this.expired,
|
||||
required this.httpOnly,
|
||||
required this.secure,
|
||||
});
|
||||
final int id;
|
||||
final int uid;
|
||||
final String token;
|
||||
@JsonKey(fromJson: _fromJson, toJson: _toJson)
|
||||
final DateTime expired;
|
||||
@JsonKey(name: 'http_only')
|
||||
final bool httpOnly;
|
||||
final bool secure;
|
||||
static DateTime _fromJson(String d) => DateTime.parse(d);
|
||||
static String _toJson(DateTime d) => d.toIso8601String();
|
||||
factory Token.fromJson(Map<String, dynamic> json) => _$TokenFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$TokenToJson(this);
|
||||
}
|
||||
25
lib/api/token.g.dart
Normal file
25
lib/api/token.g.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'token.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Token _$TokenFromJson(Map<String, dynamic> json) => Token(
|
||||
id: json['id'] as int,
|
||||
uid: json['uid'] as int,
|
||||
token: json['token'] as String,
|
||||
expired: Token._fromJson(json['expired'] as String),
|
||||
httpOnly: json['http_only'] as bool,
|
||||
secure: json['secure'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$TokenToJson(Token instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'uid': instance.uid,
|
||||
'token': instance.token,
|
||||
'expired': Token._toJson(instance.expired),
|
||||
'http_only': instance.httpOnly,
|
||||
'secure': instance.secure,
|
||||
};
|
||||
@@ -18,12 +18,13 @@ class BUser {
|
||||
const BUser({
|
||||
required this.id,
|
||||
required this.username,
|
||||
required this.is_admin,
|
||||
required this.isAdmin,
|
||||
required this.permissions,
|
||||
});
|
||||
final int id;
|
||||
final String username;
|
||||
final bool is_admin;
|
||||
@JsonKey(name: 'is_admin')
|
||||
final bool isAdmin;
|
||||
final UserPermission permissions;
|
||||
factory BUser.fromJson(Map<String, dynamic> json) => _$BUserFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$BUserToJson(this);
|
||||
|
||||
@@ -9,14 +9,14 @@ part of 'user.dart';
|
||||
BUser _$BUserFromJson(Map<String, dynamic> json) => BUser(
|
||||
id: json['id'] as int,
|
||||
username: json['username'] as String,
|
||||
is_admin: json['is_admin'] as bool,
|
||||
isAdmin: json['is_admin'] as bool,
|
||||
permissions: $enumDecode(_$UserPermissionEnumMap, json['permissions']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$BUserToJson(BUser instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'username': instance.username,
|
||||
'is_admin': instance.is_admin,
|
||||
'is_admin': instance.isAdmin,
|
||||
'permissions': _$UserPermissionEnumMap[instance.permissions]!,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ import 'package:dio/dio.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'api/client.dart';
|
||||
|
||||
final dio = Dio();
|
||||
final dio = Dio()..options.validateStatus = (int? _) {return true;};
|
||||
SharedPreferences? _prefs;
|
||||
EHApi? _api;
|
||||
|
||||
Future<void> prepareJar() async {
|
||||
final Directory appDocDir = await getApplicationDocumentsDirectory();
|
||||
@@ -27,3 +29,18 @@ SharedPreferences get prefs {
|
||||
}
|
||||
return _prefs!;
|
||||
}
|
||||
|
||||
void initApi(String baseUrl) {
|
||||
_api = EHApi(dio, baseUrl: baseUrl);
|
||||
}
|
||||
|
||||
bool get apiInited {
|
||||
return _api != null;
|
||||
}
|
||||
|
||||
EHApi get api {
|
||||
if (_api == null) {
|
||||
throw Exception('EHApi not initialized');
|
||||
}
|
||||
return _api!;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class HomePage extends HookWidget {
|
||||
});
|
||||
return;
|
||||
}
|
||||
initApi(baseUrl);
|
||||
return;
|
||||
}, []);
|
||||
return const Scaffold(
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'globals.dart';
|
||||
|
||||
class SetServerPage extends StatefulWidget {
|
||||
const SetServerPage({Key? key}) : super(key: key);
|
||||
@@ -10,29 +12,92 @@ class SetServerPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SetServerPageState extends State<SetServerPage> {
|
||||
String _serverUrl = "";
|
||||
String _apiPath = "/api/";
|
||||
bool _isValid = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
String? baseUrl = prefs.getString('baseUrl');
|
||||
if (baseUrl != null) {
|
||||
try {
|
||||
Uri url = Uri.parse(baseUrl);
|
||||
_serverUrl = url.origin;
|
||||
_apiPath = url.path;
|
||||
_isValid = true;
|
||||
} catch (e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool _checkIsValid(String serverUrl, String apiPath) {
|
||||
try {
|
||||
Uri url = Uri.parse(serverUrl + apiPath);
|
||||
return url.isAbsolute;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void _serverUrlChanged(String value) {
|
||||
bool isValid = _checkIsValid(value, _apiPath);
|
||||
setState(() {
|
||||
_serverUrl = value;
|
||||
_isValid = isValid;
|
||||
});
|
||||
}
|
||||
|
||||
void _apiPathChanged(String value) {
|
||||
bool isValid = _checkIsValid(_serverUrl, value);
|
||||
setState(() {
|
||||
_apiPath = value;
|
||||
_isValid = isValid;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const Text('EH Downloader server url:'),
|
||||
const TextField(
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Server URL',
|
||||
),
|
||||
),
|
||||
const Text('API path'),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'API Path',
|
||||
),
|
||||
controller: TextEditingController(text: "/api"),
|
||||
),
|
||||
],
|
||||
)),
|
||||
body: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 100),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'EH Downloader Server URL',
|
||||
),
|
||||
initialValue: _serverUrl,
|
||||
onChanged: _serverUrlChanged,
|
||||
)),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'API Path',
|
||||
),
|
||||
initialValue: _apiPath,
|
||||
onChanged: _apiPathChanged,
|
||||
)),
|
||||
ElevatedButton(
|
||||
onPressed: _isValid
|
||||
? () {
|
||||
prefs.setString('baseUrl', _serverUrl + _apiPath);
|
||||
context.go('/');
|
||||
}
|
||||
: null,
|
||||
child: const Text("Save")),
|
||||
],
|
||||
))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import cryptography_flutter
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
CryptographyFlutterPlugin.register(with: registry.registrar(forPlugin: "CryptographyFlutterPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
||||
16
pubspec.lock
16
pubspec.lock
@@ -169,6 +169,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
cryptography:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
cryptography_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cryptography_flutter
|
||||
sha256: a66ce021e0b600688c2d51b0594cb156ee677ce9bfbc981b62219ad577dd302e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -7,6 +7,8 @@ environment:
|
||||
sdk: '>=3.0.5 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
cryptography: ^2.5.0
|
||||
cryptography_flutter: ^2.3.0
|
||||
dio: ^5.3.2
|
||||
dio_cookie_manager: ^3.1.0+1
|
||||
flutter:
|
||||
|
||||
Reference in New Issue
Block a user