Compare commits
44 Commits
v1.0.18.01
...
feature-ho
| Author | SHA1 | Date | |
|---|---|---|---|
| 41c40dfbc4 | |||
| 5d9ecab1b0 | |||
| 3de009ac43 | |||
| b29256f598 | |||
| e7cf472a0f | |||
| 03c59d23b8 | |||
| b6f805f0e4 | |||
| e23c2469ed | |||
| 387c799de1 | |||
| 230dd81342 | |||
| 47bdfec8c2 | |||
| 6a844da259 | |||
| 18bb58d293 | |||
| 045186b3c8 | |||
| b531599893 | |||
| 1da84508d8 | |||
| 4c44fab217 | |||
| 5c3d438a7e | |||
| 92a8efdee1 | |||
| eb1e2ca5f4 | |||
| 5b1022628c | |||
| 33f61ac0fa | |||
| 0b349e102e | |||
| 81371c5a31 | |||
| 85a59e11b9 | |||
| e24ccc16fa | |||
| 89a43b1285 | |||
| ea8af28828 | |||
| 8a2c023343 | |||
| a86fe76e59 | |||
| d703e38c3f | |||
| 9e93b50860 | |||
| 9907967a0a | |||
| 331969cc8d | |||
| e603942b5f | |||
| 0c4bad406e | |||
| 9663278916 | |||
| 545def36e6 | |||
| aaeecc9e53 | |||
| 16895b5c32 | |||
| a68c04001b | |||
| 1dd70f482f | |||
| 103423abf7 | |||
| 569184a507 |
15
change_log/1.0.19.0131.md
Normal file
15
change_log/1.0.19.0131.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
## 1.0.19
|
||||||
|
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 视频404、评论加载错误
|
||||||
|
+ bvav转换
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 视频详情页内存占用
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
@ -4,7 +4,7 @@ import 'init.dart';
|
|||||||
|
|
||||||
class ReplyHttp {
|
class ReplyHttp {
|
||||||
static Future replyList({
|
static Future replyList({
|
||||||
required dynamic oid,
|
required int oid,
|
||||||
required int pageNum,
|
required int pageNum,
|
||||||
required int type,
|
required int type,
|
||||||
int? ps,
|
int? ps,
|
||||||
@ -76,7 +76,7 @@ class ReplyHttp {
|
|||||||
// 评论点赞
|
// 评论点赞
|
||||||
static Future likeReply({
|
static Future likeReply({
|
||||||
required int type,
|
required int type,
|
||||||
required dynamic oid,
|
required int oid,
|
||||||
required int rpid,
|
required int rpid,
|
||||||
required int action,
|
required int action,
|
||||||
}) async {
|
}) async {
|
||||||
|
|||||||
@ -224,10 +224,11 @@ class VideoHttp {
|
|||||||
// 获取投币状态
|
// 获取投币状态
|
||||||
static Future hasCoinVideo({required String bvid}) async {
|
static Future hasCoinVideo({required String bvid}) async {
|
||||||
var res = await Request().get(Api.hasCoinVideo, data: {'bvid': bvid});
|
var res = await Request().get(Api.hasCoinVideo, data: {'bvid': bvid});
|
||||||
|
print('res: $res');
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {'status': true, 'data': []};
|
return {'status': false, 'data': []};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +332,7 @@ class VideoHttp {
|
|||||||
// plat num 发送平台标识 非必要 1:web端 2:安卓客户端 3:ios客户端 4:wp客户端
|
// plat num 发送平台标识 非必要 1:web端 2:安卓客户端 3:ios客户端 4:wp客户端
|
||||||
static Future replyAdd({
|
static Future replyAdd({
|
||||||
required ReplyType type,
|
required ReplyType type,
|
||||||
required dynamic oid,
|
required int oid,
|
||||||
required String message,
|
required String message,
|
||||||
int? root,
|
int? root,
|
||||||
int? parent,
|
int? parent,
|
||||||
@ -361,7 +362,7 @@ class VideoHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {'status': true, 'data': []};
|
return {'status': false, 'data': []};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,7 +378,7 @@ class VideoHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {'status': true, 'data': []};
|
return {'status': false, 'data': []};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,6 +434,8 @@ class VideoHttp {
|
|||||||
});
|
});
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'data': null, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,10 +456,7 @@ class VideoHttp {
|
|||||||
'data': AiConclusionModel.fromJson(res.data['data']),
|
'data': AiConclusionModel.fromJson(res.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'data': []};
|
||||||
'status': false,
|
|
||||||
'data': []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
lib/models/common/dynamic_badge_mode.dart
Normal file
9
lib/models/common/dynamic_badge_mode.dart
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
enum DynamicBadgeMode { hidden, point, number }
|
||||||
|
|
||||||
|
extension DynamicBadgeModeDesc on DynamicBadgeMode {
|
||||||
|
String get description => ['隐藏', '红点', '数字'][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DynamicBadgeModeCode on DynamicBadgeMode {
|
||||||
|
int get code => [0, 1, 2][index];
|
||||||
|
}
|
||||||
@ -17,8 +17,9 @@ class LatestDataModel {
|
|||||||
url = json['url'];
|
url = json['url'];
|
||||||
tagName = json['tag_name'];
|
tagName = json['tag_name'];
|
||||||
createdAt = json['created_at'];
|
createdAt = json['created_at'];
|
||||||
assets =
|
assets = json['assets'] != null
|
||||||
json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList();
|
? json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList()
|
||||||
|
: [];
|
||||||
body = json['body'];
|
body = json['body'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -166,7 +166,7 @@ class SessionMsgDataModel {
|
|||||||
int? hasMore;
|
int? hasMore;
|
||||||
int? minSeqno;
|
int? minSeqno;
|
||||||
int? maxSeqno;
|
int? maxSeqno;
|
||||||
List? eInfos;
|
List<dynamic>? eInfos;
|
||||||
|
|
||||||
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
|
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
messages = json['messages']
|
messages = json['messages']
|
||||||
|
|||||||
@ -1,14 +1,8 @@
|
|||||||
import 'package:hive/hive.dart';
|
|
||||||
|
|
||||||
part 'hot.g.dart';
|
|
||||||
|
|
||||||
@HiveType(typeId: 6)
|
|
||||||
class HotSearchModel {
|
class HotSearchModel {
|
||||||
HotSearchModel({
|
HotSearchModel({
|
||||||
this.list,
|
this.list,
|
||||||
});
|
});
|
||||||
|
|
||||||
@HiveField(0)
|
|
||||||
List<HotSearchItem>? list;
|
List<HotSearchItem>? list;
|
||||||
|
|
||||||
HotSearchModel.fromJson(Map<String, dynamic> json) {
|
HotSearchModel.fromJson(Map<String, dynamic> json) {
|
||||||
@ -18,7 +12,6 @@ class HotSearchModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HiveType(typeId: 7)
|
|
||||||
class HotSearchItem {
|
class HotSearchItem {
|
||||||
HotSearchItem({
|
HotSearchItem({
|
||||||
this.keyword,
|
this.keyword,
|
||||||
@ -27,20 +20,19 @@ class HotSearchItem {
|
|||||||
this.icon,
|
this.icon,
|
||||||
});
|
});
|
||||||
|
|
||||||
@HiveField(0)
|
|
||||||
String? keyword;
|
String? keyword;
|
||||||
@HiveField(1)
|
|
||||||
String? showName;
|
String? showName;
|
||||||
// 4/5热 11话题 8普通 7直播
|
// 4/5热 11话题 8普通 7直播
|
||||||
@HiveField(2)
|
|
||||||
int? wordType;
|
int? wordType;
|
||||||
@HiveField(3)
|
|
||||||
String? icon;
|
String? icon;
|
||||||
|
List? liveId;
|
||||||
|
|
||||||
HotSearchItem.fromJson(Map<String, dynamic> json) {
|
HotSearchItem.fromJson(Map<String, dynamic> json) {
|
||||||
keyword = json['keyword'];
|
keyword = json['keyword'];
|
||||||
showName = json['show_name'];
|
showName = json['show_name'];
|
||||||
wordType = json['word_type'];
|
wordType = json['word_type'];
|
||||||
icon = json['icon'];
|
icon = json['icon'];
|
||||||
|
liveId = json['live_id'];
|
||||||
|
liveId = json['live_id'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,84 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'hot.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// TypeAdapterGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
class HotSearchModelAdapter extends TypeAdapter<HotSearchModel> {
|
|
||||||
@override
|
|
||||||
final int typeId = 6;
|
|
||||||
|
|
||||||
@override
|
|
||||||
HotSearchModel read(BinaryReader reader) {
|
|
||||||
final numOfFields = reader.readByte();
|
|
||||||
final fields = <int, dynamic>{
|
|
||||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
|
||||||
};
|
|
||||||
return HotSearchModel(
|
|
||||||
list: (fields[0] as List?)?.cast<HotSearchItem>(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void write(BinaryWriter writer, HotSearchModel obj) {
|
|
||||||
writer
|
|
||||||
..writeByte(1)
|
|
||||||
..writeByte(0)
|
|
||||||
..write(obj.list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => typeId.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is HotSearchModelAdapter &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
typeId == other.typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class HotSearchItemAdapter extends TypeAdapter<HotSearchItem> {
|
|
||||||
@override
|
|
||||||
final int typeId = 7;
|
|
||||||
|
|
||||||
@override
|
|
||||||
HotSearchItem read(BinaryReader reader) {
|
|
||||||
final numOfFields = reader.readByte();
|
|
||||||
final fields = <int, dynamic>{
|
|
||||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
|
||||||
};
|
|
||||||
return HotSearchItem(
|
|
||||||
keyword: fields[0] as String?,
|
|
||||||
showName: fields[1] as String?,
|
|
||||||
wordType: fields[2] as int?,
|
|
||||||
icon: fields[3] as String?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void write(BinaryWriter writer, HotSearchItem obj) {
|
|
||||||
writer
|
|
||||||
..writeByte(4)
|
|
||||||
..writeByte(0)
|
|
||||||
..write(obj.keyword)
|
|
||||||
..writeByte(1)
|
|
||||||
..write(obj.showName)
|
|
||||||
..writeByte(2)
|
|
||||||
..write(obj.wordType)
|
|
||||||
..writeByte(3)
|
|
||||||
..write(obj.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => typeId.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is HotSearchItemAdapter &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
typeId == other.typeId;
|
|
||||||
}
|
|
||||||
@ -266,7 +266,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
/// 未渲染回复组件时可能异常
|
/// 未渲染回复组件时可能异常
|
||||||
VideoReplyController videoReplyCtr =
|
VideoReplyController videoReplyCtr =
|
||||||
Get.find<VideoReplyController>(tag: Get.arguments['heroTag']);
|
Get.find<VideoReplyController>(tag: Get.arguments['heroTag']);
|
||||||
videoReplyCtr.oid = bvid;
|
videoReplyCtr.aid = aid;
|
||||||
videoReplyCtr.queryReplyList(type: 'init');
|
videoReplyCtr.queryReplyList(type: 'init');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -385,8 +385,8 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return VideoReplyNewDialog(
|
return VideoReplyNewDialog(
|
||||||
oid: _dynamicDetailController.oid?.toString() ??
|
oid: _dynamicDetailController.oid ??
|
||||||
Get.parameters['bvid'],
|
IdUtils.bv2av(Get.parameters['bvid']!),
|
||||||
root: 0,
|
root: 0,
|
||||||
parent: 0,
|
parent: 0,
|
||||||
replyType: ReplyType.values[replyType],
|
replyType: ReplyType.values[replyType],
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/reply_new/index.dart';
|
import 'package:pilipala/pages/video/detail/reply_new/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply_reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply_reply/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
@ -428,7 +427,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return VideoReplyNewDialog(
|
return VideoReplyNewDialog(
|
||||||
oid: IdUtils.av2bv(_htmlRenderCtr.oid.value),
|
oid: _htmlRenderCtr.oid.value,
|
||||||
root: 0,
|
root: 0,
|
||||||
parent: 0,
|
parent: 0,
|
||||||
replyType: ReplyType.values[type],
|
replyType: ReplyType.values[type],
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:pilipala/pages/home/view.dart';
|
|||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
import '../../models/common/dynamic_badge_mode.dart';
|
||||||
|
|
||||||
class MainController extends GetxController {
|
class MainController extends GetxController {
|
||||||
List<Widget> pages = <Widget>[
|
List<Widget> pages = <Widget>[
|
||||||
@ -65,6 +66,7 @@ class MainController extends GetxController {
|
|||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
|
late Rx<DynamicBadgeMode> dynamicBadgeType = DynamicBadgeMode.number.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -75,7 +77,12 @@ class MainController extends GetxController {
|
|||||||
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
|
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
|
||||||
var userInfo = userInfoCache.get('userInfoCache');
|
var userInfo = userInfoCache.get('userInfoCache');
|
||||||
userLogin.value = userInfo != null;
|
userLogin.value = userInfo != null;
|
||||||
getUnreadDynamic();
|
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(
|
||||||
|
SettingBoxKey.dynamicBadgeMode,
|
||||||
|
defaultValue: DynamicBadgeMode.number.code)];
|
||||||
|
if (dynamicBadgeType.value != DynamicBadgeMode.hidden) {
|
||||||
|
getUnreadDynamic();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onBackPressed(BuildContext context) {
|
void onBackPressed(BuildContext context) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/models/common/dynamic_badge_mode.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
import 'package:pilipala/pages/dynamics/index.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
import 'package:pilipala/pages/home/index.dart';
|
||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
@ -127,11 +128,21 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
destinations: <Widget>[
|
destinations: <Widget>[
|
||||||
..._mainController.navigationBars.map((e) {
|
..._mainController.navigationBars.map((e) {
|
||||||
return NavigationDestination(
|
return NavigationDestination(
|
||||||
icon: Badge(
|
icon: Obx(
|
||||||
label: Text(e['count'].toString()),
|
() => Badge(
|
||||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
label:
|
||||||
isLabelVisible: e['count'] > 0,
|
_mainController.dynamicBadgeType.value ==
|
||||||
child: e['icon'],
|
DynamicBadgeMode.number
|
||||||
|
? Text(e['count'].toString())
|
||||||
|
: null,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||||
|
isLabelVisible:
|
||||||
|
_mainController.dynamicBadgeType.value !=
|
||||||
|
DynamicBadgeMode.hidden &&
|
||||||
|
e['count'] > 0,
|
||||||
|
child: e['icon'],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
selectedIcon: e['selectIcon'],
|
selectedIcon: e['selectIcon'],
|
||||||
label: e['label'],
|
label: e['label'],
|
||||||
@ -148,11 +159,21 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
items: [
|
items: [
|
||||||
..._mainController.navigationBars.map((e) {
|
..._mainController.navigationBars.map((e) {
|
||||||
return BottomNavigationBarItem(
|
return BottomNavigationBarItem(
|
||||||
icon: Badge(
|
icon: Obx(
|
||||||
label: Text(e['count'].toString()),
|
() => Badge(
|
||||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
label:
|
||||||
isLabelVisible: e['count'] > 0,
|
_mainController.dynamicBadgeType.value ==
|
||||||
child: e['icon'],
|
DynamicBadgeMode.number
|
||||||
|
? Text(e['count'].toString())
|
||||||
|
: null,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||||
|
isLabelVisible:
|
||||||
|
_mainController.dynamicBadgeType.value !=
|
||||||
|
DynamicBadgeMode.hidden &&
|
||||||
|
e['count'] > 0,
|
||||||
|
child: e['icon'],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
activeIcon: e['selectIcon'],
|
activeIcon: e['selectIcon'],
|
||||||
label: e['label'],
|
label: e['label'],
|
||||||
|
|||||||
@ -64,7 +64,7 @@ class _MinePageState extends State<MinePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => Get.toNamed('/setting'),
|
onPressed: () => Get.toNamed('/setting', preventDuplicates: false),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
CupertinoIcons.slider_horizontal_3,
|
CupertinoIcons.slider_horizontal_3,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class SSearchController extends GetxController {
|
|||||||
// 其他页面跳转过来
|
// 其他页面跳转过来
|
||||||
if (Get.parameters.keys.isNotEmpty) {
|
if (Get.parameters.keys.isNotEmpty) {
|
||||||
if (Get.parameters['keyword'] != null) {
|
if (Get.parameters['keyword'] != null) {
|
||||||
onClickKeyword(Get.parameters['keyword']!);
|
onClickKeyword(Get.parameters['keyword']!, null);
|
||||||
}
|
}
|
||||||
if (Get.parameters['hintText'] != null) {
|
if (Get.parameters['hintText'] != null) {
|
||||||
hintText = Get.parameters['hintText']!;
|
hintText = Get.parameters['hintText']!;
|
||||||
@ -88,7 +88,12 @@ class SSearchController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 点击热搜关键词
|
// 点击热搜关键词
|
||||||
void onClickKeyword(String keyword) {
|
void onClickKeyword(String keyword, item) {
|
||||||
|
if (item != null && item.wordType == 7) {
|
||||||
|
Get.toNamed('/liveRoom?roomid=${item.liveId.first}',
|
||||||
|
arguments: {'liveItem': null, 'heroTag': '${item.liveId.first}'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
searchKeyWord.value = keyword;
|
searchKeyWord.value = keyword;
|
||||||
controller.value.text = keyword;
|
controller.value.text = keyword;
|
||||||
// 移动光标
|
// 移动光标
|
||||||
|
|||||||
@ -115,8 +115,8 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
customBorder: RoundedRectangleBorder(
|
customBorder: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
onTap: () => ssCtr
|
onTap: () => ssCtr.onClickKeyword(
|
||||||
.onClickKeyword(ssCtr.searchSuggestList[index].term!),
|
ssCtr.searchSuggestList[index].term!, null),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 20, top: 9, bottom: 9),
|
padding: const EdgeInsets.only(left: 20, top: 9, bottom: 9),
|
||||||
child: ssCtr.searchSuggestList[index].textRich,
|
child: ssCtr.searchSuggestList[index].textRich,
|
||||||
@ -178,11 +178,11 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
width: width,
|
width: width,
|
||||||
// ignore: invalid_use_of_protected_member
|
// ignore: invalid_use_of_protected_member
|
||||||
hotSearchList: _searchController.hotSearchList.value,
|
hotSearchList: _searchController.hotSearchList.value,
|
||||||
onClick: (keyword) async {
|
onClick: (keyword, item) async {
|
||||||
_searchController.searchFocusNode.unfocus();
|
_searchController.searchFocusNode.unfocus();
|
||||||
await Future.delayed(
|
await Future.delayed(
|
||||||
const Duration(milliseconds: 150));
|
const Duration(milliseconds: 150));
|
||||||
_searchController.onClickKeyword(keyword);
|
_searchController.onClickKeyword(keyword, item);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -193,15 +193,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 缓存数据
|
return const SizedBox();
|
||||||
if (_searchController.hotSearchList.isNotEmpty) {
|
|
||||||
return HotKeyword(
|
|
||||||
width: width,
|
|
||||||
hotSearchList: _searchController.hotSearchList,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -26,7 +26,7 @@ class HotKeyword extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(3),
|
borderRadius: BorderRadius.circular(3),
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => onClick!(i.keyword),
|
onTap: () => onClick!(i.keyword, i),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: 2,
|
left: 2,
|
||||||
|
|||||||
@ -25,16 +25,17 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
|||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
double width = (boxConstraints.maxWidth -
|
final double width = (boxConstraints.maxWidth -
|
||||||
StyleString.cardSpace *
|
StyleString.cardSpace *
|
||||||
6 /
|
6 /
|
||||||
MediaQuery.textScalerOf(context).scale(2.0));
|
MediaQuery.textScalerOf(context).scale(1.0)) /
|
||||||
|
2;
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(minHeight: 88),
|
constraints: const BoxConstraints(minHeight: 88),
|
||||||
height: width / StyleString.aspectRatio,
|
height: width / StyleString.aspectRatio,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: <Widget>[
|
||||||
if (list[index].imageUrls != null &&
|
if (list[index].imageUrls != null &&
|
||||||
list[index].imageUrls.isNotEmpty)
|
list[index].imageUrls.isNotEmpty)
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
|
|||||||
@ -7,6 +7,9 @@ import 'package:pilipala/models/common/theme_type.dart';
|
|||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/login.dart';
|
import 'package:pilipala/utils/login.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
import '../../models/common/dynamic_badge_mode.dart';
|
||||||
|
import '../main/index.dart';
|
||||||
|
import 'widgets/select_dialog.dart';
|
||||||
|
|
||||||
class SettingController extends GetxController {
|
class SettingController extends GetxController {
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
@ -19,6 +22,7 @@ class SettingController extends GetxController {
|
|||||||
RxInt picQuality = 10.obs;
|
RxInt picQuality = 10.obs;
|
||||||
Rx<ThemeType> themeType = ThemeType.system.obs;
|
Rx<ThemeType> themeType = ThemeType.system.obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
|
Rx<DynamicBadgeMode> dynamicBadgeType = DynamicBadgeMode.number.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -33,6 +37,9 @@ class SettingController extends GetxController {
|
|||||||
setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
|
setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
|
||||||
themeType.value = ThemeType.values[setting.get(SettingBoxKey.themeMode,
|
themeType.value = ThemeType.values[setting.get(SettingBoxKey.themeMode,
|
||||||
defaultValue: ThemeType.system.code)];
|
defaultValue: ThemeType.system.code)];
|
||||||
|
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(
|
||||||
|
SettingBoxKey.dynamicBadgeMode,
|
||||||
|
defaultValue: DynamicBadgeMode.number.code)];
|
||||||
}
|
}
|
||||||
|
|
||||||
loginOut() async {
|
loginOut() async {
|
||||||
@ -76,4 +83,31 @@ class SettingController extends GetxController {
|
|||||||
feedBackEnable.value = !feedBackEnable.value;
|
feedBackEnable.value = !feedBackEnable.value;
|
||||||
setting.put(SettingBoxKey.feedBackEnable, feedBackEnable.value);
|
setting.put(SettingBoxKey.feedBackEnable, feedBackEnable.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置动态未读标记
|
||||||
|
setDynamicBadgeMode(BuildContext context) async {
|
||||||
|
DynamicBadgeMode? result = await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return SelectDialog<DynamicBadgeMode>(
|
||||||
|
title: '动态未读标记',
|
||||||
|
value: dynamicBadgeType.value,
|
||||||
|
values: DynamicBadgeMode.values.map((e) {
|
||||||
|
return {'title': e.description, 'value': e};
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
dynamicBadgeType.value = result;
|
||||||
|
setting.put(SettingBoxKey.dynamicBadgeMode, result.code);
|
||||||
|
MainController mainController = Get.put(MainController());
|
||||||
|
mainController.dynamicBadgeType.value =
|
||||||
|
DynamicBadgeMode.values[result.code];
|
||||||
|
if (mainController.dynamicBadgeType.value != DynamicBadgeMode.hidden) {
|
||||||
|
mainController.getUnreadDynamic();
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('设置成功');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
|
|||||||
import 'package:pilipala/pages/setting/widgets/slide_dialog.dart';
|
import 'package:pilipala/pages/setting/widgets/slide_dialog.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
import '../../models/common/dynamic_badge_mode.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'widgets/switch_item.dart';
|
import 'widgets/switch_item.dart';
|
||||||
|
|
||||||
@ -241,6 +242,14 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
'当前模式:${settingController.themeType.value.description}',
|
'当前模式:${settingController.themeType.value.description}',
|
||||||
style: subTitleStyle)),
|
style: subTitleStyle)),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: false,
|
||||||
|
onTap: () => settingController.setDynamicBadgeMode(context),
|
||||||
|
title: Text('动态未读标记', style: titleStyle),
|
||||||
|
subtitle: Obx(() => Text(
|
||||||
|
'当前标记样式:${settingController.dynamicBadgeType.value.description}',
|
||||||
|
style: subTitleStyle)),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
onTap: () => Get.toNamed('/colorSetting'),
|
onTap: () => Get.toNamed('/colorSetting'),
|
||||||
|
|||||||
@ -148,7 +148,9 @@ class VideoIntroController extends GetxController {
|
|||||||
// 获取投币状态
|
// 获取投币状态
|
||||||
Future queryHasCoinVideo() async {
|
Future queryHasCoinVideo() async {
|
||||||
var result = await VideoHttp.hasCoinVideo(bvid: bvid);
|
var result = await VideoHttp.hasCoinVideo(bvid: bvid);
|
||||||
hasCoin.value = result["data"]['multiply'] == 0 ? false : true;
|
if (result['status']) {
|
||||||
|
hasCoin.value = result["data"]['multiply'] == 0 ? false : true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取收藏状态
|
// 获取收藏状态
|
||||||
@ -208,6 +210,10 @@ class VideoIntroController extends GetxController {
|
|||||||
|
|
||||||
// (取消)点赞
|
// (取消)点赞
|
||||||
Future actionLikeVideo() async {
|
Future actionLikeVideo() async {
|
||||||
|
if (userInfo == null) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
// hasLike.value = result["data"] == 1 ? true : false;
|
// hasLike.value = result["data"] == 1 ? true : false;
|
||||||
@ -478,7 +484,7 @@ class VideoIntroController extends GetxController {
|
|||||||
/// 未渲染回复组件时可能异常
|
/// 未渲染回复组件时可能异常
|
||||||
final VideoReplyController videoReplyCtr =
|
final VideoReplyController videoReplyCtr =
|
||||||
Get.find<VideoReplyController>(tag: heroTag);
|
Get.find<VideoReplyController>(tag: heroTag);
|
||||||
videoReplyCtr.oid = bvid;
|
videoReplyCtr.aid = aid;
|
||||||
videoReplyCtr.queryReplyList(type: 'init');
|
videoReplyCtr.queryReplyList(type: 'init');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
this.bvid = bvid;
|
this.bvid = bvid;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import 'package:pilipala/http/video.dart';
|
|||||||
|
|
||||||
class ReleatedController extends GetxController {
|
class ReleatedController extends GetxController {
|
||||||
// 视频aid
|
// 视频aid
|
||||||
String bvid = Get.parameters['bvid']!;
|
String bvid = Get.parameters['bvid'] ?? "";
|
||||||
// 推荐视频列表
|
// 推荐视频列表
|
||||||
List relatedVideoList = [];
|
List relatedVideoList = [];
|
||||||
|
|
||||||
|
|||||||
@ -11,13 +11,13 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
|
|
||||||
class VideoReplyController extends GetxController {
|
class VideoReplyController extends GetxController {
|
||||||
VideoReplyController(
|
VideoReplyController(
|
||||||
this.oid,
|
this.aid,
|
||||||
this.rpid,
|
this.rpid,
|
||||||
this.replyLevel,
|
this.replyLevel,
|
||||||
);
|
);
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
// 视频aid 请求时使用的oid
|
// 视频aid 请求时使用的oid
|
||||||
String? oid;
|
int? aid;
|
||||||
// 层级 2为楼中楼
|
// 层级 2为楼中楼
|
||||||
String? replyLevel;
|
String? replyLevel;
|
||||||
// rpid 请求楼中楼回复
|
// rpid 请求楼中楼回复
|
||||||
@ -57,7 +57,7 @@ class VideoReplyController extends GetxController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final res = await ReplyHttp.replyList(
|
final res = await ReplyHttp.replyList(
|
||||||
oid: oid!,
|
oid: aid!,
|
||||||
pageNum: currentPage + 1,
|
pageNum: currentPage + 1,
|
||||||
ps: ps,
|
ps: ps,
|
||||||
type: ReplyType.video.index,
|
type: ReplyType.video.index,
|
||||||
|
|||||||
@ -40,7 +40,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
bool _isFabVisible = true;
|
bool _isFabVisible = true;
|
||||||
String replyLevel = '1';
|
String replyLevel = '1';
|
||||||
late String heroTag;
|
late String heroTag;
|
||||||
late String oid;
|
|
||||||
|
|
||||||
// 添加页面缓存
|
// 添加页面缓存
|
||||||
@override
|
@override
|
||||||
@ -49,7 +48,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
oid = widget.bvid != null ? widget.bvid! : '0';
|
int oid = widget.bvid != null ? IdUtils.bv2av(widget.bvid!) : 0;
|
||||||
heroTag = Get.arguments['heroTag'];
|
heroTag = Get.arguments['heroTag'];
|
||||||
replyLevel = widget.replyLevel ?? '1';
|
replyLevel = widget.replyLevel ?? '1';
|
||||||
if (replyLevel == '2') {
|
if (replyLevel == '2') {
|
||||||
@ -298,8 +297,8 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return VideoReplyNewDialog(
|
return VideoReplyNewDialog(
|
||||||
oid:
|
oid: _videoReplyController.aid ??
|
||||||
_videoReplyController.oid ?? Get.parameters['bvid'],
|
IdUtils.bv2av(Get.parameters['bvid']!),
|
||||||
root: 0,
|
root: 0,
|
||||||
parent: 0,
|
parent: 0,
|
||||||
replyType: ReplyType.video,
|
replyType: ReplyType.video,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -353,7 +354,7 @@ class ReplyItem extends StatelessWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (builder) {
|
builder: (builder) {
|
||||||
return VideoReplyNewDialog(
|
return VideoReplyNewDialog(
|
||||||
oid: IdUtils.av2bv(replyItem!.oid!),
|
oid: replyItem!.oid,
|
||||||
root: replyItem!.rpid,
|
root: replyItem!.rpid,
|
||||||
parent: replyItem!.rpid,
|
parent: replyItem!.rpid,
|
||||||
replyType: replyType,
|
replyType: replyType,
|
||||||
@ -539,18 +540,6 @@ InlineSpan buildContent(
|
|||||||
// replyReply 查看二楼回复(回复详情)回调
|
// replyReply 查看二楼回复(回复详情)回调
|
||||||
// fReplyItem 父级回复内容,用作二楼回复(回复详情)展示
|
// fReplyItem 父级回复内容,用作二楼回复(回复详情)展示
|
||||||
final content = replyItem.content;
|
final content = replyItem.content;
|
||||||
if (content.emote.isEmpty &&
|
|
||||||
content.atNameToMid.isEmpty &&
|
|
||||||
content.jumpUrl.isEmpty &&
|
|
||||||
content.vote.isEmpty &&
|
|
||||||
content.pictures.isEmpty) {
|
|
||||||
return TextSpan(
|
|
||||||
text: content.message,
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap =
|
|
||||||
() => replyReply(replyItem.root == 0 ? replyItem : fReplyItem),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final List<InlineSpan> spanChilds = <InlineSpan>[];
|
final List<InlineSpan> spanChilds = <InlineSpan>[];
|
||||||
bool hasMatchMember = false;
|
bool hasMatchMember = false;
|
||||||
|
|
||||||
@ -582,258 +571,171 @@ InlineSpan buildContent(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' ');
|
// content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' ');
|
||||||
if (content.message.contains('&')) {
|
content.message = content.message.replaceAll('&', '&')
|
||||||
content.message = content.message.replaceAll('&', '&');
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll(''', "'")
|
||||||
|
.replaceAll(' ', ' ');
|
||||||
|
// print("content.jumpUrl.keys:" + content.jumpUrl.keys.toString());
|
||||||
|
// 构建正则表达式
|
||||||
|
final List<String> specialTokens = [
|
||||||
|
...content.emote.keys,
|
||||||
|
...content.atNameToMid.keys.map((e) => '@$e'),
|
||||||
|
...content.jumpUrl.keys.map((e) =>
|
||||||
|
e.replaceAll('?', '\\?').replaceAll('+', '\\+').replaceAll('*', '\\*')),
|
||||||
|
];
|
||||||
|
|
||||||
|
String patternStr =
|
||||||
|
specialTokens.map(RegExp.escape).join('|');
|
||||||
|
if (patternStr.isNotEmpty) {
|
||||||
|
patternStr += "|";
|
||||||
}
|
}
|
||||||
// 匹配表情
|
patternStr += r'(\b\d{1,2}[::]\d{2}\b)';
|
||||||
|
final RegExp pattern = RegExp(patternStr);
|
||||||
|
List<String> matchedStrs = [];
|
||||||
|
void addPlainTextSpan(str){
|
||||||
|
spanChilds.add(TextSpan(
|
||||||
|
text: str,
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () =>
|
||||||
|
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
||||||
|
}
|
||||||
|
// 分割文本并处理每个部分
|
||||||
content.message.splitMapJoin(
|
content.message.splitMapJoin(
|
||||||
RegExp(r"\[.*?\]"),
|
pattern,
|
||||||
onMatch: (Match match) {
|
onMatch: (Match match) {
|
||||||
final String matchStr = match[0]!;
|
String matchStr = match[0]!;
|
||||||
if (content.emote.isNotEmpty &&
|
if (content.emote.containsKey(matchStr)) {
|
||||||
matchStr.indexOf('[') == matchStr.lastIndexOf('[') &&
|
// 处理表情
|
||||||
matchStr.indexOf(']') == matchStr.lastIndexOf(']')) {
|
|
||||||
final int size = content.emote[matchStr]['meta']['size'];
|
final int size = content.emote[matchStr]['meta']['size'];
|
||||||
if (content.emote.keys.contains(matchStr)) {
|
spanChilds.add(WidgetSpan(
|
||||||
spanChilds.add(
|
child: NetworkImgLayer(
|
||||||
WidgetSpan(
|
src: content.emote[matchStr]['url'],
|
||||||
child: NetworkImgLayer(
|
type: 'emote',
|
||||||
src: content.emote[matchStr]['url'],
|
width: size * 20,
|
||||||
type: 'emote',
|
height: size * 20,
|
||||||
width: size * 20,
|
),
|
||||||
height: size * 20,
|
));
|
||||||
),
|
} else if (matchStr.startsWith("@") &&
|
||||||
),
|
content.atNameToMid.containsKey(matchStr.substring(1))) {
|
||||||
);
|
// 处理@用户
|
||||||
} else {
|
final String userName = matchStr.substring(1);
|
||||||
spanChilds.add(TextSpan(
|
final int userId = content.atNameToMid[userName];
|
||||||
text: matchStr,
|
spanChilds.add(
|
||||||
recognizer: TapGestureRecognizer()
|
TextSpan(
|
||||||
..onTap = () =>
|
|
||||||
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
|
||||||
return matchStr;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
spanChilds.add(TextSpan(
|
|
||||||
text: matchStr,
|
text: matchStr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () =>
|
..onTap = () {
|
||||||
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
final String heroTag = Utils.makeHeroTag(userId);
|
||||||
return matchStr;
|
Get.toNamed(
|
||||||
}
|
'/member?mid=$userId',
|
||||||
return '';
|
arguments: {'face': '', 'heroTag': heroTag},
|
||||||
},
|
|
||||||
onNonMatch: (String str) {
|
|
||||||
// 匹配@用户
|
|
||||||
String matchMember = str;
|
|
||||||
if (content.atNameToMid.isNotEmpty) {
|
|
||||||
final List atNameToMidKeys = content.atNameToMid.keys.toList();
|
|
||||||
RegExp reg = RegExp(atNameToMidKeys.map((key) => key).join('|'));
|
|
||||||
// if (!content.message.contains(':')) {
|
|
||||||
// reg = RegExp(r"@.*( |:)");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 只@用户没有内容
|
|
||||||
if (!content.message.contains(':') ||
|
|
||||||
(content.atNameToMid.length == 1 &&
|
|
||||||
content.message == '@${content.members.first.uname}')) {
|
|
||||||
reg = RegExp(r"@.*( |:|$)");
|
|
||||||
}
|
|
||||||
matchMember = str.splitMapJoin(
|
|
||||||
reg,
|
|
||||||
onMatch: (Match match) {
|
|
||||||
if (match[0] != null) {
|
|
||||||
hasMatchMember = true;
|
|
||||||
content.atNameToMid.forEach((key, value) {
|
|
||||||
if (str.contains('回复')) {
|
|
||||||
spanChilds.add(
|
|
||||||
TextSpan(
|
|
||||||
text: '回复 ',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
spanChilds.add(
|
|
||||||
TextSpan(
|
|
||||||
text: '@$key',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
final String heroTag = Utils.makeHeroTag(value);
|
|
||||||
Get.toNamed(
|
|
||||||
'/member?mid=$value',
|
|
||||||
arguments: {'face': '', 'heroTag': heroTag},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
}
|
),
|
||||||
return '';
|
);
|
||||||
},
|
} else if (RegExp(r'^\b[0-9]{1,2}[::][0-9]{2}\b$').hasMatch(matchStr)) {
|
||||||
onNonMatch: (String str) {
|
spanChilds.add(
|
||||||
if (!str.contains('@')) {
|
TextSpan(
|
||||||
spanChilds.add(TextSpan(text: str));
|
text: ' $matchStr ',
|
||||||
}
|
style: TextStyle(
|
||||||
print(str);
|
color: Theme.of(context).colorScheme.primary,
|
||||||
return str;
|
),
|
||||||
},
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
// 跳转到指定位置
|
||||||
|
try {
|
||||||
|
matchStr = matchStr.replaceAll(':', ':');
|
||||||
|
SmartDialog.showToast('跳转至:$matchStr');
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||||
|
.plPlayerController
|
||||||
|
.seekTo(
|
||||||
|
Duration(seconds: Utils.duration(matchStr)),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.showToast('跳转失败: $e');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
matchMember = str;
|
// print("matchStr=$matchStr");
|
||||||
}
|
String appUrlSchema = '';
|
||||||
|
final bool enableWordRe = setting.get(SettingBoxKey.enableWordRe,
|
||||||
// 匹配 jumpUrl
|
defaultValue: false) as bool;
|
||||||
String matchUrl = matchMember;
|
if (content.jumpUrl[matchStr] != null &&
|
||||||
if (content.jumpUrl.isNotEmpty) {
|
!matchedStrs.contains(matchStr)) {
|
||||||
final List urlKeys = content.jumpUrl.keys.toList().reversed.toList();
|
appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
|
||||||
for (int index = 0; index < urlKeys.length; index++) {
|
if (appUrlSchema.startsWith('bilibili://search') && !enableWordRe) {
|
||||||
var i = urlKeys[index];
|
addPlainTextSpan(matchStr);
|
||||||
if (i.contains('?')) {
|
return "";
|
||||||
urlKeys[index] = i.replaceAll('?', '\\?');
|
|
||||||
}
|
}
|
||||||
if (i.contains('+')) {
|
|
||||||
urlKeys[index] = i.replaceAll('+', '\\+');
|
|
||||||
}
|
|
||||||
if (i.contains('*')) {
|
|
||||||
urlKeys[index] = i.replaceAll('*', '\\*');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasMatchMember) {
|
|
||||||
matchMember = matchMember.split('回复 @ :').length > 1
|
|
||||||
? matchMember.split('回复 @ :')[1]
|
|
||||||
: matchMember;
|
|
||||||
}
|
|
||||||
matchUrl = matchMember.splitMapJoin(
|
|
||||||
/// RegExp.escape() 转义特殊字符
|
|
||||||
RegExp(urlKeys.map((key) => key).join("|")),
|
|
||||||
// RegExp('What does the fox say\\?'),
|
|
||||||
onMatch: (Match match) {
|
|
||||||
final String matchStr = match[0]!;
|
|
||||||
String appUrlSchema = '';
|
|
||||||
if (content.jumpUrl[matchStr] != null) {
|
|
||||||
appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
|
|
||||||
}
|
|
||||||
// 默认不显示关键词
|
|
||||||
final bool enableWordRe = setting.get(SettingBoxKey.enableWordRe,
|
|
||||||
defaultValue: false) as bool;
|
|
||||||
if (content.jumpUrl[matchStr] != null) {
|
|
||||||
spanChilds.add(
|
|
||||||
TextSpan(
|
|
||||||
text: content.jumpUrl[matchStr]['title'],
|
|
||||||
style: TextStyle(
|
|
||||||
color: enableWordRe
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
if (appUrlSchema == '') {
|
|
||||||
final String str = Uri.parse(matchStr).pathSegments[0];
|
|
||||||
final Map matchRes = IdUtils.matchAvorBv(input: str);
|
|
||||||
final List matchKeys = matchRes.keys.toList();
|
|
||||||
if (matchKeys.isNotEmpty) {
|
|
||||||
if (matchKeys.first == 'BV') {
|
|
||||||
Get.toNamed(
|
|
||||||
'/searchResult',
|
|
||||||
parameters: {'keyword': matchRes['BV']},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Get.toNamed(
|
|
||||||
'/webview',
|
|
||||||
parameters: {
|
|
||||||
'url': matchStr,
|
|
||||||
'type': 'url',
|
|
||||||
'pageTitle': ''
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (appUrlSchema.startsWith('bilibili://search') &&
|
|
||||||
enableWordRe) {
|
|
||||||
Get.toNamed('/searchResult', parameters: {
|
|
||||||
'keyword': content.jumpUrl[matchStr]['title']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appUrlSchema.startsWith('bilibili://search') && enableWordRe) {
|
|
||||||
spanChilds.add(
|
|
||||||
WidgetSpan(
|
|
||||||
child: Icon(
|
|
||||||
FontAwesomeIcons.magnifyingGlass,
|
|
||||||
size: 9,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
alignment: PlaceholderAlignment.top,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
onNonMatch: (String str) {
|
|
||||||
spanChilds.add(TextSpan(
|
|
||||||
text: str,
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () => replyReply(
|
|
||||||
replyItem.root == 0 ? replyItem : fReplyItem)));
|
|
||||||
return str;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
str = matchUrl.splitMapJoin(
|
|
||||||
RegExp(r'\b\d{2}:\d{2}\b'),
|
|
||||||
onMatch: (Match match) {
|
|
||||||
String matchStr = match[0]!;
|
|
||||||
spanChilds.add(
|
spanChilds.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ' $matchStr ',
|
text: content.jumpUrl[matchStr]['title'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
// 跳转到指定位置
|
if (appUrlSchema == '') {
|
||||||
try {
|
final String str = Uri.parse(matchStr).pathSegments[0];
|
||||||
Get.find<VideoDetailController>(
|
final Map matchRes = IdUtils.matchAvorBv(input: str);
|
||||||
tag: Get.arguments['heroTag'])
|
final List matchKeys = matchRes.keys.toList();
|
||||||
.plPlayerController
|
if (matchKeys.isNotEmpty) {
|
||||||
.seekTo(
|
if (matchKeys.first == 'BV') {
|
||||||
Duration(seconds: Utils.duration(matchStr)),
|
Get.toNamed(
|
||||||
|
'/searchResult',
|
||||||
|
parameters: {'keyword': matchRes['BV']},
|
||||||
);
|
);
|
||||||
} catch (_) {}
|
}
|
||||||
|
} else {
|
||||||
|
Get.toNamed(
|
||||||
|
'/webview',
|
||||||
|
parameters: {
|
||||||
|
'url': matchStr,
|
||||||
|
'type': 'url',
|
||||||
|
'pageTitle': ''
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (appUrlSchema.startsWith('bilibili://search')) {
|
||||||
|
Get.toNamed('/searchResult', parameters: {
|
||||||
|
'keyword': content.jumpUrl[matchStr]['title']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return '';
|
if (appUrlSchema.startsWith('bilibili://search')) {
|
||||||
},
|
spanChilds.add(
|
||||||
onNonMatch: (str) {
|
WidgetSpan(
|
||||||
return str;
|
child: Icon(
|
||||||
},
|
FontAwesomeIcons.magnifyingGlass,
|
||||||
);
|
size: 9,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
if (content.atNameToMid.isEmpty && content.jumpUrl.isEmpty) {
|
),
|
||||||
if (str != '') {
|
alignment: PlaceholderAlignment.top,
|
||||||
spanChilds.add(TextSpan(
|
),
|
||||||
text: str,
|
);
|
||||||
recognizer: TapGestureRecognizer()
|
}
|
||||||
..onTap = () =>
|
// 只显示一次
|
||||||
replyReply(replyItem.root == 0 ? replyItem : fReplyItem)));
|
matchedStrs.add(matchStr);
|
||||||
|
} else {
|
||||||
|
addPlainTextSpan(matchStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return str;
|
return '';
|
||||||
|
},
|
||||||
|
onNonMatch: (String nonMatchStr) {
|
||||||
|
addPlainTextSpan(nonMatchStr);
|
||||||
|
return nonMatchStr;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -841,10 +743,10 @@ InlineSpan buildContent(
|
|||||||
if (content.pictures.isNotEmpty) {
|
if (content.pictures.isNotEmpty) {
|
||||||
final List<String> picList = <String>[];
|
final List<String> picList = <String>[];
|
||||||
final int len = content.pictures.length;
|
final int len = content.pictures.length;
|
||||||
|
spanChilds.add(const TextSpan(text: '\n'));
|
||||||
if (len == 1) {
|
if (len == 1) {
|
||||||
Map pictureItem = content.pictures.first;
|
Map pictureItem = content.pictures.first;
|
||||||
picList.add(pictureItem['img_src']);
|
picList.add(pictureItem['img_src']);
|
||||||
spanChilds.add(const TextSpan(text: '\n'));
|
|
||||||
spanChilds.add(
|
spanChilds.add(
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import 'package:pilipala/models/video/reply/item.dart';
|
|||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
|
||||||
class VideoReplyNewDialog extends StatefulWidget {
|
class VideoReplyNewDialog extends StatefulWidget {
|
||||||
final String? oid;
|
final int? oid;
|
||||||
final int? root;
|
final int? root;
|
||||||
final int? parent;
|
final int? parent;
|
||||||
final ReplyType? replyType;
|
final ReplyType? replyType;
|
||||||
|
|||||||
@ -61,6 +61,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
final Floating floating = Floating();
|
final Floating floating = Floating();
|
||||||
// 生命周期监听
|
// 生命周期监听
|
||||||
late final AppLifecycleListener _lifecycleListener;
|
late final AppLifecycleListener _lifecycleListener;
|
||||||
|
bool isShowing = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -216,15 +217,15 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
videoIntroController.isPaused = true;
|
videoIntroController.isPaused = true;
|
||||||
plPlayerController!.removeStatusLister(playerListener);
|
plPlayerController!.removeStatusLister(playerListener);
|
||||||
plPlayerController!.pause();
|
plPlayerController!.pause();
|
||||||
plPlayerController!.danmakuController?.pause();
|
|
||||||
plPlayerController!.danmakuController?.clear();
|
|
||||||
}
|
}
|
||||||
|
setState(() => isShowing = false);
|
||||||
super.didPushNext();
|
super.didPushNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// 返回当前页面时
|
// 返回当前页面时
|
||||||
void didPopNext() async {
|
void didPopNext() async {
|
||||||
|
setState(() => isShowing = true);
|
||||||
videoDetailController.isFirstTime = false;
|
videoDetailController.isFirstTime = false;
|
||||||
final bool autoplay = autoPlayEnable;
|
final bool autoplay = autoPlayEnable;
|
||||||
videoDetailController.playerInit(autoplay: autoplay);
|
videoDetailController.playerInit(autoplay: autoplay);
|
||||||
@ -280,19 +281,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
final double videoHeight = MediaQuery.sizeOf(context).width * 9 / 16;
|
final double videoHeight = MediaQuery.sizeOf(context).width * 9 / 16;
|
||||||
final double pinnedHeaderHeight =
|
final double pinnedHeaderHeight =
|
||||||
statusBarHeight + kToolbarHeight + videoHeight;
|
statusBarHeight + kToolbarHeight + videoHeight;
|
||||||
if (MediaQuery.of(context).orientation == Orientation.landscape ||
|
|
||||||
plPlayerController?.isFullScreen.value == true) {
|
|
||||||
enterFullScreen();
|
|
||||||
} else {
|
|
||||||
exitFullScreen();
|
|
||||||
}
|
|
||||||
Widget childWhenDisabled = SafeArea(
|
Widget childWhenDisabled = SafeArea(
|
||||||
top: MediaQuery.of(context).orientation == Orientation.portrait &&
|
top: MediaQuery.of(context).orientation == Orientation.portrait &&
|
||||||
plPlayerController?.isFullScreen.value == true,
|
plPlayerController?.isFullScreen.value == true,
|
||||||
bottom: MediaQuery.of(context).orientation == Orientation.portrait &&
|
bottom: MediaQuery.of(context).orientation == Orientation.portrait &&
|
||||||
plPlayerController?.isFullScreen.value == true,
|
plPlayerController?.isFullScreen.value == true,
|
||||||
left: plPlayerController?.isFullScreen.value != true,
|
left: false, //plPlayerController?.isFullScreen.value != true,
|
||||||
right: plPlayerController?.isFullScreen.value != true,
|
right: false, //plPlayerController?.isFullScreen.value != true,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -309,187 +304,189 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
body: ExtendedNestedScrollView(
|
body: ExtendedNestedScrollView(
|
||||||
controller: _extendNestCtr,
|
controller: _extendNestCtr,
|
||||||
headerSliverBuilder:
|
headerSliverBuilder:
|
||||||
(BuildContext context, bool innerBoxIsScrolled) {
|
(BuildContext context2, bool innerBoxIsScrolled) {
|
||||||
return <Widget>[
|
return <Widget>[
|
||||||
Obx(
|
Obx(
|
||||||
() => SliverAppBar(
|
() {
|
||||||
automaticallyImplyLeading: false,
|
if (MediaQuery.of(context).orientation ==
|
||||||
// 假装使用一个非空变量,避免Obx检测不到而罢工
|
Orientation.landscape ||
|
||||||
pinned: videoDetailController.autoPlay.value ^
|
plPlayerController?.isFullScreen.value == true) {
|
||||||
false ^
|
enterFullScreen();
|
||||||
videoDetailController.autoPlay.value,
|
} else {
|
||||||
elevation: 0,
|
exitFullScreen();
|
||||||
scrolledUnderElevation: 0,
|
}
|
||||||
forceElevated: innerBoxIsScrolled,
|
return SliverAppBar(
|
||||||
expandedHeight: MediaQuery.of(context).orientation ==
|
automaticallyImplyLeading: false,
|
||||||
Orientation.landscape ||
|
// 假装使用一个非空变量,避免Obx检测不到而罢工
|
||||||
plPlayerController?.isFullScreen.value == true
|
pinned: videoDetailController.autoPlay.value ^
|
||||||
? MediaQuery.sizeOf(context).height -
|
false ^
|
||||||
(MediaQuery.of(context).orientation ==
|
videoDetailController.autoPlay.value,
|
||||||
Orientation.landscape
|
elevation: 0,
|
||||||
? 0
|
scrolledUnderElevation: 0,
|
||||||
: MediaQuery.of(context).padding.top)
|
forceElevated: innerBoxIsScrolled,
|
||||||
: videoHeight,
|
expandedHeight: MediaQuery.of(context).orientation ==
|
||||||
backgroundColor: Colors.black,
|
Orientation.landscape ||
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
plPlayerController?.isFullScreen.value == true
|
||||||
background: PopScope(
|
? MediaQuery.sizeOf(context).height -
|
||||||
canPop:
|
(MediaQuery.of(context).orientation ==
|
||||||
plPlayerController?.isFullScreen.value != true,
|
Orientation.landscape
|
||||||
onPopInvoked: (bool didPop) {
|
? 0
|
||||||
if (plPlayerController?.isFullScreen.value ==
|
: MediaQuery.of(context).padding.top)
|
||||||
true) {
|
: videoHeight,
|
||||||
plPlayerController!
|
backgroundColor: Colors.black,
|
||||||
.triggerFullScreen(status: false);
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
}
|
background: PopScope(
|
||||||
if (MediaQuery.of(context).orientation ==
|
canPop: plPlayerController?.isFullScreen.value !=
|
||||||
Orientation.landscape) {
|
true,
|
||||||
verticalScreen();
|
onPopInvoked: (bool didPop) {
|
||||||
}
|
if (plPlayerController?.isFullScreen.value ==
|
||||||
},
|
true) {
|
||||||
child: LayoutBuilder(
|
plPlayerController!
|
||||||
builder: (BuildContext context,
|
.triggerFullScreen(status: false);
|
||||||
BoxConstraints boxConstraints) {
|
}
|
||||||
final double maxWidth = boxConstraints.maxWidth;
|
if (MediaQuery.of(context).orientation ==
|
||||||
final double maxHeight =
|
Orientation.landscape) {
|
||||||
boxConstraints.maxHeight;
|
verticalScreen();
|
||||||
return Stack(
|
}
|
||||||
children: <Widget>[
|
},
|
||||||
FutureBuilder(
|
child: LayoutBuilder(
|
||||||
future: _futureBuilderFuture,
|
builder: (BuildContext context,
|
||||||
builder: (BuildContext context,
|
BoxConstraints boxConstraints) {
|
||||||
AsyncSnapshot snapshot) {
|
final double maxWidth =
|
||||||
if (snapshot.hasData &&
|
boxConstraints.maxWidth;
|
||||||
snapshot.data['status']) {
|
final double maxHeight =
|
||||||
return Obx(
|
boxConstraints.maxHeight;
|
||||||
() => !videoDetailController
|
return Stack(
|
||||||
.autoPlay.value
|
children: <Widget>[
|
||||||
? const SizedBox()
|
if (isShowing)
|
||||||
: PLVideoPlayer(
|
FutureBuilder(
|
||||||
controller:
|
future: _futureBuilderFuture,
|
||||||
plPlayerController!,
|
builder: (BuildContext context,
|
||||||
headerControl:
|
AsyncSnapshot snapshot) {
|
||||||
videoDetailController
|
if (snapshot.hasData &&
|
||||||
.headerControl,
|
snapshot.data['status']) {
|
||||||
danmuWidget: Obx(
|
return Obx(
|
||||||
() => PlDanmaku(
|
() =>
|
||||||
key: Key(
|
!videoDetailController
|
||||||
videoDetailController
|
.autoPlay.value
|
||||||
.danmakuCid
|
? nil
|
||||||
.value
|
: PLVideoPlayer(
|
||||||
.toString()),
|
controller:
|
||||||
cid:
|
plPlayerController!,
|
||||||
videoDetailController
|
headerControl:
|
||||||
.danmakuCid
|
videoDetailController
|
||||||
.value,
|
.headerControl,
|
||||||
playerController:
|
danmuWidget: Obx(
|
||||||
plPlayerController!,
|
() => PlDanmaku(
|
||||||
),
|
key: Key(videoDetailController
|
||||||
),
|
.danmakuCid
|
||||||
),
|
.value
|
||||||
);
|
.toString()),
|
||||||
} else {
|
cid: videoDetailController
|
||||||
return const SizedBox();
|
.danmakuCid
|
||||||
}
|
.value,
|
||||||
},
|
playerController:
|
||||||
),
|
plPlayerController!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
/// 关闭自动播放时 手动播放
|
/// 关闭自动播放时 手动播放
|
||||||
if (!videoDetailController
|
if (!videoDetailController
|
||||||
.autoPlay.value) ...<Widget>[
|
.autoPlay.value) ...<Widget>[
|
||||||
Obx(
|
Obx(
|
||||||
() => Visibility(
|
() => Visibility(
|
||||||
visible: videoDetailController
|
visible: videoDetailController
|
||||||
.isShowCover.value,
|
.isShowCover.value,
|
||||||
child: Positioned(
|
child: Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
handlePlay();
|
handlePlay();
|
||||||
},
|
},
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
type: 'emote',
|
type: 'emote',
|
||||||
src: videoDetailController
|
src: videoDetailController
|
||||||
.videoItem['pic'],
|
.videoItem['pic'],
|
||||||
width: maxWidth,
|
width: maxWidth,
|
||||||
height: maxHeight,
|
height: maxHeight,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Obx(
|
||||||
Obx(
|
() => Visibility(
|
||||||
() => Visibility(
|
visible: videoDetailController
|
||||||
visible: videoDetailController
|
.isShowCover.value &&
|
||||||
.isShowCover.value &&
|
videoDetailController
|
||||||
videoDetailController
|
.isEffective.value,
|
||||||
.isEffective.value,
|
child: Stack(
|
||||||
child: Stack(
|
children: [
|
||||||
children: [
|
Positioned(
|
||||||
Positioned(
|
top: 0,
|
||||||
top: 0,
|
left: 0,
|
||||||
left: 0,
|
right: 0,
|
||||||
right: 0,
|
child: AppBar(
|
||||||
child: AppBar(
|
primary: false,
|
||||||
primary: false,
|
foregroundColor:
|
||||||
foregroundColor:
|
Colors.white,
|
||||||
Colors.white,
|
elevation: 0,
|
||||||
elevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
backgroundColor:
|
|
||||||
Colors.transparent,
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
tooltip: '稍后再看',
|
|
||||||
onPressed: () async {
|
|
||||||
var res = await UserHttp
|
|
||||||
.toViewLater(
|
|
||||||
bvid:
|
|
||||||
videoDetailController
|
|
||||||
.bvid);
|
|
||||||
SmartDialog.showToast(
|
|
||||||
res['msg']);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons
|
|
||||||
.history_outlined),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 14)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 12,
|
|
||||||
bottom: 10,
|
|
||||||
child: TextButton.icon(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
MaterialStateProperty
|
Colors.transparent,
|
||||||
.resolveWith(
|
actions: [
|
||||||
(states) {
|
IconButton(
|
||||||
return Colors.white
|
tooltip: '稍后再看',
|
||||||
.withOpacity(0.8);
|
onPressed: () async {
|
||||||
}),
|
var res = await UserHttp
|
||||||
|
.toViewLater(
|
||||||
|
bvid: videoDetailController
|
||||||
|
.bvid);
|
||||||
|
SmartDialog
|
||||||
|
.showToast(
|
||||||
|
res['msg']);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons
|
||||||
|
.history_outlined),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 14)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
onPressed: () =>
|
|
||||||
handlePlay(),
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.play_circle_outline,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
label: const Text('轻触封面播放'),
|
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
],
|
right: 12,
|
||||||
)),
|
bottom: 10,
|
||||||
),
|
child: IconButton(
|
||||||
]
|
tooltip: '播放',
|
||||||
],
|
onPressed: () =>
|
||||||
);
|
handlePlay(),
|
||||||
},
|
icon: Image.asset(
|
||||||
)),
|
'assets/images/play.png',
|
||||||
),
|
width: 60,
|
||||||
),
|
height: 60,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
@ -500,7 +497,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
// },
|
// },
|
||||||
/// 不收回
|
/// 不收回
|
||||||
pinnedHeaderSliverHeightBuilder: () {
|
pinnedHeaderSliverHeightBuilder: () {
|
||||||
return plPlayerController?.isFullScreen.value == true
|
return MediaQuery.of(context).orientation ==
|
||||||
|
Orientation.landscape ||
|
||||||
|
plPlayerController?.isFullScreen.value == true
|
||||||
? MediaQuery.sizeOf(context).height
|
? MediaQuery.sizeOf(context).height
|
||||||
: pinnedHeaderHeight;
|
: pinnedHeaderHeight;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/http/msg.dart';
|
import 'package:pilipala/http/msg.dart';
|
||||||
import 'package:pilipala/models/msg/session.dart';
|
import 'package:pilipala/models/msg/session.dart';
|
||||||
@ -8,6 +9,8 @@ class WhisperDetailController extends GetxController {
|
|||||||
late String face;
|
late String face;
|
||||||
late String mid;
|
late String mid;
|
||||||
RxList<MessageItem> messageList = <MessageItem>[].obs;
|
RxList<MessageItem> messageList = <MessageItem>[].obs;
|
||||||
|
//表情转换图片规则
|
||||||
|
List<dynamic>? eInfos;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -22,6 +25,9 @@ class WhisperDetailController extends GetxController {
|
|||||||
var res = await MsgHttp.sessionMsg(talkerId: talkerId);
|
var res = await MsgHttp.sessionMsg(talkerId: talkerId);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
messageList.value = res['data'].messages;
|
messageList.value = res['data'].messages;
|
||||||
|
if (messageList.isNotEmpty && res['data'].eInfos != null) {
|
||||||
|
eInfos = res['data'].eInfos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,12 +110,16 @@ class _WhisperDetailPageState extends State<WhisperDetailPage> {
|
|||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
ChatItem(item: messageList[i]),
|
ChatItem(
|
||||||
|
item: messageList[i],
|
||||||
|
e_infos: _whisperDetailController.eInfos),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return ChatItem(item: messageList[i]);
|
return ChatItem(
|
||||||
|
item: messageList[i],
|
||||||
|
e_infos: _whisperDetailController.eInfos);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,38 +1,370 @@
|
|||||||
// ignore_for_file: must_be_immutable
|
// ignore_for_file: must_be_immutable
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.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/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
import '../../../http/search.dart';
|
||||||
|
|
||||||
|
enum MsgType {
|
||||||
|
invalid(value: 0, label: "空空的~"),
|
||||||
|
text(value: 1, label: "文本消息"),
|
||||||
|
pic(value: 2, label: "图片消息"),
|
||||||
|
audio(value: 3, label: "语音消息"),
|
||||||
|
share(value: 4, label: "分享消息"),
|
||||||
|
revoke(value: 5, label: "撤回消息"),
|
||||||
|
custom_face(value: 6, label: "自定义表情"),
|
||||||
|
share_v2(value: 7, label: "分享v2消息"),
|
||||||
|
sys_cancel(value: 8, label: "系统撤销"),
|
||||||
|
mini_program(value: 9, label: "小程序"),
|
||||||
|
notify_msg(value: 10, label: "业务通知"),
|
||||||
|
archive_card(value: 11, label: "投稿卡片"),
|
||||||
|
article_card(value: 12, label: "专栏卡片"),
|
||||||
|
pic_card(value: 13, label: "图片卡片"),
|
||||||
|
common_share(value: 14, label: "异形卡片"),
|
||||||
|
auto_reply_push(value: 16, label: "自动回复推送"),
|
||||||
|
notify_text(value: 18, label: "文本提示");
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
final String label;
|
||||||
|
const MsgType({required this.value, required this.label});
|
||||||
|
static MsgType parse(int value) {
|
||||||
|
return MsgType.values
|
||||||
|
.firstWhere((e) => e.value == value, orElse: () => MsgType.invalid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ChatItem extends StatelessWidget {
|
class ChatItem extends StatelessWidget {
|
||||||
dynamic item;
|
dynamic item;
|
||||||
|
List? e_infos;
|
||||||
|
|
||||||
ChatItem({
|
ChatItem({
|
||||||
super.key,
|
super.key,
|
||||||
this.item,
|
this.item,
|
||||||
|
this.e_infos,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool isOwner =
|
bool isOwner =
|
||||||
item.senderUid == GStrorage.userInfo.get('userInfoCache').mid;
|
item.senderUid == GStrorage.userInfo.get('userInfoCache').mid;
|
||||||
bool isPic = item.msgType == 2; // 图片
|
|
||||||
bool isText = item.msgType == 1; // 文本
|
|
||||||
// bool isAchive = item.msgType == 11; // 投稿
|
|
||||||
// bool isArticle = item.msgType == 12; // 专栏
|
|
||||||
bool isRevoke = item.msgType == 5; // 撤回消息
|
|
||||||
|
|
||||||
bool isSystem =
|
bool isPic = item.msgType == MsgType.pic.value; // 图片
|
||||||
item.msgType == 18 || item.msgType == 10 || item.msgType == 13;
|
bool isText = item.msgType == MsgType.text.value; // 文本
|
||||||
int msgType = item.msgType;
|
// bool isArchive = item.msgType == 11; // 投稿
|
||||||
|
// bool isArticle = item.msgType == 12; // 专栏
|
||||||
|
bool isRevoke = item.msgType == MsgType.revoke.value; // 撤回消息
|
||||||
|
bool isShareV2 = item.msgType == MsgType.share_v2.value;
|
||||||
|
bool isSystem = item.msgType == MsgType.notify_text.value ||
|
||||||
|
item.msgType == MsgType.notify_msg.value ||
|
||||||
|
item.msgType == MsgType.pic_card.value ||
|
||||||
|
item.msgType == MsgType.auto_reply_push.value;
|
||||||
dynamic content = item.content ?? '';
|
dynamic content = item.content ?? '';
|
||||||
|
Color textColor(BuildContext context) {
|
||||||
|
return isOwner
|
||||||
|
? Theme.of(context).colorScheme.onPrimary
|
||||||
|
: Theme.of(context).colorScheme.onSecondaryContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget richTextMessage(BuildContext context) {
|
||||||
|
var text = content['content'];
|
||||||
|
if (e_infos != null) {
|
||||||
|
final List<InlineSpan> children = [];
|
||||||
|
Map<String, String> emojiMap = {};
|
||||||
|
for (var e in e_infos!) {
|
||||||
|
emojiMap[e['text']] = e['url'];
|
||||||
|
}
|
||||||
|
text.splitMapJoin(
|
||||||
|
RegExp(r"\[.+?\]"),
|
||||||
|
onMatch: (Match match) {
|
||||||
|
final String emojiKey = match[0]!;
|
||||||
|
if (emojiMap.containsKey(emojiKey)) {
|
||||||
|
children.add(WidgetSpan(
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
src: emojiMap[emojiKey]!,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
onNonMatch: (String text) {
|
||||||
|
children.add(TextSpan(
|
||||||
|
text: text,
|
||||||
|
style: TextStyle(
|
||||||
|
color: textColor(context),
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
)));
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
color: textColor(context),
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget messageContent(BuildContext context) {
|
||||||
|
switch (MsgType.parse(item.msgType)) {
|
||||||
|
case MsgType.notify_msg:
|
||||||
|
return SystemNotice(item: item);
|
||||||
|
case MsgType.pic_card:
|
||||||
|
return SystemNotice2(item: item);
|
||||||
|
case MsgType.notify_text:
|
||||||
|
return Text(
|
||||||
|
jsonDecode(content['content'])
|
||||||
|
.map((m) => m['text'] as String)
|
||||||
|
.join("\n"),
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 5,
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case MsgType.text:
|
||||||
|
return richTextMessage(context);
|
||||||
|
case MsgType.pic:
|
||||||
|
return NetworkImgLayer(
|
||||||
|
width: 220,
|
||||||
|
height: 220 * content['height'] / content['width'],
|
||||||
|
src: content['url'],
|
||||||
|
);
|
||||||
|
case MsgType.share_v2:
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
SmartDialog.showLoading();
|
||||||
|
var bvid = content["bvid"];
|
||||||
|
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
final String heroTag = Utils.makeHeroTag(bvid);
|
||||||
|
SmartDialog.dismiss<dynamic>().then(
|
||||||
|
(e) => Get.toNamed<dynamic>('/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: <String, String?>{
|
||||||
|
'pic': content['thumb'],
|
||||||
|
'heroTag': heroTag,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 220,
|
||||||
|
height: 220 * 9 / 16,
|
||||||
|
src: content['thumb'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
content['title'],
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 1),
|
||||||
|
Text(
|
||||||
|
content['author'],
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context).withOpacity(0.6),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case MsgType.archive_card:
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
SmartDialog.showLoading();
|
||||||
|
var bvid = content["bvid"];
|
||||||
|
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
final String heroTag = Utils.makeHeroTag(bvid);
|
||||||
|
SmartDialog.dismiss<dynamic>().then(
|
||||||
|
(e) => Get.toNamed<dynamic>('/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: <String, String?>{
|
||||||
|
'pic': content['thumb'],
|
||||||
|
'heroTag': heroTag,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 220,
|
||||||
|
height: 220 * 9 / 16,
|
||||||
|
src: content['cover'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
content['title'],
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 1),
|
||||||
|
Text(
|
||||||
|
Utils.timeFormat(content['times']),
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context).withOpacity(0.6),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case MsgType.auto_reply_push:
|
||||||
|
return Container(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 300.0, // 设置最大宽度为200.0
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer
|
||||||
|
.withOpacity(0.4),
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
bottomLeft: Radius.circular(6),
|
||||||
|
bottomRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.all(12),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
content['main_title'],
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
for (var i in content['sub_cards']) ...<Widget>[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
RegExp bvRegex = RegExp(r'BV[0-9A-Za-z]{10}',
|
||||||
|
caseSensitive: false);
|
||||||
|
Iterable<Match> matches =
|
||||||
|
bvRegex.allMatches(i['jump_url']);
|
||||||
|
if (matches.isNotEmpty) {
|
||||||
|
Match match = matches.first;
|
||||||
|
String bvid = match.group(0)!;
|
||||||
|
try {
|
||||||
|
SmartDialog.showLoading();
|
||||||
|
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
final String heroTag = Utils.makeHeroTag(bvid);
|
||||||
|
SmartDialog.dismiss<dynamic>().then(
|
||||||
|
(e) => Get.toNamed<dynamic>(
|
||||||
|
'/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: <String, String?>{
|
||||||
|
'pic': i['cover_url'],
|
||||||
|
'heroTag': heroTag,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
SmartDialog.showToast(err.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast('未匹配到 BV 号');
|
||||||
|
Get.toNamed('/webview',
|
||||||
|
arguments: {'url': i['jump_url']});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: 130,
|
||||||
|
height: 130 * 9 / 16,
|
||||||
|
src: i['cover_url'],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
i['field1'],
|
||||||
|
maxLines: 2,
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
i['field2'],
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context).withOpacity(0.6),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
Utils.timeFormat(int.parse(i['field3'])),
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context).withOpacity(0.6),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
));
|
||||||
|
default:
|
||||||
|
return Text(
|
||||||
|
content['content'] ?? content.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return isSystem
|
return isSystem
|
||||||
? (msgType == 10
|
? messageContent(context)
|
||||||
? SystemNotice(item: item)
|
|
||||||
: msgType == 13
|
|
||||||
? SystemNotice2(item: item)
|
|
||||||
: const SizedBox())
|
|
||||||
: isRevoke
|
: isRevoke
|
||||||
? const SizedBox()
|
? const SizedBox()
|
||||||
: Row(
|
: Row(
|
||||||
@ -66,27 +398,7 @@ class ChatItem extends StatelessWidget {
|
|||||||
? CrossAxisAlignment.end
|
? CrossAxisAlignment.end
|
||||||
: CrossAxisAlignment.start,
|
: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
isText
|
messageContent(context),
|
||||||
? Text(
|
|
||||||
content['content'],
|
|
||||||
style: TextStyle(
|
|
||||||
color: isOwner
|
|
||||||
? Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onPrimary
|
|
||||||
: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onSecondaryContainer),
|
|
||||||
)
|
|
||||||
: isPic
|
|
||||||
? NetworkImgLayer(
|
|
||||||
width: 220,
|
|
||||||
height: 220 *
|
|
||||||
content['height'] /
|
|
||||||
content['width'],
|
|
||||||
src: content['url'],
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
SizedBox(height: isPic ? 7 : 2),
|
SizedBox(height: isPic ? 7 : 2),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@ -147,8 +147,8 @@ class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler {
|
|||||||
processingState: AudioProcessingState.idle,
|
processingState: AudioProcessingState.idle,
|
||||||
playing: false,
|
playing: false,
|
||||||
));
|
));
|
||||||
_item.removeLast();
|
|
||||||
if (_item.isNotEmpty) {
|
if (_item.isNotEmpty) {
|
||||||
|
_item.removeLast();
|
||||||
setMediaItem(_item.last);
|
setMediaItem(_item.last);
|
||||||
}
|
}
|
||||||
if (_item.isEmpty) {
|
if (_item.isEmpty) {
|
||||||
|
|||||||
@ -1,51 +1,65 @@
|
|||||||
// ignore_for_file: constant_identifier_names
|
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class IdUtils {
|
class IdUtils {
|
||||||
static const String TABLE =
|
static final XOR_CODE = BigInt.parse('23442827791579');
|
||||||
'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF';
|
static final MASK_CODE = BigInt.parse('2251799813685247');
|
||||||
static const List<int> S = [11, 10, 3, 8, 4, 6]; // 位置编码表
|
static final MAX_AID = BigInt.one << (BigInt.from(51)).toInt();
|
||||||
static const int XOR = 177451812; // 固定异或值
|
static final BASE = BigInt.from(58);
|
||||||
static const int ADD = 8728348608; // 固定加法值
|
|
||||||
static const List<String> r = [
|
static const data =
|
||||||
'B',
|
'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
|
||||||
'V',
|
|
||||||
'1',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'4',
|
|
||||||
'',
|
|
||||||
'1',
|
|
||||||
'',
|
|
||||||
'7',
|
|
||||||
'',
|
|
||||||
''
|
|
||||||
];
|
|
||||||
|
|
||||||
/// av转bv
|
/// av转bv
|
||||||
static String av2bv(int av) {
|
static String av2bv(int aid) {
|
||||||
int x_ = (av ^ XOR) + ADD;
|
List<String> bytes = [
|
||||||
List<String> newR = [];
|
'B',
|
||||||
newR.addAll(r);
|
'V',
|
||||||
for (int i = 0; i < S.length; i++) {
|
'1',
|
||||||
newR[S[i]] =
|
'0',
|
||||||
TABLE.characters.elementAt((x_ / pow(58, i).toInt() % 58).toInt());
|
'0',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
'0'
|
||||||
|
];
|
||||||
|
int bvIndex = bytes.length - 1;
|
||||||
|
BigInt tmp = (MAX_AID | BigInt.from(aid)) ^ XOR_CODE;
|
||||||
|
while (tmp > BigInt.zero) {
|
||||||
|
bytes[bvIndex] = data[(tmp % BASE).toInt()];
|
||||||
|
tmp = tmp ~/ BASE;
|
||||||
|
bvIndex -= 1;
|
||||||
}
|
}
|
||||||
return newR.join();
|
String tmpSwap = bytes[3];
|
||||||
|
bytes[3] = bytes[9];
|
||||||
|
bytes[9] = tmpSwap;
|
||||||
|
|
||||||
|
tmpSwap = bytes[4];
|
||||||
|
bytes[4] = bytes[7];
|
||||||
|
bytes[7] = tmpSwap;
|
||||||
|
|
||||||
|
return bytes.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// bv转bv
|
/// bv转av
|
||||||
static int bv2av(String bv) {
|
static int bv2av(String bvid) {
|
||||||
int r = 0;
|
List<String> bvidArr = bvid.split('');
|
||||||
for (int i = 0; i < S.length; i++) {
|
final tmpValue = bvidArr[3];
|
||||||
r += (TABLE.indexOf(bv.characters.elementAt(S[i])).toInt()) *
|
bvidArr[3] = bvidArr[9];
|
||||||
pow(58, i).toInt();
|
bvidArr[9] = tmpValue;
|
||||||
}
|
|
||||||
return (r - ADD) ^ XOR;
|
final tmpValue2 = bvidArr[4];
|
||||||
|
bvidArr[4] = bvidArr[7];
|
||||||
|
bvidArr[7] = tmpValue2;
|
||||||
|
|
||||||
|
bvidArr.removeRange(0, 3);
|
||||||
|
BigInt tmp = bvidArr.fold(BigInt.zero,
|
||||||
|
(pre, bvidChar) => pre * BASE + BigInt.from(data.indexOf(bvidChar)));
|
||||||
|
return ((tmp & MASK_CODE) ^ XOR_CODE).toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 匹配
|
// 匹配
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import 'dart:io';
|
|||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:pilipala/models/model_owner.dart';
|
import 'package:pilipala/models/model_owner.dart';
|
||||||
import 'package:pilipala/models/search/hot.dart';
|
|
||||||
import 'package:pilipala/models/user/info.dart';
|
import 'package:pilipala/models/user/info.dart';
|
||||||
|
|
||||||
class GStrorage {
|
class GStrorage {
|
||||||
@ -48,8 +47,6 @@ class GStrorage {
|
|||||||
Hive.registerAdapter(OwnerAdapter());
|
Hive.registerAdapter(OwnerAdapter());
|
||||||
Hive.registerAdapter(UserInfoDataAdapter());
|
Hive.registerAdapter(UserInfoDataAdapter());
|
||||||
Hive.registerAdapter(LevelInfoAdapter());
|
Hive.registerAdapter(LevelInfoAdapter());
|
||||||
Hive.registerAdapter(HotSearchModelAdapter());
|
|
||||||
Hive.registerAdapter(HotSearchItemAdapter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> lazyInit() async {
|
static Future<void> lazyInit() async {
|
||||||
@ -130,7 +127,8 @@ class SettingBoxKey {
|
|||||||
enableMYBar = 'enableMYBar',
|
enableMYBar = 'enableMYBar',
|
||||||
hideSearchBar = 'hideSearchBar', // 收起顶栏
|
hideSearchBar = 'hideSearchBar', // 收起顶栏
|
||||||
hideTabBar = 'hideTabBar', // 收起底栏
|
hideTabBar = 'hideTabBar', // 收起底栏
|
||||||
tabbarSort = 'tabbarSort'; // 首页tabbar
|
tabbarSort = 'tabbarSort', // 首页tabbar
|
||||||
|
dynamicBadgeMode = 'dynamicBadgeMode';
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalCacheKey {
|
class LocalCacheKey {
|
||||||
|
|||||||
@ -276,16 +276,18 @@ class Utils {
|
|||||||
// [arm64-v8a]
|
// [arm64-v8a]
|
||||||
String abi = androidInfo.supportedAbis.first;
|
String abi = androidInfo.supportedAbis.first;
|
||||||
late String downloadUrl;
|
late String downloadUrl;
|
||||||
for (var i in data.assets) {
|
if (data.assets.isNotEmpty) {
|
||||||
if (i.downloadUrl.contains(abi)) {
|
for (var i in data.assets) {
|
||||||
downloadUrl = i.downloadUrl;
|
if (i.downloadUrl.contains(abi)) {
|
||||||
|
downloadUrl = i.downloadUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// 应用外下载
|
||||||
|
launchUrl(
|
||||||
|
Uri.parse(downloadUrl),
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// 应用外下载
|
|
||||||
launchUrl(
|
|
||||||
Uri.parse(downloadUrl),
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -500,10 +500,11 @@ packages:
|
|||||||
floating:
|
floating:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: floating
|
path: "."
|
||||||
sha256: d9d563089e34fbd714ffdcdd2df447ec41b40c9226dacae6b4f78847aef8b991
|
ref: main
|
||||||
url: "https://pub.flutter-io.cn"
|
resolved-ref: d2d8421c4d80f6113f832404109853684721e11a
|
||||||
source: hosted
|
url: "https://github.com/guozhigq/floating.git"
|
||||||
|
source: git
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
|
|||||||
@ -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
|
# 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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.18+1018
|
version: 1.0.19+1019
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.19.6 <3.0.0"
|
sdk: ">=2.19.6 <3.0.0"
|
||||||
@ -124,7 +124,10 @@ dependencies:
|
|||||||
# 代理
|
# 代理
|
||||||
system_proxy: ^0.1.0
|
system_proxy: ^0.1.0
|
||||||
# pip
|
# pip
|
||||||
floating: ^2.0.1
|
floating:
|
||||||
|
git:
|
||||||
|
url: https://github.com/guozhigq/floating.git
|
||||||
|
ref: main
|
||||||
# html解析
|
# html解析
|
||||||
html: ^0.15.4
|
html: ^0.15.4
|
||||||
# html渲染
|
# html渲染
|
||||||
|
|||||||
Reference in New Issue
Block a user