Merge branch 'main' into feature-appScheme
This commit is contained in:
BIN
assets/images/coin.png
Normal file
BIN
assets/images/coin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
1
assets/loading.json
Normal file
1
assets/loading.json
Normal file
File diff suppressed because one or more lines are too long
1
assets/trail_loading.json
Normal file
1
assets/trail_loading.json
Normal file
File diff suppressed because one or more lines are too long
@ -23,7 +23,7 @@ PODS:
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- gt3_flutter_plugin (0.0.8):
|
||||
- gt3_flutter_plugin (0.0.9):
|
||||
- Flutter
|
||||
- GT3Captcha-iOS
|
||||
- GT3Captcha-iOS (0.15.8.3)
|
||||
@ -171,13 +171,13 @@ SPEC CHECKSUMS:
|
||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
|
||||
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
|
||||
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
|
||||
@ -124,7 +124,7 @@ class EpisodeBottomSheet {
|
||||
});
|
||||
return Container(
|
||||
height: sheetHeight,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
buildTitle(),
|
||||
|
||||
@ -13,8 +13,8 @@ class Skeleton extends StatelessWidget {
|
||||
var shimmerGradient = LinearGradient(
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Theme.of(context).colorScheme.background.withAlpha(10),
|
||||
Theme.of(context).colorScheme.background.withAlpha(10),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(10),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(10),
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: const [
|
||||
|
||||
@ -14,7 +14,7 @@ class StatDanMu extends StatelessWidget {
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline,
|
||||
'black': Theme.of(context).colorScheme.onBackground.withOpacity(0.8),
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
return Row(
|
||||
|
||||
@ -14,7 +14,7 @@ class StatView extends StatelessWidget {
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline,
|
||||
'black': Theme.of(context).colorScheme.onBackground.withOpacity(0.8),
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
return Row(
|
||||
|
||||
@ -189,7 +189,7 @@ class Api {
|
||||
'https://s.search.bilibili.com/main/suggest';
|
||||
|
||||
// 分类搜索
|
||||
static const String searchByType = '/x/web-interface/search/type';
|
||||
static const String searchByType = '/x/web-interface/wbi/search/type';
|
||||
|
||||
// 记录视频播放进度
|
||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md
|
||||
@ -400,12 +400,24 @@ class Api {
|
||||
'${HttpString.passBaseUrl}/x/passport-login/captcha?source=main_web';
|
||||
|
||||
// web端短信验证码
|
||||
static const String smsCode =
|
||||
static const String webSmsCode =
|
||||
'${HttpString.passBaseUrl}/x/passport-login/web/sms/send';
|
||||
|
||||
// web端验证码登录
|
||||
static const String webSmsLogin =
|
||||
'${HttpString.passBaseUrl}/x/passport-login/web/login/sms';
|
||||
|
||||
// web端密码登录
|
||||
static const String loginInByWebPwd =
|
||||
'${HttpString.passBaseUrl}/x/passport-login/web/login';
|
||||
|
||||
// web端二维码
|
||||
static const String qrCodeApi =
|
||||
'${HttpString.passBaseUrl}/x/passport-login/web/qrcode/generate';
|
||||
|
||||
// 扫码登录
|
||||
static const String loginInByQrcode =
|
||||
'${HttpString.passBaseUrl}/x/passport-login/web/qrcode/poll';
|
||||
|
||||
// app端短信验证码
|
||||
static const String appSmsCode =
|
||||
@ -523,4 +535,17 @@ class Api {
|
||||
|
||||
/// 搜索结果计数
|
||||
static const String searchCount = '/x/web-interface/wbi/search/all/v2';
|
||||
|
||||
/// 关闭会话
|
||||
static const String removeSession =
|
||||
'${HttpString.tUrl}/session_svr/v1/session_svr/remove_session';
|
||||
|
||||
/// 消息未读数
|
||||
static const String unread = '${HttpString.tUrl}/x/im/web/msgfeed/unread';
|
||||
|
||||
/// 回复我的
|
||||
static const String messageReplyAPi = '/x/msgfeed/reply';
|
||||
|
||||
/// 收到的赞
|
||||
static const String messageLikeAPi = '/x/msgfeed/like';
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import 'dart:math';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../models/login/index.dart';
|
||||
import '../utils/login.dart';
|
||||
@ -21,32 +22,32 @@ class LoginHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future sendSmsCode({
|
||||
int? cid,
|
||||
required int tel,
|
||||
required String token,
|
||||
required String challenge,
|
||||
required String validate,
|
||||
required String seccode,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.appSmsCode,
|
||||
data: {
|
||||
'cid': cid,
|
||||
'tel': tel,
|
||||
"source": "main_web",
|
||||
'token': token,
|
||||
'challenge': challenge,
|
||||
'validate': validate,
|
||||
'seccode': seccode,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
// headers: {'user-agent': ApiConstants.userAgent}
|
||||
),
|
||||
);
|
||||
print(res);
|
||||
}
|
||||
// static Future sendSmsCode({
|
||||
// int? cid,
|
||||
// required int tel,
|
||||
// required String token,
|
||||
// required String challenge,
|
||||
// required String validate,
|
||||
// required String seccode,
|
||||
// }) async {
|
||||
// var res = await Request().post(
|
||||
// Api.appSmsCode,
|
||||
// data: {
|
||||
// 'cid': cid,
|
||||
// 'tel': tel,
|
||||
// "source": "main_web",
|
||||
// 'token': token,
|
||||
// 'challenge': challenge,
|
||||
// 'validate': validate,
|
||||
// 'seccode': seccode,
|
||||
// },
|
||||
// options: Options(
|
||||
// contentType: Headers.formUrlEncodedContentType,
|
||||
// // headers: {'user-agent': ApiConstants.userAgent}
|
||||
// ),
|
||||
// );
|
||||
// print(res);
|
||||
// }
|
||||
|
||||
// web端验证码
|
||||
static Future sendWebSmsCode({
|
||||
@ -60,6 +61,7 @@ class LoginHttp {
|
||||
Map data = {
|
||||
'cid': cid,
|
||||
'tel': tel,
|
||||
"source": "main_web",
|
||||
'token': token,
|
||||
'challenge': challenge,
|
||||
'validate': validate,
|
||||
@ -67,17 +69,56 @@ class LoginHttp {
|
||||
};
|
||||
FormData formData = FormData.fromMap({...data});
|
||||
var res = await Request().post(
|
||||
Api.smsCode,
|
||||
Api.webSmsCode,
|
||||
data: formData,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
print(res);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// web端验证码登录
|
||||
static Future loginInByWebSmsCode() async {}
|
||||
static Future loginInByWebSmsCode({
|
||||
int? cid,
|
||||
required int tel,
|
||||
required int code,
|
||||
required String captchaKey,
|
||||
}) async {
|
||||
// webSmsLogin
|
||||
Map data = {
|
||||
"cid": cid,
|
||||
"tel": tel,
|
||||
"code": code,
|
||||
"source": "main_mini",
|
||||
"keep": 0,
|
||||
"captcha_key": captchaKey,
|
||||
"go_url": HttpString.baseUrl
|
||||
};
|
||||
FormData formData = FormData.fromMap({...data});
|
||||
var res = await Request().post(
|
||||
Api.webSmsLogin,
|
||||
data: formData,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// web端密码登录
|
||||
static Future liginInByWebPwd() async {}
|
||||
@ -173,4 +214,69 @@ class LoginHttp {
|
||||
);
|
||||
print(res);
|
||||
}
|
||||
|
||||
// web端密码登录
|
||||
static Future loginInByWebPwd({
|
||||
required int username,
|
||||
required String password,
|
||||
required String token,
|
||||
required String challenge,
|
||||
required String validate,
|
||||
required String seccode,
|
||||
}) async {
|
||||
Map data = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'keep': 0,
|
||||
'token': token,
|
||||
'challenge': challenge,
|
||||
'validate': validate,
|
||||
'seccode': seccode,
|
||||
'source': 'main-fe-header',
|
||||
"go_url": HttpString.baseUrl
|
||||
};
|
||||
FormData formData = FormData.fromMap({...data});
|
||||
var res = await Request().post(
|
||||
Api.loginInByWebPwd,
|
||||
data: formData,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// web端登录二维码
|
||||
static Future getWebQrcode() async {
|
||||
var res = await Request().get(Api.qrCodeApi);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// web端二维码轮询登录状态
|
||||
static Future queryWebQrcodeStatus(String qrcodeKey) async {
|
||||
var res = await Request()
|
||||
.get(Api.loginInByQrcode, data: {'qrcode_key': qrcodeKey});
|
||||
if (res.data['data']['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:pilipala/models/msg/like.dart';
|
||||
import 'package:pilipala/models/msg/reply.dart';
|
||||
import '../models/msg/account.dart';
|
||||
import '../models/msg/session.dart';
|
||||
import '../utils/wbi_sign.dart';
|
||||
@ -122,68 +126,48 @@ class MsgHttp {
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
};
|
||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 发送私信
|
||||
static Future sendMsg({
|
||||
int? senderUid,
|
||||
int? receiverId,
|
||||
required int senderUid,
|
||||
required int receiverId,
|
||||
int? receiverType,
|
||||
int? msgType,
|
||||
dynamic content,
|
||||
}) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map<String, dynamic> params = await WbiSign().makSign({
|
||||
var res = await Request().post(
|
||||
Api.sendMsg,
|
||||
data: {
|
||||
'msg[sender_uid]': senderUid,
|
||||
'msg[receiver_id]': receiverId,
|
||||
'msg[receiver_type]': receiverType ?? 1,
|
||||
'msg[msg_type]': msgType ?? 1,
|
||||
'msg[receiver_type]': 1,
|
||||
'msg[msg_type]': 1,
|
||||
'msg[msg_status]': 0,
|
||||
'msg[dev_id]': getDevId(),
|
||||
'msg[content]': jsonEncode(content),
|
||||
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
'msg[new_face_version]': 0,
|
||||
'msg[content]': content,
|
||||
'msg[dev_id]': getDevId(),
|
||||
'from_firework': 0,
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
});
|
||||
var res =
|
||||
await Request().post(Api.sendMsg, queryParameters: <String, dynamic>{
|
||||
...params,
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
}, data: {
|
||||
'w_sender_uid': params['msg[sender_uid]'],
|
||||
'w_receiver_id': params['msg[receiver_id]'],
|
||||
'w_dev_id': params['msg[dev_id]'],
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
});
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
};
|
||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,4 +204,87 @@ class MsgHttp {
|
||||
}
|
||||
return s.join();
|
||||
}
|
||||
|
||||
static Future removeSession({
|
||||
int? talkerId,
|
||||
}) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map params = await WbiSign().makSign({
|
||||
'talker_id': talkerId,
|
||||
'session_type': 1,
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf
|
||||
});
|
||||
var res = await Request().get(Api.removeSession, data: params);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future unread() async {
|
||||
var res = await Request().get(Api.unread);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 回复我的
|
||||
static Future messageReply({
|
||||
int? id,
|
||||
int? replyTime,
|
||||
}) async {
|
||||
var params = {
|
||||
if (id != null) 'id': id,
|
||||
if (replyTime != null) 'reply_time': replyTime,
|
||||
};
|
||||
var res = await Request().get(Api.messageReplyAPi, data: params);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MessageReplyModel.fromJson(res.data['data']),
|
||||
};
|
||||
} catch (err) {
|
||||
return {'status': false, 'date': [], 'msg': err.toString()};
|
||||
}
|
||||
} else {
|
||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 收到的赞
|
||||
static Future messageLike({
|
||||
int? id,
|
||||
int? likeTime,
|
||||
}) async {
|
||||
var params = {
|
||||
if (id != null) 'id': id,
|
||||
if (likeTime != null) 'like_time': likeTime,
|
||||
};
|
||||
var res = await Request().get(Api.messageLikeAPi, data: params);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MessageLikeModel.fromJson(res.data['data']),
|
||||
};
|
||||
} catch (err) {
|
||||
return {'status': false, 'date': [], 'msg': err.toString()};
|
||||
}
|
||||
} else {
|
||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +75,7 @@ class SearchHttp {
|
||||
required page,
|
||||
String? order,
|
||||
int? duration,
|
||||
int? tids,
|
||||
}) async {
|
||||
var reqData = {
|
||||
'search_type': searchType.type,
|
||||
@ -84,6 +85,7 @@ class SearchHttp {
|
||||
'page': page,
|
||||
if (order != null) 'order': order,
|
||||
if (duration != null) 'duration': duration,
|
||||
if (tids != null && tids != -1) 'tids': tids,
|
||||
};
|
||||
var res = await Request().get(Api.searchByType, data: reqData);
|
||||
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
|
||||
|
||||
@ -17,7 +17,6 @@ import 'package:pilipala/pages/search/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/router/app_pages.dart';
|
||||
import 'package:pilipala/pages/main/view.dart';
|
||||
import 'package:pilipala/services/disable_battery_opt.dart';
|
||||
import 'package:pilipala/services/service_locator.dart';
|
||||
import 'package:pilipala/utils/app_scheme.dart';
|
||||
import 'package:pilipala/utils/data.dart';
|
||||
@ -66,7 +65,6 @@ void main() async {
|
||||
}
|
||||
|
||||
PiliSchame.init();
|
||||
DisableBatteryOpt();
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
|
||||
93
lib/models/common/action_type.dart
Normal file
93
lib/models/common/action_type.dart
Normal file
@ -0,0 +1,93 @@
|
||||
// 操作类型的枚举值:点赞 不喜欢 收藏 投币 稍后再看 下载封面 后台播放 听视频 分享 下载视频
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
enum ActionType {
|
||||
like,
|
||||
coin,
|
||||
collect,
|
||||
watchLater,
|
||||
share,
|
||||
dislike,
|
||||
downloadCover,
|
||||
copyLink,
|
||||
// backgroundPlay,
|
||||
// listenVideo,
|
||||
// downloadVideo,
|
||||
}
|
||||
|
||||
extension ActionTypeExtension on ActionType {
|
||||
String get value => [
|
||||
'like',
|
||||
'coin',
|
||||
'collect',
|
||||
'watchLater',
|
||||
'share',
|
||||
'dislike',
|
||||
'downloadCover',
|
||||
'copyLink',
|
||||
// 'backgroundPlay',
|
||||
// 'listenVideo',
|
||||
// 'downloadVideo',
|
||||
][index];
|
||||
String get label => [
|
||||
'点赞视频',
|
||||
'投币',
|
||||
'收藏视频',
|
||||
'稍后再看',
|
||||
'视频分享',
|
||||
'不喜欢',
|
||||
'下载封面',
|
||||
'复制链接',
|
||||
// '后台播放',
|
||||
// '听视频',
|
||||
// '下载视频',
|
||||
][index];
|
||||
}
|
||||
|
||||
List<Map> actionMenuConfig = [
|
||||
{
|
||||
'icon': const Icon(Icons.thumb_up_alt_outlined),
|
||||
'label': '点赞视频',
|
||||
'value': ActionType.like,
|
||||
},
|
||||
{
|
||||
'icon': Image.asset(
|
||||
'assets/images/coin.png',
|
||||
width: 26,
|
||||
color: IconTheme.of(Get.context!).color!.withOpacity(0.65),
|
||||
),
|
||||
'label': '投币',
|
||||
'value': ActionType.coin,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(Icons.star_border),
|
||||
'label': '收藏视频',
|
||||
'value': ActionType.collect,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(Icons.watch_later_outlined),
|
||||
'label': '稍后再看',
|
||||
'value': ActionType.watchLater,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(Icons.share),
|
||||
'label': '视频分享',
|
||||
'value': ActionType.share,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(Icons.thumb_down_alt_outlined),
|
||||
'label': '不喜欢',
|
||||
'value': ActionType.dislike,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(Icons.image_outlined),
|
||||
'label': '下载封面',
|
||||
'value': ActionType.downloadCover,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(Icons.link_outlined),
|
||||
'label': '复制链接',
|
||||
'value': ActionType.copyLink,
|
||||
},
|
||||
];
|
||||
@ -69,9 +69,10 @@ class RecVideoItemAppModel {
|
||||
: null;
|
||||
// 由于app端api并不会直接返回与owner的关注状态
|
||||
// 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态,从而与web端接口等效
|
||||
RegExp regex = RegExp(r'已关注|新关注');
|
||||
isFollowed = rcmdReason != null &&
|
||||
rcmdReason!.content != null &&
|
||||
rcmdReason!.content!.contains('关注')
|
||||
regex.hasMatch(rcmdReason!.content!)
|
||||
? 1
|
||||
: 0;
|
||||
// 如果是,就无需再显示推荐原因,交由view统一处理即可
|
||||
|
||||
183
lib/models/msg/like.dart
Normal file
183
lib/models/msg/like.dart
Normal file
@ -0,0 +1,183 @@
|
||||
class MessageLikeModel {
|
||||
MessageLikeModel({
|
||||
this.latest,
|
||||
this.total,
|
||||
});
|
||||
|
||||
Latest? latest;
|
||||
Total? total;
|
||||
|
||||
factory MessageLikeModel.fromJson(Map<String, dynamic> json) =>
|
||||
MessageLikeModel(
|
||||
latest: json["latest"] == null ? null : Latest.fromJson(json["latest"]),
|
||||
total: json["total"] == null ? null : Total.fromJson(json["total"]),
|
||||
);
|
||||
}
|
||||
|
||||
class Latest {
|
||||
Latest({
|
||||
this.items,
|
||||
this.lastViewAt,
|
||||
});
|
||||
|
||||
List? items;
|
||||
int? lastViewAt;
|
||||
|
||||
factory Latest.fromJson(Map<String, dynamic> json) => Latest(
|
||||
items: json["items"],
|
||||
lastViewAt: json["last_view_at"],
|
||||
);
|
||||
}
|
||||
|
||||
class Total {
|
||||
Total({
|
||||
this.cursor,
|
||||
this.items,
|
||||
});
|
||||
|
||||
Cursor? cursor;
|
||||
List<MessageLikeItem>? items;
|
||||
|
||||
factory Total.fromJson(Map<String, dynamic> json) => Total(
|
||||
cursor: Cursor.fromJson(json['cursor']),
|
||||
items: json["items"] == null
|
||||
? []
|
||||
: json["items"].map<MessageLikeItem>((e) {
|
||||
return MessageLikeItem.fromJson(e);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
class Cursor {
|
||||
Cursor({
|
||||
this.id,
|
||||
this.isEnd,
|
||||
this.time,
|
||||
});
|
||||
|
||||
int? id;
|
||||
bool? isEnd;
|
||||
int? time;
|
||||
|
||||
factory Cursor.fromJson(Map<String, dynamic> json) => Cursor(
|
||||
id: json['id'],
|
||||
isEnd: json['is_end'],
|
||||
time: json['time'],
|
||||
);
|
||||
}
|
||||
|
||||
class MessageLikeItem {
|
||||
MessageLikeItem({
|
||||
this.id,
|
||||
this.users,
|
||||
this.item,
|
||||
this.counts,
|
||||
this.likeTime,
|
||||
this.noticeState,
|
||||
this.isExpand = false,
|
||||
});
|
||||
|
||||
int? id;
|
||||
List<MessageLikeUser>? users;
|
||||
MessageLikeItemItem? item;
|
||||
int? counts;
|
||||
int? likeTime;
|
||||
int? noticeState;
|
||||
bool isExpand;
|
||||
|
||||
factory MessageLikeItem.fromJson(Map<String, dynamic> json) =>
|
||||
MessageLikeItem(
|
||||
id: json["id"],
|
||||
users: json["users"] == null
|
||||
? []
|
||||
: json["users"].map<MessageLikeUser>((e) {
|
||||
return MessageLikeUser.fromJson(e);
|
||||
}).toList(),
|
||||
item: json["item"] == null
|
||||
? null
|
||||
: MessageLikeItemItem.fromJson(json["item"]),
|
||||
counts: json["counts"],
|
||||
likeTime: json["like_time"],
|
||||
noticeState: json["notice_state"],
|
||||
);
|
||||
}
|
||||
|
||||
class MessageLikeUser {
|
||||
MessageLikeUser({
|
||||
this.mid,
|
||||
this.fans,
|
||||
this.nickname,
|
||||
this.avatar,
|
||||
this.midLink,
|
||||
this.follow,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
int? fans;
|
||||
String? nickname;
|
||||
String? avatar;
|
||||
String? midLink;
|
||||
bool? follow;
|
||||
|
||||
factory MessageLikeUser.fromJson(Map<String, dynamic> json) =>
|
||||
MessageLikeUser(
|
||||
mid: json["mid"],
|
||||
fans: json["fans"],
|
||||
nickname: json["nickname"],
|
||||
avatar: json["avatar"],
|
||||
midLink: json["mid_link"],
|
||||
follow: json["follow"],
|
||||
);
|
||||
}
|
||||
|
||||
class MessageLikeItemItem {
|
||||
MessageLikeItemItem({
|
||||
this.itemId,
|
||||
this.pid,
|
||||
this.type,
|
||||
this.business,
|
||||
this.businessId,
|
||||
this.replyBusinessId,
|
||||
this.likeBusinessId,
|
||||
this.title,
|
||||
this.desc,
|
||||
this.image,
|
||||
this.uri,
|
||||
this.detailName,
|
||||
this.nativeUri,
|
||||
this.ctime,
|
||||
});
|
||||
|
||||
int? itemId;
|
||||
int? pid;
|
||||
String? type;
|
||||
String? business;
|
||||
int? businessId;
|
||||
int? replyBusinessId;
|
||||
int? likeBusinessId;
|
||||
String? title;
|
||||
String? desc;
|
||||
String? image;
|
||||
String? uri;
|
||||
String? detailName;
|
||||
String? nativeUri;
|
||||
int? ctime;
|
||||
|
||||
factory MessageLikeItemItem.fromJson(Map<String, dynamic> json) =>
|
||||
MessageLikeItemItem(
|
||||
itemId: json["item_id"],
|
||||
pid: json["pid"],
|
||||
type: json["type"],
|
||||
business: json["business"],
|
||||
businessId: json["business_id"],
|
||||
replyBusinessId: json["reply_business_id"],
|
||||
likeBusinessId: json["like_business_id"],
|
||||
title: json["title"],
|
||||
desc: json["desc"],
|
||||
image: json["image"],
|
||||
uri: json["uri"],
|
||||
detailName: json["detail_name"],
|
||||
nativeUri: json["native_uri"],
|
||||
ctime: json["ctime"],
|
||||
);
|
||||
}
|
||||
168
lib/models/msg/reply.dart
Normal file
168
lib/models/msg/reply.dart
Normal file
@ -0,0 +1,168 @@
|
||||
class MessageReplyModel {
|
||||
MessageReplyModel({
|
||||
this.cursor,
|
||||
this.items,
|
||||
});
|
||||
|
||||
Cursor? cursor;
|
||||
List<MessageReplyItem>? items;
|
||||
|
||||
MessageReplyModel.fromJson(Map<String, dynamic> json) {
|
||||
cursor = Cursor.fromJson(json['cursor']);
|
||||
items = json["items"] != null
|
||||
? json["items"].map<MessageReplyItem>((e) {
|
||||
return MessageReplyItem.fromJson(e);
|
||||
}).toList()
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
class Cursor {
|
||||
Cursor({
|
||||
this.id,
|
||||
this.isEnd,
|
||||
this.time,
|
||||
});
|
||||
|
||||
int? id;
|
||||
bool? isEnd;
|
||||
int? time;
|
||||
|
||||
Cursor.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
isEnd = json['is_end'];
|
||||
time = json['time'];
|
||||
}
|
||||
}
|
||||
|
||||
class MessageReplyItem {
|
||||
MessageReplyItem({
|
||||
this.count,
|
||||
this.id,
|
||||
this.isMulti,
|
||||
this.item,
|
||||
this.replyTime,
|
||||
this.user,
|
||||
});
|
||||
|
||||
int? count;
|
||||
int? id;
|
||||
int? isMulti;
|
||||
ReplyContentItem? item;
|
||||
int? replyTime;
|
||||
ReplyUser? user;
|
||||
|
||||
MessageReplyItem.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
id = json['id'];
|
||||
isMulti = json['is_multi'];
|
||||
item = ReplyContentItem.fromJson(json["item"]);
|
||||
replyTime = json['reply_time'];
|
||||
user = ReplyUser.fromJson(json['user']);
|
||||
}
|
||||
}
|
||||
|
||||
class ReplyContentItem {
|
||||
ReplyContentItem({
|
||||
this.subjectId,
|
||||
this.rootId,
|
||||
this.sourceId,
|
||||
this.targetId,
|
||||
this.type,
|
||||
this.businessId,
|
||||
this.business,
|
||||
this.title,
|
||||
this.desc,
|
||||
this.image,
|
||||
this.uri,
|
||||
this.nativeUri,
|
||||
this.detailTitle,
|
||||
this.rootReplyContent,
|
||||
this.sourceContent,
|
||||
this.targetReplyContent,
|
||||
this.atDetails,
|
||||
this.topicDetails,
|
||||
this.hideReplyButton,
|
||||
this.hideLikeButton,
|
||||
this.likeState,
|
||||
this.danmu,
|
||||
this.message,
|
||||
});
|
||||
|
||||
int? subjectId;
|
||||
int? rootId;
|
||||
int? sourceId;
|
||||
int? targetId;
|
||||
String? type;
|
||||
int? businessId;
|
||||
String? business;
|
||||
String? title;
|
||||
String? desc;
|
||||
String? image;
|
||||
String? uri;
|
||||
String? nativeUri;
|
||||
String? detailTitle;
|
||||
String? rootReplyContent;
|
||||
String? sourceContent;
|
||||
String? targetReplyContent;
|
||||
List? atDetails;
|
||||
List? topicDetails;
|
||||
bool? hideReplyButton;
|
||||
bool? hideLikeButton;
|
||||
int? likeState;
|
||||
String? danmu;
|
||||
String? message;
|
||||
|
||||
ReplyContentItem.fromJson(Map<String, dynamic> json) {
|
||||
subjectId = json['subject_id'];
|
||||
rootId = json['root_id'];
|
||||
sourceId = json['source_id'];
|
||||
targetId = json['target_id'];
|
||||
type = json['type'];
|
||||
businessId = json['business_id'];
|
||||
business = json['business'];
|
||||
title = json['title'];
|
||||
desc = json['desc'];
|
||||
image = json['image'];
|
||||
uri = json['uri'];
|
||||
nativeUri = json['native_uri'];
|
||||
detailTitle = json['detail_title'];
|
||||
rootReplyContent = json['root_reply_content'];
|
||||
sourceContent = json['source_content'];
|
||||
targetReplyContent = json['target_reply_content'];
|
||||
atDetails = json['at_details'];
|
||||
topicDetails = json['topic_details'];
|
||||
hideReplyButton = json['hide_reply_button'];
|
||||
hideLikeButton = json['hide_like_button'];
|
||||
likeState = json['like_state'];
|
||||
danmu = json['danmu'];
|
||||
message = json['message'];
|
||||
}
|
||||
}
|
||||
|
||||
class ReplyUser {
|
||||
ReplyUser({
|
||||
this.mid,
|
||||
this.fans,
|
||||
this.nickname,
|
||||
this.avatar,
|
||||
this.midLink,
|
||||
this.follow,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
int? fans;
|
||||
String? nickname;
|
||||
String? avatar;
|
||||
String? midLink;
|
||||
bool? follow;
|
||||
|
||||
ReplyUser.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
fans = json['fans'];
|
||||
nickname = json['nickname'];
|
||||
avatar = json['avatar'];
|
||||
midLink = json['mid_link'];
|
||||
follow = json['follow'];
|
||||
}
|
||||
}
|
||||
@ -20,10 +20,10 @@ class IntroDetail extends StatelessWidget {
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
TextStyle smallTitle = TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
);
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
height: sheetHeight,
|
||||
child: Column(
|
||||
|
||||
@ -167,8 +167,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
thumbDecoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
|
||||
@ -19,7 +19,7 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
||||
};
|
||||
Color bgColor = floor == 1
|
||||
? Theme.of(context).dividerColor.withOpacity(0.08)
|
||||
: Theme.of(context).colorScheme.background;
|
||||
: Theme.of(context).colorScheme.surface;
|
||||
switch (type) {
|
||||
case 'ADDITIONAL_TYPE_UGC':
|
||||
// 转发的投稿
|
||||
|
||||
@ -52,7 +52,7 @@ class AuthorPanel extends StatelessWidget {
|
||||
color: item.modules.moduleAuthor!.vip != null &&
|
||||
item.modules.moduleAuthor!.vip['status'] > 0
|
||||
? const Color.fromARGB(255, 251, 100, 163)
|
||||
: Theme.of(context).colorScheme.onBackground,
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
),
|
||||
),
|
||||
|
||||
@ -69,7 +69,7 @@ class _UpPanelState extends State<UpPanel> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@ -95,7 +95,7 @@ class _UpPanelState extends State<UpPanel> {
|
||||
),
|
||||
Container(
|
||||
height: 90,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/login.dart';
|
||||
import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart';
|
||||
import 'package:pilipala/models/login/index.dart';
|
||||
import 'package:pilipala/utils/login.dart';
|
||||
|
||||
class LoginPageController extends GetxController {
|
||||
final GlobalKey mobFormKey = GlobalKey<FormState>();
|
||||
@ -26,9 +29,23 @@ class LoginPageController extends GetxController {
|
||||
|
||||
final Gt3FlutterPlugin captcha = Gt3FlutterPlugin();
|
||||
|
||||
// 倒计时60s
|
||||
RxInt seconds = 60.obs;
|
||||
Timer? timer;
|
||||
RxBool smsCodeSendStatus = false.obs;
|
||||
|
||||
// 默认密码登录
|
||||
RxInt loginType = 0.obs;
|
||||
|
||||
late String captchaKey;
|
||||
|
||||
late int tel;
|
||||
late int webSmsCode;
|
||||
|
||||
RxInt validSeconds = 180.obs;
|
||||
Timer? validTimer;
|
||||
late String qrcodeKey;
|
||||
|
||||
// 监听pageView切换
|
||||
void onPageChange(int index) {
|
||||
currentIndex.value = index;
|
||||
@ -43,6 +60,7 @@ class LoginPageController extends GetxController {
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
passwordTextFieldNode.requestFocus();
|
||||
(mobFormKey.currentState as FormState).save();
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,18 +104,64 @@ class LoginPageController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码登录
|
||||
void loginInByCode() {
|
||||
if ((msgCodeFormKey.currentState as FormState).validate()) {}
|
||||
// web端密码登录
|
||||
void loginInByWebPassword() async {
|
||||
if ((passwordFormKey.currentState as FormState).validate()) {
|
||||
getCaptcha((data) async {
|
||||
CaptchaDataModel captchaData = data;
|
||||
var webKeyRes = await LoginHttp.getWebKey();
|
||||
if (webKeyRes['status']) {
|
||||
String rhash = webKeyRes['data']['hash'];
|
||||
String key = webKeyRes['data']['key'];
|
||||
dynamic publicKey = RSAKeyParser().parse(key);
|
||||
String passwordEncryptyed = Encrypter(RSA(publicKey: publicKey))
|
||||
.encrypt(rhash + passwordTextController.text)
|
||||
.base64;
|
||||
var res = await LoginHttp.loginInByWebPwd(
|
||||
username: tel,
|
||||
password: passwordEncryptyed,
|
||||
token: captchaData.token!,
|
||||
challenge: captchaData.geetest!.challenge!,
|
||||
validate: captchaData.validate!,
|
||||
seccode: captchaData.seccode!,
|
||||
);
|
||||
if (res['status']) {
|
||||
await LoginUtils.confirmLogin('', null);
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(webKeyRes['msg']);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// app端验证码
|
||||
void getMsgCode() async {
|
||||
// web端验证码登录
|
||||
void loginInByCode() async {
|
||||
if ((msgCodeFormKey.currentState as FormState).validate()) {
|
||||
(msgCodeFormKey.currentState as FormState).save();
|
||||
var res = await LoginHttp.loginInByWebSmsCode(
|
||||
cid: 86,
|
||||
tel: tel,
|
||||
code: webSmsCode,
|
||||
captchaKey: captchaKey,
|
||||
);
|
||||
if (res['status']) {
|
||||
await LoginUtils.confirmLogin('', null);
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取app端验证码
|
||||
void getAppMsgCode() async {
|
||||
getCaptcha((data) async {
|
||||
CaptchaDataModel captchaData = data;
|
||||
var res = await LoginHttp.sendAppSmsCode(
|
||||
cid: 86,
|
||||
tel: 13734077064,
|
||||
tel: tel,
|
||||
token: captchaData.token!,
|
||||
challenge: captchaData.geetest!.challenge!,
|
||||
validate: captchaData.validate!,
|
||||
@ -121,7 +185,7 @@ class LoginPageController extends GetxController {
|
||||
captcha.addEventHandler(onShow: (Map<String, dynamic> message) async {
|
||||
SmartDialog.dismiss();
|
||||
}, onClose: (Map<String, dynamic> message) async {
|
||||
SmartDialog.showToast('关闭验证');
|
||||
SmartDialog.showToast('取消验证');
|
||||
}, onResult: (Map<String, dynamic> message) async {
|
||||
debugPrint("Captcha result: $message");
|
||||
String code = message["code"];
|
||||
@ -201,4 +265,72 @@ class LoginPageController extends GetxController {
|
||||
captcha.startCaptcha(registerData);
|
||||
} else {}
|
||||
}
|
||||
|
||||
// 获取web端验证码
|
||||
void getWebMsgCode() async {
|
||||
getCaptcha((data) async {
|
||||
CaptchaDataModel captchaData = data;
|
||||
var res = await LoginHttp.sendWebSmsCode(
|
||||
cid: 86,
|
||||
tel: tel,
|
||||
token: captchaData.token!,
|
||||
challenge: captchaData.geetest!.challenge!,
|
||||
validate: captchaData.validate!,
|
||||
seccode: captchaData.seccode!,
|
||||
);
|
||||
if (res['status']) {
|
||||
captchaKey = res['data']['captcha_key'];
|
||||
SmartDialog.showToast('验证码已发送');
|
||||
// 倒计时60s
|
||||
smsCodeSendStatus.value = true;
|
||||
startTimer();
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 验证码倒计时
|
||||
void startTimer() {
|
||||
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (seconds.value > 0) {
|
||||
seconds.value--;
|
||||
} else {
|
||||
seconds.value = 60;
|
||||
smsCodeSendStatus.value = false;
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取登录二维码
|
||||
Future getWebQrcode() async {
|
||||
var res = await LoginHttp.getWebQrcode();
|
||||
validSeconds.value = 180;
|
||||
if (res['status']) {
|
||||
qrcodeKey = res['data']['qrcode_key'];
|
||||
validTimer = Timer.periodic(const Duration(seconds: 1), (validTimer) {
|
||||
if (validSeconds.value > 0) {
|
||||
validSeconds.value--;
|
||||
queryWebQrcodeStatus();
|
||||
} else {
|
||||
getWebQrcode();
|
||||
validTimer.cancel();
|
||||
}
|
||||
});
|
||||
return res;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
// 轮询二维码登录状态
|
||||
Future queryWebQrcodeStatus() async {
|
||||
var res = await LoginHttp.queryWebQrcodeStatus(qrcodeKey);
|
||||
if (res['status']) {
|
||||
await LoginUtils.confirmLogin('', null);
|
||||
validTimer?.cancel();
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
@ -14,8 +15,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||
final LoginPageController _loginPageCtr = Get.put(LoginPageController());
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
void dispose() {
|
||||
_loginPageCtr.validTimer?.cancel();
|
||||
_loginPageCtr.timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -37,6 +40,107 @@ class _LoginPageState extends State<LoginPage> {
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: '浏览器打开',
|
||||
onPressed: () {
|
||||
Get.offNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': 'https://passport.bilibili.com/h5-app/passport/login',
|
||||
'type': 'login',
|
||||
'pageTitle': '登录bilibili',
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.language, size: 20),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '二维码登录',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, StateSetter setState) {
|
||||
return AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
const Text('扫码登录'),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {});
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
],
|
||||
),
|
||||
contentPadding: const EdgeInsets.fromLTRB(0, 0, 0, 4),
|
||||
content: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Container(
|
||||
width: 200,
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: FutureBuilder(
|
||||
future: _loginPageCtr.getWebQrcode(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
Map data = snapshot.data as Map;
|
||||
return QrImageView(
|
||||
data: data['data']['url'],
|
||||
backgroundColor: Colors.white,
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Obx(() {
|
||||
return Text(
|
||||
'有效期: ${_loginPageCtr.validSeconds.value}s',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
);
|
||||
}),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
'检查登录状态',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.fontSize,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
).then((value) {
|
||||
_loginPageCtr.validTimer!.cancel();
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.qr_code, size: 20),
|
||||
),
|
||||
const SizedBox(width: 22),
|
||||
],
|
||||
),
|
||||
body: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
@ -64,18 +168,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'请使用您的 BiliBili 账号登录。',
|
||||
style: Theme.of(context).textTheme.titleSmall!,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: const Icon(Icons.info_outline, size: 16),
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 38, bottom: 15),
|
||||
child: TextFormField(
|
||||
@ -93,35 +189,12 @@ class _LoginPageState extends State<LoginPage> {
|
||||
validator: (v) {
|
||||
return v!.trim().isNotEmpty ? null : "手机号码不能为空";
|
||||
},
|
||||
onSaved: (val) {
|
||||
print(val);
|
||||
},
|
||||
onSaved: (val) => _loginPageCtr.tel = int.parse(val!),
|
||||
onEditingComplete: () {
|
||||
_loginPageCtr.nextStep();
|
||||
},
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Get.offNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url':
|
||||
'https://passport.bilibili.com/h5-app/passport/login',
|
||||
'type': 'login',
|
||||
'pageTitle': '登录bilibili',
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 2),
|
||||
child: Text(
|
||||
'使用网页端登录',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@ -236,7 +309,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
onPressed: () =>
|
||||
_loginPageCtr.loginInByAppPassword(),
|
||||
_loginPageCtr.loginInByWebPassword(),
|
||||
child: const Text('确认登录'),
|
||||
)
|
||||
],
|
||||
@ -308,21 +381,28 @@ class _LoginPageState extends State<LoginPage> {
|
||||
? null
|
||||
: "验证码不能为空";
|
||||
},
|
||||
onSaved: (val) {
|
||||
print(val);
|
||||
},
|
||||
onSaved: (val) => _loginPageCtr.webSmsCode =
|
||||
int.parse(val!),
|
||||
),
|
||||
Positioned(
|
||||
Obx(() {
|
||||
return Positioned(
|
||||
right: 8,
|
||||
top: 4,
|
||||
top: 0,
|
||||
child: Center(
|
||||
child: TextButton(
|
||||
onPressed: () =>
|
||||
_loginPageCtr.getMsgCode(),
|
||||
child: const Text('获取验证码'),
|
||||
),
|
||||
),
|
||||
onPressed: _loginPageCtr
|
||||
.smsCodeSendStatus.value
|
||||
? null
|
||||
: () =>
|
||||
_loginPageCtr.getWebMsgCode(),
|
||||
child: _loginPageCtr
|
||||
.smsCodeSendStatus.value
|
||||
? Text(
|
||||
'重新获取(${_loginPageCtr.seconds.value}s)')
|
||||
: const Text('获取验证码')),
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -90,9 +90,8 @@ class _MemberPageState extends State<MemberPage>
|
||||
() => Text(
|
||||
_memberController.memberInfo.value.name ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
fontSize: 14),
|
||||
),
|
||||
),
|
||||
|
||||
@ -208,7 +208,17 @@ class ProfilePanel extends StatelessWidget {
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
Get.toNamed(
|
||||
'/whisperDetail',
|
||||
parameters: {
|
||||
'name': memberInfo.name!,
|
||||
'face': memberInfo.face!,
|
||||
'mid': memberInfo.mid.toString(),
|
||||
'heroTag': ctr.heroTag!,
|
||||
},
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
|
||||
3
lib/pages/message/at/controller.dart
Normal file
3
lib/pages/message/at/controller.dart
Normal file
@ -0,0 +1,3 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MessageAtController extends GetxController {}
|
||||
4
lib/pages/message/at/index.dart
Normal file
4
lib/pages/message/at/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library message_at;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
19
lib/pages/message/at/view.dart
Normal file
19
lib/pages/message/at/view.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MessageAtPage extends StatefulWidget {
|
||||
const MessageAtPage({super.key});
|
||||
|
||||
@override
|
||||
State<MessageAtPage> createState() => _MessageAtPageState();
|
||||
}
|
||||
|
||||
class _MessageAtPageState extends State<MessageAtPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('@我的'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/pages/message/like/controller.dart
Normal file
30
lib/pages/message/like/controller.dart
Normal file
@ -0,0 +1,30 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/msg.dart';
|
||||
import 'package:pilipala/models/msg/like.dart';
|
||||
|
||||
class MessageLikeController extends GetxController {
|
||||
Cursor? cursor;
|
||||
RxList<MessageLikeItem> likeItems = <MessageLikeItem>[].obs;
|
||||
|
||||
Future queryMessageLike({String type = 'init'}) async {
|
||||
if (cursor != null && cursor!.isEnd == true) {
|
||||
return {};
|
||||
}
|
||||
var params = {
|
||||
if (type == 'onLoad') 'id': cursor!.id,
|
||||
if (type == 'onLoad') 'likeTime': cursor!.time,
|
||||
};
|
||||
var res = await MsgHttp.messageLike(
|
||||
id: params['id'], likeTime: params['likeTime']);
|
||||
if (res['status']) {
|
||||
cursor = res['data'].total.cursor;
|
||||
likeItems.addAll(res['data'].total.items);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Future expandedUsersAvatar(i) async {
|
||||
likeItems[i].isExpand = !likeItems[i].isExpand;
|
||||
likeItems.refresh();
|
||||
}
|
||||
}
|
||||
4
lib/pages/message/like/index.dart
Normal file
4
lib/pages/message/like/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library message_like;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
319
lib/pages/message/like/view.dart
Normal file
319
lib/pages/message/like/view.dart
Normal file
@ -0,0 +1,319 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/msg/like.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class MessageLikePage extends StatefulWidget {
|
||||
const MessageLikePage({super.key});
|
||||
|
||||
@override
|
||||
State<MessageLikePage> createState() => _MessageLikePageState();
|
||||
}
|
||||
|
||||
class _MessageLikePageState extends State<MessageLikePage> {
|
||||
final MessageLikeController _messageLikeCtr =
|
||||
Get.put(MessageLikeController());
|
||||
late Future _futureBuilderFuture;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _messageLikeCtr.queryMessageLike();
|
||||
scrollController.addListener(
|
||||
() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||
_messageLikeCtr.queryMessageLike(type: 'onLoad');
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('收到的赞'),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _messageLikeCtr.queryMessageLike(type: 'init');
|
||||
},
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
if (snapshot.data['status']) {
|
||||
final likeItems = _messageLikeCtr.likeItems;
|
||||
return Obx(
|
||||
() => ListView.separated(
|
||||
controller: scrollController,
|
||||
itemBuilder: (context, index) => LikeItem(
|
||||
item: likeItems[index],
|
||||
index: index,
|
||||
messageLikeCtr: _messageLikeCtr,
|
||||
),
|
||||
itemCount: likeItems.length,
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return Divider(
|
||||
indent: 66,
|
||||
endIndent: 14,
|
||||
height: 1,
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_messageLikeCtr.queryMessageLike();
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LikeItem extends StatelessWidget {
|
||||
final MessageLikeItem item;
|
||||
final int index;
|
||||
final MessageLikeController messageLikeCtr;
|
||||
|
||||
const LikeItem(
|
||||
{super.key,
|
||||
required this.item,
|
||||
required this.index,
|
||||
required this.messageLikeCtr});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color outline = Theme.of(context).colorScheme.outline;
|
||||
final nickNameList = item.users!.map((e) => e.nickname).take(2).toList();
|
||||
int usersLen = item.users!.length > 3 ? 3 : item.users!.length;
|
||||
final String bvid = item.item!.uri!.split('/').last;
|
||||
// 页码
|
||||
final String page =
|
||||
item.item!.nativeUri!.split('page=').last.split('&').first;
|
||||
// 根评论id
|
||||
final String commentRootId =
|
||||
item.item!.nativeUri!.split('comment_root_id=').last.split('&').first;
|
||||
// 二级评论id
|
||||
final String commentSecondaryId =
|
||||
item.item!.nativeUri!.split('comment_secondary_id=').last;
|
||||
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
try {
|
||||
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
final String heroTag = Utils.makeHeroTag(bvid);
|
||||
Get.toNamed<dynamic>(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: <String, String?>{
|
||||
'pic': '',
|
||||
'heroTag': heroTag,
|
||||
},
|
||||
);
|
||||
} catch (_) {
|
||||
SmartDialog.showToast('视频可能失效了');
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
if (usersLen == 1) {
|
||||
final String heroTag =
|
||||
Utils.makeHeroTag(item.users!.first.mid);
|
||||
Get.toNamed('/member?mid=${item.users!.first.mid}',
|
||||
arguments: {
|
||||
'face': item.users!.first.avatar,
|
||||
'heroTag': heroTag
|
||||
});
|
||||
} else {
|
||||
messageLikeCtr.expandedUsersAvatar(index);
|
||||
}
|
||||
},
|
||||
// 多个头像层叠
|
||||
child: SizedBox(
|
||||
width: 50,
|
||||
height: 50,
|
||||
child: Stack(
|
||||
children: [
|
||||
for (var i = 0; i < usersLen; i++)
|
||||
Positioned(
|
||||
top: i % 2 * (50 / (usersLen >= 2 ? 2 : 1)),
|
||||
left: i / 2 * (50 / (usersLen >= 2 ? 2 : 1)),
|
||||
child: NetworkImgLayer(
|
||||
width: 50 / (usersLen >= 2 ? 2 : 1),
|
||||
height: 50 / (usersLen >= 2 ? 2 : 1),
|
||||
type: 'avatar',
|
||||
src: item.users![i].avatar,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text.rich(TextSpan(children: [
|
||||
TextSpan(text: nickNameList.join('、')),
|
||||
const TextSpan(text: ' '),
|
||||
if (item.users!.length > 1)
|
||||
TextSpan(
|
||||
text: '等总计${item.users!.length}人',
|
||||
style: TextStyle(color: outline),
|
||||
),
|
||||
TextSpan(
|
||||
text: '赞了我的评论',
|
||||
style: TextStyle(color: outline),
|
||||
),
|
||||
])),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
Utils.dateFormat(item.likeTime!, formatType: 'detail'),
|
||||
style: TextStyle(color: outline),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 25),
|
||||
if (item.item!.type! == 'reply')
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Text(
|
||||
item.item!.title!,
|
||||
maxLines: 4,
|
||||
style: const TextStyle(fontSize: 12, letterSpacing: 0.3),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (item.item!.type! == 'video')
|
||||
NetworkImgLayer(
|
||||
width: 60,
|
||||
height: 60,
|
||||
src: item.item!.image,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: item.isExpand ? Get.size.width - 74 : 0,
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
child: ListView.builder(
|
||||
itemCount: item.users!.length,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(i == 0 ? 12 : 4, 8, 4, 0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final String heroTag =
|
||||
Utils.makeHeroTag(item.users![i].mid);
|
||||
Get.toNamed(
|
||||
'/member?mid=${item.users![i].mid}',
|
||||
arguments: {
|
||||
'face': item.users![i].avatar,
|
||||
'heroTag': heroTag
|
||||
},
|
||||
);
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: 42,
|
||||
height: 42,
|
||||
type: 'avatar',
|
||||
src: item.users![i].avatar,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
width: 68,
|
||||
child: Text(
|
||||
textAlign: TextAlign.center,
|
||||
item.users![i].nickname!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(color: outline),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
messageLikeCtr.expandedUsersAvatar(index);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: item.isExpand ? 74 : 0,
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
25
lib/pages/message/reply/controller.dart
Normal file
25
lib/pages/message/reply/controller.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/msg.dart';
|
||||
import 'package:pilipala/models/msg/reply.dart';
|
||||
|
||||
class MessageReplyController extends GetxController {
|
||||
Cursor? cursor;
|
||||
RxList<MessageReplyItem> replyItems = <MessageReplyItem>[].obs;
|
||||
|
||||
Future queryMessageReply({String type = 'init'}) async {
|
||||
if (cursor != null && cursor!.isEnd == true) {
|
||||
return {};
|
||||
}
|
||||
var params = {
|
||||
if (type == 'onLoad') 'id': cursor!.id,
|
||||
if (type == 'onLoad') 'replyTime': cursor!.time,
|
||||
};
|
||||
var res = await MsgHttp.messageReply(
|
||||
id: params['id'], replyTime: params['replyTime']);
|
||||
if (res['status']) {
|
||||
cursor = res['data'].cursor;
|
||||
replyItems.addAll(res['data'].items);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
4
lib/pages/message/reply/index.dart
Normal file
4
lib/pages/message/reply/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library message_reply;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
272
lib/pages/message/reply/view.dart
Normal file
272
lib/pages/message/reply/view.dart
Normal file
@ -0,0 +1,272 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/msg/reply.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class MessageReplyPage extends StatefulWidget {
|
||||
const MessageReplyPage({super.key});
|
||||
|
||||
@override
|
||||
State<MessageReplyPage> createState() => _MessageReplyPageState();
|
||||
}
|
||||
|
||||
class _MessageReplyPageState extends State<MessageReplyPage> {
|
||||
final MessageReplyController _messageReplyCtr =
|
||||
Get.put(MessageReplyController());
|
||||
late Future _futureBuilderFuture;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _messageReplyCtr.queryMessageReply();
|
||||
scrollController.addListener(
|
||||
() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||
_messageReplyCtr.queryMessageReply(type: 'onLoad');
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('回复我的'),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _messageReplyCtr.queryMessageReply(type: 'init');
|
||||
},
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
if (snapshot.data['status']) {
|
||||
final replyItems = _messageReplyCtr.replyItems;
|
||||
return Obx(
|
||||
() => ListView.separated(
|
||||
controller: scrollController,
|
||||
itemBuilder: (context, index) =>
|
||||
ReplyItem(item: replyItems[index]),
|
||||
itemCount: replyItems.length,
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return Divider(
|
||||
indent: 66,
|
||||
endIndent: 14,
|
||||
height: 1,
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_messageReplyCtr.queryMessageReply();
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReplyItem extends StatelessWidget {
|
||||
final MessageReplyItem item;
|
||||
|
||||
const ReplyItem({super.key, required this.item});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color outline = Theme.of(context).colorScheme.outline;
|
||||
final String heroTag = Utils.makeHeroTag(item.user!.mid);
|
||||
final String bvid = item.item!.uri!.split('/').last;
|
||||
// 页码
|
||||
final String page =
|
||||
item.item!.nativeUri!.split('page=').last.split('&').first;
|
||||
// 根评论id
|
||||
final String commentRootId =
|
||||
item.item!.nativeUri!.split('comment_root_id=').last.split('&').first;
|
||||
// 二级评论id
|
||||
final String commentSecondaryId =
|
||||
item.item!.nativeUri!.split('comment_secondary_id=').last;
|
||||
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
final String heroTag = Utils.makeHeroTag(bvid);
|
||||
Get.toNamed<dynamic>(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: <String, String?>{
|
||||
'pic': '',
|
||||
'heroTag': heroTag,
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Get.toNamed('/member?mid=${item.user!.mid}',
|
||||
arguments: {'face': item.user!.avatar, 'heroTag': heroTag});
|
||||
},
|
||||
child: Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
width: 42,
|
||||
height: 42,
|
||||
type: 'avatar',
|
||||
src: item.user!.avatar,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text.rich(TextSpan(children: [
|
||||
TextSpan(text: item.user!.nickname!),
|
||||
const TextSpan(text: ' '),
|
||||
if (item.item!.type! == 'video')
|
||||
TextSpan(
|
||||
text: '对我的视频发表了评论', style: TextStyle(color: outline)),
|
||||
if (item.item!.type! == 'reply')
|
||||
TextSpan(
|
||||
text: '回复了我的评论',
|
||||
style: TextStyle(color: outline),
|
||||
),
|
||||
])),
|
||||
const SizedBox(height: 6),
|
||||
Text.rich(
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(letterSpacing: 0.3),
|
||||
buildContent(context, item.item)),
|
||||
if (item.item!.targetReplyContent != '') ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
item.item!.targetReplyContent!,
|
||||
style: TextStyle(color: outline),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
Utils.dateFormat(item.replyTime!, formatType: 'detail'),
|
||||
style: TextStyle(color: outline),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text('回复', style: TextStyle(color: outline)),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 25),
|
||||
if (item.item!.type! == 'reply')
|
||||
Container(
|
||||
width: 60,
|
||||
height: 80,
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Text(
|
||||
item.item!.rootReplyContent!,
|
||||
maxLines: 4,
|
||||
style: const TextStyle(fontSize: 12, letterSpacing: 0.3),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (item.item!.type! == 'video')
|
||||
NetworkImgLayer(
|
||||
width: 60,
|
||||
height: 60,
|
||||
src: item.item!.image,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, item) {
|
||||
List? atDetails = item!.atDetails;
|
||||
final List<InlineSpan> spanChilds = <InlineSpan>[];
|
||||
if (atDetails!.isNotEmpty) {
|
||||
final String patternStr =
|
||||
atDetails.map<String>((e) => '@${e['nickname']}').toList().join('|');
|
||||
final RegExp regExp = RegExp(patternStr);
|
||||
item.sourceContent!.splitMapJoin(
|
||||
regExp,
|
||||
onMatch: (Match match) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: match.group(0),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
var currentUser = atDetails
|
||||
.where((e) => e['nickname'] == match.group(0)!.substring(1))
|
||||
.first;
|
||||
Get.toNamed('/member?mid=${currentUser['mid']}', arguments: {
|
||||
'face': currentUser['avatar'],
|
||||
});
|
||||
},
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
);
|
||||
return '';
|
||||
},
|
||||
onNonMatch: (String nonMatch) {
|
||||
spanChilds.add(
|
||||
TextSpan(text: nonMatch),
|
||||
);
|
||||
return '';
|
||||
},
|
||||
);
|
||||
} else {
|
||||
spanChilds.add(
|
||||
TextSpan(text: item.sourceContent),
|
||||
);
|
||||
}
|
||||
|
||||
return TextSpan(children: spanChilds);
|
||||
}
|
||||
3
lib/pages/message/system/controller.dart
Normal file
3
lib/pages/message/system/controller.dart
Normal file
@ -0,0 +1,3 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MessageSystemController extends GetxController {}
|
||||
4
lib/pages/message/system/index.dart
Normal file
4
lib/pages/message/system/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library message_system;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
19
lib/pages/message/system/view.dart
Normal file
19
lib/pages/message/system/view.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MessageSystemPage extends StatefulWidget {
|
||||
const MessageSystemPage({super.key});
|
||||
|
||||
@override
|
||||
State<MessageSystemPage> createState() => _MessageSystemPageState();
|
||||
}
|
||||
|
||||
class _MessageSystemPageState extends State<MessageSystemPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('系统通知'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,6 @@ import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/models/common/theme_type.dart';
|
||||
import 'package:pilipala/models/user/info.dart';
|
||||
import 'package:pilipala/models/user/stat.dart';
|
||||
import 'package:pilipala/utils/route_push.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class MineController extends GetxController {
|
||||
@ -34,8 +33,7 @@ class MineController extends GetxController {
|
||||
|
||||
onLogin() async {
|
||||
if (!userLogin.value) {
|
||||
RoutePush.loginPush();
|
||||
// Get.toNamed('/loginPage');
|
||||
Get.toNamed('/loginPage', preventDuplicates: false);
|
||||
} else {
|
||||
int mid = userInfo.value.mid!;
|
||||
String face = userInfo.value.face!;
|
||||
@ -59,8 +57,6 @@ class MineController extends GetxController {
|
||||
} else {
|
||||
resetUserInfo();
|
||||
}
|
||||
} else {
|
||||
resetUserInfo();
|
||||
}
|
||||
await queryUserStatOwner();
|
||||
return res;
|
||||
|
||||
@ -5,18 +5,22 @@ class SearchText extends StatelessWidget {
|
||||
final Function? onSelect;
|
||||
final int? searchTextIdx;
|
||||
final Function? onLongSelect;
|
||||
final bool isSelect;
|
||||
const SearchText({
|
||||
super.key,
|
||||
this.searchText,
|
||||
this.onSelect,
|
||||
this.searchTextIdx,
|
||||
this.onLongSelect,
|
||||
this.isSelect = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
|
||||
color: isSelect
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
@ -34,7 +38,10 @@ class SearchText extends StatelessWidget {
|
||||
child: Text(
|
||||
searchText!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
color: isSelect
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -16,6 +16,8 @@ class SearchPanelController extends GetxController {
|
||||
RxString order = ''.obs;
|
||||
// 视频时长筛选 仅用于搜索视频
|
||||
RxInt duration = 0.obs;
|
||||
// 视频分区筛选 仅用于搜索视频 -1时不传
|
||||
RxInt tids = (-1).obs;
|
||||
|
||||
Future onSearch({type = 'init'}) async {
|
||||
var result = await SearchHttp.searchByType(
|
||||
@ -23,7 +25,9 @@ class SearchPanelController extends GetxController {
|
||||
keyword: keyword!,
|
||||
page: page.value,
|
||||
order: searchType!.type != 'video' ? null : order.value,
|
||||
duration: searchType!.type != 'video' ? null : duration.value);
|
||||
duration: searchType!.type != 'video' ? null : duration.value,
|
||||
tids: searchType!.type != 'video' ? null : tids.value,
|
||||
);
|
||||
if (result['status']) {
|
||||
if (type == 'onRefresh') {
|
||||
resultList.value = result['data'].list;
|
||||
|
||||
@ -3,6 +3,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/pages/search/widgets/search_text.dart';
|
||||
import 'package:pilipala/pages/search_panel/index.dart';
|
||||
|
||||
class SearchVideoPanel extends StatelessWidget {
|
||||
@ -94,7 +95,7 @@ class SearchVideoPanel extends StatelessWidget {
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () => controller.onShowFilterDialog(ctr),
|
||||
onPressed: () => controller.onShowFilterSheet(ctr),
|
||||
icon: Icon(
|
||||
Icons.filter_list_outlined,
|
||||
size: 18,
|
||||
@ -165,7 +166,33 @@ class VideoPanelController extends GetxController {
|
||||
{'label': '30-60分钟', 'value': 3},
|
||||
{'label': '60分钟+', 'value': 4},
|
||||
];
|
||||
List<Map<String, dynamic>> partFiltersList = [
|
||||
{'label': '全部', 'value': -1},
|
||||
{'label': '动画', 'value': 1},
|
||||
{'label': '番剧', 'value': 13},
|
||||
{'label': '国创', 'value': 167},
|
||||
{'label': '音乐', 'value': 3},
|
||||
{'label': '舞蹈', 'value': 129},
|
||||
{'label': '游戏', 'value': 4},
|
||||
{'label': '知识', 'value': 36},
|
||||
{'label': '科技', 'value': 188},
|
||||
{'label': '运动', 'value': 234},
|
||||
{'label': '汽车', 'value': 223},
|
||||
{'label': '生活', 'value': 160},
|
||||
{'label': '美食', 'value': 211},
|
||||
{'label': '动物', 'value': 217},
|
||||
{'label': '鬼畜', 'value': 119},
|
||||
{'label': '时尚', 'value': 155},
|
||||
{'label': '资讯', 'value': 202},
|
||||
{'label': '娱乐', 'value': 5},
|
||||
{'label': '影视', 'value': 181},
|
||||
{'label': '记录', 'value': 177},
|
||||
{'label': '电影', 'value': 23},
|
||||
{'label': '电视', 'value': 11},
|
||||
];
|
||||
|
||||
RxInt currentTimeFilterval = 0.obs;
|
||||
RxInt currentPartFilterval = (-1).obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -219,4 +246,100 @@ class VideoPanelController extends GetxController {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onShowFilterSheet(searchPanelCtr) {
|
||||
showModalBottomSheet(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, StateSetter setState) {
|
||||
return Container(
|
||||
color: Theme.of(Get.context!).colorScheme.surface,
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('内容时长'),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 14,
|
||||
right: 14,
|
||||
bottom: 14,
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
direction: Axis.horizontal,
|
||||
textDirection: TextDirection.ltr,
|
||||
children: [
|
||||
for (var i in timeFiltersList)
|
||||
Obx(
|
||||
() => SearchText(
|
||||
searchText: i['label'],
|
||||
searchTextIdx: i['value'],
|
||||
isSelect:
|
||||
currentTimeFilterval.value == i['value'],
|
||||
onSelect: (value) async {
|
||||
currentTimeFilterval.value = i['value'];
|
||||
setState(() {});
|
||||
SmartDialog.showToast("「${i['label']}」的筛选结果");
|
||||
SearchPanelController ctr =
|
||||
Get.find<SearchPanelController>(
|
||||
tag: 'video${searchPanelCtr.keyword!}');
|
||||
ctr.duration.value = i['value'];
|
||||
Get.back();
|
||||
SmartDialog.showLoading(msg: '获取中');
|
||||
await ctr.onRefresh();
|
||||
SmartDialog.dismiss();
|
||||
},
|
||||
onLongSelect: (value) => {},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const ListTile(
|
||||
title: Text('内容分区'),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
child: Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
direction: Axis.horizontal,
|
||||
textDirection: TextDirection.ltr,
|
||||
children: [
|
||||
for (var i in partFiltersList)
|
||||
SearchText(
|
||||
searchText: i['label'],
|
||||
searchTextIdx: i['value'],
|
||||
isSelect: currentPartFilterval.value == i['value'],
|
||||
onSelect: (value) async {
|
||||
currentPartFilterval.value = i['value'];
|
||||
setState(() {});
|
||||
SmartDialog.showToast("「${i['label']}」的筛选结果");
|
||||
SearchPanelController ctr =
|
||||
Get.find<SearchPanelController>(
|
||||
tag: 'video${searchPanelCtr.keyword!}');
|
||||
ctr.tids.value = i['value'];
|
||||
Get.back();
|
||||
SmartDialog.showLoading(msg: '获取中');
|
||||
await ctr.onRefresh();
|
||||
SmartDialog.dismiss();
|
||||
},
|
||||
onLongSelect: (value) => {},
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
102
lib/pages/setting/pages/action_menu_set.dart
Normal file
102
lib/pages/setting/pages/action_menu_set.dart
Normal file
@ -0,0 +1,102 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/common/action_type.dart';
|
||||
import 'package:pilipala/utils/global_data.dart';
|
||||
import '../../../utils/storage.dart';
|
||||
|
||||
class ActionMenuSetPage extends StatefulWidget {
|
||||
const ActionMenuSetPage({super.key});
|
||||
|
||||
@override
|
||||
State<ActionMenuSetPage> createState() => _ActionMenuSetPageState();
|
||||
}
|
||||
|
||||
class _ActionMenuSetPageState extends State<ActionMenuSetPage> {
|
||||
Box setting = GStrorage.setting;
|
||||
late List<String> actionTypeSort;
|
||||
late List<Map> allLabels;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
actionTypeSort = setting.get(SettingBoxKey.actionTypeSort,
|
||||
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
|
||||
allLabels = actionMenuConfig;
|
||||
allLabels.sort((a, b) {
|
||||
int indexA = actionTypeSort.indexOf((a['value'] as ActionType).value);
|
||||
int indexB = actionTypeSort.indexOf((b['value'] as ActionType).value);
|
||||
if (indexA == -1) indexA = actionTypeSort.length;
|
||||
if (indexB == -1) indexB = actionTypeSort.length;
|
||||
return indexA.compareTo(indexB);
|
||||
});
|
||||
}
|
||||
|
||||
void saveEdit() {
|
||||
List<String> sortedTabbar = allLabels
|
||||
.where((i) => actionTypeSort.contains((i['value'] as ActionType).value))
|
||||
.map<String>((i) => (i['value'] as ActionType).value)
|
||||
.toList();
|
||||
setting.put(SettingBoxKey.actionTypeSort, sortedTabbar);
|
||||
GlobalData().actionTypeSort = sortedTabbar;
|
||||
SmartDialog.showToast('操作成功');
|
||||
}
|
||||
|
||||
void onReorder(int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
if (newIndex > oldIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final tabsItem = allLabels.removeAt(oldIndex);
|
||||
allLabels.insert(newIndex, tabsItem);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final listTiles = [
|
||||
for (int i = 0; i < allLabels.length; i++) ...[
|
||||
CheckboxListTile(
|
||||
key: Key((allLabels[i]['value'] as ActionType).value),
|
||||
value: actionTypeSort
|
||||
.contains((allLabels[i]['value'] as ActionType).value),
|
||||
onChanged: (bool? newValue) {
|
||||
String actionTypeId = (allLabels[i]['value'] as ActionType).value;
|
||||
if (!newValue!) {
|
||||
actionTypeSort.remove(actionTypeId);
|
||||
} else {
|
||||
actionTypeSort.add(actionTypeId);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
title: Row(
|
||||
children: [
|
||||
allLabels[i]['icon'],
|
||||
const SizedBox(width: 8),
|
||||
Text(allLabels[i]['label']),
|
||||
],
|
||||
),
|
||||
secondary: const Icon(Icons.drag_indicator_rounded),
|
||||
)
|
||||
]
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('视频操作菜单'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => saveEdit(), child: const Text('保存')),
|
||||
const SizedBox(width: 12)
|
||||
],
|
||||
),
|
||||
body: ReorderableListView(
|
||||
onReorder: onReorder,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
footer: SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 30,
|
||||
),
|
||||
children: listTiles,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -66,7 +66,7 @@ class _FontSizeSelectPageState extends State<FontSizeSelectPage> {
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.3))),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@ -41,7 +41,8 @@ class _LogsPageState extends State<LogsPage> {
|
||||
.replaceAll('DEVICE INFO', '设备信息')
|
||||
.replaceAll('APP INFO', '应用信息')
|
||||
.replaceAll('ERROR', '错误信息')
|
||||
.replaceAll('STACK TRACE', '错误堆栈');
|
||||
.replaceAll('STACK TRACE', '错误堆栈')
|
||||
.replaceAll('#', 'Line');
|
||||
}).toList();
|
||||
List<Map<String, dynamic>> result = [];
|
||||
for (String i in contentList) {
|
||||
@ -50,7 +51,7 @@ class _LogsPageState extends State<LogsPage> {
|
||||
.split("\n")
|
||||
.map((l) {
|
||||
if (l.startsWith("Crash occurred on")) {
|
||||
date = DateTime.parse(
|
||||
date = DateTime.tryParse(
|
||||
l.split("Crash occurred on")[1].trim().split('.')[0],
|
||||
);
|
||||
return "";
|
||||
|
||||
@ -289,6 +289,11 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
onTap: () => Get.toNamed('/navbarSetting'),
|
||||
title: Text('底部导航栏设置', style: titleStyle),
|
||||
),
|
||||
// ListTile(
|
||||
// dense: false,
|
||||
// onTap: () => Get.toNamed('/actionMenuSet'),
|
||||
// title: Text('操作菜单设置', style: titleStyle),
|
||||
// ),
|
||||
if (Platform.isAndroid)
|
||||
ListTile(
|
||||
dense: false,
|
||||
|
||||
@ -53,6 +53,7 @@ class _SubPageState extends State<SubPage> {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
if (_subController.subFolderData.value.list!.isNotEmpty) {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
controller: scrollController,
|
||||
@ -65,6 +66,12 @@ class _SubPageState extends State<SubPage> {
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const CustomScrollView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
slivers: [HttpError(errMsg: '', btnText: '没有数据', fn: null)],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
|
||||
@ -114,16 +114,15 @@ class VideoContent extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 35,
|
||||
width: 35,
|
||||
child: IconButton(
|
||||
onPressed: () => cancelSub?.call(subFolderItem),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.outline,
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||
IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
icon: const Icon(Icons.delete_outline, size: 18),
|
||||
onPressed: () => cancelSub?.call(subFolderItem),
|
||||
icon: Icon(
|
||||
Icons.clear_outlined,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
size: 18,
|
||||
),
|
||||
)
|
||||
],
|
||||
|
||||
@ -38,6 +38,8 @@ class VideoIntroController extends GetxController {
|
||||
RxBool hasCoin = false.obs;
|
||||
// 是否收藏
|
||||
RxBool hasFav = false.obs;
|
||||
// 是否不喜欢
|
||||
RxBool hasDisLike = false.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
bool userLogin = false;
|
||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||
@ -153,36 +155,16 @@ class VideoIntroController extends GetxController {
|
||||
SmartDialog.showToast('🙏 UP已经收到了~');
|
||||
return false;
|
||||
}
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('一键三连 给UP送温暖'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
child: const Text('点错了')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var result = await VideoHttp.oneThree(bvid: bvid);
|
||||
print('🤣🦴:${result["data"]}');
|
||||
if (result['status']) {
|
||||
hasLike.value = result["data"]["like"];
|
||||
hasCoin.value = result["data"]["coin"];
|
||||
hasFav.value = result["data"]["fav"];
|
||||
SmartDialog.showToast('三连成功 🎉');
|
||||
SmartDialog.showToast('三连成功');
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// (取消)点赞
|
||||
@ -193,9 +175,8 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
||||
if (result['status']) {
|
||||
// hasLike.value = result["data"] == 1 ? true : false;
|
||||
if (!hasLike.value) {
|
||||
SmartDialog.showToast('点赞成功 👍');
|
||||
SmartDialog.showToast('点赞成功');
|
||||
hasLike.value = true;
|
||||
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
|
||||
} else if (hasLike.value) {
|
||||
@ -215,6 +196,10 @@ class VideoIntroController extends GetxController {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
if (hasCoin.value) {
|
||||
SmartDialog.showToast('已投过币了');
|
||||
return;
|
||||
}
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
@ -236,7 +221,7 @@ class VideoIntroController extends GetxController {
|
||||
var res = await VideoHttp.coinVideo(
|
||||
bvid: bvid, multiply: _tempThemeValue);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功 👏');
|
||||
SmartDialog.showToast('投币成功');
|
||||
hasCoin.value = true;
|
||||
videoDetail.value.stat!.coin =
|
||||
videoDetail.value.stat!.coin! + _tempThemeValue;
|
||||
@ -269,7 +254,7 @@ class VideoIntroController extends GetxController {
|
||||
if (result['status']) {
|
||||
// 重新获取收藏状态
|
||||
await queryHasFavVideo();
|
||||
SmartDialog.showToast('✅ 操作成功');
|
||||
SmartDialog.showToast('操作成功');
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
@ -299,7 +284,7 @@ class VideoIntroController extends GetxController {
|
||||
Get.back();
|
||||
// 重新获取收藏状态
|
||||
await queryHasFavVideo();
|
||||
SmartDialog.showToast('✅ 操作成功');
|
||||
SmartDialog.showToast('操作成功');
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
@ -15,6 +16,7 @@ import 'package:pilipala/models/video_detail_res.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/global_data.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import '../../../../http/user.dart';
|
||||
@ -97,11 +99,14 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
child: Lottie.asset(
|
||||
'assets/loading.json',
|
||||
width: 200,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -143,13 +148,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
RxBool isExpand = false.obs;
|
||||
late ExpandableController _expandableCtr;
|
||||
|
||||
void Function()? handleState(Future Function() action) {
|
||||
// 一键三连动画
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _scaleTransition;
|
||||
final RxDouble _progress = 0.0.obs;
|
||||
|
||||
void Function()? handleState(Future<dynamic> Function() action) {
|
||||
return isProcessing
|
||||
? null
|
||||
: () async {
|
||||
setState(() => isProcessing = true);
|
||||
await action();
|
||||
setState(() => isProcessing = false);
|
||||
isProcessing = true;
|
||||
await action.call();
|
||||
isProcessing = false;
|
||||
};
|
||||
}
|
||||
|
||||
@ -166,6 +176,26 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
follower = Utils.numFormat(videoIntroController.userStat['follower']);
|
||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||
_expandableCtr = ExpandableController(initialExpanded: false);
|
||||
|
||||
/// 一键三连动画
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
reverseDuration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_scaleTransition = Tween<double>(begin: 0.5, end: 1.5).animate(_controller)
|
||||
..addListener(() async {
|
||||
_progress.value =
|
||||
double.parse((_scaleTransition.value - 0.5).toStringAsFixed(3));
|
||||
if (_progress.value == 1) {
|
||||
if (_controller.status == AnimationStatus.completed) {
|
||||
await videoIntroController.actionOneThree();
|
||||
}
|
||||
_progress.value = 0;
|
||||
_scaleTransition.removeListener(() {});
|
||||
_controller.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 收藏
|
||||
@ -246,6 +276,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
@override
|
||||
void dispose() {
|
||||
_expandableCtr.dispose();
|
||||
_controller.dispose();
|
||||
_scaleTransition.removeListener(() {});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -535,47 +567,132 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Widget actionGrid(BuildContext context, videoIntroController) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 6, bottom: 4),
|
||||
height: constraints.maxWidth / 5 * 0.8,
|
||||
child: GridView.count(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
primary: false,
|
||||
padding: EdgeInsets.zero,
|
||||
crossAxisCount: 5,
|
||||
childAspectRatio: 1.25,
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
text: widget.videoDetail!.stat!.like!.toString()),
|
||||
final actionTypeSort = GlobalData().actionTypeSort;
|
||||
|
||||
Widget progressWidget(progress) {
|
||||
return SizedBox(
|
||||
width: const IconThemeData.fallback().size! + 5,
|
||||
height: const IconThemeData.fallback().size! + 5,
|
||||
child: CircularProgressIndicator(
|
||||
value: progress.value,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Widget> menuListWidgets = {
|
||||
'like': Obx(
|
||||
() {
|
||||
bool likeStatus = videoIntroController.hasLike.value;
|
||||
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: ((Get.size.width - 24) / 5) / 2 -
|
||||
(const IconThemeData.fallback().size!),
|
||||
left: ((Get.size.width - 24) / 5) / 2 -
|
||||
(const IconThemeData.fallback().size! + 5) / 2,
|
||||
child: progressWidget(_progress)),
|
||||
InkWell(
|
||||
onTapDown: (details) {
|
||||
feedBack();
|
||||
if (videoIntroController.userInfo == null) {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
_controller.forward();
|
||||
},
|
||||
onTapUp: (TapUpDetails details) {
|
||||
if (_progress.value == 0) {
|
||||
feedBack();
|
||||
EasyThrottle.throttle(
|
||||
'my-throttler', const Duration(milliseconds: 200), () {
|
||||
videoIntroController.actionLikeVideo();
|
||||
});
|
||||
}
|
||||
_controller.reverse();
|
||||
},
|
||||
borderRadius: StyleString.mdRadius,
|
||||
child: SizedBox(
|
||||
width: (Get.size.width - 24) / 5,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation, child: child);
|
||||
},
|
||||
child: Icon(
|
||||
key: ValueKey<bool>(likeStatus),
|
||||
likeStatus
|
||||
? Icons.thumb_up
|
||||
: Icons.thumb_up_alt_outlined,
|
||||
color: likeStatus
|
||||
? colorScheme.primary
|
||||
: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
widget.videoDetail!.stat!.like!.toString(),
|
||||
style: TextStyle(
|
||||
color: likeStatus ? colorScheme.primary : null,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
'coin': Obx(
|
||||
() => Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: ((Get.size.width - 24) / 5) / 2 -
|
||||
(const IconThemeData.fallback().size!),
|
||||
left: ((Get.size.width - 24) / 5) / 2 -
|
||||
(const IconThemeData.fallback().size! + 5) / 2,
|
||||
child: progressWidget(_progress)),
|
||||
ActionItem(
|
||||
icon: Image.asset('assets/images/coin.png', width: 30),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
text: widget.videoDetail!.stat!.coin!.toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
),
|
||||
'collect': Obx(
|
||||
() => Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: ((Get.size.width - 24) / 5) / 2 -
|
||||
(const IconThemeData.fallback().size!),
|
||||
left: ((Get.size.width - 24) / 5) / 2 -
|
||||
(const IconThemeData.fallback().size! + 5) / 2,
|
||||
child: progressWidget(_progress)),
|
||||
ActionItem(
|
||||
icon: const Icon(Icons.star_border),
|
||||
selectIcon: const Icon(Icons.star),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
text: widget.videoDetail!.stat!.favorite!.toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.clock),
|
||||
),
|
||||
'watchLater': ActionItem(
|
||||
icon: const Icon(Icons.watch_later_outlined),
|
||||
onTap: () async {
|
||||
final res =
|
||||
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
|
||||
@ -584,19 +701,49 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
selectStatus: false,
|
||||
text: '稍后看',
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
'share': ActionItem(
|
||||
icon: const Icon(Icons.share),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
text: '分享',
|
||||
),
|
||||
],
|
||||
'dislike': Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(Icons.thumb_down_alt_outlined),
|
||||
selectIcon: const Icon(Icons.thumb_down),
|
||||
onTap: () {},
|
||||
selectStatus: videoIntroController.hasDisLike.value,
|
||||
text: '不喜欢',
|
||||
),
|
||||
),
|
||||
'downloadCover': ActionItem(
|
||||
icon: const Icon(Icons.image_outlined),
|
||||
onTap: () {},
|
||||
selectStatus: false,
|
||||
text: '下载封面',
|
||||
),
|
||||
'copyLink': ActionItem(
|
||||
icon: const Icon(Icons.link_outlined),
|
||||
onTap: () {},
|
||||
selectStatus: false,
|
||||
text: '复制链接',
|
||||
),
|
||||
};
|
||||
final List<Widget> list = [];
|
||||
for (var i = 0; i < actionTypeSort.length; i++) {
|
||||
list.add(menuListWidgets[actionTypeSort[i]]!);
|
||||
}
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 6, bottom: 4),
|
||||
height: constraints.maxWidth / 5,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: list,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Widget StaffPanel(BuildContext context, videoIntroController) {
|
||||
// return
|
||||
// }
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
|
||||
class ActionItem extends StatelessWidget {
|
||||
final Icon? icon;
|
||||
final dynamic icon;
|
||||
final Icon? selectIcon;
|
||||
final Function? onTap;
|
||||
final Function? onLongPress;
|
||||
@ -31,27 +32,47 @@ class ActionItem extends StatelessWidget {
|
||||
if (onLongPress != null) {onLongPress!()}
|
||||
},
|
||||
borderRadius: StyleString.mdRadius,
|
||||
child: SizedBox(
|
||||
width: (Get.size.width - 24) / 5,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(scale: animation, child: child);
|
||||
},
|
||||
child: icon is Icon
|
||||
? Icon(
|
||||
selectStatus
|
||||
? Icon(selectIcon!.icon!,
|
||||
size: 18, color: Theme.of(context).colorScheme.primary)
|
||||
: Icon(icon!.icon!,
|
||||
size: 18, color: Theme.of(context).colorScheme.outline),
|
||||
? selectIcon!.icon ?? icon!.icon
|
||||
: icon!.icon,
|
||||
color: selectStatus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
)
|
||||
: Image.asset(
|
||||
key: ValueKey<bool>(selectStatus),
|
||||
'assets/images/coin.png',
|
||||
width: const IconThemeData.fallback().size,
|
||||
color: selectStatus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
text ?? '',
|
||||
style: TextStyle(
|
||||
color: selectStatus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
color:
|
||||
selectStatus ? Theme.of(context).colorScheme.primary : null,
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ class _FavPanelState extends State<FavPanel> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: sheetHeight,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
AppBar(
|
||||
|
||||
@ -57,7 +57,7 @@ class _GroupPanelState extends State<GroupPanel> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: sheetHeight,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
AppBar(
|
||||
|
||||
@ -12,7 +12,7 @@ class MenuRow extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
padding: const EdgeInsets.only(top: 9, bottom: 9, left: 12),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
@ -84,7 +84,7 @@ class MenuRow extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: selectStatus
|
||||
? Theme.of(context).colorScheme.onBackground
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
|
||||
@ -97,7 +97,7 @@ class ReplyItem extends StatelessWidget {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
color: colorScheme.background,
|
||||
color: colorScheme.surface,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.offline_bolt,
|
||||
|
||||
@ -117,6 +117,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
final String newText = currentText.substring(0, cursorPosition) +
|
||||
emote.text! +
|
||||
currentText.substring(cursorPosition);
|
||||
message.value = newText;
|
||||
_replyContentController.value = TextEditingValue(
|
||||
text: newText,
|
||||
selection:
|
||||
@ -170,7 +171,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -214,7 +215,15 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
),
|
||||
Container(
|
||||
height: 52,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
margin: EdgeInsets.only(
|
||||
bottom: toolbarType == 'input' && keyboardHeight == 0.0
|
||||
? MediaQuery.of(context).padding.bottom
|
||||
: 0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
||||
@ -78,7 +78,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: widget.source == 'videoDetail' ? widget.sheetHeight : null,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.source == 'videoDetail')
|
||||
|
||||
@ -514,6 +514,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
showEposideCb: () => vdCtr.videoType == SearchType.video
|
||||
? videoIntroController.showEposideHandler()
|
||||
: bangumiIntroController.showEposideHandler(),
|
||||
fullScreenCb: (bool status) {
|
||||
if (status) {
|
||||
videoHeight.value = Get.size.height;
|
||||
} else {
|
||||
videoHeight.value = defaultVideoHeight;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -591,7 +598,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
verticalScreen();
|
||||
}
|
||||
},
|
||||
child: Hero(
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context,
|
||||
BoxConstraints constraints) {
|
||||
return Hero(
|
||||
tag: heroTag,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
@ -612,6 +622,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -23,7 +23,7 @@ class AiDetail extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
height: sheetHeight,
|
||||
child: Column(
|
||||
@ -108,7 +108,7 @@ class AiDetail extends StatelessWidget {
|
||||
fontSize: 13,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground,
|
||||
.onSurface,
|
||||
height: 1.5,
|
||||
),
|
||||
children: [
|
||||
|
||||
@ -29,7 +29,7 @@ class ScrollAppBar extends StatelessWidget {
|
||||
opacity: scrollDistance / (videoHeight - kToolbarHeight),
|
||||
child: Container(
|
||||
height: statusBarHeight + kToolbarHeight,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
padding: EdgeInsets.only(top: statusBarHeight),
|
||||
child: AppBar(
|
||||
primary: false,
|
||||
|
||||
@ -93,7 +93,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
height: 460,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
@ -317,7 +317,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
height: 500,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
@ -377,7 +377,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
inactiveThumbColor:
|
||||
Theme.of(context).colorScheme.primaryContainer,
|
||||
inactiveTrackColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
Theme.of(context).colorScheme.surface,
|
||||
splashRadius: 10.0,
|
||||
// boolean variable value
|
||||
value: shutdownTimerService.waitForPlayingCompleted,
|
||||
@ -570,7 +570,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
height: 310,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
@ -660,7 +660,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
height: 250,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
@ -734,7 +734,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
height: 250,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
@ -828,7 +828,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
height: 580,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
@ -1069,7 +1069,9 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
).then((value) {
|
||||
widget.controller!.cacheDanmakuOption();
|
||||
});
|
||||
}
|
||||
|
||||
/// 播放顺序
|
||||
@ -1084,7 +1086,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
height: 250,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
|
||||
@ -76,7 +76,7 @@ class WebviewController extends GetxController {
|
||||
(url.startsWith(
|
||||
'https://passport.bilibili.com/web/sso/exchange_cookie') ||
|
||||
url.startsWith('https://m.bilibili.com/'))) {
|
||||
confirmLogin(url);
|
||||
LoginUtils.confirmLogin(url, controller);
|
||||
}
|
||||
},
|
||||
onWebResourceError: (WebResourceError error) {},
|
||||
@ -97,54 +97,4 @@ class WebviewController extends GetxController {
|
||||
)
|
||||
..loadRequest(Uri.parse(url));
|
||||
}
|
||||
|
||||
confirmLogin(url) async {
|
||||
var content = '';
|
||||
if (url != null) {
|
||||
content = '${content + url}; \n';
|
||||
}
|
||||
try {
|
||||
await SetCookie.onSet();
|
||||
final result = await UserHttp.userInfo();
|
||||
if (result['status'] && result['data'].isLogin) {
|
||||
SmartDialog.showToast('登录成功');
|
||||
try {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
if (!userInfoCache.isOpen) {
|
||||
userInfoCache = await Hive.openBox('userInfo');
|
||||
}
|
||||
await userInfoCache.put('userInfoCache', result['data']);
|
||||
|
||||
final HomeController homeCtr = Get.find<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
homeCtr.userFace.value = result['data'].face;
|
||||
final MediaController mediaCtr = Get.find<MediaController>();
|
||||
mediaCtr.mid = result['data'].mid;
|
||||
await LoginUtils.refreshLoginStatus(true);
|
||||
} catch (err) {
|
||||
SmartDialog.show(builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('登录遇到问题'),
|
||||
content: Text(err.toString()),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => controller.reload(),
|
||||
child: const Text('确认'),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
Get.back();
|
||||
} else {
|
||||
// 获取用户信息失败
|
||||
SmartDialog.showToast(result['msg']);
|
||||
Clipboard.setData(ClipboardData(text: result['msg']));
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.showNotify(msg: e.toString(), notifyType: NotifyType.warning);
|
||||
content = content + e.toString();
|
||||
Clipboard.setData(ClipboardData(text: content));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/utils/login.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'controller.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
@ -43,7 +44,8 @@ class _WebviewPageState extends State<WebviewPage> {
|
||||
Obx(
|
||||
() => _webviewController.type.value == 'login'
|
||||
? TextButton(
|
||||
onPressed: () => _webviewController.confirmLogin(null),
|
||||
onPressed: () =>
|
||||
LoginUtils.confirmLogin(null, _webviewController),
|
||||
child: const Text('刷新登录状态'),
|
||||
)
|
||||
: const SizedBox(),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/msg.dart';
|
||||
import 'package:pilipala/models/msg/account.dart';
|
||||
@ -7,6 +8,38 @@ class WhisperController extends GetxController {
|
||||
RxList<SessionList> sessionList = <SessionList>[].obs;
|
||||
RxList<AccountListModel> accountList = <AccountListModel>[].obs;
|
||||
bool isLoading = false;
|
||||
RxList noticesList = [
|
||||
{
|
||||
'icon': Icons.message_outlined,
|
||||
'title': '回复我的',
|
||||
'path': '/messageReply',
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': Icons.alternate_email,
|
||||
'title': '@ 我的',
|
||||
'path': '/messageAt',
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': Icons.thumb_up_outlined,
|
||||
'title': '收到的赞',
|
||||
'path': '/messageLike',
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': Icons.notifications_none_outlined,
|
||||
'title': '系统通知',
|
||||
'path': '/messageSystem',
|
||||
'count': 0,
|
||||
}
|
||||
].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
unread();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
Future querySessionList(String? type) async {
|
||||
if (isLoading) return;
|
||||
@ -62,4 +95,31 @@ class WhisperController extends GetxController {
|
||||
Future onRefresh() async {
|
||||
querySessionList('onRefresh');
|
||||
}
|
||||
|
||||
void refreshLastMsg(int talkerId, String content) {
|
||||
final SessionList currentItem =
|
||||
sessionList.where((p0) => p0.talkerId == talkerId).first;
|
||||
currentItem.lastMsg!.content['content'] = content;
|
||||
sessionList.removeWhere((p0) => p0.talkerId == talkerId);
|
||||
sessionList.insert(0, currentItem);
|
||||
sessionList.refresh();
|
||||
}
|
||||
|
||||
// 移除会话
|
||||
void removeSessionMsg(int talkerId) {
|
||||
sessionList.removeWhere((p0) => p0.talkerId == talkerId);
|
||||
sessionList.refresh();
|
||||
}
|
||||
|
||||
// 消息未读数
|
||||
void unread() async {
|
||||
var res = await MsgHttp.unread();
|
||||
if (res['status']) {
|
||||
noticesList[0]['count'] = res['data']['reply'];
|
||||
noticesList[1]['count'] = res['data']['at'];
|
||||
noticesList[2]['count'] = res['data']['like'];
|
||||
noticesList[3]['count'] = res['data']['sys_msg'];
|
||||
noticesList.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/skeleton/skeleton.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
@ -43,67 +45,65 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
appBar: AppBar(
|
||||
title: const Text('消息'),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// LayoutBuilder(
|
||||
// builder: (BuildContext context, BoxConstraints constraints) {
|
||||
// // 在这里根据父级容器的约束条件构建小部件树
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.only(left: 20, right: 20),
|
||||
// child: SizedBox(
|
||||
// height: constraints.maxWidth / 5,
|
||||
// child: GridView.count(
|
||||
// primary: false,
|
||||
// crossAxisCount: 4,
|
||||
// padding: const EdgeInsets.all(0),
|
||||
// childAspectRatio: 1.25,
|
||||
// children: [
|
||||
// Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// width: 36,
|
||||
// height: 36,
|
||||
// child: IconButton(
|
||||
// style: ButtonStyle(
|
||||
// padding:
|
||||
// MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// backgroundColor:
|
||||
// MaterialStateProperty.resolveWith((states) {
|
||||
// return Theme.of(context)
|
||||
// .colorScheme
|
||||
// .primary
|
||||
// .withOpacity(0.1);
|
||||
// }),
|
||||
// ),
|
||||
// onPressed: () {},
|
||||
// icon: Icon(
|
||||
// Icons.message_outlined,
|
||||
// size: 18,
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 6),
|
||||
// const Text('回复我的', style: TextStyle(fontSize: 13))
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
_whisperController.unread();
|
||||
await _whisperController.onRefresh();
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
child: Column(
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
// 在这里根据父级容器的约束条件构建小部件树
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20),
|
||||
child: SizedBox(
|
||||
height: constraints.maxWidth / 4,
|
||||
child: Obx(
|
||||
() => GridView.count(
|
||||
primary: false,
|
||||
crossAxisCount: 4,
|
||||
padding: const EdgeInsets.all(0),
|
||||
children: [
|
||||
..._whisperController.noticesList.map((element) {
|
||||
return InkWell(
|
||||
onTap: () => Get.toNamed(element['path']),
|
||||
onLongPress: () {},
|
||||
borderRadius: StyleString.mdRadius,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Badge(
|
||||
isLabelVisible: element['count'] > 0,
|
||||
label: Text(element['count'] > 99
|
||||
? '99+'
|
||||
: element['count'].toString()),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Icon(
|
||||
element['icon'],
|
||||
size: 21,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(element['title'])
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
@ -117,93 +117,11 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
: ListView.separated(
|
||||
itemCount: sessionList.length,
|
||||
shrinkWrap: true,
|
||||
physics:
|
||||
const NeverScrollableScrollPhysics(),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (_, int i) {
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
sessionList[i].unreadCount = 0;
|
||||
sessionList.refresh();
|
||||
Get.toNamed(
|
||||
'/whisperDetail',
|
||||
parameters: {
|
||||
'talkerId': sessionList[i]
|
||||
.talkerId
|
||||
.toString(),
|
||||
'name': sessionList[i]
|
||||
.accountInfo
|
||||
.name,
|
||||
'face': sessionList[i]
|
||||
.accountInfo
|
||||
.face,
|
||||
'mid': sessionList[i]
|
||||
.accountInfo
|
||||
.mid
|
||||
.toString(),
|
||||
},
|
||||
);
|
||||
},
|
||||
leading: Badge(
|
||||
isLabelVisible:
|
||||
sessionList[i].unreadCount > 0,
|
||||
label: Text(sessionList[i]
|
||||
.unreadCount
|
||||
.toString()),
|
||||
alignment: Alignment.topRight,
|
||||
child: NetworkImgLayer(
|
||||
width: 45,
|
||||
height: 45,
|
||||
type: 'avatar',
|
||||
src: sessionList[i]
|
||||
.accountInfo
|
||||
.face,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
sessionList[i].accountInfo.name),
|
||||
subtitle: Text(
|
||||
sessionList[i].lastMsg.content !=
|
||||
null &&
|
||||
sessionList[i]
|
||||
.lastMsg
|
||||
.content !=
|
||||
''
|
||||
? (sessionList[i]
|
||||
.lastMsg
|
||||
.content['text'] ??
|
||||
sessionList[i]
|
||||
.lastMsg
|
||||
.content['content'] ??
|
||||
sessionList[i]
|
||||
.lastMsg
|
||||
.content['title'] ??
|
||||
sessionList[i]
|
||||
.lastMsg
|
||||
.content[
|
||||
'reply_content'] ??
|
||||
'不支持的消息类型')
|
||||
: '不支持的消息类型',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline)),
|
||||
trailing: Text(
|
||||
Utils.dateFormat(sessionList[i]
|
||||
.lastMsg
|
||||
.timestamp),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline),
|
||||
),
|
||||
return SessionItem(
|
||||
sessionItem: sessionList[i],
|
||||
changeFucCall: () => sessionList.refresh(),
|
||||
);
|
||||
},
|
||||
separatorBuilder:
|
||||
@ -225,17 +143,121 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return const SizedBox();
|
||||
return ListView.builder(
|
||||
itemCount: 15,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, int i) {
|
||||
return Skeleton(
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
),
|
||||
title: Container(
|
||||
width: 100,
|
||||
height: 14,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
),
|
||||
subtitle: Container(
|
||||
width: 80,
|
||||
height: 14,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SessionItem extends StatelessWidget {
|
||||
final dynamic sessionItem;
|
||||
final Function changeFucCall;
|
||||
|
||||
const SessionItem({
|
||||
super.key,
|
||||
required this.sessionItem,
|
||||
required this.changeFucCall,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String heroTag = Utils.makeHeroTag(sessionItem.accountInfo.mid);
|
||||
final content = sessionItem.lastMsg.content;
|
||||
final msgStatus = sessionItem.lastMsg.msgStatus;
|
||||
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
sessionItem.unreadCount = 0;
|
||||
changeFucCall.call();
|
||||
Get.toNamed(
|
||||
'/whisperDetail',
|
||||
parameters: {
|
||||
'talkerId': sessionItem.talkerId.toString(),
|
||||
'name': sessionItem.accountInfo.name,
|
||||
'face': sessionItem.accountInfo.face,
|
||||
'mid': sessionItem.accountInfo.mid.toString(),
|
||||
'heroTag': heroTag,
|
||||
},
|
||||
);
|
||||
},
|
||||
leading: Badge(
|
||||
isLabelVisible: sessionItem.unreadCount > 0,
|
||||
label: Text(sessionItem.unreadCount.toString()),
|
||||
alignment: Alignment.topRight,
|
||||
child: Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
width: 45,
|
||||
height: 45,
|
||||
type: 'avatar',
|
||||
src: sessionItem.accountInfo.face,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(sessionItem.accountInfo.name),
|
||||
subtitle: Text(
|
||||
msgStatus == 1
|
||||
? '你撤回了一条消息'
|
||||
: content != null && content != ''
|
||||
? (content['text'] ??
|
||||
content['content'] ??
|
||||
content['title'] ??
|
||||
content['reply_content'] ??
|
||||
'不支持的消息类型')
|
||||
: '不支持的消息类型',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline)),
|
||||
trailing: Text(
|
||||
Utils.dateFormat(sessionItem.lastMsg.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,30 +1,40 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/msg.dart';
|
||||
import 'package:pilipala/models/msg/session.dart';
|
||||
import 'package:pilipala/pages/whisper/index.dart';
|
||||
import '../../utils/feed_back.dart';
|
||||
import '../../utils/storage.dart';
|
||||
|
||||
class WhisperDetailController extends GetxController {
|
||||
late int talkerId;
|
||||
int? talkerId;
|
||||
late String name;
|
||||
late String face;
|
||||
late String mid;
|
||||
late String heroTag;
|
||||
RxList<MessageItem> messageList = <MessageItem>[].obs;
|
||||
//表情转换图片规则
|
||||
List<dynamic>? eInfos;
|
||||
RxList<dynamic> eInfos = [].obs;
|
||||
final TextEditingController replyContentController = TextEditingController();
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
List emoteList = [];
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (Get.parameters.containsKey('talkerId')) {
|
||||
talkerId = int.parse(Get.parameters['talkerId']!);
|
||||
} else {
|
||||
talkerId = int.parse(Get.parameters['mid']!);
|
||||
}
|
||||
name = Get.parameters['name']!;
|
||||
face = Get.parameters['face']!;
|
||||
mid = Get.parameters['mid']!;
|
||||
heroTag = Get.parameters['heroTag']!;
|
||||
}
|
||||
|
||||
Future querySessionMsg() async {
|
||||
@ -34,7 +44,7 @@ class WhisperDetailController extends GetxController {
|
||||
if (messageList.isNotEmpty) {
|
||||
ackSessionMsg();
|
||||
if (res['data'].eInfos != null) {
|
||||
eInfos = res['data'].eInfos;
|
||||
eInfos.value = res['data'].eInfos;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -73,9 +83,64 @@ class WhisperDetailController extends GetxController {
|
||||
msgType: 1,
|
||||
);
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast('发送成功');
|
||||
String content = jsonDecode(result['data']['msg_content'])['content'];
|
||||
messageList.insert(
|
||||
0,
|
||||
MessageItem(
|
||||
msgSeqno: result['data']['msg_key'],
|
||||
senderUid: userInfo.mid,
|
||||
receiverId: int.parse(mid),
|
||||
content: {'content': content},
|
||||
msgType: 1,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
eInfos.addAll(emoteList);
|
||||
replyContentController.clear();
|
||||
try {
|
||||
late final WhisperController whisperController =
|
||||
Get.find<WhisperController>();
|
||||
whisperController.refreshLastMsg(talkerId!, message);
|
||||
} catch (_) {}
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
void removeSession(context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
title: const Text('提示'),
|
||||
content: const Text('确认清空会话内容并移除会话?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var res = await MsgHttp.removeSession(talkerId: talkerId);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('操作成功');
|
||||
try {
|
||||
late final WhisperController whisperController =
|
||||
Get.find<WhisperController>();
|
||||
whisperController.removeSessionMsg(talkerId!);
|
||||
Get.back();
|
||||
} catch (_) {}
|
||||
}
|
||||
},
|
||||
child: const Text('确认'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/video/reply/emote.dart';
|
||||
import 'package:pilipala/pages/emote/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/reply_new/toolbar_icon_button.dart';
|
||||
import 'package:pilipala/pages/whisper_detail/controller.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import '../../utils/storage.dart';
|
||||
@ -24,9 +27,9 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
late TextEditingController _replyContentController;
|
||||
final FocusNode replyContentFocusNode = FocusNode();
|
||||
final _debouncer = Debouncer(milliseconds: 200); // 设置延迟时间
|
||||
late double emoteHeight = 0.0;
|
||||
late double emoteHeight = 230.0;
|
||||
double keyboardHeight = 0.0; // 键盘高度
|
||||
String toolbarType = 'input';
|
||||
RxString toolbarType = ''.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
|
||||
@override
|
||||
@ -41,9 +44,7 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
_focuslistener() {
|
||||
replyContentFocusNode.addListener(() {
|
||||
if (replyContentFocusNode.hasFocus) {
|
||||
setState(() {
|
||||
toolbarType = 'input';
|
||||
});
|
||||
toolbarType.value = 'input';
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -52,7 +53,7 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
void didChangeMetrics() {
|
||||
super.didChangeMetrics();
|
||||
final String routePath = Get.currentRoute;
|
||||
if (mounted && routePath.startsWith('/whisper_detail')) {
|
||||
if (mounted && routePath.startsWith('/whisperDetail')) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// 键盘高度
|
||||
final viewInsets = EdgeInsets.fromViewPadding(
|
||||
@ -61,8 +62,11 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
if (mounted) {
|
||||
if (keyboardHeight == 0) {
|
||||
setState(() {
|
||||
emoteHeight = keyboardHeight =
|
||||
keyboardHeight =
|
||||
keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
|
||||
if (keyboardHeight != 0) {
|
||||
emoteHeight = keyboardHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -79,6 +83,23 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void onChooseEmote(PackageItem package, Emote emote) {
|
||||
_whisperDetailController.emoteList.add(
|
||||
{'text': emote.text, 'url': emote.url},
|
||||
);
|
||||
final int cursorPosition =
|
||||
max(_replyContentController.selection.baseOffset, 0);
|
||||
final String currentText = _replyContentController.text;
|
||||
final String newText = currentText.substring(0, cursorPosition) +
|
||||
emote.text! +
|
||||
currentText.substring(cursorPosition);
|
||||
_replyContentController.value = TextEditingValue(
|
||||
text: newText,
|
||||
selection:
|
||||
TextSelection.collapsed(offset: cursorPosition + emote.text!.length),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -88,30 +109,20 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor: MaterialStateProperty.resolveWith(
|
||||
(Set<MaterialState> states) {
|
||||
return Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer
|
||||
.withOpacity(0.6);
|
||||
}),
|
||||
),
|
||||
onPressed: () => Get.back(),
|
||||
icon: Icon(
|
||||
Icons.arrow_back_outlined,
|
||||
Icons.arrow_back_ios,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
feedBack();
|
||||
@ -125,13 +136,16 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
NetworkImgLayer(
|
||||
Hero(
|
||||
tag: _whisperDetailController.heroTag,
|
||||
child: NetworkImgLayer(
|
||||
width: 34,
|
||||
height: 34,
|
||||
type: 'avatar',
|
||||
src: _whisperDetailController.face,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
_whisperDetailController.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
@ -143,13 +157,26 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert_outlined, size: 20),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
PopupMenuItem(
|
||||
onTap: () => _whisperDetailController.removeSession(context),
|
||||
child: const Text('关闭会话'),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: GestureDetector(
|
||||
const SizedBox(width: 14)
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
setState(() {
|
||||
keyboardHeight = 0;
|
||||
});
|
||||
toolbarType.value = '';
|
||||
},
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
@ -164,7 +191,9 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
return Obx(
|
||||
() => messageList.isEmpty
|
||||
? const SizedBox()
|
||||
: ListView.builder(
|
||||
: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ListView.builder(
|
||||
itemCount: messageList.length,
|
||||
shrinkWrap: true,
|
||||
reverse: true,
|
||||
@ -174,17 +203,20 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
children: [
|
||||
ChatItem(
|
||||
item: messageList[i],
|
||||
e_infos: _whisperDetailController.eInfos),
|
||||
const SizedBox(height: 12),
|
||||
e_infos: _whisperDetailController
|
||||
.eInfos),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return ChatItem(
|
||||
item: messageList[i],
|
||||
e_infos: _whisperDetailController.eInfos);
|
||||
e_infos:
|
||||
_whisperDetailController.eInfos);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
@ -197,70 +229,62 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
},
|
||||
),
|
||||
),
|
||||
// resizeToAvoidBottomInset: true,
|
||||
bottomNavigationBar: Container(
|
||||
width: double.infinity,
|
||||
height: MediaQuery.of(context).padding.bottom + 70 + keyboardHeight,
|
||||
padding: EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 12,
|
||||
top: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
Obx(
|
||||
() => Container(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
8,
|
||||
12,
|
||||
12,
|
||||
toolbarType.value == ''
|
||||
? MediaQuery.of(context).padding.bottom + 6
|
||||
: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 4,
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
width: 1,
|
||||
color: Colors.grey.withOpacity(0.15),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: Icon(
|
||||
// Icons.add_circle_outline,
|
||||
// color: Theme.of(context).colorScheme.outline,
|
||||
// ),
|
||||
// ),
|
||||
IconButton(
|
||||
ToolbarIconButton(
|
||||
onPressed: () {
|
||||
// if (toolbarType == 'input') {
|
||||
// setState(() {
|
||||
// toolbarType = 'emote';
|
||||
// });
|
||||
// }
|
||||
// FocusScope.of(context).unfocus();
|
||||
if (toolbarType.value == '') {
|
||||
toolbarType.value = 'emote';
|
||||
} else if (toolbarType.value == 'input') {
|
||||
FocusScope.of(context).unfocus();
|
||||
toolbarType.value = 'emote';
|
||||
} else if (toolbarType.value == 'emote') {
|
||||
FocusScope.of(context).requestFocus();
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.emoji_emotions_outlined,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
icon: const Icon(Icons.emoji_emotions_outlined, size: 22),
|
||||
toolbarType: toolbarType.value,
|
||||
selected: false,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.08),
|
||||
.outline
|
||||
.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(40.0),
|
||||
),
|
||||
child: TextField(
|
||||
readOnly: true,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
controller: _replyContentController,
|
||||
autofocus: false,
|
||||
focusNode: replyContentFocusNode,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none, // 移除默认边框
|
||||
hintText: '开发中 ...', // 提示文本
|
||||
hintText: '文明发言 ~', // 提示文本
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 12.0), // 内边距
|
||||
),
|
||||
@ -268,30 +292,36 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
// onPressed: _whisperDetailController.sendMsg,
|
||||
onPressed: null,
|
||||
onPressed: _whisperDetailController.sendMsg,
|
||||
icon: Icon(
|
||||
Icons.send,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
// const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
AnimatedSize(
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => AnimatedSize(
|
||||
curve: Curves.linear,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: toolbarType == 'input' ? keyboardHeight : emoteHeight,
|
||||
height: toolbarType.value == 'input'
|
||||
? keyboardHeight
|
||||
: toolbarType.value == 'emote'
|
||||
? emoteHeight
|
||||
: 0,
|
||||
child: EmotePanel(
|
||||
onChoose: (package, emote) => {},
|
||||
onChoose: (package, emote) => onChooseEmote(package, emote),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
resizeToAvoidBottomInset: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
// ignore_for_file: must_be_immutable
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -69,9 +68,13 @@ class ChatItem extends StatelessWidget {
|
||||
Color textColor(BuildContext context) {
|
||||
return isOwner
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: Theme.of(context).colorScheme.onSecondaryContainer;
|
||||
: Theme.of(context).colorScheme.onBackground;
|
||||
}
|
||||
|
||||
const double safeDistanceval = 6;
|
||||
const double borderRadiusVal = 12;
|
||||
const double paddingVal = 10;
|
||||
|
||||
Widget richTextMessage(BuildContext context) {
|
||||
var text = content['content'];
|
||||
if (e_infos != null) {
|
||||
@ -267,11 +270,8 @@ class ChatItem extends StatelessWidget {
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
.withOpacity(0.4),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
bottomLeft: Radius.circular(6),
|
||||
bottomRight: Radius.circular(16),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(16),
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
@ -292,8 +292,8 @@ class ChatItem extends StatelessWidget {
|
||||
const SizedBox(height: 6),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
RegExp bvRegex = RegExp(r'BV[0-9A-Za-z]{10}',
|
||||
caseSensitive: false);
|
||||
RegExp bvRegex =
|
||||
RegExp(r'BV[0-9A-Za-z]{10}', caseSensitive: false);
|
||||
Iterable<Match> matches =
|
||||
bvRegex.allMatches(i['jump_url']);
|
||||
if (matches.isNotEmpty) {
|
||||
@ -353,7 +353,7 @@ class ChatItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
Utils.timeFormat(int.parse(i['field3'])),
|
||||
i['field3'],
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
@ -364,10 +364,12 @@ class ChatItem extends StatelessWidget {
|
||||
],
|
||||
)),
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
));
|
||||
),
|
||||
);
|
||||
default:
|
||||
return Text(
|
||||
content != null && content != ''
|
||||
@ -387,73 +389,109 @@ class ChatItem extends StatelessWidget {
|
||||
? messageContent(context)
|
||||
: isRevoke
|
||||
? const SizedBox()
|
||||
: Row(
|
||||
: Container(
|
||||
padding: const EdgeInsets.only(top: 6, bottom: 6),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: item.msgStatus == 1 && !isOwner
|
||||
? BorderSide(
|
||||
width: 4, color: Theme.of(context).dividerColor)
|
||||
: BorderSide.none,
|
||||
right: item.msgStatus == 1 && isOwner
|
||||
? BorderSide(
|
||||
width: 4, color: Theme.of(context).primaryColor)
|
||||
: BorderSide.none,
|
||||
)),
|
||||
child: Row(
|
||||
mainAxisAlignment: !isOwner
|
||||
? MainAxisAlignment.start
|
||||
: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (!isOwner) const SizedBox(width: 12),
|
||||
if (isOwner) const Spacer(),
|
||||
const SizedBox(width: safeDistanceval),
|
||||
if (isOwner)
|
||||
Text(
|
||||
Utils.dateFormat(item.timestamp),
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 300.0, // 设置最大宽度为200.0
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isOwner
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: const Radius.circular(16),
|
||||
topRight: const Radius.circular(16),
|
||||
bottomLeft: Radius.circular(isOwner ? 16 : 6),
|
||||
bottomRight: Radius.circular(isOwner ? 6 : 16),
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
padding: EdgeInsets.only(
|
||||
top: 8,
|
||||
bottom: 6,
|
||||
left: isPic ? 8 : 12,
|
||||
right: isPic ? 8 : 12,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: isOwner
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
messageContent(context),
|
||||
SizedBox(height: isPic ? 7 : 2),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
Utils.dateFormat(item.timestamp),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.copyWith(
|
||||
color: isOwner
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary
|
||||
.withOpacity(0.8)
|
||||
.primary
|
||||
.withAlpha(180)
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withOpacity(0.8)),
|
||||
),
|
||||
item.msgStatus == 1
|
||||
? Text(
|
||||
' 已撤回',
|
||||
style:
|
||||
Theme.of(context).textTheme.labelSmall!,
|
||||
)
|
||||
: const SizedBox()
|
||||
],
|
||||
)
|
||||
],
|
||||
.outlineVariant
|
||||
.withOpacity(0.6)
|
||||
.withAlpha(125),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: const Radius.circular(borderRadiusVal),
|
||||
topRight: const Radius.circular(borderRadiusVal),
|
||||
bottomLeft:
|
||||
Radius.circular(isOwner ? borderRadiusVal : 2),
|
||||
bottomRight:
|
||||
Radius.circular(isOwner ? 2 : borderRadiusVal),
|
||||
),
|
||||
),
|
||||
if (!isOwner) const Spacer(),
|
||||
if (isOwner) const SizedBox(width: 12),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
padding: const EdgeInsets.all(paddingVal),
|
||||
child: messageContent(context),
|
||||
// child: Column(
|
||||
// crossAxisAlignment: isOwner
|
||||
// ? CrossAxisAlignment.end
|
||||
// : CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// messageContent(context),
|
||||
// SizedBox(height: isPic ? 7 : 2),
|
||||
// Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// Text(
|
||||
// Utils.dateFormat(item.timestamp),
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .labelSmall!
|
||||
// .copyWith(
|
||||
// color: isOwner
|
||||
// ? Theme.of(context)
|
||||
// .colorScheme
|
||||
// .onPrimary
|
||||
// .withOpacity(0.8)
|
||||
// : Theme.of(context)
|
||||
// .colorScheme
|
||||
// .onSecondaryContainer
|
||||
// .withOpacity(0.8)),
|
||||
// ),
|
||||
// item.msgStatus == 1
|
||||
// ? Text(
|
||||
// ' 已撤回',
|
||||
// style:
|
||||
// Theme.of(context).textTheme.labelSmall!,
|
||||
// )
|
||||
// : const SizedBox()
|
||||
// ],
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
),
|
||||
if (!isOwner)
|
||||
Text(
|
||||
Utils.dateFormat(item.timestamp),
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
const SizedBox(width: safeDistanceval),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1089,6 +1089,16 @@ class PlPlayerController {
|
||||
videoStorage.put(VideoBoxKey.playRepeat, type.value);
|
||||
}
|
||||
|
||||
/// 缓存本次弹幕选项
|
||||
cacheDanmakuOption() {
|
||||
localCache.put(LocalCacheKey.danmakuBlockType, blockTypes);
|
||||
localCache.put(LocalCacheKey.danmakuShowArea, showArea);
|
||||
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);
|
||||
localCache.put(LocalCacheKey.danmakuFontScale, fontSizeVal);
|
||||
localCache.put(LocalCacheKey.danmakuDuration, danmakuDurationVal);
|
||||
localCache.put(LocalCacheKey.strokeWidth, strokeWidth);
|
||||
}
|
||||
|
||||
Future<void> dispose({String type = 'single'}) async {
|
||||
// 每次减1,最后销毁
|
||||
if (type == 'single' && playerCount.value > 1) {
|
||||
@ -1118,12 +1128,7 @@ class PlPlayerController {
|
||||
// dataStatus.status.close();
|
||||
|
||||
/// 缓存本次弹幕选项
|
||||
localCache.put(LocalCacheKey.danmakuBlockType, blockTypes);
|
||||
localCache.put(LocalCacheKey.danmakuShowArea, showArea);
|
||||
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);
|
||||
localCache.put(LocalCacheKey.danmakuFontScale, fontSizeVal);
|
||||
localCache.put(LocalCacheKey.danmakuDuration, danmakuDurationVal);
|
||||
localCache.put(LocalCacheKey.strokeWidth, strokeWidth);
|
||||
cacheDanmakuOption();
|
||||
if (_videoPlayerController != null) {
|
||||
var pp = _videoPlayerController!.platform as NativePlayer;
|
||||
await pp.setProperty('audio-files', '');
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:pilipala/models/common/gesture_mode.dart';
|
||||
@ -38,6 +39,7 @@ class PLVideoPlayer extends StatefulWidget {
|
||||
this.customWidget,
|
||||
this.customWidgets,
|
||||
this.showEposideCb,
|
||||
this.fullScreenCb,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -51,6 +53,7 @@ class PLVideoPlayer extends StatefulWidget {
|
||||
final Widget? customWidget;
|
||||
final List<Widget>? customWidgets;
|
||||
final Function? showEposideCb;
|
||||
final Function? fullScreenCb;
|
||||
|
||||
@override
|
||||
State<PLVideoPlayer> createState() => _PLVideoPlayerState();
|
||||
@ -334,7 +337,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
fuc: () => _.triggerFullScreen(status: !_.isFullScreen.value),
|
||||
fuc: () {
|
||||
_.triggerFullScreen(status: !_.isFullScreen.value);
|
||||
widget.fullScreenCb?.call(!_.isFullScreen.value);
|
||||
},
|
||||
),
|
||||
};
|
||||
final List<Widget> list = [];
|
||||
@ -913,9 +919,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
colors: [Colors.black26, Colors.transparent],
|
||||
),
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/images/loading.gif',
|
||||
height: 25,
|
||||
child: Lottie.asset(
|
||||
'assets/loading.json',
|
||||
width: 200,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -939,7 +945,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
begin: 0.0,
|
||||
end: _hideSeekBackwardButton.value ? 0.0 : 1.0,
|
||||
),
|
||||
duration: const Duration(milliseconds: 200),
|
||||
duration: const Duration(milliseconds: 150),
|
||||
builder: (BuildContext context, double value,
|
||||
Widget? child) =>
|
||||
Opacity(
|
||||
@ -982,7 +988,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
begin: 0.0,
|
||||
end: _hideSeekForwardButton.value ? 0.0 : 1.0,
|
||||
),
|
||||
duration: const Duration(milliseconds: 200),
|
||||
duration: const Duration(milliseconds: 150),
|
||||
builder: (BuildContext context, double value,
|
||||
Widget? child) =>
|
||||
Opacity(
|
||||
|
||||
@ -30,14 +30,14 @@ class BackwardSeekIndicatorState extends State<BackwardSeekIndicator> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
timer = Timer(const Duration(milliseconds: 400), () {
|
||||
timer = Timer(const Duration(milliseconds: 200), () {
|
||||
widget.onSubmitted.call(value);
|
||||
});
|
||||
}
|
||||
|
||||
void increment() {
|
||||
timer?.cancel();
|
||||
timer = Timer(const Duration(milliseconds: 400), () {
|
||||
timer = Timer(const Duration(milliseconds: 200), () {
|
||||
widget.onSubmitted.call(value);
|
||||
});
|
||||
widget.onChanged.call(value);
|
||||
|
||||
@ -30,14 +30,14 @@ class ForwardSeekIndicatorState extends State<ForwardSeekIndicator> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
timer = Timer(const Duration(milliseconds: 400), () {
|
||||
timer = Timer(const Duration(milliseconds: 200), () {
|
||||
widget.onSubmitted.call(value);
|
||||
});
|
||||
}
|
||||
|
||||
void increment() {
|
||||
timer?.cancel();
|
||||
timer = Timer(const Duration(milliseconds: 400), () {
|
||||
timer = Timer(const Duration(milliseconds: 200), () {
|
||||
widget.onSubmitted.call(value);
|
||||
});
|
||||
widget.onChanged.call(value);
|
||||
|
||||
@ -4,6 +4,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/pages/follow_search/view.dart';
|
||||
import 'package:pilipala/pages/message/at/index.dart';
|
||||
import 'package:pilipala/pages/message/like/index.dart';
|
||||
import 'package:pilipala/pages/message/reply/index.dart';
|
||||
import 'package:pilipala/pages/message/system/index.dart';
|
||||
import 'package:pilipala/pages/setting/pages/logs.dart';
|
||||
|
||||
import '../pages/about/index.dart';
|
||||
@ -35,6 +39,7 @@ import '../pages/search/index.dart';
|
||||
import '../pages/search_result/index.dart';
|
||||
import '../pages/setting/extra_setting.dart';
|
||||
import '../pages/setting/index.dart';
|
||||
import '../pages/setting/pages/action_menu_set.dart';
|
||||
import '../pages/setting/pages/color_select.dart';
|
||||
import '../pages/setting/pages/display_mode.dart';
|
||||
import '../pages/setting/pages/font_size_select.dart';
|
||||
@ -174,6 +179,18 @@ class Routes {
|
||||
// navigation bar
|
||||
CustomGetPage(
|
||||
name: '/navbarSetting', page: () => const NavigationBarSetPage()),
|
||||
// 操作菜单
|
||||
CustomGetPage(
|
||||
name: '/actionMenuSet', page: () => const ActionMenuSetPage()),
|
||||
// 回复我的
|
||||
CustomGetPage(name: '/messageReply', page: () => const MessageReplyPage()),
|
||||
// @我的
|
||||
CustomGetPage(name: '/messageAt', page: () => const MessageAtPage()),
|
||||
// 收到的赞
|
||||
CustomGetPage(name: '/messageLike', page: () => const MessageLikePage()),
|
||||
// 系统通知
|
||||
CustomGetPage(
|
||||
name: '/messageSystem', page: () => const MessageSystemPage()),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -205,9 +205,7 @@ class PiliSchame {
|
||||
parameters: {'url': redirectUrl, 'type': 'url', 'pageTitle': ''},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (path != null) {
|
||||
} else if (path != null) {
|
||||
final String area = path.split('/').last;
|
||||
switch (area) {
|
||||
case 'bangumi':
|
||||
|
||||
@ -11,7 +11,8 @@ class GlobalData {
|
||||
bool enablePlayerControlAnimation = true;
|
||||
final bool enableMYBar =
|
||||
setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
|
||||
|
||||
List<String> actionTypeSort = setting.get(SettingBoxKey.actionTypeSort,
|
||||
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
|
||||
// 私有构造函数
|
||||
GlobalData._();
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ Future imageSaveDialog(context, videoItem, closeFn) {
|
||||
builder: (context) => Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
child: Column(
|
||||
|
||||
@ -2,12 +2,18 @@ import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/pages/dynamics/index.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/media/index.dart';
|
||||
import 'package:pilipala/pages/mine/index.dart';
|
||||
import 'package:pilipala/utils/cookie.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class LoginUtils {
|
||||
@ -57,4 +63,56 @@ class LoginUtils {
|
||||
String uuid = getUUID() + getUUID();
|
||||
return 'XY${uuid.substring(0, 35).toUpperCase()}';
|
||||
}
|
||||
|
||||
static confirmLogin(url, controller) async {
|
||||
var content = '';
|
||||
if (url != null) {
|
||||
content = '${content + url}; \n';
|
||||
}
|
||||
try {
|
||||
await SetCookie.onSet();
|
||||
final result = await UserHttp.userInfo();
|
||||
if (result['status'] && result['data'].isLogin) {
|
||||
SmartDialog.showToast('登录成功');
|
||||
try {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
if (!userInfoCache.isOpen) {
|
||||
userInfoCache = await Hive.openBox('userInfo');
|
||||
}
|
||||
await userInfoCache.put('userInfoCache', result['data']);
|
||||
|
||||
final HomeController homeCtr = Get.find<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
homeCtr.userFace.value = result['data'].face;
|
||||
final MediaController mediaCtr = Get.find<MediaController>();
|
||||
mediaCtr.mid = result['data'].mid;
|
||||
await LoginUtils.refreshLoginStatus(true);
|
||||
} catch (err) {
|
||||
SmartDialog.show(builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('登录遇到问题'),
|
||||
content: Text(err.toString()),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: controller != null
|
||||
? () => controller.reload()
|
||||
: SmartDialog.dismiss,
|
||||
child: const Text('确认'),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
Get.back();
|
||||
} else {
|
||||
// 获取用户信息失败
|
||||
SmartDialog.showToast(result['msg']);
|
||||
Clipboard.setData(ClipboardData(text: result['msg']));
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.showNotify(msg: e.toString(), notifyType: NotifyType.warning);
|
||||
content = content + e.toString();
|
||||
Clipboard.setData(ClipboardData(text: content));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +149,8 @@ class SettingBoxKey {
|
||||
tabbarSort = 'tabbarSort', // 首页tabbar
|
||||
dynamicBadgeMode = 'dynamicBadgeMode',
|
||||
enableGradientBg = 'enableGradientBg',
|
||||
navBarSort = 'navBarSort';
|
||||
navBarSort = 'navBarSort',
|
||||
actionTypeSort = 'actionTypeSort';
|
||||
}
|
||||
|
||||
class LocalCacheKey {
|
||||
|
||||
80
pubspec.lock
80
pubspec.lock
@ -674,10 +674,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: gt3_flutter_plugin
|
||||
sha256: f12bff2bfbcf27467833f8d564dcc24ee2f1b3254a7c7cf5eb2c4590baf11cc1
|
||||
sha256: "08f35692e937770ad6b3e2017eb8ef81839a82b8a63f5acf3abab14b688fc36c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.0.8"
|
||||
version: "0.1.0"
|
||||
hive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -762,10 +762,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
version: "0.18.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -794,26 +794,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.0.4"
|
||||
version: "10.0.0"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "2.0.1"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "2.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -862,6 +862,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
lottie:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: lottie
|
||||
sha256: "6a24ade5d3d918c306bb1c21a6b9a04aab0489d51a2582522eea820b4093b62b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
mailer:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -965,18 +973,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
version: "1.11.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5"
|
||||
nm:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1046,10 +1054,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.3"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1062,10 +1070,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.4.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1210,6 +1218,22 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
qr_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: qr_flutter
|
||||
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1230,10 +1254,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: saver_gallery
|
||||
sha256: "2657953427ebe5a3b2d08157d41587c01923ccce3f1a616d55082be7470f8530"
|
||||
sha256: "0f740608072053a0da3b19cc5812a87e36f5c3c0b959d2475c4eb3d697f4a782"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.3"
|
||||
screen_brightness:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1443,10 +1467,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
version: "0.6.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1603,10 +1627,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "14.2.1"
|
||||
version: "13.0.0"
|
||||
volume_controller:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1683,10 +1707,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: f42447ca49523f11d8f70abea55ea211b3cafe172dd7a0e7ac007bb35dd356dc
|
||||
sha256: "0d21cfc3bfdd2e30ab2ebeced66512b91134b39e72e97b43db2d47dda1c4e53a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.16.4"
|
||||
version: "3.16.3"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1744,5 +1768,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.4.0 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.19.0"
|
||||
|
||||
@ -132,7 +132,7 @@ dependencies:
|
||||
# html渲染
|
||||
flutter_html: ^3.0.0-beta.2
|
||||
# 极验
|
||||
gt3_flutter_plugin: ^0.0.8
|
||||
gt3_flutter_plugin: ^0.1.0
|
||||
uuid: ^3.0.7
|
||||
scrollable_positioned_list: ^0.3.8
|
||||
catcher_2: ^1.2.6
|
||||
@ -144,6 +144,9 @@ dependencies:
|
||||
expandable: ^5.0.1
|
||||
# 投屏
|
||||
dlna_dart: ^0.0.8
|
||||
lottie: ^3.1.2
|
||||
# 二维码
|
||||
qr_flutter: ^4.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -203,6 +206,7 @@ flutter:
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/
|
||||
- assets/images/
|
||||
- assets/images/lv/
|
||||
- assets/images/logo/
|
||||
|
||||
Reference in New Issue
Block a user