完成基础的登录流程

main
落雨楓 2 years ago
parent e4c942862e
commit 28d9d832c3

@ -7,7 +7,7 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTask"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
@ -24,6 +24,14 @@
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<!-- Deep Links -->
<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="isekaiwiki" android:host="*" />
</intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
@ -31,4 +39,10 @@
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
</application> </application>
<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
</manifest> </manifest>

@ -1,9 +1,19 @@
PODS: PODS:
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_inappwebview (0.0.1):
- Flutter
- flutter_inappwebview/Core (= 0.0.1)
- OrderedSet (~> 5.0)
- flutter_inappwebview/Core (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
- flutter_web_browser (0.17.1): - flutter_web_browser (0.17.1):
- Flutter - Flutter
- OrderedSet (5.0.0)
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
- shared_preferences_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1): - video_player_avfoundation (0.0.1):
- Flutter - Flutter
- wakelock (0.0.1): - wakelock (0.0.1):
@ -13,19 +23,29 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
- flutter_web_browser (from `.symlinks/plugins/flutter_web_browser/ios`) - flutter_web_browser (from `.symlinks/plugins/flutter_web_browser/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`) - wakelock (from `.symlinks/plugins/wakelock/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS:
trunk:
- OrderedSet
EXTERNAL SOURCES: EXTERNAL SOURCES:
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_inappwebview:
:path: ".symlinks/plugins/flutter_inappwebview/ios"
flutter_web_browser: flutter_web_browser:
:path: ".symlinks/plugins/flutter_web_browser/ios" :path: ".symlinks/plugins/flutter_web_browser/ios"
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
video_player_avfoundation: video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios" :path: ".symlinks/plugins/video_player_avfoundation/ios"
wakelock: wakelock:
@ -35,8 +55,11 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f

@ -1,9 +1,13 @@
import 'dart:convert'; import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:isekai_wiki/global.dart'; import 'package:isekai_wiki/global.dart';
import 'package:isekai_wiki/utils/api_utils.dart'; import 'package:isekai_wiki/utils/api_utils.dart';
import 'package:http/http.dart' as http;
class HttpResponseCodeError extends Error { class HttpResponseCodeError extends Error {
int? statusCode; int? statusCode;
@ -17,25 +21,68 @@ class HttpResponseCodeError extends Error {
} }
class BaseApi { class BaseApi {
static Future<Map<String, String>> _getHeaders() async { static Dio? _dioInstance;
Map<String, String> headers = {}; static CookieJar? cookieJar;
static Dio createClient() {
var dio = Dio();
return dio;
}
static Dio getClient() {
_dioInstance ??= createClient();
if (!kIsWeb) { if (!kIsWeb) {
headers["X-IsekaiWikiApp-Version"] = Global.packageInfo?.version ?? "unknow"; // HTTP2
headers["User-Agent"] = await ApiUtils.getUserAgent(); _dioInstance!.httpClientAdapter = Http2Adapter(
ConnectionManager(
idleTimeout: 10000,
),
);
// Cookie
cookieJar = PersistCookieJar();
_dioInstance!.interceptors.add(CookieManager(cookieJar!));
//
final cacheOptions = CacheOptions(
store: MemCacheStore(),
policy: CachePolicy.request,
maxStale: const Duration(days: 7),
priority: CachePriority.normal,
cipher: null,
keyBuilder: CacheOptions.defaultCacheKeyBuilder,
allowPostMethod: false,
);
_dioInstance!.interceptors
.add(DioCacheInterceptor(options: cacheOptions));
// Header
_dioInstance!.interceptors.add(
InterceptorsWrapper(onRequest: (options, handler) {
options.headers["X-IsekaiWikiApp-Version"] =
Global.packageInfo?.version ?? "unknow";
options.headers["User-Agent"] = "";
return handler.next(options);
}),
);
} }
return headers; return _dioInstance!;
} }
static Future<String> get(Uri uri, {Map<String, dynamic>? search}) async { static Future<String> get(Uri uri, {Map<String, dynamic>? search}) async {
var res = await http.get(uri, headers: await _getHeaders()); var res = await getClient().get<String>(
uri.toString(),
options: Options(responseType: ResponseType.plain),
);
if (res.statusCode != 200) { if (res.statusCode != 200) {
throw HttpResponseCodeError(res.statusCode); throw HttpResponseCodeError(res.statusCode);
} }
return res.body; return res.data ?? "";
} }
static Future<Map> getJson(Uri uri) async { static Future<Map> getJson(Uri uri) async {

@ -0,0 +1,73 @@
import 'package:isekai_wiki/api/response/userinfo.dart';
import '../response/mugenapp.dart';
import 'mw_api.dart';
class MWApiUser {
static Future<MWResponse<MugenAppStartAuthInfo>> startAuth() async {
var query = {
"method": "startauth",
};
var mwRes = await MWApi.get("mugenapp", query: query);
if (!mwRes.ok) {
return MWResponse(errorList: mwRes.errorList);
}
if (mwRes.data != null) {
var authRes = MugenAppStartAuthResponse.fromJson(mwRes.data!);
return MWResponse(
ok: true, data: authRes.startauth, continueInfo: mwRes.continueInfo);
} else {
return MWResponse(
errorList: [MWError(code: 'response_data_empty', info: '加载的数据为空')]);
}
}
static Future<MWResponse<MugenAppAttemptAuthInfo>> attemptAuth(
String loginRequestKey) async {
var query = {
"method": "attemptauth",
"requestkey": loginRequestKey,
};
var mwRes = await MWApi.get("mugenapp", query: query);
if (!mwRes.ok) {
return MWResponse(errorList: mwRes.errorList);
}
if (mwRes.data != null) {
var authRes = MugenAppAttemptAuthResponse.fromJson(mwRes.data!);
return MWResponse(
ok: true,
data: authRes.attemptauth,
continueInfo: mwRes.continueInfo);
} else {
return MWResponse(
errorList: [MWError(code: 'response_data_empty', info: '加载的数据为空')]);
}
}
static Future<MWResponse<MetaUserInfoResponse>> getCurrentUserInfo() async {
var query = {
"meta": "userinfo|useravatar",
"uiprop": "blockinfo|groups|rights|options|email|realname|latestcontrib",
};
var mwRes = await MWApi.get("query", query: query);
if (!mwRes.ok) {
return MWResponse(errorList: mwRes.errorList);
}
if (mwRes.data != null) {
var userInfoRes = MetaUserInfoResponse.fromJson(mwRes.data!);
return MWResponse(ok: true, data: userInfoRes);
} else {
return MWResponse(
errorList: [MWError(code: 'response_data_empty', info: '加载的数据为空')]);
}
}
}

@ -0,0 +1,65 @@
import 'package:json_annotation/json_annotation.dart';
part 'mugenapp.g.dart';
@JsonSerializable()
class MugenAppStartAuthInfo {
String loginUrl;
String loginRequestKey;
int ttl;
MugenAppStartAuthInfo({
required this.loginUrl,
required this.loginRequestKey,
this.ttl = 0,
});
factory MugenAppStartAuthInfo.fromJson(Map<String, dynamic> json) =>
_$MugenAppStartAuthInfoFromJson(json);
Map<String, dynamic> toJson() => _$MugenAppStartAuthInfoToJson(this);
}
@JsonSerializable()
class MugenAppStartAuthResponse {
MugenAppStartAuthInfo startauth;
MugenAppStartAuthResponse({required this.startauth});
factory MugenAppStartAuthResponse.fromJson(Map<String, dynamic> json) =>
_$MugenAppStartAuthResponseFromJson(json);
Map<String, dynamic> toJson() => _$MugenAppStartAuthResponseToJson(this);
}
@JsonSerializable()
class MugenAppAttemptAuthInfo {
String status;
int? userid;
String? username;
MugenAppAttemptAuthInfo({
required this.status,
this.userid,
this.username,
});
factory MugenAppAttemptAuthInfo.fromJson(Map<String, dynamic> json) =>
_$MugenAppAttemptAuthInfoFromJson(json);
Map<String, dynamic> toJson() => _$MugenAppAttemptAuthInfoToJson(this);
}
@JsonSerializable()
class MugenAppAttemptAuthResponse {
MugenAppAttemptAuthInfo attemptauth;
MugenAppAttemptAuthResponse({
required this.attemptauth,
});
factory MugenAppAttemptAuthResponse.fromJson(Map<String, dynamic> json) =>
_$MugenAppAttemptAuthResponseFromJson(json);
Map<String, dynamic> toJson() => _$MugenAppAttemptAuthResponseToJson(this);
}

@ -0,0 +1,65 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'mugenapp.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
MugenAppStartAuthInfo _$MugenAppStartAuthInfoFromJson(
Map<String, dynamic> json) =>
MugenAppStartAuthInfo(
loginUrl: json['loginUrl'] as String,
loginRequestKey: json['loginRequestKey'] as String,
ttl: json['ttl'] as int? ?? 0,
);
Map<String, dynamic> _$MugenAppStartAuthInfoToJson(
MugenAppStartAuthInfo instance) =>
<String, dynamic>{
'loginUrl': instance.loginUrl,
'loginRequestKey': instance.loginRequestKey,
'ttl': instance.ttl,
};
MugenAppStartAuthResponse _$MugenAppStartAuthResponseFromJson(
Map<String, dynamic> json) =>
MugenAppStartAuthResponse(
startauth: MugenAppStartAuthInfo.fromJson(
json['startauth'] as Map<String, dynamic>),
);
Map<String, dynamic> _$MugenAppStartAuthResponseToJson(
MugenAppStartAuthResponse instance) =>
<String, dynamic>{
'startauth': instance.startauth,
};
MugenAppAttemptAuthInfo _$MugenAppAttemptAuthInfoFromJson(
Map<String, dynamic> json) =>
MugenAppAttemptAuthInfo(
status: json['status'] as String,
userid: json['userid'] as int?,
username: json['username'] as String?,
);
Map<String, dynamic> _$MugenAppAttemptAuthInfoToJson(
MugenAppAttemptAuthInfo instance) =>
<String, dynamic>{
'status': instance.status,
'userid': instance.userid,
'username': instance.username,
};
MugenAppAttemptAuthResponse _$MugenAppAttemptAuthResponseFromJson(
Map<String, dynamic> json) =>
MugenAppAttemptAuthResponse(
attemptauth: MugenAppAttemptAuthInfo.fromJson(
json['attemptauth'] as Map<String, dynamic>),
);
Map<String, dynamic> _$MugenAppAttemptAuthResponseToJson(
MugenAppAttemptAuthResponse instance) =>
<String, dynamic>{
'attemptauth': instance.attemptauth,
};

@ -0,0 +1,101 @@
import 'package:json_annotation/json_annotation.dart';
part 'userinfo.g.dart';
@JsonSerializable()
class UserGroupMembership {
String group;
String expiry;
UserGroupMembership({
required this.group,
required this.expiry,
});
factory UserGroupMembership.fromJson(Map<String, dynamic> json) =>
_$UserGroupMembershipFromJson(json);
Map<String, dynamic> toJson() => _$UserGroupMembershipToJson(this);
}
@JsonSerializable()
class UserAcceptLang {
double q;
@JsonKey(name: '*')
String langCode;
UserAcceptLang({
required this.q,
required this.langCode,
});
factory UserAcceptLang.fromJson(Map<String, dynamic> json) =>
_$UserAcceptLangFromJson(json);
Map<String, dynamic> toJson() => _$UserAcceptLangToJson(this);
}
@JsonSerializable()
class MetaUserInfo {
int id;
String name;
List<String>? groups;
List<UserGroupMembership>? groupmemberships;
List<String>? implicitgroups;
List<String>? rights;
Map<String, List<String>>? changeablegroups;
Map<String, dynamic>? options;
int? editcount;
String? realname;
String? email;
DateTime? emailauthenticated;
DateTime? registrationdate;
List<UserAcceptLang>? acceptlang;
int? unreadcount;
Map<String, int>? centralids;
Map<String, String>? attachedlocal;
DateTime? latestcontrib;
MetaUserInfo({
required this.id,
required this.name,
this.groups,
this.groupmemberships,
this.implicitgroups,
this.rights,
this.changeablegroups,
this.options,
this.editcount,
this.realname,
this.email,
this.emailauthenticated,
this.registrationdate,
this.acceptlang,
this.unreadcount,
this.centralids,
this.attachedlocal,
this.latestcontrib,
});
factory MetaUserInfo.fromJson(Map<String, dynamic> json) =>
_$MetaUserInfoFromJson(json);
Map<String, dynamic> toJson() => _$MetaUserInfoToJson(this);
}
@JsonSerializable()
class MetaUserInfoResponse {
MetaUserInfo userinfo;
Map<int, String>? useravatar;
MetaUserInfoResponse({
required this.userinfo,
this.useravatar,
});
factory MetaUserInfoResponse.fromJson(Map<String, dynamic> json) =>
_$MetaUserInfoResponseFromJson(json);
Map<String, dynamic> toJson() => _$MetaUserInfoResponseToJson(this);
}

@ -0,0 +1,114 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'userinfo.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
UserGroupMembership _$UserGroupMembershipFromJson(Map<String, dynamic> json) =>
UserGroupMembership(
group: json['group'] as String,
expiry: json['expiry'] as String,
);
Map<String, dynamic> _$UserGroupMembershipToJson(
UserGroupMembership instance) =>
<String, dynamic>{
'group': instance.group,
'expiry': instance.expiry,
};
UserAcceptLang _$UserAcceptLangFromJson(Map<String, dynamic> json) =>
UserAcceptLang(
q: (json['q'] as num).toDouble(),
langCode: json['*'] as String,
);
Map<String, dynamic> _$UserAcceptLangToJson(UserAcceptLang instance) =>
<String, dynamic>{
'q': instance.q,
'*': instance.langCode,
};
MetaUserInfo _$MetaUserInfoFromJson(Map<String, dynamic> json) => MetaUserInfo(
id: json['id'] as int,
name: json['name'] as String,
groups:
(json['groups'] as List<dynamic>?)?.map((e) => e as String).toList(),
groupmemberships: (json['groupmemberships'] as List<dynamic>?)
?.map((e) => UserGroupMembership.fromJson(e as Map<String, dynamic>))
.toList(),
implicitgroups: (json['implicitgroups'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
rights:
(json['rights'] as List<dynamic>?)?.map((e) => e as String).toList(),
changeablegroups:
(json['changeablegroups'] as Map<String, dynamic>?)?.map(
(k, e) =>
MapEntry(k, (e as List<dynamic>).map((e) => e as String).toList()),
),
options: json['options'] as Map<String, dynamic>?,
editcount: json['editcount'] as int?,
realname: json['realname'] as String?,
email: json['email'] as String?,
emailauthenticated: json['emailauthenticated'] == null
? null
: DateTime.parse(json['emailauthenticated'] as String),
registrationdate: json['registrationdate'] == null
? null
: DateTime.parse(json['registrationdate'] as String),
acceptlang: (json['acceptlang'] as List<dynamic>?)
?.map((e) => UserAcceptLang.fromJson(e as Map<String, dynamic>))
.toList(),
unreadcount: json['unreadcount'] as int?,
centralids: (json['centralids'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as int),
),
attachedlocal: (json['attachedlocal'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
),
latestcontrib: json['latestcontrib'] == null
? null
: DateTime.parse(json['latestcontrib'] as String),
);
Map<String, dynamic> _$MetaUserInfoToJson(MetaUserInfo instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'groups': instance.groups,
'groupmemberships': instance.groupmemberships,
'implicitgroups': instance.implicitgroups,
'rights': instance.rights,
'changeablegroups': instance.changeablegroups,
'options': instance.options,
'editcount': instance.editcount,
'realname': instance.realname,
'email': instance.email,
'emailauthenticated': instance.emailauthenticated?.toIso8601String(),
'registrationdate': instance.registrationdate?.toIso8601String(),
'acceptlang': instance.acceptlang,
'unreadcount': instance.unreadcount,
'centralids': instance.centralids,
'attachedlocal': instance.attachedlocal,
'latestcontrib': instance.latestcontrib?.toIso8601String(),
};
MetaUserInfoResponse _$MetaUserInfoResponseFromJson(
Map<String, dynamic> json) =>
MetaUserInfoResponse(
userinfo: MetaUserInfo.fromJson(json['userinfo'] as Map<String, dynamic>),
useravatar: (json['useravatar'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(int.parse(k), e as String),
),
);
Map<String, dynamic> _$MetaUserInfoResponseToJson(
MetaUserInfoResponse instance) =>
<String, dynamic>{
'userinfo': instance.userinfo,
'useravatar':
instance.useravatar?.map((k, e) => MapEntry(k.toString(), e)),
};

@ -9,6 +9,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart';
/// Standard iOS navigation bar height without the status bar. /// Standard iOS navigation bar height without the status bar.
/// ///
@ -420,7 +421,8 @@ class IsekaiNavigationBar extends StatefulWidget
@override @override
Size get preferredSize { Size get preferredSize {
return const Size.fromHeight(_kNavBarPersistentHeight); double scaleFactor = MediaQuery.of(Get.context!).textScaleFactor;
return Size.fromHeight(_kNavBarPersistentHeight * scaleFactor);
} }
@override @override
@ -727,6 +729,7 @@ class _IsekaiSliverNavigationBarState extends State<IsekaiSliverNavigationBar> {
large: true, large: true,
); );
double scaleFactor = MediaQuery.of(context).textScaleFactor;
return SliverPersistentHeader( return SliverPersistentHeader(
pinned: true, // iOS navigation bars are always pinned. pinned: true, // iOS navigation bars are always pinned.
delegate: _LargeTitleNavigationBarSliverDelegate( delegate: _LargeTitleNavigationBarSliverDelegate(
@ -742,8 +745,8 @@ class _IsekaiSliverNavigationBarState extends State<IsekaiSliverNavigationBar> {
actionsForegroundColor: CupertinoTheme.of(context).primaryColor, actionsForegroundColor: CupertinoTheme.of(context).primaryColor,
transitionBetweenRoutes: widget.transitionBetweenRoutes, transitionBetweenRoutes: widget.transitionBetweenRoutes,
heroTag: widget.heroTag, heroTag: widget.heroTag,
persistentHeight: persistentHeight: (_kNavBarPersistentHeight * scaleFactor) +
_kNavBarPersistentHeight + MediaQuery.of(context).padding.top, MediaQuery.of(context).padding.top,
alwaysShowMiddle: widget.middle != null, alwaysShowMiddle: widget.middle != null,
stretchConfiguration: stretchConfiguration:
widget.stretch ? OverScrollHeaderStretchConfiguration() : null, widget.stretch ? OverScrollHeaderStretchConfiguration() : null,
@ -789,7 +792,10 @@ class _LargeTitleNavigationBarSliverDelegate
double get minExtent => persistentHeight; double get minExtent => persistentHeight;
@override @override
double get maxExtent => persistentHeight + _kNavBarLargeTitleHeightExtension; double get maxExtent =>
persistentHeight +
(_kNavBarLargeTitleHeightExtension *
MediaQuery.of(Get.context!).textScaleFactor);
@override @override
OverScrollHeaderStretchConfiguration? stretchConfiguration; OverScrollHeaderStretchConfiguration? stretchConfiguration;
@ -985,8 +991,11 @@ class _PersistentNavigationBar extends StatelessWidget {
); );
} }
double scaleFactor = MediaQuery.of(context).textScaleFactor;
return SizedBox( return SizedBox(
height: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top, height: (_kNavBarPersistentHeight * scaleFactor) +
MediaQuery.of(context).padding.top,
child: SafeArea( child: SafeArea(
bottom: false, bottom: false,
child: paddedToolbar, child: paddedToolbar,

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:isekai_wiki/models/user.dart'; import 'package:isekai_wiki/models/user.dart';
@ -17,11 +18,8 @@ class LifeCycleController extends SuperController {
@override @override
void onResumed() { void onResumed() {
debugPrint("onResume"); debugPrint("onResume");
try {
var uc = Get.find<UserController>(); var uc = Get.find<UserController>();
uc.attemptFinishAuth().catchError((err) {
err.printError(info: 'attemptFinishAuth'); uc.attemptFinishAuth();
});
} catch (_) {}
} }
} }

@ -1,8 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_web_browser/flutter_web_browser.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:isekai_wiki/api/mw/user.dart';
import 'package:isekai_wiki/global.dart'; import 'package:isekai_wiki/global.dart';
import 'package:isekai_wiki/utils/dialog.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; part 'user.g.dart';
@ -12,11 +16,17 @@ class UserInfo {
int userId; int userId;
String userName; String userName;
String? nickName; String? nickName;
String? avatarUrl; Map<int, String>? avatarUrlSet;
UserInfo({required this.userId, required this.userName, this.nickName, this.avatarUrl}); UserInfo({
required this.userId,
required this.userName,
this.nickName,
this.avatarUrlSet,
});
factory UserInfo.fromJson(Map<String, dynamic> json) => _$UserInfoFromJson(json); factory UserInfo.fromJson(Map<String, dynamic> json) =>
_$UserInfoFromJson(json);
Map<String, dynamic> toJson() => _$UserInfoToJson(this); Map<String, dynamic> toJson() => _$UserInfoToJson(this);
} }
@ -24,13 +34,15 @@ class UserInfo {
class UserController extends GetxController { class UserController extends GetxController {
bool _ignoreSave = false; bool _ignoreSave = false;
var authProcessing = false.obs;
var loginRequestToken = "".obs; var loginRequestToken = "".obs;
var userId = 0.obs; var userId = 0.obs;
var userName = "".obs; var userName = "".obs;
var nickName = "".obs; var nickName = "".obs;
var avatarUrl = "".obs; var avatarUrlSet = RxMap<int, String>({});
bool get isLoggedIn { bool get isLoggedIn {
return userId.value > 0; return userId.value > 0;
@ -45,6 +57,7 @@ class UserController extends GetxController {
super.onInit(); super.onInit();
loadFromStorage(); loadFromStorage();
updateProfile();
ever(loginRequestToken, (String token) { ever(loginRequestToken, (String token) {
saveToStorage(); saveToStorage();
@ -52,7 +65,35 @@ class UserController extends GetxController {
} }
/// ///
Future<void> updateProfile() async {} Future<void> updateProfile() async {
var userInfoMWRes = await MWApiUser.getCurrentUserInfo();
if (!userInfoMWRes.ok) {
if (kDebugMode) {
print("Cannot update profile of current user");
print(userInfoMWRes.errorList);
}
}
var userInfoRes = userInfoMWRes.data!;
nickName.value = userInfoRes.userinfo.realname ?? "";
if (userInfoRes.useravatar != null) {
avatarUrlSet.value = userInfoRes.useravatar!;
}
}
String? getAvatar(int size) {
if (avatarUrlSet.isEmpty) {
return null;
}
for (var imgSize in avatarUrlSet.keys) {
if (size < imgSize) {
return avatarUrlSet[size];
}
}
return avatarUrlSet.values.last;
}
/// ///
void loadFromStorage() { void loadFromStorage() {
@ -70,7 +111,7 @@ class UserController extends GetxController {
userId.value = userInfo.userId; userId.value = userInfo.userId;
userName.value = userInfo.userName; userName.value = userInfo.userName;
nickName.value = userInfo.nickName ?? ""; nickName.value = userInfo.nickName ?? "";
avatarUrl.value = userInfo.avatarUrl ?? ""; avatarUrlSet.value = userInfo.avatarUrlSet ?? {};
_ignoreSave = false; _ignoreSave = false;
var savedLoginRequestToken = prefs.getString("loginRequestToken"); var savedLoginRequestToken = prefs.getString("loginRequestToken");
@ -98,7 +139,7 @@ class UserController extends GetxController {
userId: userId.value, userId: userId.value,
userName: userName.value, userName: userName.value,
nickName: nickName.isNotEmpty ? nickName.value : null, nickName: nickName.isNotEmpty ? nickName.value : null,
avatarUrl: avatarUrl.isNotEmpty ? avatarUrl.value : null, avatarUrlSet: avatarUrlSet.isNotEmpty ? avatarUrlSet : null,
); );
var userInfoJson = jsonEncode(userInfo.toJson()); var userInfoJson = jsonEncode(userInfo.toJson());
@ -114,12 +155,72 @@ class UserController extends GetxController {
/// ///
/// loginRequestToken /// loginRequestToken
Future<bool> startAuthFlow() async { Future<void> startAuthFlow() async {
return false; authProcessing.value = true;
var startAuthRes = await MWApiUser.startAuth();
if (!startAuthRes.ok) {
authProcessing.value = false;
alert(Get.overlayContext!, startAuthRes.errorList?[0].info ?? "未知错误",
title: "错误");
return;
}
var startAuthInfo = startAuthRes.data!;
loginRequestToken.value = startAuthInfo.loginRequestKey;
await FlutterWebBrowser.openWebPage(
url: startAuthInfo.loginUrl,
customTabsOptions: const CustomTabsOptions(
defaultColorSchemeParams: CustomTabsColorSchemeParams(
toolbarColor: Color.fromRGBO(33, 37, 41, 1),
),
shareState: CustomTabsShareState.off,
showTitle: true,
),
safariVCOptions: const SafariViewControllerOptions(
barCollapsingEnabled: true,
));
} }
// Token // Token
Future<void> attemptFinishAuth() async { Future<void> attemptFinishAuth() async {
if (loginRequestToken.isEmpty) return; if (loginRequestToken.isEmpty) {
authProcessing.value = false;
return;
}
var attemptAuthRes = await MWApiUser.attemptAuth(loginRequestToken.value);
if (!attemptAuthRes.ok) {
authProcessing.value = false;
alert(Get.overlayContext!, attemptAuthRes.errorList?[0].info ?? "未知错误",
title: "错误");
return;
} }
var attemptAuthInfo = attemptAuthRes.data!;
if (attemptAuthInfo.status == "pending") {
authProcessing.value = false;
loginRequestToken.value = "";
alert(Get.overlayContext!, "已取消登录", title: "提示");
return;
} else if (attemptAuthInfo.status == "success") {
userId.value = attemptAuthInfo.userid!;
userName.value = attemptAuthInfo.username!;
try {
await updateProfile();
} catch (err) {
err.printError(info: 'Cannot update profile after auth');
}
authProcessing.value = false;
saveToStorage();
}
}
Future<void> logout() async {}
} }

@ -10,12 +10,15 @@ UserInfo _$UserInfoFromJson(Map<String, dynamic> json) => UserInfo(
userId: json['userId'] as int, userId: json['userId'] as int,
userName: json['userName'] as String, userName: json['userName'] as String,
nickName: json['nickName'] as String?, nickName: json['nickName'] as String?,
avatarUrl: json['avatarUrl'] as String?, avatarUrlSet: (json['avatarUrlSet'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(int.parse(k), e as String),
),
); );
Map<String, dynamic> _$UserInfoToJson(UserInfo instance) => <String, dynamic>{ Map<String, dynamic> _$UserInfoToJson(UserInfo instance) => <String, dynamic>{
'userId': instance.userId, 'userId': instance.userId,
'userName': instance.userName, 'userName': instance.userName,
'nickName': instance.nickName, 'nickName': instance.nickName,
'avatarUrl': instance.avatarUrl, 'avatarUrlSet':
instance.avatarUrlSet?.map((k, e) => MapEntry(k.toString(), e)),
}; };

@ -33,7 +33,10 @@ class AboutPage extends StatelessWidget {
var c = Get.put(AboutPageController()); var c = Get.put(AboutPageController());
return IsekaiPageScaffold( return IsekaiPageScaffold(
navigationBar: const IsekaiNavigationBar(middle: Text('关于'), previousPageTitle: "设置"), navigationBar: const IsekaiNavigationBar(
middle: Text('关于'),
previousPageTitle: "我的",
),
child: ListView( child: ListView(
children: [ children: [
const SizedBox(height: 18), const SizedBox(height: 18),
@ -43,7 +46,8 @@ class AboutPage extends StatelessWidget {
top: false, top: false,
bottom: false, bottom: false,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: Column( child: Column(
children: const <Widget>[ children: const <Widget>[
Text("异世界百科APP", style: Styles.articleTitle), Text("异世界百科APP", style: Styles.articleTitle),
@ -59,7 +63,8 @@ class AboutPage extends StatelessWidget {
backgroundColor: Styles.themePageBackgroundColor, backgroundColor: Styles.themePageBackgroundColor,
children: <CupertinoListTile>[ children: <CupertinoListTile>[
CupertinoListTile.notched( CupertinoListTile.notched(
title: const Text('异世界百科', style: TextStyle(color: Styles.linkColor)), title: const Text('异世界百科',
style: TextStyle(color: Styles.linkColor)),
leading: const DummyIcon( leading: const DummyIcon(
color: CupertinoColors.systemBlue, color: CupertinoColors.systemBlue,
icon: CupertinoIcons.globe, icon: CupertinoIcons.globe,

@ -4,6 +4,7 @@ import 'dart:math';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:isekai_wiki/components/isekai_nav_bar.dart';
import 'package:isekai_wiki/components/recent_page_list.dart'; import 'package:isekai_wiki/components/recent_page_list.dart';
import 'package:isekai_wiki/models/user.dart'; import 'package:isekai_wiki/models/user.dart';
import 'package:isekai_wiki/pages/tab_page.dart'; import 'package:isekai_wiki/pages/tab_page.dart';
@ -15,7 +16,10 @@ import '../styles.dart';
enum HomeTabs { newest, followed } enum HomeTabs { newest, followed }
class HomeController extends GetxController with GetSingleTickerProviderStateMixin { class HomeController extends GetxController
with GetSingleTickerProviderStateMixin {
double _navSearchButtonOffset = 90;
var showNavSearchButton = false.obs; var showNavSearchButton = false.obs;
var isScrolling = false.obs; var isScrolling = false.obs;
@ -32,10 +36,15 @@ class HomeController extends GetxController with GetSingleTickerProviderStateMix
void onInit() { void onInit() {
tabController = TabController(length: 2, vsync: this); tabController = TabController(length: 2, vsync: this);
_navSearchButtonOffset =
48 * MediaQuery.of(Get.context!).textScaleFactor + 48;
scrollController.addListener(() { scrollController.addListener(() {
if (scrollController.offset >= 90 && !showNavSearchButton.value) { if (scrollController.offset >= _navSearchButtonOffset &&
!showNavSearchButton.value) {
showNavSearchButton.value = true; showNavSearchButton.value = true;
} else if (scrollController.offset < 90 && showNavSearchButton.value) { } else if (scrollController.offset < _navSearchButtonOffset &&
showNavSearchButton.value) {
showNavSearchButton.value = false; showNavSearchButton.value = false;
} }
}); });
@ -93,7 +102,8 @@ class HomeTab extends StatelessWidget {
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
child: CupertinoButton( child: CupertinoButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.search, size: 26, color: Styles.themeNavTitleColor), child: const Icon(CupertinoIcons.search,
size: 26, color: Styles.themeNavTitleColor),
onPressed: () { onPressed: () {
onSearchClick?.call(); onSearchClick?.call();
}, },
@ -111,7 +121,8 @@ class HomeTab extends StatelessWidget {
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
child: CupertinoButton( child: CupertinoButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.search, size: 26, color: Styles.themeNavTitleColor), child: const Icon(CupertinoIcons.search,
size: 26, color: Styles.themeNavTitleColor),
onPressed: () { onPressed: () {
onSearchClick?.call(); onSearchClick?.call();
}, },
@ -132,11 +143,12 @@ class HomeTab extends StatelessWidget {
parent: AlwaysScrollableScrollPhysics(), parent: AlwaysScrollableScrollPhysics(),
), ),
slivers: <Widget>[ slivers: <Widget>[
CupertinoSliverNavigationBar( IsekaiSliverNavigationBar(
leading: _buildNotificationIconButton(), leading: _buildNotificationIconButton(),
backgroundColor: Styles.themeMainColor, backgroundColor: Styles.themeMainColor,
brightness: Brightness.dark, brightness: Brightness.dark,
largeTitle: const Text('首页', style: TextStyle(color: Styles.themeNavTitleColor)), largeTitle: const Text('首页',
style: TextStyle(color: Styles.themeNavTitleColor)),
border: Border.all(style: BorderStyle.none), border: Border.all(style: BorderStyle.none),
trailing: _buildSearchIconButton(), trailing: _buildSearchIconButton(),
), ),
@ -169,10 +181,12 @@ class HomeTab extends StatelessWidget {
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(1), padding: const EdgeInsets.all(1),
child: const Icon(CupertinoIcons.search, color: Colors.black54), child: const Icon(CupertinoIcons.search,
color: Colors.black54),
), ),
const Text("搜索页面...", const Text("搜索页面...",
textAlign: TextAlign.center, style: TextStyle(color: Colors.black54)) textAlign: TextAlign.center,
style: TextStyle(color: Colors.black54))
], ],
), ),
), ),
@ -202,7 +216,10 @@ class HomeTab extends StatelessWidget {
indicatorColor: Styles.themeMainColor, indicatorColor: Styles.themeMainColor,
labelColor: Styles.themeMainColor, labelColor: Styles.themeMainColor,
unselectedLabelColor: Colors.black45, unselectedLabelColor: Colors.black45,
tabs: const [CollapsedTabText('最新'), CollapsedTabText('关注')], tabs: const [
CollapsedTabText('最新'),
CollapsedTabText('关注')
],
onTap: (int selected) {}, onTap: (int selected) {},
), ),
), ),
@ -247,7 +264,8 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
double get maxExtent => max(maxHeight, minHeight); double get maxExtent => max(maxHeight, minHeight);
@override @override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child); return SizedBox.expand(child: child);
} }

@ -1,6 +1,8 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cupertino_lists/cupertino_lists.dart'; import 'package:cupertino_lists/cupertino_lists.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:isekai_wiki/components/isekai_nav_bar.dart';
import 'package:isekai_wiki/models/user.dart'; import 'package:isekai_wiki/models/user.dart';
import 'package:isekai_wiki/pages/about.dart'; import 'package:isekai_wiki/pages/about.dart';
import 'package:isekai_wiki/styles.dart'; import 'package:isekai_wiki/styles.dart';
@ -11,8 +13,6 @@ import '../components/follow_scale.dart';
class OwnProfileController extends GetxController { class OwnProfileController extends GetxController {
late UserController uc; late UserController uc;
var loginLoading = false.obs;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -25,16 +25,51 @@ class OwnProfileController extends GetxController {
} }
Future<void> handleStartAuth() async { Future<void> handleStartAuth() async {
loginLoading.value = true;
await uc.startAuthFlow(); await uc.startAuthFlow();
loginLoading.value = false; }
Future<void> handleLogoutClick() {
handleLogout();
return Future.delayed(const Duration(milliseconds: 100));
}
Future<void> handleLogout() async {
await uc.logout();
} }
} }
class OwnProfileTab extends StatelessWidget { class OwnProfileTab extends StatelessWidget {
const OwnProfileTab({super.key}); const OwnProfileTab({super.key});
Widget _buildUserSection() { Widget _buildUserAvatar(UserController uc, {double size = 56}) {
return Obx(() {
var avatarUrl = uc.getAvatar(128);
if (avatarUrl != null && avatarUrl.isNotEmpty) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(size / 2)),
),
child: CachedNetworkImage(
width: size,
height: size,
placeholder: (_, __) =>
const CupertinoActivityIndicator(radius: 12),
imageUrl: avatarUrl,
),
);
} else {
return DummyIcon(
color: CupertinoColors.systemGrey,
icon: CupertinoIcons.person_fill,
size: size,
rounded: true,
);
}
});
}
Widget _buildUserSection(BuildContext context) {
var c = Get.find<OwnProfileController>(); var c = Get.find<OwnProfileController>();
var uc = Get.find<UserController>(); var uc = Get.find<UserController>();
@ -57,7 +92,7 @@ class OwnProfileTab extends StatelessWidget {
), ),
leadingSize: 80, leadingSize: 80,
leadingToTitle: 4, leadingToTitle: 4,
trailing: c.loginLoading.value trailing: uc.authProcessing.value
? const Padding( ? const Padding(
padding: EdgeInsets.only(right: 5), padding: EdgeInsets.only(right: 5),
child: CupertinoActivityIndicator( child: CupertinoActivityIndicator(
@ -75,12 +110,7 @@ class OwnProfileTab extends StatelessWidget {
CupertinoListTile.notched( CupertinoListTile.notched(
title: Text(uc.getDisplayName, title: Text(uc.getDisplayName,
style: Styles.listTileLargeTitle), style: Styles.listTileLargeTitle),
leading: const DummyIcon( leading: _buildUserAvatar(uc),
color: CupertinoColors.systemGrey,
icon: CupertinoIcons.person_fill,
size: 56,
rounded: true,
),
leadingSize: 80, leadingSize: 80,
leadingToTitle: 4, leadingToTitle: 4,
trailing: const CupertinoListTileChevron(), trailing: const CupertinoListTileChevron(),
@ -90,7 +120,7 @@ class OwnProfileTab extends StatelessWidget {
title: const Text('退出登录'), title: const Text('退出登录'),
leading: const DummyIcon( leading: const DummyIcon(
color: CupertinoColors.systemRed, color: CupertinoColors.systemRed,
icon: CupertinoIcons.selection_pin_in_out, icon: CupertinoIcons.arrow_right_square,
), ),
trailing: const CupertinoListTileChevron(), trailing: const CupertinoListTileChevron(),
onTap: () {}, onTap: () {},
@ -101,7 +131,7 @@ class OwnProfileTab extends StatelessWidget {
); );
} }
Widget _buildArticleListsSection() { Widget _buildArticleListsSection(BuildContext context) {
return FollowTextScale( return FollowTextScale(
child: CupertinoListSection.insetGrouped( child: CupertinoListSection.insetGrouped(
backgroundColor: Styles.themePageBackgroundColor, backgroundColor: Styles.themePageBackgroundColor,
@ -137,7 +167,7 @@ class OwnProfileTab extends StatelessWidget {
)); ));
} }
Widget _buildSettingsSection() { Widget _buildSettingsSection(BuildContext context) {
return FollowTextScale( return FollowTextScale(
child: CupertinoListSection.insetGrouped( child: CupertinoListSection.insetGrouped(
backgroundColor: Styles.themePageBackgroundColor, backgroundColor: Styles.themePageBackgroundColor,
@ -159,24 +189,28 @@ class OwnProfileTab extends StatelessWidget {
), ),
trailing: const CupertinoListTileChevron(), trailing: const CupertinoListTileChevron(),
onTap: () async { onTap: () async {
await Navigator.of(Get.context!, rootNavigator: true) await Navigator.of(context, rootNavigator: false).push(
.push(CupertinoPageRoute(builder: (_) => const AboutPage())); CupertinoPageRoute(
builder: (_) => const AboutPage(),
),
);
}, },
), ),
], ],
)); ));
} }
SliverChildBuilderDelegate _buildSliverChildBuilderDelegate() { SliverChildBuilderDelegate _buildSliverChildBuilderDelegate(
BuildContext context) {
return SliverChildBuilderDelegate( return SliverChildBuilderDelegate(
(context, index) { (context, index) {
switch (index) { switch (index) {
case 0: case 0:
return _buildUserSection(); return _buildUserSection(context);
case 1: case 1:
return _buildArticleListsSection(); return _buildArticleListsSection(context);
case 2: case 2:
return _buildSettingsSection(); return _buildSettingsSection(context);
default: default:
// Do nothing. For now. // Do nothing. For now.
} }
@ -191,14 +225,14 @@ class OwnProfileTab extends StatelessWidget {
return CustomScrollView( return CustomScrollView(
slivers: <Widget>[ slivers: <Widget>[
const CupertinoSliverNavigationBar( const IsekaiSliverNavigationBar(
largeTitle: Text('我的'), largeTitle: Text('我的'),
), ),
SliverSafeArea( SliverSafeArea(
top: false, top: false,
minimum: const EdgeInsets.only(top: 4), minimum: const EdgeInsets.only(top: 4),
sliver: SliverList( sliver: SliverList(
delegate: _buildSliverChildBuilderDelegate(), delegate: _buildSliverChildBuilderDelegate(context),
), ),
) )
], ],

@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:isekai_wiki/global.dart'; import 'package:isekai_wiki/global.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../extension/string.dart'; import '../extension/string.dart';
@ -15,9 +14,7 @@ class ApiUtils {
String osName = Platform.operatingSystem.capitalize(); String osName = Platform.operatingSystem.capitalize();
String osVersion = Platform.operatingSystemVersion; String osVersion = Platform.operatingSystemVersion;
Global.packageInfo ??= await PackageInfo.fromPlatform(); String appVersion = Global.packageInfo?.version ?? "0.0";
String appVersion = Global.packageInfo!.version;
return "IsekaiWikiApp/$appVersion ($osName $osVersion)"; return "IsekaiWikiApp/$appVersion ($osName $osVersion)";
} }

@ -6,11 +6,15 @@ import FlutterMacOS
import Foundation import Foundation
import package_info_plus import package_info_plus
import path_provider_macos
import shared_preferences_macos import shared_preferences_macos
import sqflite
import wakelock_macos import wakelock_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
} }

@ -99,6 +99,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.4.2" version: "8.4.2"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.3"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -155,6 +176,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
cookie_jar:
dependency: transitive
description:
name: cookie_jar
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -190,6 +218,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.4" version: "2.2.4"
dio:
dependency: "direct main"
description:
name: dio
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.6"
dio_cache_interceptor:
dependency: "direct main"
description:
name: dio_cache_interceptor
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.1"
dio_cookie_manager:
dependency: "direct main"
description:
name: dio_cookie_manager
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
dio_http2_adapter:
dependency: "direct main"
description:
name: dio_http2_adapter
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
equatable: equatable:
dependency: transitive dependency: transitive
description: description:
@ -230,6 +286,20 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_blurhash:
dependency: transitive
description:
name: flutter_blurhash
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.0"
flutter_displaymode: flutter_displaymode:
dependency: "direct main" dependency: "direct main"
description: description:
@ -351,12 +421,19 @@ packages:
source: hosted source: hosted
version: "0.15.1" version: "0.15.1"
http: http:
dependency: "direct main" dependency: transitive
description: description:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.13.5" version: "0.13.5"
http2:
dependency: transitive
description:
name: http2
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -476,6 +553,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
octo_image:
dependency: transitive
description:
name: octo_image
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -518,6 +602,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.1" version: "0.2.1"
path_provider:
dependency: transitive
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.22"
path_provider_ios:
dependency: transitive
description:
name: path_provider_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -525,6 +630,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.7" version: "2.1.7"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -539,6 +651,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -616,6 +735,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.0.1"
rxdart:
dependency: transitive
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
version: "0.27.7"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -719,6 +845,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
sqflite:
dependency: transitive
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0+2"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -747,6 +887,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0+3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -782,6 +929,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.7"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -938,4 +1092,4 @@ packages:
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=2.18.0 <3.0.0" dart: ">=2.18.0 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.3.0"

@ -57,7 +57,11 @@ dependencies:
ruby_text: ^3.0.1 ruby_text: ^3.0.1
package_info_plus: ^3.0.2 package_info_plus: ^3.0.2
pull_down_button: ^0.4.1 pull_down_button: ^0.4.1
http: ^0.13.5 cached_network_image: ^3.2.3
dio: ^4.0.6
dio_cookie_manager: ^2.0.0
dio_http2_adapter: ^2.0.0
dio_cache_interceptor: ^3.3.1
get: get:
dev_dependencies: dev_dependencies:

Loading…
Cancel
Save