Merge branch 'main' into fix
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 1.7 KiB |
23
change_log/1.0.24.0626.md
Normal file
23
change_log/1.0.24.0626.md
Normal file
@ -0,0 +1,23 @@
|
||||
## 1.0.24
|
||||
|
||||
### 功能
|
||||
+ 私信功能
|
||||
+ 回复我的、收到的赞查看
|
||||
+ 新的登录方式
|
||||
+ 全屏选集
|
||||
+ 一键三连
|
||||
+ 按分区搜索
|
||||
|
||||
### 优化
|
||||
+ 页面跳转动画
|
||||
+ 评论区跳转
|
||||
|
||||
### 修复
|
||||
+ 音画不同步问题
|
||||
+ 分集字幕未同步
|
||||
+ 多语言字幕
|
||||
+ 弹幕设置未生效
|
||||
+
|
||||
|
||||
|
||||
问题反馈、功能建议请查看「关于」页面。
|
||||
@ -2,12 +2,18 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class HttpError extends StatelessWidget {
|
||||
const HttpError(
|
||||
{required this.errMsg, required this.fn, this.btnText, super.key});
|
||||
const HttpError({
|
||||
required this.errMsg,
|
||||
required this.fn,
|
||||
this.btnText,
|
||||
this.isShowBtn = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String? errMsg;
|
||||
final Function()? fn;
|
||||
final String? btnText;
|
||||
final bool isShowBtn;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -29,20 +35,22 @@ class HttpError extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
FilledButton.tonal(
|
||||
onPressed: () {
|
||||
fn!();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).colorScheme.primary.withAlpha(20);
|
||||
}),
|
||||
if (isShowBtn)
|
||||
FilledButton.tonal(
|
||||
onPressed: () {
|
||||
fn!();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).colorScheme.primary.withAlpha(20);
|
||||
}),
|
||||
),
|
||||
child: Text(
|
||||
btnText ?? '点击重试',
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
btnText ?? '点击重试',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -535,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';
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/member/like.dart';
|
||||
import '../common/constants.dart';
|
||||
import '../models/dynamics/result.dart';
|
||||
import '../models/follow/result.dart';
|
||||
@ -328,7 +329,9 @@ class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
||||
'data': res.data['data']['list']
|
||||
.map<MemberLikeDataModel>((e) => MemberLikeDataModel.fromJson(e))
|
||||
.toList(),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
|
||||
@ -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({
|
||||
'msg[sender_uid]': senderUid,
|
||||
'msg[receiver_id]': receiverId,
|
||||
'msg[receiver_type]': receiverType ?? 1,
|
||||
'msg[msg_type]': msgType ?? 1,
|
||||
'msg[msg_status]': 0,
|
||||
'msg[dev_id]': getDevId(),
|
||||
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
'msg[new_face_version]': 0,
|
||||
'msg[content]': content,
|
||||
'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,
|
||||
});
|
||||
var res = await Request().post(
|
||||
Api.sendMsg,
|
||||
data: {
|
||||
'msg[sender_uid]': senderUid,
|
||||
'msg[receiver_id]': receiverId,
|
||||
'msg[receiver_type]': 1,
|
||||
'msg[msg_type]': 1,
|
||||
'msg[msg_status]': 0,
|
||||
'msg[content]': jsonEncode(content),
|
||||
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
'msg[new_face_version]': 0,
|
||||
'msg[dev_id]': getDevId(),
|
||||
'from_firework': 0,
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
'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']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,11 @@ class SearchHttp {
|
||||
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) {
|
||||
if (res.data['code'] == 0) {
|
||||
if (res.data['data']['numPages'] == 0) {
|
||||
// 我想返回数据,使得可以通过data.list 取值,结果为[]
|
||||
return {'status': true, 'data': Data()};
|
||||
}
|
||||
Object data;
|
||||
try {
|
||||
switch (searchType) {
|
||||
@ -125,9 +129,7 @@ class SearchHttp {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['data'] != null && res.data['data']['numPages'] == 0
|
||||
? '没有相关数据'
|
||||
: res.data['message'],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -206,3 +208,9 @@ class SearchHttp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Data {
|
||||
List<dynamic> list;
|
||||
|
||||
Data({this.list = const []});
|
||||
}
|
||||
|
||||
210
lib/models/member/like.dart
Normal file
210
lib/models/member/like.dart
Normal file
@ -0,0 +1,210 @@
|
||||
class MemberLikeDataModel {
|
||||
MemberLikeDataModel({
|
||||
this.aid,
|
||||
this.videos,
|
||||
this.tid,
|
||||
this.tname,
|
||||
this.pic,
|
||||
this.title,
|
||||
this.pubdate,
|
||||
this.ctime,
|
||||
this.desc,
|
||||
this.state,
|
||||
this.duration,
|
||||
this.redirectUrl,
|
||||
this.rights,
|
||||
this.owner,
|
||||
this.stat,
|
||||
this.dimension,
|
||||
this.cover43,
|
||||
this.bvid,
|
||||
this.interVideo,
|
||||
this.resourceType,
|
||||
this.subtitle,
|
||||
this.enableVt,
|
||||
});
|
||||
|
||||
final int? aid;
|
||||
final int? videos;
|
||||
final int? tid;
|
||||
final String? tname;
|
||||
final String? pic;
|
||||
final String? title;
|
||||
final int? pubdate;
|
||||
final int? ctime;
|
||||
final String? desc;
|
||||
final int? state;
|
||||
final int? duration;
|
||||
final String? redirectUrl;
|
||||
final Rights? rights;
|
||||
final Owner? owner;
|
||||
final Stat? stat;
|
||||
final Dimension? dimension;
|
||||
final String? cover43;
|
||||
final String? bvid;
|
||||
final bool? interVideo;
|
||||
final String? resourceType;
|
||||
final String? subtitle;
|
||||
final int? enableVt;
|
||||
|
||||
factory MemberLikeDataModel.fromJson(Map<String, dynamic> json) =>
|
||||
MemberLikeDataModel(
|
||||
aid: json["aid"],
|
||||
videos: json["videos"],
|
||||
tid: json["tid"],
|
||||
tname: json["tname"],
|
||||
pic: json["pic"],
|
||||
title: json["title"],
|
||||
pubdate: json["pubdate"],
|
||||
ctime: json["ctime"],
|
||||
desc: json["desc"],
|
||||
state: json["state"],
|
||||
duration: json["duration"],
|
||||
redirectUrl: json["redirect_url"],
|
||||
rights: Rights.fromJson(json["rights"]),
|
||||
owner: Owner.fromJson(json["owner"]),
|
||||
stat: Stat.fromJson(json["stat"]),
|
||||
dimension: Dimension.fromJson(json["dimension"]),
|
||||
cover43: json["cover43"],
|
||||
bvid: json["bvid"],
|
||||
interVideo: json["inter_video"],
|
||||
resourceType: json["resource_type"],
|
||||
subtitle: json["subtitle"],
|
||||
enableVt: json["enable_vt"],
|
||||
);
|
||||
}
|
||||
|
||||
class Dimension {
|
||||
Dimension({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.rotate,
|
||||
});
|
||||
|
||||
final int width;
|
||||
final int height;
|
||||
final int rotate;
|
||||
|
||||
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
|
||||
width: json["width"],
|
||||
height: json["height"],
|
||||
rotate: json["rotate"],
|
||||
);
|
||||
}
|
||||
|
||||
class Owner {
|
||||
Owner({
|
||||
required this.mid,
|
||||
required this.name,
|
||||
required this.face,
|
||||
});
|
||||
|
||||
final int mid;
|
||||
final String name;
|
||||
final String face;
|
||||
|
||||
factory Owner.fromJson(Map<String, dynamic> json) => Owner(
|
||||
mid: json["mid"],
|
||||
name: json["name"],
|
||||
face: json["face"],
|
||||
);
|
||||
}
|
||||
|
||||
class Rights {
|
||||
Rights({
|
||||
required this.bp,
|
||||
required this.elec,
|
||||
required this.download,
|
||||
required this.movie,
|
||||
required this.pay,
|
||||
required this.hd5,
|
||||
required this.noReprint,
|
||||
required this.autoplay,
|
||||
required this.ugcPay,
|
||||
required this.isCooperation,
|
||||
required this.ugcPayPreview,
|
||||
required this.noBackground,
|
||||
required this.arcPay,
|
||||
required this.payFreeWatch,
|
||||
});
|
||||
|
||||
final int bp;
|
||||
final int elec;
|
||||
final int download;
|
||||
final int movie;
|
||||
final int pay;
|
||||
final int hd5;
|
||||
final int noReprint;
|
||||
final int autoplay;
|
||||
final int ugcPay;
|
||||
final int isCooperation;
|
||||
final int ugcPayPreview;
|
||||
final int noBackground;
|
||||
final int arcPay;
|
||||
final int payFreeWatch;
|
||||
|
||||
factory Rights.fromJson(Map<String, dynamic> json) => Rights(
|
||||
bp: json["bp"],
|
||||
elec: json["elec"],
|
||||
download: json["download"],
|
||||
movie: json["movie"],
|
||||
pay: json["pay"],
|
||||
hd5: json["hd5"],
|
||||
noReprint: json["no_reprint"],
|
||||
autoplay: json["autoplay"],
|
||||
ugcPay: json["ugc_pay"],
|
||||
isCooperation: json["is_cooperation"],
|
||||
ugcPayPreview: json["ugc_pay_preview"],
|
||||
noBackground: json["no_background"],
|
||||
arcPay: json["arc_pay"],
|
||||
payFreeWatch: json["pay_free_watch"],
|
||||
);
|
||||
}
|
||||
|
||||
class Stat {
|
||||
Stat({
|
||||
required this.aid,
|
||||
required this.view,
|
||||
required this.danmaku,
|
||||
required this.reply,
|
||||
required this.favorite,
|
||||
required this.coin,
|
||||
required this.share,
|
||||
required this.nowRank,
|
||||
required this.hisRank,
|
||||
required this.like,
|
||||
required this.dislike,
|
||||
required this.vt,
|
||||
required this.vv,
|
||||
});
|
||||
|
||||
final int aid;
|
||||
final int view;
|
||||
final int danmaku;
|
||||
final int reply;
|
||||
final int favorite;
|
||||
final int coin;
|
||||
final int share;
|
||||
final int nowRank;
|
||||
final int hisRank;
|
||||
final int like;
|
||||
final int dislike;
|
||||
final int vt;
|
||||
final int vv;
|
||||
|
||||
factory Stat.fromJson(Map<String, dynamic> json) => Stat(
|
||||
aid: json["aid"],
|
||||
view: json["view"],
|
||||
danmaku: json["danmaku"],
|
||||
reply: json["reply"],
|
||||
favorite: json["favorite"],
|
||||
coin: json["coin"],
|
||||
share: json["share"],
|
||||
nowRank: json["now_rank"],
|
||||
hisRank: json["his_rank"],
|
||||
like: json["like"],
|
||||
dislike: json["dislike"],
|
||||
vt: json["vt"],
|
||||
vv: json["vv"],
|
||||
);
|
||||
}
|
||||
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'];
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,12 @@ class SearchVideoModel {
|
||||
SearchVideoModel({this.list});
|
||||
List<SearchVideoItemModel>? list;
|
||||
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
||||
list = json['result']
|
||||
.where((e) => e['available'] == true)
|
||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||
.toList();
|
||||
list = json['result'] == null
|
||||
? []
|
||||
: json['result']
|
||||
.where((e) => e['available'] == true)
|
||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
lib/models/video/play/ao_output.dart
Normal file
6
lib/models/video/play/ao_output.dart
Normal file
@ -0,0 +1,6 @@
|
||||
final List aoOutputList = [
|
||||
{'title': 'audiotrack,opensles', 'value': '0'},
|
||||
{'title': 'opensles,audiotrack', 'value': '1'},
|
||||
{'title': 'audiotrack', 'value': '2'},
|
||||
{'title': 'opensles', 'value': '3'},
|
||||
];
|
||||
@ -357,25 +357,29 @@ class CustomChip extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final ColorScheme colorTheme = Theme.of(context).colorScheme;
|
||||
final Color secondaryContainer = colorTheme.secondaryContainer;
|
||||
final Color onPrimary = colorTheme.onPrimary;
|
||||
final Color primary = colorTheme.primary;
|
||||
final TextStyle chipTextStyle = selected
|
||||
? const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)
|
||||
: const TextStyle(fontSize: 13);
|
||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
? TextStyle(fontSize: 13, color: onPrimary)
|
||||
: TextStyle(fontSize: 13, color: colorTheme.onSecondaryContainer);
|
||||
const VisualDensity visualDensity =
|
||||
VisualDensity(horizontal: -4.0, vertical: -2.0);
|
||||
return InputChip(
|
||||
side: BorderSide(
|
||||
color: selected
|
||||
? colorScheme.onSecondaryContainer.withOpacity(0.2)
|
||||
: Colors.transparent,
|
||||
),
|
||||
side: BorderSide.none,
|
||||
backgroundColor: secondaryContainer,
|
||||
selectedColor: secondaryContainer,
|
||||
color: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) => secondaryContainer.withAlpha(200)),
|
||||
padding: const EdgeInsets.fromLTRB(7, 1, 7, 1),
|
||||
color: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected) ||
|
||||
states.contains(MaterialState.hovered)) {
|
||||
return primary;
|
||||
}
|
||||
return colorTheme.secondaryContainer;
|
||||
}),
|
||||
padding: const EdgeInsets.fromLTRB(6, 1, 6, 1),
|
||||
label: Text(label, style: chipTextStyle),
|
||||
onPressed: () => onTap(),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
selected: selected,
|
||||
showCheckmark: false,
|
||||
visualDensity: visualDensity,
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/member/archive.dart';
|
||||
import 'package:pilipala/models/member/coin.dart';
|
||||
import 'package:pilipala/models/member/info.dart';
|
||||
import 'package:pilipala/models/member/like.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
@ -25,6 +26,7 @@ class MemberController extends GetxController {
|
||||
RxInt attribute = (-1).obs;
|
||||
RxString attributeText = '关注'.obs;
|
||||
RxList<MemberCoinsDataModel> recentCoinsList = <MemberCoinsDataModel>[].obs;
|
||||
RxList<MemberLikeDataModel> recentLikeList = <MemberLikeDataModel>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -190,12 +192,17 @@ class MemberController extends GetxController {
|
||||
Share.share('${memberInfo.value.name} - https://space.bilibili.com/$mid');
|
||||
}
|
||||
|
||||
// 请求专栏
|
||||
// 请求合集
|
||||
Future getMemberSeasons() async {
|
||||
if (userInfo == null) return;
|
||||
var res = await MemberHttp.getMemberSeasons(mid, 1, 10);
|
||||
if (!res['status']) {
|
||||
SmartDialog.showToast("用户专栏请求异常:${res['msg']}");
|
||||
} else {
|
||||
// 只取前四个专栏
|
||||
res['data'].seasonsList.map((e) {
|
||||
e.archives = e.archives!.sublist(0, 4);
|
||||
}).toList();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -208,6 +215,14 @@ class MemberController extends GetxController {
|
||||
return res;
|
||||
}
|
||||
|
||||
// 请求点赞视频
|
||||
Future getRecentLikeVideo() async {
|
||||
if (userInfo == null) return;
|
||||
var res = await MemberHttp.getRecentLikeVideo(mid: mid);
|
||||
recentLikeList.value = res['data'];
|
||||
return res;
|
||||
}
|
||||
|
||||
// 跳转查看动态
|
||||
void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid');
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:pilipala/pages/member/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import 'widgets/conis.dart';
|
||||
import 'widgets/like.dart';
|
||||
import 'widgets/profile.dart';
|
||||
import 'widgets/seasons.dart';
|
||||
|
||||
@ -26,6 +27,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
late Future _futureBuilderFuture;
|
||||
late Future _memberSeasonsFuture;
|
||||
late Future _memberCoinsFuture;
|
||||
late Future _memberLikeFuture;
|
||||
final ScrollController _extendNestCtr = ScrollController();
|
||||
final StreamController<bool> appbarStream = StreamController<bool>();
|
||||
late int mid;
|
||||
@ -39,6 +41,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
_futureBuilderFuture = _memberController.getInfo();
|
||||
_memberSeasonsFuture = _memberController.getMemberSeasons();
|
||||
_memberCoinsFuture = _memberController.getRecentCoinVideo();
|
||||
_memberLikeFuture = _memberController.getRecentLikeVideo();
|
||||
_extendNestCtr.addListener(
|
||||
() {
|
||||
final double offset = _extendNestCtr.position.pixels;
|
||||
@ -162,6 +165,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
),
|
||||
const Divider(height: 1, thickness: 0.1),
|
||||
|
||||
/// 视频
|
||||
ListTile(
|
||||
@ -170,45 +174,41 @@ class _MemberPageState extends State<MemberPage>
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
),
|
||||
const Divider(height: 1, thickness: 0.1),
|
||||
|
||||
/// 专栏
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
title: const Text('Ta的专栏'),
|
||||
),
|
||||
const ListTile(title: Text('Ta的专栏')),
|
||||
const Divider(height: 1, thickness: 0.1),
|
||||
|
||||
/// 合集
|
||||
const ListTile(title: Text('Ta的合集')),
|
||||
MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
removeBottom: true,
|
||||
context: context,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: _memberSeasonsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
if (snapshot.data['status']) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['data'].seasonsList.isEmpty) {
|
||||
return commenWidget('用户没有设置专栏');
|
||||
} else {
|
||||
return MemberSeasonsPanel(data: data['data']);
|
||||
}
|
||||
} else {
|
||||
// 请求错误
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
child: FutureBuilder(
|
||||
future: _memberSeasonsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
if (snapshot.data['status']) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['data'].seasonsList.isEmpty) {
|
||||
return commenWidget('用户没有设置合集');
|
||||
} else {
|
||||
return MemberSeasonsPanel(data: data['data']);
|
||||
}
|
||||
} else {
|
||||
// 请求错误
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@ -218,12 +218,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
/// 最近投币
|
||||
Obx(
|
||||
() => _memberController.recentCoinsList.isNotEmpty
|
||||
? ListTile(
|
||||
onTap: () {},
|
||||
title: const Text('最近投币的视频'),
|
||||
// trailing: const Icon(Icons.arrow_forward_outlined,
|
||||
// size: 19),
|
||||
)
|
||||
? const ListTile(title: Text('最近投币的视频'))
|
||||
: const SizedBox(),
|
||||
),
|
||||
MediaQuery.removePadding(
|
||||
@ -257,13 +252,44 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
// 最近点赞
|
||||
// ListTile(
|
||||
// onTap: () {},
|
||||
// title: const Text('最近点赞的视频'),
|
||||
// trailing:
|
||||
// const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
// ),
|
||||
|
||||
/// 最近点赞
|
||||
Obx(
|
||||
() => _memberController.recentLikeList.isNotEmpty
|
||||
? const ListTile(title: Text('最近点赞的视频'))
|
||||
: const SizedBox(),
|
||||
),
|
||||
MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
removeBottom: true,
|
||||
context: context,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: _memberLikeFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
if (snapshot.data['status']) {
|
||||
Map data = snapshot.data as Map;
|
||||
return MemberLikePanel(data: data['data']);
|
||||
} else {
|
||||
// 请求错误
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -4,8 +4,8 @@ import 'package:pilipala/models/member/coin.dart';
|
||||
import 'package:pilipala/pages/member_coin/widgets/item.dart';
|
||||
|
||||
class MemberCoinsPanel extends StatelessWidget {
|
||||
final List<MemberCoinsDataModel>? data;
|
||||
const MemberCoinsPanel({super.key, this.data});
|
||||
final List<MemberCoinsDataModel> data;
|
||||
const MemberCoinsPanel({super.key, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -20,9 +20,9 @@ class MemberCoinsPanel extends StatelessWidget {
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: data!.length,
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, i) {
|
||||
return MemberCoinsItem(coinItem: data![i]);
|
||||
return MemberCoinsItem(coinItem: data[i]);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
31
lib/pages/member/widgets/like.dart
Normal file
31
lib/pages/member/widgets/like.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/models/member/like.dart';
|
||||
import 'package:pilipala/pages/member_like/widgets/item.dart';
|
||||
|
||||
class MemberLikePanel extends StatelessWidget {
|
||||
final List<MemberLikeDataModel> data;
|
||||
const MemberLikePanel({super.key, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2, // Use a fixed count for GridView
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
childAspectRatio: 0.94,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, i) {
|
||||
return MemberLikeItem(likeItem: data[i]);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -25,7 +25,7 @@ class MemberSeasonsPanel extends StatelessWidget {
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () => Get.toNamed(
|
||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}&seasonName=${item.meta!.name}'),
|
||||
title: Text(
|
||||
item.meta!.name!,
|
||||
maxLines: 1,
|
||||
@ -44,24 +44,30 @@ class MemberSeasonsPanel extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2, // Use a fixed count for GridView
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
childAspectRatio: 0.94,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: item.archives!.length,
|
||||
itemBuilder: (context, i) {
|
||||
return MemberSeasonsItem(seasonItem: item.archives![i]);
|
||||
},
|
||||
);
|
||||
},
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2, // Use a fixed count for GridView
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
childAspectRatio: 0.94,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: item.archives!.length,
|
||||
itemBuilder: (context, i) {
|
||||
return MemberSeasonsItem(seasonItem: item.archives![i]);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
96
lib/pages/member_like/widgets/item.dart
Normal file
96
lib/pages/member_like/widgets/item.dart
Normal file
@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/member/like.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class MemberLikeItem extends StatelessWidget {
|
||||
final MemberLikeDataModel likeItem;
|
||||
|
||||
const MemberLikeItem({
|
||||
Key? key,
|
||||
required this.likeItem,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(likeItem.aid);
|
||||
return Card(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
int cid =
|
||||
await SearchHttp.ab2c(aid: likeItem.aid, bvid: likeItem.bvid);
|
||||
Get.toNamed('/video?bvid=${likeItem.bvid}&cid=$cid',
|
||||
arguments: {'videoItem': likeItem, 'heroTag': heroTag});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: likeItem.pic,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (likeItem.duration != null)
|
||||
PBadge(
|
||||
bottom: 6,
|
||||
right: 6,
|
||||
type: 'gray',
|
||||
text: Utils.timeFormat(likeItem.duration),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(5, 6, 0, 0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
likeItem.title!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
view: likeItem.stat!.view,
|
||||
theme: 'gray',
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
Utils.CustomStamp_str(
|
||||
timestamp: likeItem.pubdate, date: 'MM-DD'),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -43,7 +43,8 @@ class _MemberSeasonsPageState extends State<MemberSeasonsPage> {
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
title: Text('他的专栏', style: Theme.of(context).textTheme.titleMedium),
|
||||
title: Text(Get.parameters['seasonName']!,
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
|
||||
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('系统通知'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -30,9 +30,9 @@ class SearchPanelController extends GetxController {
|
||||
);
|
||||
if (result['status']) {
|
||||
if (type == 'onRefresh') {
|
||||
resultList.value = result['data'].list;
|
||||
resultList.value = result['data'].list ?? [];
|
||||
} else {
|
||||
resultList.addAll(result['data'].list);
|
||||
resultList.addAll(result['data'].list ?? []);
|
||||
}
|
||||
page.value++;
|
||||
onPushDetail(keyword, resultList);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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/video_card_h.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/pages/search/widgets/search_text.dart';
|
||||
@ -25,25 +26,35 @@ class SearchVideoPanel extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 36),
|
||||
child: ListView.builder(
|
||||
controller: ctr!.scrollController,
|
||||
addAutomaticKeepAlives: false,
|
||||
addRepaintBoundaries: false,
|
||||
itemCount: list!.length,
|
||||
itemBuilder: (context, index) {
|
||||
var i = list![index];
|
||||
return Padding(
|
||||
padding: index == 0
|
||||
? const EdgeInsets.only(top: 2)
|
||||
: EdgeInsets.zero,
|
||||
child: VideoCardH(
|
||||
videoItem: i,
|
||||
showPubdate: true,
|
||||
source: 'search',
|
||||
child: list!.isNotEmpty
|
||||
? ListView.builder(
|
||||
controller: ctr!.scrollController,
|
||||
addAutomaticKeepAlives: false,
|
||||
addRepaintBoundaries: false,
|
||||
itemCount: list!.length,
|
||||
itemBuilder: (context, index) {
|
||||
var i = list![index];
|
||||
return Padding(
|
||||
padding: index == 0
|
||||
? const EdgeInsets.only(top: 2)
|
||||
: EdgeInsets.zero,
|
||||
child: VideoCardH(
|
||||
videoItem: i,
|
||||
showPubdate: true,
|
||||
source: 'search',
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: '没有数据',
|
||||
isShowBtn: false,
|
||||
fn: () => {},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// 分类筛选
|
||||
Container(
|
||||
|
||||
@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/video/play/ao_output.dart';
|
||||
import 'package:pilipala/models/video/play/quality.dart';
|
||||
import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
@ -28,6 +29,7 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
late dynamic defaultDecode;
|
||||
late int defaultFullScreenMode;
|
||||
late int defaultBtmProgressBehavior;
|
||||
late String defaultAoOutput;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -44,6 +46,8 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
defaultValue: FullScreenMode.values.first.code);
|
||||
defaultBtmProgressBehavior = setting.get(SettingBoxKey.btmProgressBehavior,
|
||||
defaultValue: BtmProgresBehavior.values.first.code);
|
||||
defaultAoOutput =
|
||||
setting.get(SettingBoxKey.defaultAoOutput, defaultValue: '0');
|
||||
}
|
||||
|
||||
@override
|
||||
@ -263,6 +267,31 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('音频输出方式', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前输出方式 ${aoOutputList.firstWhere((element) => element['value'] == defaultAoOutput)['title']}',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
onTap: () async {
|
||||
String? result = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<String>(
|
||||
title: '音频输出方式',
|
||||
value: defaultAoOutput,
|
||||
values: aoOutputList,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
defaultAoOutput = result;
|
||||
setting.put(SettingBoxKey.defaultAoOutput, result);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认全屏方式', style: titleStyle),
|
||||
|
||||
@ -109,6 +109,7 @@ class VideoDetailController extends GetxController
|
||||
].obs;
|
||||
RxDouble sheetHeight = 0.0.obs;
|
||||
RxString archiveSourceType = 'dash'.obs;
|
||||
ScrollController? replyScrillController;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -551,4 +552,15 @@ class VideoDetailController extends GetxController
|
||||
cover.value = videoItem['pic'] = pic;
|
||||
}
|
||||
}
|
||||
|
||||
void onControllerCreated(ScrollController controller) {
|
||||
replyScrillController = controller;
|
||||
}
|
||||
|
||||
void onTapTabbar(int index) {
|
||||
if (index == 1 && tabCtr.index == 1) {
|
||||
replyScrillController?.animateTo(0,
|
||||
duration: const Duration(milliseconds: 300), curve: Curves.ease);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ 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';
|
||||
@ -629,11 +630,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
child: Icon(
|
||||
key: ValueKey<bool>(likeStatus),
|
||||
likeStatus
|
||||
? Icons.thumb_up
|
||||
: Icons.thumb_up_alt_outlined,
|
||||
? FontAwesomeIcons.solidThumbsUp
|
||||
: FontAwesomeIcons.thumbsUp,
|
||||
color: likeStatus
|
||||
? colorScheme.primary
|
||||
: colorScheme.outline,
|
||||
size: 21,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
@ -663,7 +665,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
(const IconThemeData.fallback().size! + 5) / 2,
|
||||
child: progressWidget(_progress)),
|
||||
ActionItem(
|
||||
icon: Image.asset('assets/images/coin.png', width: 30),
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
text: widget.videoDetail!.stat!.coin!.toString(),
|
||||
@ -681,8 +684,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
(const IconThemeData.fallback().size! + 5) / 2,
|
||||
child: progressWidget(_progress)),
|
||||
ActionItem(
|
||||
icon: const Icon(Icons.star_border),
|
||||
selectIcon: const Icon(Icons.star),
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
@ -692,7 +695,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
),
|
||||
),
|
||||
'watchLater': ActionItem(
|
||||
icon: const Icon(Icons.watch_later_outlined),
|
||||
icon: const Icon(FontAwesomeIcons.clock),
|
||||
onTap: () async {
|
||||
final res =
|
||||
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
|
||||
@ -702,15 +705,15 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
text: '稍后看',
|
||||
),
|
||||
'share': ActionItem(
|
||||
icon: const Icon(Icons.share),
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
text: '分享',
|
||||
),
|
||||
'dislike': Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(Icons.thumb_down_alt_outlined),
|
||||
selectIcon: const Icon(Icons.thumb_down),
|
||||
icon: const Icon(FontAwesomeIcons.thumbsDown),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
|
||||
onTap: () {},
|
||||
selectStatus: videoIntroController.hasDisLike.value,
|
||||
text: '不喜欢',
|
||||
|
||||
@ -51,6 +51,7 @@ class ActionItem extends StatelessWidget {
|
||||
color: selectStatus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
size: 20,
|
||||
)
|
||||
: Image.asset(
|
||||
key: ValueKey<bool>(selectStatus),
|
||||
|
||||
@ -19,12 +19,14 @@ class VideoReplyPanel extends StatefulWidget {
|
||||
final int? oid;
|
||||
final int rpid;
|
||||
final String? replyLevel;
|
||||
final Function(ScrollController)? onControllerCreated;
|
||||
|
||||
const VideoReplyPanel({
|
||||
this.bvid,
|
||||
this.oid,
|
||||
this.rpid = 0,
|
||||
this.replyLevel,
|
||||
this.onControllerCreated,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -68,6 +70,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
|
||||
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
||||
scrollController = ScrollController();
|
||||
widget.onControllerCreated?.call(scrollController);
|
||||
fabAnimationCtr.forward();
|
||||
scrollListener();
|
||||
}
|
||||
|
||||
@ -387,6 +387,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
dividerColor: Colors.transparent,
|
||||
tabs:
|
||||
vdCtr.tabs.map((String name) => Tab(text: name)).toList(),
|
||||
onTap: (index) => vdCtr.onTapTabbar(index),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -683,6 +684,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
() => VideoReplyPanel(
|
||||
bvid: vdCtr.bvid,
|
||||
oid: vdCtr.oid.value,
|
||||
onControllerCreated: vdCtr.onControllerCreated,
|
||||
),
|
||||
)
|
||||
],
|
||||
|
||||
@ -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:flutter_smart_dialog/flutter_smart_dialog.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';
|
||||
@ -44,147 +46,157 @@ 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(
|
||||
onRefresh: () async {
|
||||
await _whisperController.onRefresh();
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
RxList sessionList = _whisperController.sessionList;
|
||||
return Obx(
|
||||
() => sessionList.isEmpty
|
||||
? const SizedBox()
|
||||
: ListView.separated(
|
||||
itemCount: sessionList.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (_, int i) {
|
||||
return SessionItem(
|
||||
sessionItem: sessionList[i],
|
||||
changeFucCall: () =>
|
||||
sessionList.refresh(),
|
||||
);
|
||||
},
|
||||
separatorBuilder:
|
||||
(BuildContext context, int index) {
|
||||
return Divider(
|
||||
indent: 72,
|
||||
endIndent: 20,
|
||||
height: 6,
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
);
|
||||
},
|
||||
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: () {
|
||||
if (['/messageAt', '/messageSystem']
|
||||
.contains(element['path'])) {
|
||||
SmartDialog.showToast('功能开发中');
|
||||
return;
|
||||
}
|
||||
Get.toNamed(element['path']);
|
||||
|
||||
if (element['count'] > 0) {
|
||||
element['count'] = 0;
|
||||
}
|
||||
_whisperController.noticesList.refresh();
|
||||
},
|
||||
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'])
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return Center(
|
||||
child: Text(data?['msg'] ?? '请求异常'),
|
||||
);
|
||||
}
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
RxList sessionList = _whisperController.sessionList;
|
||||
return Obx(
|
||||
() => sessionList.isEmpty
|
||||
? const SizedBox()
|
||||
: ListView.separated(
|
||||
itemCount: sessionList.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (_, int i) {
|
||||
return SessionItem(
|
||||
sessionItem: sessionList[i],
|
||||
changeFucCall: () => sessionList.refresh(),
|
||||
);
|
||||
},
|
||||
separatorBuilder:
|
||||
(BuildContext context, int index) {
|
||||
return Divider(
|
||||
indent: 72,
|
||||
endIndent: 20,
|
||||
height: 6,
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 骨架屏
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
// 请求错误
|
||||
return Center(
|
||||
child: Text(data?['msg'] ?? '请求异常'),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
} else {
|
||||
// 骨架屏
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -202,7 +214,10 @@ class SessionItem extends StatelessWidget {
|
||||
|
||||
@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;
|
||||
@ -214,6 +229,7 @@ class SessionItem extends StatelessWidget {
|
||||
'name': sessionItem.accountInfo.name,
|
||||
'face': sessionItem.accountInfo.face,
|
||||
'mid': sessionItem.accountInfo.mid.toString(),
|
||||
'heroTag': heroTag,
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -221,22 +237,27 @@ class SessionItem extends StatelessWidget {
|
||||
isLabelVisible: sessionItem.unreadCount > 0,
|
||||
label: Text(sessionItem.unreadCount.toString()),
|
||||
alignment: Alignment.topRight,
|
||||
child: NetworkImgLayer(
|
||||
width: 45,
|
||||
height: 45,
|
||||
type: 'avatar',
|
||||
src: sessionItem.accountInfo.face,
|
||||
child: Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
width: 45,
|
||||
height: 45,
|
||||
type: 'avatar',
|
||||
src: sessionItem.accountInfo.face,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(sessionItem.accountInfo.name),
|
||||
subtitle: Text(
|
||||
content != null && content != ''
|
||||
? (content['text'] ??
|
||||
content['content'] ??
|
||||
content['title'] ??
|
||||
content['reply_content'] ??
|
||||
'不支持的消息类型')
|
||||
: '不支持的消息类型',
|
||||
msgStatus == 1
|
||||
? '你撤回了一条消息'
|
||||
: content != null && content != ''
|
||||
? (content['text'] ??
|
||||
content['content'] ??
|
||||
content['title'] ??
|
||||
content['reply_content'] ??
|
||||
'不支持的消息类型')
|
||||
: '不支持的消息类型',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
@ -245,10 +266,10 @@ class SessionItem extends StatelessWidget {
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline)),
|
||||
trailing: Text(
|
||||
Utils.dateFormat(sessionItem.lastMsg.timestamp),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
||||
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();
|
||||
talkerId = int.parse(Get.parameters['talkerId']!);
|
||||
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(
|
||||
width: 34,
|
||||
height: 34,
|
||||
type: 'avatar',
|
||||
src: _whisperDetailController.face,
|
||||
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,155 +157,171 @@ 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('关闭会话'),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 14)
|
||||
],
|
||||
),
|
||||
body: GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
setState(() {
|
||||
keyboardHeight = 0;
|
||||
});
|
||||
},
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
final Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
List messageList = _whisperDetailController.messageList;
|
||||
return Obx(
|
||||
() => messageList.isEmpty
|
||||
? const SizedBox()
|
||||
: ListView.builder(
|
||||
itemCount: messageList.length,
|
||||
shrinkWrap: true,
|
||||
reverse: true,
|
||||
itemBuilder: (_, int i) {
|
||||
if (i == 0) {
|
||||
return Column(
|
||||
children: [
|
||||
ChatItem(
|
||||
item: messageList[i],
|
||||
e_infos: _whisperDetailController.eInfos),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return ChatItem(
|
||||
item: messageList[i],
|
||||
e_infos: _whisperDetailController.eInfos);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
// 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,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 4,
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
toolbarType.value = '';
|
||||
},
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
final Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
List messageList = _whisperDetailController.messageList;
|
||||
return Obx(
|
||||
() => messageList.isEmpty
|
||||
? const SizedBox()
|
||||
: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ListView.builder(
|
||||
itemCount: messageList.length,
|
||||
shrinkWrap: true,
|
||||
reverse: true,
|
||||
itemBuilder: (_, int i) {
|
||||
if (i == 0) {
|
||||
return Column(
|
||||
children: [
|
||||
ChatItem(
|
||||
item: messageList[i],
|
||||
e_infos: _whisperDetailController
|
||||
.eInfos),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return ChatItem(
|
||||
item: messageList[i],
|
||||
e_infos:
|
||||
_whisperDetailController.eInfos);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: Icon(
|
||||
// Icons.add_circle_outline,
|
||||
// color: Theme.of(context).colorScheme.outline,
|
||||
// ),
|
||||
// ),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
// if (toolbarType == 'input') {
|
||||
// setState(() {
|
||||
// toolbarType = 'emote';
|
||||
// });
|
||||
// }
|
||||
// FocusScope.of(context).unfocus();
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.emoji_emotions_outlined,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
Obx(
|
||||
() => Container(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
8,
|
||||
12,
|
||||
12,
|
||||
toolbarType.value == ''
|
||||
? MediaQuery.of(context).padding.bottom + 6
|
||||
: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 1,
|
||||
color: Colors.grey.withOpacity(0.15),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.08),
|
||||
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: '开发中 ...', // 提示文本
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 12.0), // 内边距
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ToolbarIconButton(
|
||||
onPressed: () {
|
||||
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: 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
|
||||
.outline
|
||||
.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(40.0),
|
||||
),
|
||||
child: TextField(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
controller: _replyContentController,
|
||||
autofocus: false,
|
||||
focusNode: replyContentFocusNode,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none, // 移除默认边框
|
||||
hintText: '文明发言 ~', // 提示文本
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 12.0), // 内边距
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
// onPressed: _whisperDetailController.sendMsg,
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.send,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
IconButton(
|
||||
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,7 @@
|
||||
// ignore_for_file: must_be_immutable
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -9,7 +9,6 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/utils/route_push.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import '../../../http/search.dart';
|
||||
|
||||
enum MsgType {
|
||||
@ -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) {
|
||||
@ -386,73 +389,97 @@ class ChatItem extends StatelessWidget {
|
||||
? messageContent(context)
|
||||
: isRevoke
|
||||
? const SizedBox()
|
||||
: Row(
|
||||
children: [
|
||||
if (!isOwner) const SizedBox(width: 12),
|
||||
if (isOwner) const Spacer(),
|
||||
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),
|
||||
: 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: [
|
||||
const SizedBox(width: safeDistanceval),
|
||||
Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 300.0, // 设置最大宽度为200.0
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isOwner
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withAlpha(180)
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.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),
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
padding: const EdgeInsets.all(paddingVal),
|
||||
child: Column(
|
||||
crossAxisAlignment: isOwner
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
messageContent(context),
|
||||
SizedBox(height: isPic ? 7 : 4),
|
||||
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()
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
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)
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withOpacity(0.8)),
|
||||
),
|
||||
item.msgStatus == 1
|
||||
? Text(
|
||||
' 已撤回',
|
||||
style:
|
||||
Theme.of(context).textTheme.labelSmall!,
|
||||
)
|
||||
: const SizedBox()
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!isOwner) const Spacer(),
|
||||
if (isOwner) const SizedBox(width: 12),
|
||||
],
|
||||
const SizedBox(width: safeDistanceval),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:ns_danmaku/ns_danmaku.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/video/play/ao_output.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||
import 'package:pilipala/services/service_locator.dart';
|
||||
@ -453,7 +454,13 @@ class PlPlayerController {
|
||||
// 音量不一致
|
||||
if (Platform.isAndroid) {
|
||||
await pp.setProperty("volume-max", "100");
|
||||
await pp.setProperty("ao", "audiotrack,opensles");
|
||||
String defaultAoOutput =
|
||||
setting.get(SettingBoxKey.defaultAoOutput, defaultValue: '0');
|
||||
await pp.setProperty(
|
||||
"ao",
|
||||
aoOutputList
|
||||
.where((e) => e['value'] == defaultAoOutput)
|
||||
.first['title']);
|
||||
}
|
||||
|
||||
await player.setAudioTrack(
|
||||
|
||||
@ -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';
|
||||
@ -178,6 +182,15 @@ class Routes {
|
||||
// 操作菜单
|
||||
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()),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:appscheme/appscheme.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:pilipala/utils/route_push.dart';
|
||||
@ -38,60 +39,82 @@ class PiliSchame {
|
||||
final String host = value.host;
|
||||
final String path = value.path;
|
||||
if (scheme == 'bilibili') {
|
||||
if (host == 'root') {
|
||||
Navigator.popUntil(
|
||||
Get.context!, (Route<dynamic> route) => route.isFirst);
|
||||
} else if (host == 'space') {
|
||||
final String mid = path.split('/').last;
|
||||
Get.toNamed<dynamic>(
|
||||
'/member?mid=$mid',
|
||||
arguments: <String, dynamic>{'face': null},
|
||||
);
|
||||
} else if (host == 'video') {
|
||||
String pathQuery = path.split('/').last;
|
||||
final numericRegex = RegExp(r'^[0-9]+$');
|
||||
if (numericRegex.hasMatch(pathQuery)) {
|
||||
pathQuery = 'AV$pathQuery';
|
||||
}
|
||||
Map map = IdUtils.matchAvorBv(input: pathQuery);
|
||||
if (map.containsKey('AV')) {
|
||||
_videoPush(map['AV'], null);
|
||||
} else if (map.containsKey('BV')) {
|
||||
_videoPush(null, map['BV']);
|
||||
} else {
|
||||
SmartDialog.showToast('投稿匹配失败');
|
||||
}
|
||||
} else if (host == 'live') {
|
||||
final String roomId = path.split('/').last;
|
||||
Get.toNamed<dynamic>('/liveRoom?roomid=$roomId',
|
||||
arguments: <String, String?>{'liveItem': null, 'heroTag': roomId});
|
||||
} else if (host == 'bangumi') {
|
||||
if (path.startsWith('/season')) {
|
||||
final String seasonId = path.split('/').last;
|
||||
RoutePush.bangumiPush(int.parse(seasonId), null);
|
||||
}
|
||||
} else if (host == 'opus') {
|
||||
if (path.startsWith('/detail')) {
|
||||
var opusId = path.split('/').last;
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': 'https://www.bilibili.com/opus/$opusId',
|
||||
'type': 'url',
|
||||
'pageTitle': '',
|
||||
},
|
||||
switch (host) {
|
||||
case 'root':
|
||||
Navigator.popUntil(
|
||||
Get.context!, (Route<dynamic> route) => route.isFirst);
|
||||
break;
|
||||
case 'space':
|
||||
final String mid = path.split('/').last;
|
||||
Get.toNamed<dynamic>(
|
||||
'/member?mid=$mid',
|
||||
arguments: <String, dynamic>{'face': null},
|
||||
);
|
||||
}
|
||||
} else if (host == 'search') {
|
||||
Get.toNamed('/searchResult', parameters: {'keyword': ''});
|
||||
} else if (host == 'article') {
|
||||
final String id = path.split('/').last.split('?').first;
|
||||
Get.toNamed('/htmlRender', parameters: {
|
||||
'url': 'https://www.bilibili.com/read/cv$id',
|
||||
'title': 'cv$id',
|
||||
'id': 'cv$id',
|
||||
'dynamicType': 'read'
|
||||
});
|
||||
break;
|
||||
case 'video':
|
||||
String pathQuery = path.split('/').last;
|
||||
final numericRegex = RegExp(r'^[0-9]+$');
|
||||
if (numericRegex.hasMatch(pathQuery)) {
|
||||
pathQuery = 'AV$pathQuery';
|
||||
}
|
||||
Map map = IdUtils.matchAvorBv(input: pathQuery);
|
||||
if (map.containsKey('AV')) {
|
||||
_videoPush(map['AV'], null);
|
||||
} else if (map.containsKey('BV')) {
|
||||
_videoPush(null, map['BV']);
|
||||
} else {
|
||||
SmartDialog.showToast('投稿匹配失败');
|
||||
}
|
||||
break;
|
||||
case 'live':
|
||||
final String roomId = path.split('/').last;
|
||||
Get.toNamed<dynamic>(
|
||||
'/liveRoom?roomid=$roomId',
|
||||
arguments: <String, String?>{'liveItem': null, 'heroTag': roomId},
|
||||
);
|
||||
break;
|
||||
case 'bangumi':
|
||||
if (path.startsWith('/season')) {
|
||||
final String seasonId = path.split('/').last;
|
||||
RoutePush.bangumiPush(int.parse(seasonId), null);
|
||||
}
|
||||
break;
|
||||
case 'opus':
|
||||
if (path.startsWith('/detail')) {
|
||||
var opusId = path.split('/').last;
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': 'https://www.bilibili.com/opus/$opusId',
|
||||
'type': 'url',
|
||||
'pageTitle': '',
|
||||
},
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'search':
|
||||
Get.toNamed('/searchResult', parameters: {'keyword': ''});
|
||||
break;
|
||||
case 'article':
|
||||
final String id = path.split('/').last.split('?').first;
|
||||
Get.toNamed('/htmlRender', parameters: {
|
||||
'url': 'https://www.bilibili.com/read/cv$id',
|
||||
'title': 'cv$id',
|
||||
'id': 'cv$id',
|
||||
'dynamicType': 'read'
|
||||
});
|
||||
break;
|
||||
case 'pgc':
|
||||
if (path.contains('ep')) {
|
||||
final String lastPathSegment = path.split('/').last;
|
||||
RoutePush.bangumiPush(
|
||||
null, int.parse(lastPathSegment.split('?').first));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
SmartDialog.showToast('未匹配地址,请联系开发者');
|
||||
Clipboard.setData(ClipboardData(text: value.toJson().toString()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (scheme == 'https') {
|
||||
|
||||
@ -102,6 +102,8 @@ class SettingBoxKey {
|
||||
autoPiP = 'autoPiP',
|
||||
enableAutoLongPressSpeed = 'enableAutoLongPressSpeed',
|
||||
enablePlayerControlAnimation = 'enablePlayerControlAnimation',
|
||||
// 默认音频输出方式
|
||||
defaultAoOutput = 'defaultAoOutput',
|
||||
|
||||
// youtube 双击快进快退
|
||||
enableQuickDouble = 'enableQuickDouble',
|
||||
|
||||
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.23+1023
|
||||
version: 1.0.24+1024
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
Reference in New Issue
Block a user