Merge branch 'main' into feature-minePage
This commit is contained in:
3
.github/workflows/beta_ci.yml
vendored
3
.github/workflows/beta_ci.yml
vendored
@ -12,7 +12,6 @@ on:
|
|||||||
- ".idea/**"
|
- ".idea/**"
|
||||||
- "!.github/workflows/**"
|
- "!.github/workflows/**"
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update_version:
|
update_version:
|
||||||
name: Read and update version
|
name: Read and update version
|
||||||
@ -96,7 +95,7 @@ jobs:
|
|||||||
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: 3.16.5
|
flutter-version: 3.19.6
|
||||||
channel: any
|
channel: any
|
||||||
|
|
||||||
- name: 下载项目依赖
|
- name: 下载项目依赖
|
||||||
|
|||||||
4
.github/workflows/release_ci.yml
vendored
4
.github/workflows/release_ci.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
|||||||
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: 3.16.5
|
flutter-version: 3.19.6
|
||||||
channel: any
|
channel: any
|
||||||
|
|
||||||
- name: 下载项目依赖
|
- name: 下载项目依赖
|
||||||
@ -98,7 +98,7 @@ jobs:
|
|||||||
uses: subosito/flutter-action@v2.10.0
|
uses: subosito/flutter-action@v2.10.0
|
||||||
with:
|
with:
|
||||||
cache: true
|
cache: true
|
||||||
flutter-version: 3.16.5
|
flutter-version: 3.19.6
|
||||||
|
|
||||||
- name: flutter build ipa
|
- name: flutter build ipa
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@ -47,13 +47,14 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.guozhigq.pilipala.MainActivity"
|
android:name="com.guozhigq.pilipala.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
|
android:autoVerify="true"
|
||||||
>
|
>
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
@ -63,10 +64,21 @@
|
|||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
|
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!-- Deep Link -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="pili"/>
|
||||||
|
<data android:scheme="pilipala"/>
|
||||||
|
</intent-filter>
|
||||||
|
<!-- App Link -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
|||||||
39
change_log/1.0.25.1010.md
Normal file
39
change_log/1.0.25.1010.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
## 1.0.25
|
||||||
|
|
||||||
|
### 功能
|
||||||
|
+ 直播弹幕
|
||||||
|
+ 稍后再看、收藏夹播放全部
|
||||||
|
+ 收藏夹新建、编辑
|
||||||
|
+ 评论删除
|
||||||
|
+ 评论保存为图片
|
||||||
|
+ 动态页滑动切换up
|
||||||
|
+ up投稿筛选充电视频
|
||||||
|
+ 直播tab展示关注up
|
||||||
|
+ up主页专栏展示
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 视频详情页一键三连
|
||||||
|
+ 动态页标识充电视频
|
||||||
|
+ 播放器亮度、音量调整百分比展示
|
||||||
|
+ 封面预览时视频标题可复制
|
||||||
|
+ 竖屏直播布局
|
||||||
|
+ 图片预览
|
||||||
|
+ 专栏渲染优化
|
||||||
|
+ 私信图片查看
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 收藏夹点击异常
|
||||||
|
+ 搜索up异常
|
||||||
|
+ 系统通知已读异常
|
||||||
|
+ [赞了我的]展示错误
|
||||||
|
+ 部分up合集无法打开
|
||||||
|
+ 切换合集视频投币个数未重置
|
||||||
|
+ 搜索条件筛选面板无法滚动
|
||||||
|
+ 部分机型导航条未沉浸
|
||||||
|
+ 专栏图片渲染问题
|
||||||
|
+ 专栏浏览历史记录
|
||||||
|
+ 直播间历史记录
|
||||||
|
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
@ -1,4 +1,6 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- app_links (0.0.2):
|
||||||
|
- Flutter
|
||||||
- appscheme (1.0.4):
|
- appscheme (1.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- audio_service (0.0.1):
|
- audio_service (0.0.1):
|
||||||
@ -66,6 +68,7 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
|
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||||
- appscheme (from `.symlinks/plugins/appscheme/ios`)
|
- appscheme (from `.symlinks/plugins/appscheme/ios`)
|
||||||
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
||||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||||
@ -102,6 +105,8 @@ SPEC REPOS:
|
|||||||
- Toast
|
- Toast
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
app_links:
|
||||||
|
:path: ".symlinks/plugins/app_links/ios"
|
||||||
appscheme:
|
appscheme:
|
||||||
:path: ".symlinks/plugins/appscheme/ios"
|
:path: ".symlinks/plugins/appscheme/ios"
|
||||||
audio_service:
|
audio_service:
|
||||||
@ -160,6 +165,7 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
|
||||||
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
||||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
||||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
||||||
|
|||||||
@ -65,44 +65,29 @@
|
|||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleURLName</key>
|
<key>CFBundleURLName</key>
|
||||||
<string></string>
|
<string>bilibili</string>
|
||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>http</string>
|
<string>http</string>
|
||||||
<string>https</string>
|
<string>https</string>
|
||||||
</array>
|
|
||||||
<key>CFBundleURLTypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleURLName</key>
|
|
||||||
<string></string>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>m.bilibili.com</string>
|
|
||||||
<string>bilibili.com</string>
|
|
||||||
<string>www.bilibili.com</string>
|
|
||||||
<string>bangumi.bilibili.com</string>
|
|
||||||
<string>bilibili.cn</string>
|
|
||||||
<string>www.bilibili.cn</string>
|
|
||||||
<string>bangumi.bilibili.cn</string>
|
|
||||||
<string>bilibili.tv</string>
|
|
||||||
<string>www.bilibili.tv</string>
|
|
||||||
<string>bangumi.bilibili.tv</string>
|
|
||||||
<string>miniapp.bilibili.com</string>
|
|
||||||
<string>live.bilibili.com</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
|
|
||||||
<!-- 当其他应用程序或系统通过 bilibili -->
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleURLName</key>
|
|
||||||
<string>bilibili</string>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>bilibili</string>
|
<string>bilibili</string>
|
||||||
|
<string>m.bilibili.com</string>
|
||||||
|
<string>bilibili.com</string>
|
||||||
|
<string>www.bilibili.com</string>
|
||||||
|
<string>bangumi.bilibili.com</string>
|
||||||
|
<string>bilibili.cn</string>
|
||||||
|
<string>www.bilibili.cn</string>
|
||||||
|
<string>bangumi.bilibili.cn</string>
|
||||||
|
<string>bilibili.tv</string>
|
||||||
|
<string>www.bilibili.tv</string>
|
||||||
|
<string>bangumi.bilibili.tv</string>
|
||||||
|
<string>miniapp.bilibili.com</string>
|
||||||
|
<string>live.bilibili.com</string>
|
||||||
|
<string>pili</string>
|
||||||
|
<string>pilipala</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>FlutterDeepLinkingEnabled</key>
|
||||||
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
|
|||||||
@ -33,7 +33,11 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final int defaultImgQuality = GlobalDataCache().imgQuality;
|
int defaultImgQuality = 10;
|
||||||
|
try {
|
||||||
|
defaultImgQuality = GlobalDataCache().imgQuality;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
if (src == '' || src == null) {
|
if (src == '' || src == null) {
|
||||||
return placeholder(context);
|
return placeholder(context);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -282,9 +282,10 @@ class VideoStat extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
StatView(view: videoItem.stat.view),
|
if (videoItem.stat.view != null) StatView(view: videoItem.stat.view),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
StatDanMu(danmu: videoItem.stat.danmu),
|
if (videoItem.stat.danmu != null)
|
||||||
|
StatDanMu(danmu: videoItem.stat.danmu),
|
||||||
if (videoItem is RecVideoItemModel) ...<Widget>[
|
if (videoItem is RecVideoItemModel) ...<Widget>[
|
||||||
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
|
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
|
||||||
RichText(
|
RichText(
|
||||||
|
|||||||
@ -598,4 +598,7 @@ class Api {
|
|||||||
|
|
||||||
/// 更新用户信息
|
/// 更新用户信息
|
||||||
static const String updateAccountInfo = '/x/member/web/update';
|
static const String updateAccountInfo = '/x/member/web/update';
|
||||||
|
|
||||||
|
/// 删除评论
|
||||||
|
static const String replyDel = '/x/v2/reply/del';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -217,12 +217,13 @@ class Request {
|
|||||||
/*
|
/*
|
||||||
* get请求
|
* get请求
|
||||||
*/
|
*/
|
||||||
get(url, {data, options, cancelToken, extra}) async {
|
get(url, {data, Options? options, cancelToken, extra}) async {
|
||||||
Response response;
|
Response response;
|
||||||
final Options options = Options();
|
options ??= Options(); // 如果 options 为 null,则初始化一个新的 Options 对象
|
||||||
ResponseType resType = ResponseType.json;
|
ResponseType resType = ResponseType.json;
|
||||||
|
|
||||||
if (extra != null) {
|
if (extra != null) {
|
||||||
resType = extra!['resType'] ?? ResponseType.json;
|
resType = extra['resType'] ?? ResponseType.json;
|
||||||
if (extra['ua'] != null) {
|
if (extra['ua'] != null) {
|
||||||
options.headers = {'user-agent': headerUa(type: extra['ua'])};
|
options.headers = {'user-agent': headerUa(type: extra['ua'])};
|
||||||
}
|
}
|
||||||
@ -238,14 +239,11 @@ class Request {
|
|||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
Response errResponse = Response(
|
return Response(
|
||||||
data: {
|
data: {'message': await ApiInterceptor.dioError(e)},
|
||||||
'message': await ApiInterceptor.dioError(e)
|
|
||||||
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
requestOptions: RequestOptions(),
|
requestOptions: RequestOptions(),
|
||||||
);
|
);
|
||||||
return errResponse;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:hive/hive.dart';
|
|||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:pilipala/models/member/article.dart';
|
import 'package:pilipala/models/member/article.dart';
|
||||||
import 'package:pilipala/models/member/like.dart';
|
import 'package:pilipala/models/member/like.dart';
|
||||||
|
import 'package:pilipala/utils/global_data_cache.dart';
|
||||||
import '../common/constants.dart';
|
import '../common/constants.dart';
|
||||||
import '../models/dynamics/result.dart';
|
import '../models/dynamics/result.dart';
|
||||||
import '../models/follow/result.dart';
|
import '../models/follow/result.dart';
|
||||||
@ -19,14 +20,20 @@ import 'index.dart';
|
|||||||
|
|
||||||
class MemberHttp {
|
class MemberHttp {
|
||||||
static Future memberInfo({
|
static Future memberInfo({
|
||||||
int? mid,
|
required int mid,
|
||||||
String token = '',
|
String token = '',
|
||||||
}) async {
|
}) async {
|
||||||
|
String? wWebid;
|
||||||
|
if ((await getWWebid(mid: mid))['status']) {
|
||||||
|
wWebid = GlobalDataCache().wWebid;
|
||||||
|
}
|
||||||
|
|
||||||
Map params = await WbiSign().makSign({
|
Map params = await WbiSign().makSign({
|
||||||
'mid': mid,
|
'mid': mid,
|
||||||
'token': token,
|
'token': token,
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'web_location': 1550101,
|
'web_location': 1550101,
|
||||||
|
...wWebid != null ? {'w_webid': wWebid} : {},
|
||||||
});
|
});
|
||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
Api.memberInfo,
|
Api.memberInfo,
|
||||||
@ -566,6 +573,10 @@ class MemberHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future getWWebid({required int mid}) async {
|
static Future getWWebid({required int mid}) async {
|
||||||
|
String? wWebid = GlobalDataCache().wWebid;
|
||||||
|
if (wWebid != null) {
|
||||||
|
return {'status': true, 'data': wWebid};
|
||||||
|
}
|
||||||
var res = await Request().get('https://space.bilibili.com/$mid/article');
|
var res = await Request().get('https://space.bilibili.com/$mid/article');
|
||||||
String? headContent = parse(res.data).head?.outerHtml;
|
String? headContent = parse(res.data).head?.outerHtml;
|
||||||
final regex = RegExp(
|
final regex = RegExp(
|
||||||
@ -576,6 +587,7 @@ class MemberHttp {
|
|||||||
final content = match.group(1);
|
final content = match.group(1);
|
||||||
String decodedString = Uri.decodeComponent(content!);
|
String decodedString = Uri.decodeComponent(content!);
|
||||||
Map<String, dynamic> map = jsonDecode(decodedString);
|
Map<String, dynamic> map = jsonDecode(decodedString);
|
||||||
|
GlobalDataCache().wWebid = map['access_id'];
|
||||||
return {'status': true, 'data': map['access_id']};
|
return {'status': true, 'data': map['access_id']};
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': '请检查登录状态'};
|
return {'status': false, 'data': '请检查登录状态'};
|
||||||
@ -588,25 +600,20 @@ class MemberHttp {
|
|||||||
static Future getMemberArticle({
|
static Future getMemberArticle({
|
||||||
required int mid,
|
required int mid,
|
||||||
required int pn,
|
required int pn,
|
||||||
required String wWebid,
|
|
||||||
String? offset,
|
String? offset,
|
||||||
}) async {
|
}) async {
|
||||||
|
String? wWebid;
|
||||||
|
if ((await getWWebid(mid: mid))['status']) {
|
||||||
|
wWebid = GlobalDataCache().wWebid;
|
||||||
|
}
|
||||||
Map params = await WbiSign().makSign({
|
Map params = await WbiSign().makSign({
|
||||||
'host_mid': mid,
|
'host_mid': mid,
|
||||||
'page': pn,
|
'page': pn,
|
||||||
'offset': offset,
|
'offset': offset,
|
||||||
'web_location': 333.999,
|
'web_location': 333.999,
|
||||||
'w_webid': wWebid,
|
...wWebid != null ? {'w_webid': wWebid} : {},
|
||||||
});
|
|
||||||
var res = await Request().get(Api.opusList, data: {
|
|
||||||
'host_mid': mid,
|
|
||||||
'page': pn,
|
|
||||||
'offset': offset,
|
|
||||||
'web_location': 333.999,
|
|
||||||
'w_webid': wWebid,
|
|
||||||
'w_rid': params['w_rid'],
|
|
||||||
'wts': params['wts'],
|
|
||||||
});
|
});
|
||||||
|
var res = await Request().get(Api.opusList, data: params);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
|
|||||||
@ -115,4 +115,25 @@ class ReplyHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future replyDel({
|
||||||
|
required int type, //replyType
|
||||||
|
required int oid,
|
||||||
|
required int rpid,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.replyDel,
|
||||||
|
queryParameters: {
|
||||||
|
'type': type, //type.index
|
||||||
|
'oid': oid,
|
||||||
|
'rpid': rpid,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'msg': '删除成功'};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -536,7 +536,8 @@ class VideoHttp {
|
|||||||
// 获取字幕内容
|
// 获取字幕内容
|
||||||
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
|
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
|
||||||
var res = await Request().get('https:$url');
|
var res = await Request().get('https:$url');
|
||||||
final String content = SubTitleUtils.convertToWebVTT(res.data['body']);
|
final String content =
|
||||||
|
await SubTitleUtils.convertToWebVTT(res.data['body']);
|
||||||
final List body = res.data['body'];
|
final List body = res.data['body'];
|
||||||
return {'content': content, 'body': body};
|
return {'content': content, 'body': body};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,7 @@ void main() async {
|
|||||||
systemNavigationBarColor: Colors.transparent,
|
systemNavigationBarColor: Colors.transparent,
|
||||||
systemNavigationBarDividerColor: Colors.transparent,
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarContrastEnforced: false,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,12 @@ enum FullScreenGestureMode {
|
|||||||
|
|
||||||
/// 从下滑到上
|
/// 从下滑到上
|
||||||
fromBottomtoTop,
|
fromBottomtoTop,
|
||||||
|
|
||||||
|
/// 关闭手势
|
||||||
|
none,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FullScreenGestureModeExtension on FullScreenGestureMode {
|
extension FullScreenGestureModeExtension on FullScreenGestureMode {
|
||||||
String get values => ['fromToptoBottom', 'fromBottomtoTop'][index];
|
String get values => ['fromToptoBottom', 'fromBottomtoTop', 'none'][index];
|
||||||
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index];
|
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏', '关闭手势'][index];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ extension SearchTypeExtension on SearchType {
|
|||||||
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
|
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索类型为视频、专栏及相簿时
|
// 搜索类型为视频时
|
||||||
enum ArchiveFilterType {
|
enum ArchiveFilterType {
|
||||||
totalrank,
|
totalrank,
|
||||||
click,
|
click,
|
||||||
@ -44,3 +44,21 @@ extension ArchiveFilterTypeExtension on ArchiveFilterType {
|
|||||||
String get description =>
|
String get description =>
|
||||||
['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index];
|
['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索类型为专栏时
|
||||||
|
enum ArticleFilterType {
|
||||||
|
// 综合排序
|
||||||
|
totalrank,
|
||||||
|
// 最新发布
|
||||||
|
pubdate,
|
||||||
|
// 最多点击
|
||||||
|
click,
|
||||||
|
// 最多喜欢
|
||||||
|
attention,
|
||||||
|
// 最多评论
|
||||||
|
scores,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ArticleFilterTypeExtension on ArticleFilterType {
|
||||||
|
String get description => ['综合排序', '最新发布', '最多点击', '最多喜欢', '最多评论'][index];
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
class MediaVideoItemModel {
|
class MediaVideoItemModel {
|
||||||
MediaVideoItemModel({
|
MediaVideoItemModel({
|
||||||
this.id,
|
this.id,
|
||||||
|
this.aid,
|
||||||
this.offset,
|
this.offset,
|
||||||
this.index,
|
this.index,
|
||||||
this.intro,
|
this.intro,
|
||||||
@ -14,12 +15,13 @@ class MediaVideoItemModel {
|
|||||||
this.likeState,
|
this.likeState,
|
||||||
this.favState,
|
this.favState,
|
||||||
this.page,
|
this.page,
|
||||||
|
this.cid,
|
||||||
this.pages,
|
this.pages,
|
||||||
this.title,
|
this.title,
|
||||||
this.type,
|
this.type,
|
||||||
this.upper,
|
this.upper,
|
||||||
this.link,
|
this.link,
|
||||||
this.bvId,
|
this.bvid,
|
||||||
this.shortLink,
|
this.shortLink,
|
||||||
this.rights,
|
this.rights,
|
||||||
this.elecInfo,
|
this.elecInfo,
|
||||||
@ -32,6 +34,7 @@ class MediaVideoItemModel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
int? id;
|
int? id;
|
||||||
|
int? aid;
|
||||||
int? offset;
|
int? offset;
|
||||||
int? index;
|
int? index;
|
||||||
String? intro;
|
String? intro;
|
||||||
@ -45,12 +48,13 @@ class MediaVideoItemModel {
|
|||||||
int? likeState;
|
int? likeState;
|
||||||
int? favState;
|
int? favState;
|
||||||
int? page;
|
int? page;
|
||||||
|
int? cid;
|
||||||
List<Page>? pages;
|
List<Page>? pages;
|
||||||
String? title;
|
String? title;
|
||||||
int? type;
|
int? type;
|
||||||
Upper? upper;
|
Upper? upper;
|
||||||
String? link;
|
String? link;
|
||||||
String? bvId;
|
String? bvid;
|
||||||
String? shortLink;
|
String? shortLink;
|
||||||
Rights? rights;
|
Rights? rights;
|
||||||
dynamic elecInfo;
|
dynamic elecInfo;
|
||||||
@ -64,6 +68,7 @@ class MediaVideoItemModel {
|
|||||||
factory MediaVideoItemModel.fromJson(Map<String, dynamic> json) =>
|
factory MediaVideoItemModel.fromJson(Map<String, dynamic> json) =>
|
||||||
MediaVideoItemModel(
|
MediaVideoItemModel(
|
||||||
id: json["id"],
|
id: json["id"],
|
||||||
|
aid: json["id"],
|
||||||
offset: json["offset"],
|
offset: json["offset"],
|
||||||
index: json["index"],
|
index: json["index"],
|
||||||
intro: json["intro"],
|
intro: json["intro"],
|
||||||
@ -77,6 +82,7 @@ class MediaVideoItemModel {
|
|||||||
likeState: json["like_state"],
|
likeState: json["like_state"],
|
||||||
favState: json["fav_state"],
|
favState: json["fav_state"],
|
||||||
page: json["page"],
|
page: json["page"],
|
||||||
|
cid: json["pages"] == null ? -1 : json["pages"].first['id'],
|
||||||
// json["pages"] 可能为null
|
// json["pages"] 可能为null
|
||||||
pages: json["pages"] == null
|
pages: json["pages"] == null
|
||||||
? []
|
? []
|
||||||
@ -85,7 +91,7 @@ class MediaVideoItemModel {
|
|||||||
type: json["type"],
|
type: json["type"],
|
||||||
upper: Upper.fromJson(json["upper"]),
|
upper: Upper.fromJson(json["upper"]),
|
||||||
link: json["link"],
|
link: json["link"],
|
||||||
bvId: json["bv_id"],
|
bvid: json["bv_id"],
|
||||||
shortLink: json["short_link"],
|
shortLink: json["short_link"],
|
||||||
rights: Rights.fromJson(json["rights"]),
|
rights: Rights.fromJson(json["rights"]),
|
||||||
elecInfo: json["elec_info"],
|
elecInfo: json["elec_info"],
|
||||||
|
|||||||
@ -295,7 +295,7 @@ class AboutController extends GetxController {
|
|||||||
displayTime: const Duration(milliseconds: 500),
|
displayTime: const Duration(milliseconds: 500),
|
||||||
).then(
|
).then(
|
||||||
(value) => launchUrl(
|
(value) => launchUrl(
|
||||||
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
|
Uri.parse('https://www.123684.com/s/9sVqVv-DEZ0A'),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -349,7 +349,7 @@ class AboutController extends GetxController {
|
|||||||
// 官网
|
// 官网
|
||||||
webSiteUrl() {
|
webSiteUrl() {
|
||||||
launchUrl(
|
launchUrl(
|
||||||
Uri.parse('https://pilipalanet.mysxl.cn'),
|
Uri.parse('https://pilipala.life'),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -189,8 +189,8 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
width: 105,
|
width: 115,
|
||||||
height: 160,
|
height: 115 / 0.75,
|
||||||
src: widget.bangumiDetail!.cover!,
|
src: widget.bangumiDetail!.cover!,
|
||||||
),
|
),
|
||||||
PBadge(
|
PBadge(
|
||||||
@ -208,7 +208,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => showIntroDetail(),
|
onTap: () => showIntroDetail(),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 158,
|
height: 115 / 0.75,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@ -96,7 +96,8 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 268,
|
height: Get.size.width / 3 / 0.75 +
|
||||||
|
MediaQuery.textScalerOf(context).scale(50.0),
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _futureBuilderFutureFollow,
|
future: _futureBuilderFutureFollow,
|
||||||
builder:
|
builder:
|
||||||
@ -117,7 +118,6 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
return Container(
|
||||||
width: Get.size.width / 3,
|
width: Get.size.width / 3,
|
||||||
height: 254,
|
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
left: StyleString.safeSpace,
|
left: StyleString.safeSpace,
|
||||||
right: index ==
|
right: index ==
|
||||||
@ -208,8 +208,8 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
crossAxisSpacing: StyleString.cardSpace,
|
crossAxisSpacing: StyleString.cardSpace,
|
||||||
// 列数
|
// 列数
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
mainAxisExtent: Get.size.width / 3 / 0.65 +
|
mainAxisExtent: Get.size.width / 3 / 0.75 +
|
||||||
MediaQuery.textScalerOf(context).scale(32.0),
|
MediaQuery.textScalerOf(context).scale(42.0),
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
|
|||||||
@ -86,9 +86,11 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
item.aid,
|
item.aid,
|
||||||
item.cover,
|
item.cover,
|
||||||
);
|
);
|
||||||
if (_bottomSheetController != null) {
|
try {
|
||||||
_bottomSheetController?.close();
|
if (_bottomSheetController != null) {
|
||||||
}
|
_bottomSheetController?.close();
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
currentIndex.value = i;
|
currentIndex.value = i;
|
||||||
scrollToIndex();
|
scrollToIndex();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ class BangumiCardV extends StatelessWidget {
|
|||||||
StyleString.imgRadius,
|
StyleString.imgRadius,
|
||||||
),
|
),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 0.65,
|
aspectRatio: 0.75,
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
final double maxWidth = boxConstraints.maxWidth;
|
final double maxWidth = boxConstraints.maxWidth;
|
||||||
final double maxHeight = boxConstraints.maxHeight;
|
final double maxHeight = boxConstraints.maxHeight;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
import '../../http/index.dart';
|
import '../../http/index.dart';
|
||||||
|
|
||||||
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||||
bool flag = false;
|
bool flag = true;
|
||||||
late RxList tabs = [].obs;
|
late RxList tabs = [].obs;
|
||||||
RxInt initialIndex = 1.obs;
|
RxInt initialIndex = 1.obs;
|
||||||
late TabController tabController;
|
late TabController tabController;
|
||||||
|
|||||||
@ -64,7 +64,7 @@ class LiveRoomController extends GetxController {
|
|||||||
? liveItem.pic
|
? liveItem.pic
|
||||||
: (liveItem.cover != null && liveItem.cover != '')
|
: (liveItem.cover != null && liveItem.cover != '')
|
||||||
? liveItem.cover
|
? liveItem.cover
|
||||||
: null;
|
: '';
|
||||||
}
|
}
|
||||||
Request.getBuvid().then((value) => buvid = value);
|
Request.getBuvid().then((value) => buvid = value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,6 +108,12 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
final isPortrait = mediaQuery.orientation == Orientation.portrait;
|
||||||
|
final isLandscape = mediaQuery.orientation == Orientation.landscape;
|
||||||
|
|
||||||
|
final padding = mediaQuery.padding;
|
||||||
|
|
||||||
Widget videoPlayerPanel = FutureBuilder(
|
Widget videoPlayerPanel = FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||||
@ -187,10 +193,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
children: [
|
children: [
|
||||||
Obx(
|
Obx(
|
||||||
() => SizedBox(
|
() => SizedBox(
|
||||||
height: MediaQuery.of(context).padding.top +
|
height: padding.top +
|
||||||
(_liveRoomController.isPortrait.value ||
|
(_liveRoomController.isPortrait.value || isLandscape
|
||||||
MediaQuery.of(context).orientation ==
|
|
||||||
Orientation.landscape
|
|
||||||
? 0
|
? 0
|
||||||
: kToolbarHeight),
|
: kToolbarHeight),
|
||||||
),
|
),
|
||||||
@ -201,21 +205,18 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
if (plPlayerController.isFullScreen.value == true) {
|
if (plPlayerController.isFullScreen.value == true) {
|
||||||
plPlayerController.triggerFullScreen(status: false);
|
plPlayerController.triggerFullScreen(status: false);
|
||||||
}
|
}
|
||||||
if (MediaQuery.of(context).orientation ==
|
if (isLandscape) {
|
||||||
Orientation.landscape) {
|
|
||||||
verticalScreen();
|
verticalScreen();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => Container(
|
() => Container(
|
||||||
width: Get.size.width,
|
width: Get.size.width,
|
||||||
height: MediaQuery.of(context).orientation ==
|
height: isLandscape
|
||||||
Orientation.landscape
|
|
||||||
? Get.size.height
|
? Get.size.height
|
||||||
: !_liveRoomController.isPortrait.value
|
: !_liveRoomController.isPortrait.value
|
||||||
? Get.size.width * 9 / 16
|
? Get.size.width * 9 / 16
|
||||||
: Get.size.height -
|
: Get.size.height - padding.top,
|
||||||
MediaQuery.of(context).padding.top,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||||
@ -229,7 +230,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
// 定位 快速滑动到底部
|
// 定位 快速滑动到底部
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 20,
|
right: 20,
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
bottom: padding.bottom + 80,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
position: Tween<Offset>(
|
position: Tween<Offset>(
|
||||||
begin: const Offset(0, 4),
|
begin: const Offset(0, 4),
|
||||||
@ -262,10 +263,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
toolbarHeight:
|
toolbarHeight: isPortrait ? 56 : 0,
|
||||||
MediaQuery.of(context).orientation == Orientation.portrait
|
|
||||||
? 56
|
|
||||||
: 0,
|
|
||||||
title: FutureBuilder(
|
title: FutureBuilder(
|
||||||
future: _futureBuilder,
|
future: _futureBuilder,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@ -317,35 +315,38 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
),
|
),
|
||||||
// 消息列表
|
// 消息列表
|
||||||
Obx(
|
Obx(
|
||||||
() => Positioned(
|
() => Align(
|
||||||
top: MediaQuery.of(context).padding.top +
|
alignment: Alignment.bottomCenter,
|
||||||
kToolbarHeight +
|
child: Container(
|
||||||
(_liveRoomController.isPortrait.value
|
margin: EdgeInsets.only(
|
||||||
? Get.size.width
|
bottom: 90 + padding.bottom,
|
||||||
: Get.size.width * 9 / 16),
|
),
|
||||||
bottom: 90 + MediaQuery.of(context).padding.bottom,
|
height: Get.size.height -
|
||||||
left: 0,
|
(padding.top +
|
||||||
right: 0,
|
kToolbarHeight +
|
||||||
child: buildMessageListUI(
|
(_liveRoomController.isPortrait.value
|
||||||
context,
|
? Get.size.width
|
||||||
_liveRoomController,
|
: Get.size.width * 9 / 16) +
|
||||||
_scrollController,
|
100 +
|
||||||
|
padding.bottom),
|
||||||
|
child: buildMessageListUI(
|
||||||
|
context,
|
||||||
|
_liveRoomController,
|
||||||
|
_scrollController,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 消息输入框
|
// 消息输入框
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: MediaQuery.of(context).orientation == Orientation.portrait,
|
visible: isPortrait,
|
||||||
child: Positioned(
|
child: Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: 14,
|
left: 14, right: 14, top: 4, bottom: padding.bottom + 20),
|
||||||
right: 14,
|
|
||||||
top: 4,
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 20),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey.withOpacity(0.1),
|
color: Colors.grey.withOpacity(0.1),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
@ -421,6 +422,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
return PiPSwitcher(
|
return PiPSwitcher(
|
||||||
childWhenDisabled: childWhenDisabled,
|
childWhenDisabled: childWhenDisabled,
|
||||||
@ -438,84 +440,82 @@ Widget buildMessageListUI(
|
|||||||
LiveRoomController liveRoomController,
|
LiveRoomController liveRoomController,
|
||||||
ScrollController scrollController,
|
ScrollController scrollController,
|
||||||
) {
|
) {
|
||||||
return Expanded(
|
return Obx(
|
||||||
child: Obx(
|
() => MediaQuery.removePadding(
|
||||||
() => MediaQuery.removePadding(
|
context: context,
|
||||||
context: context,
|
removeTop: true,
|
||||||
removeTop: true,
|
removeBottom: true,
|
||||||
removeBottom: true,
|
child: ShaderMask(
|
||||||
child: ShaderMask(
|
shaderCallback: (Rect bounds) {
|
||||||
shaderCallback: (Rect bounds) {
|
return LinearGradient(
|
||||||
return LinearGradient(
|
begin: Alignment.topCenter,
|
||||||
begin: Alignment.topCenter,
|
end: Alignment.bottomCenter,
|
||||||
end: Alignment.bottomCenter,
|
colors: [
|
||||||
colors: [
|
Colors.transparent,
|
||||||
Colors.transparent,
|
Colors.black.withOpacity(0.5),
|
||||||
Colors.black.withOpacity(0.5),
|
Colors.black,
|
||||||
Colors.black,
|
],
|
||||||
],
|
stops: const [0.01, 0.05, 0.2],
|
||||||
stops: const [0.01, 0.05, 0.2],
|
).createShader(bounds);
|
||||||
).createShader(bounds);
|
},
|
||||||
|
blendMode: BlendMode.dstIn,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
// 键盘失去焦点
|
||||||
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
},
|
},
|
||||||
blendMode: BlendMode.dstIn,
|
child: ListView.builder(
|
||||||
child: GestureDetector(
|
controller: scrollController,
|
||||||
onTap: () {
|
itemCount: liveRoomController.messageList.length,
|
||||||
// 键盘失去焦点
|
itemBuilder: (context, index) {
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
final LiveMessageModel liveMsgItem =
|
||||||
},
|
liveRoomController.messageList[index];
|
||||||
child: ListView.builder(
|
return Align(
|
||||||
controller: scrollController,
|
alignment: Alignment.centerLeft,
|
||||||
itemCount: liveRoomController.messageList.length,
|
child: Container(
|
||||||
itemBuilder: (context, index) {
|
decoration: BoxDecoration(
|
||||||
final LiveMessageModel liveMsgItem =
|
color: liveRoomController.isPortrait.value
|
||||||
liveRoomController.messageList[index];
|
? Colors.black.withOpacity(0.3)
|
||||||
return Align(
|
: Colors.grey.withOpacity(0.1),
|
||||||
alignment: Alignment.centerLeft,
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
child: Container(
|
),
|
||||||
decoration: BoxDecoration(
|
margin: EdgeInsets.only(
|
||||||
color: liveRoomController.isPortrait.value
|
top: index == 0 ? 20.0 : 0.0,
|
||||||
? Colors.black.withOpacity(0.3)
|
bottom: 6.0,
|
||||||
: Colors.grey.withOpacity(0.1),
|
left: 14.0,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
right: 14.0,
|
||||||
),
|
),
|
||||||
margin: EdgeInsets.only(
|
padding: const EdgeInsets.symmetric(
|
||||||
top: index == 0 ? 20.0 : 0.0,
|
vertical: 3.0,
|
||||||
bottom: 6.0,
|
horizontal: 10.0,
|
||||||
left: 14.0,
|
),
|
||||||
right: 14.0,
|
child: Text.rich(
|
||||||
),
|
TextSpan(
|
||||||
padding: const EdgeInsets.symmetric(
|
style: const TextStyle(color: Colors.white),
|
||||||
vertical: 3.0,
|
children: [
|
||||||
horizontal: 10.0,
|
TextSpan(
|
||||||
),
|
text: '${liveMsgItem.userName}: ',
|
||||||
child: Text.rich(
|
style: TextStyle(
|
||||||
TextSpan(
|
color: Colors.white.withOpacity(0.6),
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: '${liveMsgItem.userName}: ',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white.withOpacity(0.6),
|
|
||||||
),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
// 处理点击事件
|
|
||||||
print('Text clicked');
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
TextSpan(
|
recognizer: TapGestureRecognizer()
|
||||||
children: [
|
..onTap = () {
|
||||||
...buildMessageTextSpan(context, liveMsgItem)
|
// 处理点击事件
|
||||||
],
|
print('Text clicked');
|
||||||
// text: liveMsgItem.message,
|
},
|
||||||
),
|
),
|
||||||
],
|
TextSpan(
|
||||||
),
|
children: [
|
||||||
|
...buildMessageTextSpan(context, liveMsgItem)
|
||||||
|
],
|
||||||
|
// text: liveMsgItem.message,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import '../../models/common/nav_bar_config.dart';
|
|||||||
|
|
||||||
class MainController extends GetxController {
|
class MainController extends GetxController {
|
||||||
List<Widget> pages = <Widget>[];
|
List<Widget> pages = <Widget>[];
|
||||||
|
List<int> pagesIds = <int>[];
|
||||||
RxList navigationBars = [].obs;
|
RxList navigationBars = [].obs;
|
||||||
late List defaultNavTabs;
|
late List defaultNavTabs;
|
||||||
late List<int> navBarSort;
|
late List<int> navBarSort;
|
||||||
@ -45,7 +46,8 @@ class MainController extends GetxController {
|
|||||||
SettingBoxKey.dynamicBadgeMode,
|
SettingBoxKey.dynamicBadgeMode,
|
||||||
defaultValue: DynamicBadgeMode.number.code)];
|
defaultValue: DynamicBadgeMode.number.code)];
|
||||||
setNavBarConfig();
|
setNavBarConfig();
|
||||||
if (dynamicBadgeType.value != DynamicBadgeMode.hidden) {
|
if (dynamicBadgeType.value != DynamicBadgeMode.hidden &&
|
||||||
|
pagesIds.contains(2)) {
|
||||||
getUnreadDynamic();
|
getUnreadDynamic();
|
||||||
}
|
}
|
||||||
enableGradientBg =
|
enableGradientBg =
|
||||||
@ -114,6 +116,7 @@ class MainController extends GetxController {
|
|||||||
// 如果找不到匹配项,默认索引设置为0或其他合适的值
|
// 如果找不到匹配项,默认索引设置为0或其他合适的值
|
||||||
selectedIndex = defaultIndex != -1 ? defaultIndex : 0;
|
selectedIndex = defaultIndex != -1 ? defaultIndex : 0;
|
||||||
pages = navigationBars.map<Widget>((e) => e['page']).toList();
|
pages = navigationBars.map<Widget>((e) => e['page']).toList();
|
||||||
|
pagesIds = navigationBars.map<int>((e) => e['id']).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.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/models/common/dynamic_badge_mode.dart';
|
||||||
@ -22,10 +23,10 @@ class MainApp extends StatefulWidget {
|
|||||||
|
|
||||||
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||||
final MainController _mainController = Get.put(MainController());
|
final MainController _mainController = Get.put(MainController());
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
late HomeController _homeController;
|
||||||
final RankController _rankController = Get.put(RankController());
|
RankController? _rankController;
|
||||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
late DynamicsController _dynamicController;
|
||||||
final MineController _mineController = Get.put(MineController());
|
late MineController _mineController;
|
||||||
|
|
||||||
int? _lastSelectTime; //上次点击时间
|
int? _lastSelectTime; //上次点击时间
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
@ -38,6 +39,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
_mainController.pageController =
|
_mainController.pageController =
|
||||||
PageController(initialPage: _mainController.selectedIndex);
|
PageController(initialPage: _mainController.selectedIndex);
|
||||||
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
|
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
|
||||||
|
controllerInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setIndex(int value) async {
|
void setIndex(int value) async {
|
||||||
@ -60,18 +62,18 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentPage is RankPage) {
|
if (currentPage is RankPage) {
|
||||||
if (_rankController.flag) {
|
if (_rankController!.flag) {
|
||||||
// 单击返回顶部 双击并刷新
|
// 单击返回顶部 双击并刷新
|
||||||
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
||||||
_rankController.onRefresh();
|
_rankController!.onRefresh();
|
||||||
} else {
|
} else {
|
||||||
_rankController.animateToTop();
|
_rankController!.animateToTop();
|
||||||
}
|
}
|
||||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
}
|
}
|
||||||
_rankController.flag = true;
|
_rankController!.flag = true;
|
||||||
} else {
|
} else {
|
||||||
_rankController.flag = false;
|
_rankController?.flag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPage is DynamicsPage) {
|
if (currentPage is DynamicsPage) {
|
||||||
@ -96,6 +98,18 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void controllerInit() {
|
||||||
|
_homeController = Get.put(HomeController());
|
||||||
|
_dynamicController = Get.put(DynamicsController());
|
||||||
|
_mineController = Get.put(MineController());
|
||||||
|
if (_mainController.pagesIds.contains(1)) {
|
||||||
|
_rankController = Get.put(RankController());
|
||||||
|
}
|
||||||
|
if (_mainController.pagesIds.contains(2)) {
|
||||||
|
_dynamicController = Get.put(DynamicsController());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() async {
|
void dispose() async {
|
||||||
await GStrorage.close();
|
await GStrorage.close();
|
||||||
@ -112,6 +126,14 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
MediaQuery.sizeOf(context).width * 9 / 16;
|
MediaQuery.sizeOf(context).width * 9 / 16;
|
||||||
localCache.put('sheetHeight', sheetHeight);
|
localCache.put('sheetHeight', sheetHeight);
|
||||||
localCache.put('statusBarHeight', statusBarHeight);
|
localCache.put('statusBarHeight', statusBarHeight);
|
||||||
|
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
|
SystemUiOverlayStyle(
|
||||||
|
systemNavigationBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarIconBrightness:
|
||||||
|
Get.isDarkMode ? Brightness.light : Brightness.dark,
|
||||||
|
),
|
||||||
|
);
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvoked: (bool didPop) async {
|
onPopInvoked: (bool didPop) async {
|
||||||
|
|||||||
@ -49,6 +49,8 @@ class MemberController extends GetxController {
|
|||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
memberInfo.value = res['data'];
|
memberInfo.value = res['data'];
|
||||||
face.value = res['data'].face;
|
face.value = res['data'].face;
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast('用户信息请求异常:${res['msg']}');
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -78,42 +80,10 @@ class MemberController extends GetxController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (attribute.value == 128) {
|
if (attribute.value == 128) {
|
||||||
blockUser();
|
modifyRelation('block');
|
||||||
return;
|
} else {
|
||||||
|
modifyRelation('follow');
|
||||||
}
|
}
|
||||||
SmartDialog.show(
|
|
||||||
useSystem: true,
|
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('提示'),
|
|
||||||
content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
|
||||||
child: Text(
|
|
||||||
'点错了',
|
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await VideoHttp.relationMod(
|
|
||||||
mid: mid,
|
|
||||||
act: memberInfo.value.isFollowed! ? 2 : 1,
|
|
||||||
reSrc: 11,
|
|
||||||
);
|
|
||||||
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
|
|
||||||
relationSearch();
|
|
||||||
SmartDialog.dismiss();
|
|
||||||
memberInfo.update((val) {});
|
|
||||||
},
|
|
||||||
child: const Text('确认'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关系查询
|
// 关系查询
|
||||||
@ -123,24 +93,15 @@ class MemberController extends GetxController {
|
|||||||
var res = await UserHttp.hasFollow(mid);
|
var res = await UserHttp.hasFollow(mid);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
attribute.value = res['data']['attribute'];
|
attribute.value = res['data']['attribute'];
|
||||||
switch (attribute.value) {
|
final Map<int, String> attributeTextMap = {
|
||||||
case 1:
|
1: '悄悄关注',
|
||||||
attributeText.value = '悄悄关注';
|
2: '已关注',
|
||||||
break;
|
6: '已互关',
|
||||||
case 2:
|
128: '已拉黑',
|
||||||
attributeText.value = '已关注';
|
};
|
||||||
break;
|
attributeText.value = attributeTextMap[attribute.value] ?? '关注';
|
||||||
case 6:
|
|
||||||
attributeText.value = '已互关';
|
|
||||||
break;
|
|
||||||
case 128:
|
|
||||||
attributeText.value = '已拉黑';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
attributeText.value = '关注';
|
|
||||||
}
|
|
||||||
if (res['data']['special'] == 1) {
|
if (res['data']['special'] == 1) {
|
||||||
attributeText.value += 'SP';
|
attributeText.value = '特别关注';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,16 +112,37 @@ class MemberController extends GetxController {
|
|||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SmartDialog.show(
|
modifyRelation('block');
|
||||||
useSystem: true,
|
}
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
|
||||||
|
// 合并关注/取关和拉黑逻辑
|
||||||
|
Future modifyRelation(String actionType) async {
|
||||||
|
if (userInfo == null) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String contentText;
|
||||||
|
int act;
|
||||||
|
if (actionType == 'follow') {
|
||||||
|
contentText = memberInfo.value.isFollowed! ? '确定取消关注UP主?' : '确定关注UP主?';
|
||||||
|
act = memberInfo.value.isFollowed! ? 2 : 1;
|
||||||
|
} else if (actionType == 'block') {
|
||||||
|
contentText = attribute.value != 128 ? '确定拉黑UP主?' : '确定从黑名单移除UP主?';
|
||||||
|
act = attribute.value != 128 ? 5 : 6;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: Get.context!,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('提示'),
|
title: const Text('提示'),
|
||||||
content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
|
content: Text(contentText),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text(
|
child: Text(
|
||||||
'点错了',
|
'点错了',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
@ -170,19 +152,26 @@ class MemberController extends GetxController {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var res = await VideoHttp.relationMod(
|
var res = await VideoHttp.relationMod(
|
||||||
mid: mid,
|
mid: mid,
|
||||||
act: attribute.value != 128 ? 5 : 6,
|
act: act,
|
||||||
reSrc: 11,
|
reSrc: 11,
|
||||||
);
|
);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
attribute.value = attribute.value != 128 ? 128 : 0;
|
if (actionType == 'follow') {
|
||||||
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';
|
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
|
||||||
memberInfo.value.isFollowed = false;
|
} else if (actionType == 'block') {
|
||||||
|
attribute.value = attribute.value != 128 ? 128 : 0;
|
||||||
|
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';
|
||||||
|
memberInfo.value.isFollowed = false;
|
||||||
|
}
|
||||||
relationSearch();
|
relationSearch();
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
memberInfo.update((val) {});
|
memberInfo.update((val) {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('确认'),
|
child: const Text('确定'),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -228,17 +217,14 @@ class MemberController extends GetxController {
|
|||||||
|
|
||||||
// 跳转查看动态
|
// 跳转查看动态
|
||||||
void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid');
|
void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid');
|
||||||
|
|
||||||
// 跳转查看投稿
|
// 跳转查看投稿
|
||||||
void pushArchivesPage() => Get.toNamed('/memberArchive?mid=$mid');
|
void pushArchivesPage() => Get.toNamed('/memberArchive?mid=$mid');
|
||||||
|
|
||||||
// 跳转查看专栏
|
|
||||||
void pushSeasonsPage() {}
|
|
||||||
// 跳转查看最近投币
|
// 跳转查看最近投币
|
||||||
void pushRecentCoinsPage() async {
|
void pushRecentCoinsPage() async {
|
||||||
if (recentCoinsList.isNotEmpty) {}
|
if (recentCoinsList.isNotEmpty) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 跳转查看收藏夹
|
||||||
void pushfavPage() => Get.toNamed('/fav?mid=$mid');
|
void pushfavPage() => Get.toNamed('/fav?mid=$mid');
|
||||||
// 跳转图文专栏
|
// 跳转图文专栏
|
||||||
void pushArticlePage() => Get.toNamed('/memberArticle?mid=$mid');
|
void pushArticlePage() => Get.toNamed('/memberArticle?mid=$mid');
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/member/info.dart';
|
||||||
import 'package:pilipala/pages/member/index.dart';
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
import 'widgets/commen_widget.dart';
|
||||||
import 'widgets/conis.dart';
|
import 'widgets/conis.dart';
|
||||||
import 'widgets/like.dart';
|
import 'widgets/like.dart';
|
||||||
import 'widgets/profile.dart';
|
import 'widgets/profile.dart';
|
||||||
@ -65,259 +65,233 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
primary: true,
|
appBar: AppBar(
|
||||||
body: Column(
|
title: StreamBuilder(
|
||||||
children: [
|
stream: appbarStream.stream.distinct(),
|
||||||
AppBar(
|
initialData: false,
|
||||||
title: StreamBuilder(
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||||
stream: appbarStream.stream.distinct(),
|
return AnimatedOpacity(
|
||||||
initialData: false,
|
opacity: snapshot.data ? 1 : 0,
|
||||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
curve: Curves.easeOut,
|
||||||
return AnimatedOpacity(
|
duration: const Duration(milliseconds: 500),
|
||||||
opacity: snapshot.data ? 1 : 0,
|
child: Row(
|
||||||
curve: Curves.easeOut,
|
children: [
|
||||||
duration: const Duration(milliseconds: 500),
|
Obx(
|
||||||
child: Row(
|
() => NetworkImgLayer(
|
||||||
children: [
|
width: 35,
|
||||||
Row(
|
height: 35,
|
||||||
children: [
|
type: 'avatar',
|
||||||
Obx(
|
src: _memberController.face.value,
|
||||||
() => NetworkImgLayer(
|
),
|
||||||
width: 35,
|
|
||||||
height: 35,
|
|
||||||
type: 'avatar',
|
|
||||||
src: _memberController.face.value,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Obx(
|
|
||||||
() => Text(
|
|
||||||
_memberController.memberInfo.value.name ?? '',
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.onSurface,
|
|
||||||
fontSize: 14),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(width: 10),
|
||||||
},
|
Obx(
|
||||||
),
|
() => Text(
|
||||||
actions: [
|
_memberController.memberInfo.value.name ?? '',
|
||||||
IconButton(
|
style: TextStyle(
|
||||||
onPressed: () => Get.toNamed(
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
'/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'),
|
fontSize: 14),
|
||||||
icon: const Icon(Icons.search_outlined),
|
|
||||||
),
|
|
||||||
PopupMenuButton(
|
|
||||||
icon: const Icon(Icons.more_vert),
|
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
|
||||||
if (_memberController.ownerMid != _memberController.mid) ...[
|
|
||||||
PopupMenuItem(
|
|
||||||
onTap: () => _memberController.blockUser(),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.block, size: 19),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(_memberController.attribute.value != 128
|
|
||||||
? '加入黑名单'
|
|
||||||
: '移除黑名单'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
PopupMenuItem(
|
|
||||||
onTap: () => _memberController.shareUser(),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.share_outlined, size: 19),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(_memberController.ownerMid != _memberController.mid
|
|
||||||
? '分享UP主'
|
|
||||||
: '分享我的主页'),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
);
|
||||||
],
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
),
|
),
|
||||||
Expanded(
|
PopupMenuButton(
|
||||||
child: SingleChildScrollView(
|
icon: const Icon(Icons.more_vert),
|
||||||
controller: _extendNestCtr,
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
child: Padding(
|
if (_memberController.ownerMid != _memberController.mid) ...[
|
||||||
padding: EdgeInsets.only(
|
PopupMenuItem(
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 20,
|
onTap: () => _memberController.blockUser(),
|
||||||
),
|
child: Row(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.block, size: 19),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(_memberController.attribute.value != 128
|
||||||
|
? '加入黑名单'
|
||||||
|
: '移除黑名单'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () => _memberController.shareUser(),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
profileWidget(),
|
const Icon(Icons.share_outlined, size: 19),
|
||||||
|
const SizedBox(width: 10),
|
||||||
/// 动态链接
|
Text(_memberController.ownerMid != _memberController.mid
|
||||||
Obx(
|
? '分享UP主'
|
||||||
() => ListTile(
|
: '分享我的主页'),
|
||||||
onTap: _memberController.pushDynamicsPage,
|
|
||||||
title: Text(
|
|
||||||
'${_memberController.isOwner.value ? '我' : 'Ta'}的动态'),
|
|
||||||
trailing:
|
|
||||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 视频
|
|
||||||
Obx(
|
|
||||||
() => ListTile(
|
|
||||||
onTap: _memberController.pushArchivesPage,
|
|
||||||
title: Text(
|
|
||||||
'${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'),
|
|
||||||
trailing:
|
|
||||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 他的收藏夹
|
|
||||||
Obx(
|
|
||||||
() => ListTile(
|
|
||||||
onTap: _memberController.pushfavPage,
|
|
||||||
title: Text(
|
|
||||||
'${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'),
|
|
||||||
trailing:
|
|
||||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 专栏
|
|
||||||
Obx(
|
|
||||||
() => ListTile(
|
|
||||||
onTap: _memberController.pushArticlePage,
|
|
||||||
title: Text(
|
|
||||||
'${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'),
|
|
||||||
trailing:
|
|
||||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 合集
|
|
||||||
Obx(
|
|
||||||
() => ListTile(
|
|
||||||
title: Text(
|
|
||||||
'${_memberController.isOwner.value ? '我' : 'Ta'}的合集')),
|
|
||||||
),
|
|
||||||
MediaQuery.removePadding(
|
|
||||||
removeTop: true,
|
|
||||||
removeBottom: true,
|
|
||||||
context: context,
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 追番
|
|
||||||
/// 最近投币
|
|
||||||
Obx(
|
|
||||||
() => _memberController.recentCoinsList.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: _memberCoinsFuture,
|
|
||||||
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 MemberCoinsPanel(data: data['data']);
|
|
||||||
} else {
|
|
||||||
// 请求错误
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 最近点赞
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
primary: true,
|
||||||
|
body: ListView(
|
||||||
|
controller: _extendNestCtr,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 20,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
profileWidget(),
|
||||||
|
|
||||||
|
/// 动态链接
|
||||||
|
Obx(
|
||||||
|
() => ListTile(
|
||||||
|
onTap: _memberController.pushDynamicsPage,
|
||||||
|
title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的动态'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// 视频
|
||||||
|
Obx(
|
||||||
|
() => ListTile(
|
||||||
|
onTap: _memberController.pushArchivesPage,
|
||||||
|
title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// 他的收藏夹
|
||||||
|
Obx(
|
||||||
|
() => ListTile(
|
||||||
|
onTap: _memberController.pushfavPage,
|
||||||
|
title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// 专栏
|
||||||
|
Obx(
|
||||||
|
() => ListTile(
|
||||||
|
onTap: _memberController.pushArticlePage,
|
||||||
|
title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// 合集
|
||||||
|
Obx(
|
||||||
|
() => ListTile(
|
||||||
|
title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的合集'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MediaQuery.removePadding(
|
||||||
|
removeTop: true,
|
||||||
|
removeBottom: true,
|
||||||
|
context: context,
|
||||||
|
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 const CommenWidget(msg: '用户没有设置合集');
|
||||||
|
} else {
|
||||||
|
return MemberSeasonsPanel(data: data['data']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// 追番
|
||||||
|
/// 最近投币
|
||||||
|
Obx(
|
||||||
|
() => _memberController.recentCoinsList.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: _memberCoinsFuture,
|
||||||
|
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 MemberCoinsPanel(data: data['data']);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// 最近点赞
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -334,115 +308,90 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
Map? data = snapshot.data;
|
Map? data = snapshot.data;
|
||||||
if (data != null && data['status']) {
|
if (data != null && data['status']) {
|
||||||
|
Rx<MemberInfoModel> memberInfo = _memberController.memberInfo;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => Stack(
|
() => Column(
|
||||||
alignment: AlignmentDirectional.center,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
ProfilePanel(ctr: _memberController),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
ProfilePanel(ctr: _memberController),
|
Flexible(
|
||||||
const SizedBox(height: 20),
|
child: Text(
|
||||||
Row(
|
memberInfo.value.name!,
|
||||||
children: [
|
maxLines: 1,
|
||||||
Flexible(
|
overflow: TextOverflow.ellipsis,
|
||||||
child: Text(
|
style: Theme.of(context)
|
||||||
_memberController.memberInfo.value.name!,
|
.textTheme
|
||||||
maxLines: 1,
|
.titleMedium!
|
||||||
overflow: TextOverflow.ellipsis,
|
.copyWith(
|
||||||
style: Theme.of(context)
|
fontWeight: FontWeight.bold,
|
||||||
.textTheme
|
color: memberInfo.value.vip!.nicknameColor !=
|
||||||
.titleMedium!
|
null
|
||||||
.copyWith(
|
? Color(_memberController
|
||||||
fontWeight: FontWeight.bold,
|
.memberInfo.value.vip!.nicknameColor!)
|
||||||
color: _memberController.memberInfo.value
|
: null),
|
||||||
.vip!.nicknameColor !=
|
)),
|
||||||
null
|
const SizedBox(width: 2),
|
||||||
? Color(_memberController.memberInfo
|
if (memberInfo.value.sex == '女')
|
||||||
.value.vip!.nicknameColor!)
|
const Icon(
|
||||||
: null),
|
FontAwesomeIcons.venus,
|
||||||
)),
|
size: 14,
|
||||||
const SizedBox(width: 2),
|
color: Colors.pink,
|
||||||
if (_memberController.memberInfo.value.sex == '女')
|
),
|
||||||
const Icon(
|
if (memberInfo.value.sex == '男')
|
||||||
FontAwesomeIcons.venus,
|
const Icon(
|
||||||
size: 14,
|
FontAwesomeIcons.mars,
|
||||||
color: Colors.pink,
|
size: 14,
|
||||||
),
|
color: Colors.blue,
|
||||||
if (_memberController.memberInfo.value.sex == '男')
|
),
|
||||||
const Icon(
|
const SizedBox(width: 4),
|
||||||
FontAwesomeIcons.mars,
|
Image.asset(
|
||||||
size: 14,
|
'assets/images/lv/lv${memberInfo.value.level}.png',
|
||||||
color: Colors.blue,
|
height: 11,
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
|
|
||||||
height: 11,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
if (_memberController
|
|
||||||
.memberInfo.value.vip!.status ==
|
|
||||||
1 &&
|
|
||||||
_memberController.memberInfo.value.vip!
|
|
||||||
.label!['img_label_uri_hans'] !=
|
|
||||||
'') ...[
|
|
||||||
Image.network(
|
|
||||||
_memberController.memberInfo.value.vip!
|
|
||||||
.label!['img_label_uri_hans'],
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
] else if (_memberController
|
|
||||||
.memberInfo.value.vip!.status ==
|
|
||||||
1 &&
|
|
||||||
_memberController.memberInfo.value.vip!
|
|
||||||
.label!['img_label_uri_hans_static'] !=
|
|
||||||
'') ...[
|
|
||||||
Image.network(
|
|
||||||
_memberController.memberInfo.value.vip!
|
|
||||||
.label!['img_label_uri_hans_static'],
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
if (_memberController
|
const SizedBox(width: 6),
|
||||||
.memberInfo.value.official!['title'] !=
|
if (memberInfo.value.vip!.status == 1 &&
|
||||||
'') ...[
|
memberInfo
|
||||||
const SizedBox(height: 6),
|
.value.vip!.label!['img_label_uri_hans'] !=
|
||||||
Text.rich(
|
'') ...[
|
||||||
maxLines: 2,
|
Image.network(
|
||||||
TextSpan(
|
memberInfo.value.vip!.label!['img_label_uri_hans'],
|
||||||
text: _memberController
|
height: 20,
|
||||||
.memberInfo.value.official!['role'] ==
|
|
||||||
1
|
|
||||||
? '个人认证:'
|
|
||||||
: '企业认证:',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: _memberController
|
|
||||||
.memberInfo.value.official!['title'],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
softWrap: true,
|
|
||||||
),
|
),
|
||||||
],
|
] else if (memberInfo.value.vip!.status == 1 &&
|
||||||
const SizedBox(height: 6),
|
memberInfo.value.vip!
|
||||||
if (_memberController.memberInfo.value.sign != '')
|
.label!['img_label_uri_hans_static'] !=
|
||||||
SelectableText(
|
'') ...[
|
||||||
_memberController.memberInfo.value.sign!,
|
Image.network(
|
||||||
|
memberInfo
|
||||||
|
.value.vip!.label!['img_label_uri_hans_static'],
|
||||||
|
height: 20,
|
||||||
),
|
),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (memberInfo.value.official!['title'] != '') ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
memberInfo.value.official!['role'] == 1
|
||||||
|
? '个人认证:${memberInfo.value.official!['title']}'
|
||||||
|
: '企业认证:${memberInfo.value.official!['title']}',
|
||||||
|
maxLines: 2,
|
||||||
|
softWrap: true,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
SelectableText(memberInfo.value.sign ?? ''),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const SizedBox();
|
return ProfilePanel(ctr: _memberController, loadingStatus: true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 骨架屏
|
// 骨架屏
|
||||||
@ -452,22 +401,4 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget commenWidget(msg) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 20,
|
|
||||||
bottom: 30,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
msg,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
24
lib/pages/member/widgets/commen_widget.dart
Normal file
24
lib/pages/member/widgets/commen_widget.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CommenWidget extends StatelessWidget {
|
||||||
|
final String msg;
|
||||||
|
|
||||||
|
const CommenWidget({required this.msg, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
msg,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.copyWith(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,253 +17,245 @@ class ProfilePanel extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
MemberInfoModel memberInfo = ctr.memberInfo.value;
|
MemberInfoModel memberInfo = ctr.memberInfo.value;
|
||||||
return Builder(
|
final int? mid = memberInfo.mid;
|
||||||
builder: ((context) {
|
final String? name = memberInfo.name;
|
||||||
return Padding(
|
|
||||||
padding:
|
Map<String, dynamic> buildStatItem({
|
||||||
EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
|
required String label,
|
||||||
child: Row(
|
required String value,
|
||||||
children: [
|
required VoidCallback onTap,
|
||||||
Hero(
|
}) {
|
||||||
tag: ctr.heroTag!,
|
return {
|
||||||
child: Stack(
|
'label': label,
|
||||||
children: [
|
'value': value,
|
||||||
NetworkImgLayer(
|
'fn': onTap,
|
||||||
width: 90,
|
};
|
||||||
height: 90,
|
}
|
||||||
type: 'avatar',
|
|
||||||
src: !loadingStatus ? memberInfo.face : ctr.face.value,
|
final List<Map<String, dynamic>> statList = [
|
||||||
),
|
buildStatItem(
|
||||||
if (!loadingStatus &&
|
label: '关注',
|
||||||
memberInfo.liveRoom != null &&
|
value: !loadingStatus ? "${ctr.userStat!['following']}" : '-',
|
||||||
memberInfo.liveRoom!.liveStatus == 1)
|
onTap: () {
|
||||||
Positioned(
|
Get.toNamed('/follow?mid=$mid&name=$name');
|
||||||
bottom: 0,
|
},
|
||||||
left: 14,
|
),
|
||||||
child: GestureDetector(
|
buildStatItem(
|
||||||
onTap: () {
|
label: '粉丝',
|
||||||
LiveItemModel liveItem = LiveItemModel.fromJson({
|
value: !loadingStatus
|
||||||
'title': memberInfo.liveRoom!.title,
|
? ctr.userStat!['follower'] != null
|
||||||
'uname': memberInfo.name,
|
? Utils.numFormat(ctr.userStat!['follower'])
|
||||||
'face': memberInfo.face,
|
: '-'
|
||||||
'roomid': memberInfo.liveRoom!.roomId,
|
: '-',
|
||||||
'watched_show': memberInfo.liveRoom!.watchedShow,
|
onTap: () {
|
||||||
});
|
Get.toNamed('/fan?mid=$mid&name=$name');
|
||||||
Get.toNamed(
|
},
|
||||||
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
|
),
|
||||||
arguments: {'liveItem': liveItem},
|
buildStatItem(
|
||||||
);
|
label: '获赞',
|
||||||
},
|
value: !loadingStatus
|
||||||
child: Container(
|
? ctr.userStat!['likes'] != null
|
||||||
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
|
? Utils.numFormat(ctr.userStat!['likes'])
|
||||||
decoration: BoxDecoration(
|
: '-'
|
||||||
color: Theme.of(context).colorScheme.primary,
|
: '-',
|
||||||
borderRadius:
|
onTap: () {},
|
||||||
const BorderRadius.all(Radius.circular(10)),
|
),
|
||||||
),
|
];
|
||||||
child: Row(children: [
|
|
||||||
Image.asset(
|
return Padding(
|
||||||
'assets/images/live.gif',
|
padding: const EdgeInsets.only(top: 30, left: 4),
|
||||||
height: 10,
|
child: Row(
|
||||||
),
|
children: [
|
||||||
Text(
|
Hero(
|
||||||
' 直播中',
|
tag: ctr.heroTag!,
|
||||||
style: TextStyle(
|
child: Stack(
|
||||||
color: Colors.white,
|
children: [
|
||||||
fontSize: Theme.of(context)
|
NetworkImgLayer(
|
||||||
.textTheme
|
width: 90,
|
||||||
.labelSmall!
|
height: 90,
|
||||||
.fontSize),
|
type: 'avatar',
|
||||||
)
|
src: !loadingStatus ? memberInfo.face : ctr.face.value,
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
if (!loadingStatus &&
|
||||||
const SizedBox(width: 12),
|
memberInfo.liveRoom != null &&
|
||||||
Expanded(
|
memberInfo.liveRoom!.liveStatus == 1)
|
||||||
child: Column(
|
Positioned(
|
||||||
mainAxisSize: MainAxisSize.min,
|
bottom: 0,
|
||||||
children: [
|
left: 14,
|
||||||
Padding(
|
child: GestureDetector(
|
||||||
padding:
|
onTap: () {
|
||||||
const EdgeInsets.only(top: 10, left: 10, right: 10),
|
LiveItemModel liveItem = LiveItemModel(
|
||||||
child: Row(
|
title: memberInfo.liveRoom!.title,
|
||||||
mainAxisSize: MainAxisSize.max,
|
uname: memberInfo.name,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
face: memberInfo.face,
|
||||||
children: [
|
roomId: memberInfo.liveRoom!.roomId,
|
||||||
InkWell(
|
watchedShow: memberInfo.liveRoom!.watchedShow,
|
||||||
onTap: () {
|
);
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
|
||||||
},
|
arguments: {'liveItem': liveItem},
|
||||||
child: Column(
|
);
|
||||||
children: [
|
},
|
||||||
Text(
|
child: Container(
|
||||||
!loadingStatus
|
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
|
||||||
? ctr.userStat!['following'].toString()
|
decoration: BoxDecoration(
|
||||||
: '-',
|
color: Theme.of(context).colorScheme.primary,
|
||||||
style: const TextStyle(
|
borderRadius:
|
||||||
fontWeight: FontWeight.bold),
|
const BorderRadius.all(Radius.circular(10)),
|
||||||
),
|
),
|
||||||
Text(
|
child: Row(children: [
|
||||||
'关注',
|
Image.asset(
|
||||||
style: TextStyle(
|
'assets/images/live.gif',
|
||||||
fontSize: Theme.of(context)
|
height: 10,
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.fontSize),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
InkWell(
|
Text(
|
||||||
onTap: () {
|
' 直播中',
|
||||||
Get.toNamed(
|
style: TextStyle(
|
||||||
'/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
color: Colors.white,
|
||||||
},
|
fontSize: Theme.of(context)
|
||||||
child: Column(
|
.textTheme
|
||||||
children: [
|
.labelSmall!
|
||||||
Text(
|
.fontSize),
|
||||||
!loadingStatus
|
)
|
||||||
? ctr.userStat!['follower'] != null
|
]),
|
||||||
? Utils.numFormat(
|
|
||||||
ctr.userStat!['follower'],
|
|
||||||
)
|
|
||||||
: '-'
|
|
||||||
: '-',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold)),
|
|
||||||
Text(
|
|
||||||
'粉丝',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.fontSize),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
!loadingStatus
|
|
||||||
? ctr.userStat!['likes'] != null
|
|
||||||
? Utils.numFormat(
|
|
||||||
ctr.userStat!['likes'],
|
|
||||||
)
|
|
||||||
: '-'
|
|
||||||
: '-',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold)),
|
|
||||||
Text(
|
|
||||||
'获赞',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.fontSize),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
)
|
||||||
if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[
|
],
|
||||||
Row(
|
),
|
||||||
children: [
|
|
||||||
Obx(
|
|
||||||
() => Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () => loadingStatus
|
|
||||||
? null
|
|
||||||
: ctr.actionRelationMod(),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: ctr.attribute.value == -1
|
|
||||||
? Colors.transparent
|
|
||||||
: ctr.attribute.value != 0
|
|
||||||
? Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.outline
|
|
||||||
: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onPrimary,
|
|
||||||
backgroundColor: ctr.attribute.value != 0
|
|
||||||
? Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface
|
|
||||||
: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary, // 设置按钮背景色
|
|
||||||
),
|
|
||||||
child: Obx(() => Text(ctr.attributeText.value)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
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
|
|
||||||
.onInverseSurface,
|
|
||||||
),
|
|
||||||
child: const Text('发消息'),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.toNamed('/mineEdit');
|
|
||||||
},
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.only(left: 80, right: 80),
|
|
||||||
foregroundColor:
|
|
||||||
Theme.of(context).colorScheme.onPrimary,
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
child: const Text('编辑资料'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
if (ctr.ownerMid == -1) ...[
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {},
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.only(left: 80, right: 80),
|
|
||||||
foregroundColor:
|
|
||||||
Theme.of(context).colorScheme.outline,
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).colorScheme.onInverseSurface,
|
|
||||||
),
|
|
||||||
child: const Text('未登录'),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(width: 12),
|
||||||
}),
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: statList.map((item) {
|
||||||
|
return buildStatColumn(
|
||||||
|
context,
|
||||||
|
item['label'],
|
||||||
|
item['value'],
|
||||||
|
item['fn'],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1)
|
||||||
|
buildActionButtons(context, ctr, memberInfo),
|
||||||
|
if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1)
|
||||||
|
buildEditProfileButton(context),
|
||||||
|
if (ctr.ownerMid == -1) buildNotLoggedInButton(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildStatColumn(
|
||||||
|
BuildContext context,
|
||||||
|
String label,
|
||||||
|
String value,
|
||||||
|
VoidCallback? onTap,
|
||||||
|
) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildActionButtons(
|
||||||
|
BuildContext context,
|
||||||
|
dynamic ctr,
|
||||||
|
MemberInfoModel memberInfo,
|
||||||
|
) {
|
||||||
|
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Obx(
|
||||||
|
() => Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => loadingStatus ? null : ctr.actionRelationMod(),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: ctr.attribute.value == -1
|
||||||
|
? Colors.transparent
|
||||||
|
: ctr.attribute.value != 0
|
||||||
|
? colorScheme.outline
|
||||||
|
: colorScheme.onPrimary,
|
||||||
|
backgroundColor: ctr.attribute.value != 0
|
||||||
|
? colorScheme.onInverseSurface
|
||||||
|
: colorScheme.primary,
|
||||||
|
),
|
||||||
|
child: Obx(() => Text(ctr.attributeText.value)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.toNamed(
|
||||||
|
'/whisperDetail',
|
||||||
|
parameters: {
|
||||||
|
'name': memberInfo.name!,
|
||||||
|
'face': memberInfo.face!,
|
||||||
|
'mid': memberInfo.mid.toString(),
|
||||||
|
'heroTag': ctr.heroTag!,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: colorScheme.onInverseSurface,
|
||||||
|
),
|
||||||
|
child: const Text('发消息'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildEditProfileButton(BuildContext context) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.toNamed('/mineEdit');
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 80),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
child: const Text('编辑资料'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildNotLoggedInButton(BuildContext context) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 80),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.outline,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
),
|
||||||
|
child: const Text('未登录'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ class MemberArticleController extends GetxController {
|
|||||||
int pn = 1;
|
int pn = 1;
|
||||||
String? offset;
|
String? offset;
|
||||||
bool hasMore = true;
|
bool hasMore = true;
|
||||||
String? wWebid;
|
|
||||||
RxBool isLoading = false.obs;
|
RxBool isLoading = false.obs;
|
||||||
RxList<MemberArticleItemModel> articleList = <MemberArticleItemModel>[].obs;
|
RxList<MemberArticleItemModel> articleList = <MemberArticleItemModel>[].obs;
|
||||||
|
|
||||||
@ -20,25 +19,11 @@ class MemberArticleController extends GetxController {
|
|||||||
mid = int.parse(Get.parameters['mid']!);
|
mid = int.parse(Get.parameters['mid']!);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取wWebid
|
|
||||||
Future getWWebid() async {
|
|
||||||
var res = await MemberHttp.getWWebid(mid: mid);
|
|
||||||
if (res['status']) {
|
|
||||||
wWebid = res['data'];
|
|
||||||
} else {
|
|
||||||
wWebid = '-1';
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future getMemberArticle(type) async {
|
Future getMemberArticle(type) async {
|
||||||
if (isLoading.value) {
|
if (isLoading.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
if (wWebid == null) {
|
|
||||||
await getWWebid();
|
|
||||||
}
|
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
pn = 1;
|
pn = 1;
|
||||||
articleList.clear();
|
articleList.clear();
|
||||||
@ -47,7 +32,6 @@ class MemberArticleController extends GetxController {
|
|||||||
mid: mid,
|
mid: mid,
|
||||||
pn: pn,
|
pn: pn,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
wWebid: wWebid!,
|
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
offset = res['data'].offset;
|
offset = res['data'].offset;
|
||||||
|
|||||||
@ -125,21 +125,29 @@ class LikeItem extends StatelessWidget {
|
|||||||
Color outline = Theme.of(context).colorScheme.outline;
|
Color outline = Theme.of(context).colorScheme.outline;
|
||||||
final nickNameList = item.users!.map((e) => e.nickname).take(2).toList();
|
final nickNameList = item.users!.map((e) => e.nickname).take(2).toList();
|
||||||
int usersLen = item.users!.length > 3 ? 3 : item.users!.length;
|
int usersLen = item.users!.length > 3 ? 3 : item.users!.length;
|
||||||
final String bvid = item.item!.uri!.split('/').last;
|
final Uri uri = Uri.parse(item.item!.uri!);
|
||||||
|
final String path = uri.path;
|
||||||
|
final String bvid = path.split('/').last;
|
||||||
|
|
||||||
|
/// bilibili://
|
||||||
|
final Uri nativeUri = Uri.parse(item.item!.nativeUri!);
|
||||||
|
final Map<String, String> queryParameters = nativeUri.queryParameters;
|
||||||
|
final String type = item.item!.type!;
|
||||||
|
// cid
|
||||||
|
final String? argCid = queryParameters['cid'];
|
||||||
// 页码
|
// 页码
|
||||||
final String page =
|
final String? page = queryParameters['page'];
|
||||||
item.item!.nativeUri!.split('page=').last.split('&').first;
|
|
||||||
// 根评论id
|
// 根评论id
|
||||||
final String commentRootId =
|
final String? commentRootId = queryParameters['comment_root_id'];
|
||||||
item.item!.nativeUri!.split('comment_root_id=').last.split('&').first;
|
|
||||||
// 二级评论id
|
// 二级评论id
|
||||||
final String commentSecondaryId =
|
final String? commentSecondaryId = queryParameters['comment_secondary_id'];
|
||||||
item.item!.nativeUri!.split('comment_secondary_id=').last;
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
try {
|
||||||
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
final int cid = argCid != null
|
||||||
|
? int.parse(argCid)
|
||||||
|
: await SearchHttp.ab2c(bvid: bvid);
|
||||||
final String heroTag = Utils.makeHeroTag(bvid);
|
final String heroTag = Utils.makeHeroTag(bvid);
|
||||||
Get.toNamed<dynamic>(
|
Get.toNamed<dynamic>(
|
||||||
'/video?bvid=$bvid&cid=$cid',
|
'/video?bvid=$bvid&cid=$cid',
|
||||||
@ -148,8 +156,8 @@ class LikeItem extends StatelessWidget {
|
|||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (_) {
|
} catch (e) {
|
||||||
SmartDialog.showToast('视频可能失效了');
|
SmartDialog.showToast('视频可能失效了$e');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@ -222,7 +230,7 @@ class LikeItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 25),
|
const SizedBox(width: 25),
|
||||||
if (item.item!.type! == 'reply')
|
if (type == 'reply' || type == 'danmu')
|
||||||
Container(
|
Container(
|
||||||
width: 60,
|
width: 60,
|
||||||
height: 60,
|
height: 60,
|
||||||
@ -234,7 +242,7 @@ class LikeItem extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (item.item!.type! == 'video')
|
if (type == 'video')
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
width: 60,
|
width: 60,
|
||||||
height: 60,
|
height: 60,
|
||||||
|
|||||||
@ -157,7 +157,7 @@ class _OpusPageState extends State<OpusPage> {
|
|||||||
Container(
|
Container(
|
||||||
alignment: TextHelper.getAlignment(paragraph.align),
|
alignment: TextHelper.getAlignment(paragraph.align),
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
child: Text.rich(
|
child: SelectableText.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: paragraph.text?.nodes?.map((node) {
|
children: paragraph.text?.nodes?.map((node) {
|
||||||
return TextHelper.buildTextSpan(
|
return TextHelper.buildTextSpan(
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class ReadPageController extends GetxController {
|
|||||||
super.onInit();
|
super.onInit();
|
||||||
title.value = Get.parameters['title'] ?? '';
|
title.value = Get.parameters['title'] ?? '';
|
||||||
id = Get.parameters['id']!;
|
id = Get.parameters['id']!;
|
||||||
articleType = Get.parameters['articleType']!;
|
articleType = Get.parameters['articleType'] ?? 'read';
|
||||||
url = 'https://www.bilibili.com/read/cv$id';
|
url = 'https://www.bilibili.com/read/cv$id';
|
||||||
scrollController.addListener(_scrollListener);
|
scrollController.addListener(_scrollListener);
|
||||||
fetchViewInfo();
|
fetchViewInfo();
|
||||||
|
|||||||
@ -126,7 +126,6 @@ class _ReadPageState extends State<ReadPage> {
|
|||||||
Widget _buildContent(ReadDataModel cvData) {
|
Widget _buildContent(ReadDataModel cvData) {
|
||||||
final List<String> picList = _extractPicList(cvData);
|
final List<String> picList = _extractPicList(cvData);
|
||||||
final List<String> imgList = extractDataSrc(cvData.readInfo!.content!);
|
final List<String> imgList = extractDataSrc(cvData.readInfo!.content!);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
16, 0, 16, MediaQuery.of(context).padding.bottom + 40),
|
16, 0, 16, MediaQuery.of(context).padding.bottom + 40),
|
||||||
@ -163,9 +162,11 @@ class _ReadPageState extends State<ReadPage> {
|
|||||||
padding: const EdgeInsets.only(bottom: 20),
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
child: _buildAuthorWidget(cvData),
|
child: _buildAuthorWidget(cvData),
|
||||||
),
|
),
|
||||||
HtmlRender(
|
SelectionArea(
|
||||||
htmlContent: cvData.readInfo!.content!,
|
child: HtmlRender(
|
||||||
imgList: imgList,
|
htmlContent: cvData.readInfo!.content!,
|
||||||
|
imgList: imgList,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -206,7 +207,7 @@ class _ReadPageState extends State<ReadPage> {
|
|||||||
return Container(
|
return Container(
|
||||||
alignment: TextHelper.getAlignment(paragraph.align),
|
alignment: TextHelper.getAlignment(paragraph.align),
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
child: Text.rich(
|
child: SelectableText.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: paragraph.text?.nodes?.map((node) {
|
children: paragraph.text?.nodes?.map((node) {
|
||||||
return TextHelper.buildTextSpan(node, paragraph.align, context);
|
return TextHelper.buildTextSpan(node, paragraph.align, context);
|
||||||
|
|||||||
@ -24,7 +24,9 @@ class SearchPanelController extends GetxController {
|
|||||||
searchType: searchType!,
|
searchType: searchType!,
|
||||||
keyword: keyword!,
|
keyword: keyword!,
|
||||||
page: page.value,
|
page: page.value,
|
||||||
order: searchType!.type != 'video' ? null : order.value,
|
order: !['video', 'article'].contains(searchType!.type)
|
||||||
|
? null
|
||||||
|
: (order.value == '' ? null : order.value),
|
||||||
duration: searchType!.type != 'video' ? null : duration.value,
|
duration: searchType!.type != 'video' ? null : duration.value,
|
||||||
tids: searchType!.type != 'video' ? null : tids.value,
|
tids: searchType!.type != 'video' ? null : tids.value,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: invalid_use_of_protected_member
|
||||||
|
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -45,6 +47,11 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
),
|
),
|
||||||
tag: widget.searchType!.type + widget.keyword!,
|
tag: widget.searchType!.type + widget.keyword!,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// 专栏默认排序
|
||||||
|
if (widget.searchType == SearchType.article) {
|
||||||
|
_searchPanelController.order.value = 'totalrank';
|
||||||
|
}
|
||||||
scrollController = _searchPanelController.scrollController;
|
scrollController = _searchPanelController.scrollController;
|
||||||
scrollController.addListener(() async {
|
scrollController.addListener(() async {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
@ -84,7 +91,6 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
case SearchType.video:
|
case SearchType.video:
|
||||||
return SearchVideoPanel(
|
return SearchVideoPanel(
|
||||||
ctr: _searchPanelController,
|
ctr: _searchPanelController,
|
||||||
// ignore: invalid_use_of_protected_member
|
|
||||||
list: list.value,
|
list: list.value,
|
||||||
);
|
);
|
||||||
case SearchType.media_bangumi:
|
case SearchType.media_bangumi:
|
||||||
@ -94,7 +100,10 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
case SearchType.live_room:
|
case SearchType.live_room:
|
||||||
return searchLivePanel(context, ctr, list);
|
return searchLivePanel(context, ctr, list);
|
||||||
case SearchType.article:
|
case SearchType.article:
|
||||||
return searchArticlePanel(context, ctr, list);
|
return SearchArticlePanel(
|
||||||
|
ctr: _searchPanelController,
|
||||||
|
list: list.value,
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,106 +1,249 @@
|
|||||||
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:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
|
import 'package:pilipala/pages/search_panel/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
class SearchArticlePanel extends StatelessWidget {
|
||||||
|
SearchArticlePanel({
|
||||||
|
required this.ctr,
|
||||||
|
this.list,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final SearchPanelController ctr;
|
||||||
|
final List? list;
|
||||||
|
|
||||||
|
final ArticlePanelController controller = Get.put(ArticlePanelController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
children: [
|
||||||
|
searchArticlePanel(context, ctr, list),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 36,
|
||||||
|
padding: const EdgeInsets.only(left: 8, top: 0, right: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Obx(
|
||||||
|
() => Wrap(
|
||||||
|
// spacing: ,
|
||||||
|
children: [
|
||||||
|
for (var i in controller.filterList) ...[
|
||||||
|
CustomFilterChip(
|
||||||
|
label: i['label'],
|
||||||
|
type: i['type'],
|
||||||
|
selectedType: controller.selectedType.value,
|
||||||
|
callFn: (bool selected) async {
|
||||||
|
controller.selectedType.value = i['type'];
|
||||||
|
ctr.order.value =
|
||||||
|
i['type'].toString().split('.').last;
|
||||||
|
SmartDialog.showLoading(msg: 'loading');
|
||||||
|
await ctr.onRefresh();
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget searchArticlePanel(BuildContext context, ctr, list) {
|
Widget searchArticlePanel(BuildContext context, ctr, list) {
|
||||||
TextStyle textStyle = TextStyle(
|
TextStyle textStyle = TextStyle(
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
color: Theme.of(context).colorScheme.outline);
|
color: Theme.of(context).colorScheme.outline);
|
||||||
return ListView.builder(
|
return Padding(
|
||||||
controller: ctr!.scrollController,
|
padding: const EdgeInsets.only(top: 36),
|
||||||
itemCount: list.length,
|
child: list!.isNotEmpty
|
||||||
itemBuilder: (context, index) {
|
? ListView.builder(
|
||||||
return InkWell(
|
controller: ctr!.scrollController,
|
||||||
onTap: () {
|
addAutomaticKeepAlives: false,
|
||||||
Get.toNamed('/read', parameters: {
|
addRepaintBoundaries: false,
|
||||||
'title': list[index].subTitle,
|
itemCount: list.length,
|
||||||
'id': list[index].id.toString(),
|
itemBuilder: (context, index) {
|
||||||
'articleType': 'read'
|
return InkWell(
|
||||||
});
|
onTap: () {
|
||||||
},
|
Get.toNamed('/read', parameters: {
|
||||||
child: Padding(
|
'title': list[index].subTitle,
|
||||||
padding: const EdgeInsets.fromLTRB(
|
'id': list[index].id.toString(),
|
||||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
'articleType': 'read'
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
});
|
||||||
final double width = (boxConstraints.maxWidth -
|
},
|
||||||
StyleString.cardSpace *
|
child: Padding(
|
||||||
6 /
|
padding: const EdgeInsets.fromLTRB(
|
||||||
MediaQuery.textScalerOf(context).scale(1.0)) /
|
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||||
2;
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
return Container(
|
final double width = (boxConstraints.maxWidth -
|
||||||
constraints: const BoxConstraints(minHeight: 88),
|
StyleString.cardSpace *
|
||||||
height: width / StyleString.aspectRatio,
|
6 /
|
||||||
child: Row(
|
MediaQuery.textScalerOf(context).scale(1.0)) /
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
2;
|
||||||
children: <Widget>[
|
return Container(
|
||||||
if (list[index].imageUrls != null &&
|
constraints: const BoxConstraints(minHeight: 88),
|
||||||
list[index].imageUrls.isNotEmpty)
|
height: width / StyleString.aspectRatio,
|
||||||
AspectRatio(
|
child: Row(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
|
||||||
return NetworkImgLayer(
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
src: list[index].imageUrls.first,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: <Widget>[
|
||||||
RichText(
|
if (list[index].imageUrls != null &&
|
||||||
maxLines: 2,
|
list[index].imageUrls.isNotEmpty)
|
||||||
text: TextSpan(
|
AspectRatio(
|
||||||
children: [
|
aspectRatio: StyleString.aspectRatio,
|
||||||
for (var i in list[index].title) ...[
|
child: LayoutBuilder(
|
||||||
TextSpan(
|
builder: (context, boxConstraints) {
|
||||||
text: i['text'],
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
style: TextStyle(
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
fontWeight: FontWeight.w500,
|
return NetworkImgLayer(
|
||||||
letterSpacing: 0.3,
|
width: maxWidth,
|
||||||
color: i['type'] == 'em'
|
height: maxHeight,
|
||||||
? Theme.of(context)
|
src: list[index].imageUrls.first,
|
||||||
.colorScheme
|
);
|
||||||
.primary
|
}),
|
||||||
: Theme.of(context)
|
),
|
||||||
.colorScheme
|
Expanded(
|
||||||
.onSurface,
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
RichText(
|
||||||
|
maxLines: 2,
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
for (var i in list[index].title) ...[
|
||||||
|
TextSpan(
|
||||||
|
text: i['text'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0.3,
|
||||||
|
color: i['type'] == 'em'
|
||||||
|
? Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
const Spacer(),
|
||||||
],
|
Text(
|
||||||
|
Utils.dateFormat(list[index].pubTime,
|
||||||
|
formatType: 'detail'),
|
||||||
|
style: textStyle),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text('${list[index].view}浏览',
|
||||||
|
style: textStyle),
|
||||||
|
Text(' • ', style: textStyle),
|
||||||
|
Text('${list[index].reply}评论',
|
||||||
|
style: textStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
Utils.dateFormat(list[index].pubTime,
|
|
||||||
formatType: 'detail'),
|
|
||||||
style: textStyle),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text('${list[index].view}浏览', style: textStyle),
|
|
||||||
Text(' • ', style: textStyle),
|
|
||||||
Text('${list[index].reply}评论', style: textStyle),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
}),
|
||||||
],
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
}),
|
)
|
||||||
),
|
: CustomScrollView(
|
||||||
);
|
slivers: [
|
||||||
},
|
HttpError(
|
||||||
|
errMsg: '没有数据',
|
||||||
|
isShowBtn: false,
|
||||||
|
fn: () => {},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomFilterChip extends StatelessWidget {
|
||||||
|
const CustomFilterChip({
|
||||||
|
this.label,
|
||||||
|
this.type,
|
||||||
|
this.selectedType,
|
||||||
|
this.callFn,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String? label;
|
||||||
|
final ArticleFilterType? type;
|
||||||
|
final ArticleFilterType? selectedType;
|
||||||
|
final Function? callFn;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 34,
|
||||||
|
child: FilterChip(
|
||||||
|
padding: const EdgeInsets.only(left: 11, right: 11),
|
||||||
|
labelPadding: EdgeInsets.zero,
|
||||||
|
label: Text(
|
||||||
|
label!,
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
color: type == selectedType
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.outline),
|
||||||
|
selected: type == selectedType,
|
||||||
|
showCheckmark: false,
|
||||||
|
shape: ContinuousRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
selectedColor: Colors.transparent,
|
||||||
|
// backgroundColor:
|
||||||
|
// Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
side: BorderSide.none,
|
||||||
|
onSelected: (bool selected) => callFn!(selected),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArticlePanelController extends GetxController {
|
||||||
|
RxList<Map> filterList = [{}].obs;
|
||||||
|
Rx<ArticleFilterType> selectedType = ArticleFilterType.values.first.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
List<Map<String, dynamic>> list = ArticleFilterType.values
|
||||||
|
.map((type) => {
|
||||||
|
'label': type.description,
|
||||||
|
'type': type,
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
filterList.value = list;
|
||||||
|
super.onInit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/utils/global_data_cache.dart';
|
import 'package:pilipala/utils/global_data_cache.dart';
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ class _PlayGesturePageState extends State<PlayGesturePage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
fullScreenGestureMode = setting.get(SettingBoxKey.fullScreenGestureMode,
|
fullScreenGestureMode = setting.get(SettingBoxKey.fullScreenGestureMode,
|
||||||
defaultValue: FullScreenGestureMode.values.last.index);
|
defaultValue: FullScreenGestureMode.fromBottomtoTop.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -71,6 +72,7 @@ class _PlayGesturePageState extends State<PlayGesturePage> {
|
|||||||
GlobalDataCache().fullScreenGestureMode.index;
|
GlobalDataCache().fullScreenGestureMode.index;
|
||||||
setting.put(
|
setting.put(
|
||||||
SettingBoxKey.fullScreenGestureMode, fullScreenGestureMode);
|
SettingBoxKey.fullScreenGestureMode, fullScreenGestureMode);
|
||||||
|
SmartDialog.showToast('设置成功');
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -675,7 +675,6 @@ class VideoDetailController extends GetxController
|
|||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
super.onClose();
|
super.onClose();
|
||||||
plPlayerController.dispose();
|
|
||||||
tabCtr.removeListener(() {
|
tabCtr.removeListener(() {
|
||||||
onTabChanged();
|
onTabChanged();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import 'dart:async';
|
|||||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
import 'package:bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.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:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/constants.dart';
|
import 'package:pilipala/http/constants.dart';
|
||||||
@ -154,11 +153,10 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
if (hasLike.value && hasCoin.value && hasFav.value) {
|
if (hasLike.value && hasCoin.value && hasFav.value) {
|
||||||
// 已点赞、投币、收藏
|
// 已点赞、投币、收藏
|
||||||
SmartDialog.showToast('🙏 UP已经收到了~');
|
SmartDialog.showToast('UP已经收到了~');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var result = await VideoHttp.oneThree(bvid: bvid);
|
var result = await VideoHttp.oneThree(bvid: bvid);
|
||||||
print('🤣🦴:${result["data"]}');
|
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
hasLike.value = result["data"]["like"];
|
hasLike.value = result["data"]["like"];
|
||||||
hasCoin.value = result["data"]["coin"];
|
hasCoin.value = result["data"]["coin"];
|
||||||
@ -413,7 +411,12 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 修改分P或番剧分集
|
// 修改分P或番剧分集
|
||||||
Future changeSeasonOrbangu(bvid, cid, aid, cover) async {
|
Future changeSeasonOrbangu(
|
||||||
|
String bvid,
|
||||||
|
int cid,
|
||||||
|
int? aid,
|
||||||
|
String? cover,
|
||||||
|
) async {
|
||||||
// 重新获取视频资源
|
// 重新获取视频资源
|
||||||
final VideoDetailController videoDetailCtr =
|
final VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: heroTag);
|
Get.find<VideoDetailController>(tag: heroTag);
|
||||||
@ -424,13 +427,14 @@ class VideoIntroController extends GetxController {
|
|||||||
releatedCtr.queryRelatedVideo();
|
releatedCtr.queryRelatedVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
videoDetailCtr.bvid = bvid;
|
videoDetailCtr
|
||||||
videoDetailCtr.oid.value = aid ?? IdUtils.bv2av(bvid);
|
..bvid = bvid
|
||||||
videoDetailCtr.cid.value = cid;
|
..oid.value = aid ?? IdUtils.bv2av(bvid)
|
||||||
videoDetailCtr.danmakuCid.value = cid;
|
..cid.value = cid
|
||||||
videoDetailCtr.cover.value = cover;
|
..danmakuCid.value = cid
|
||||||
videoDetailCtr.queryVideoUrl();
|
..cover.value = cover ?? ''
|
||||||
videoDetailCtr.clearSubtitleContent();
|
..queryVideoUrl()
|
||||||
|
..clearSubtitleContent();
|
||||||
await videoDetailCtr.getSubtitle();
|
await videoDetailCtr.getSubtitle();
|
||||||
videoDetailCtr.setSubtitleContent();
|
videoDetailCtr.setSubtitleContent();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
@ -480,7 +484,13 @@ class VideoIntroController extends GetxController {
|
|||||||
final List episodes = [];
|
final List episodes = [];
|
||||||
bool isPages = false;
|
bool isPages = false;
|
||||||
late String cover;
|
late String cover;
|
||||||
if (videoDetail.value.ugcSeason != null) {
|
final VideoDetailController videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
|
||||||
|
/// 优先稍后再看、收藏夹
|
||||||
|
if (videoDetailCtr.isWatchLaterVisible.value) {
|
||||||
|
episodes.addAll(videoDetailCtr.mediaList);
|
||||||
|
} else if (videoDetail.value.ugcSeason != null) {
|
||||||
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
|
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
|
||||||
final List<SectionItem> sections = ugcSeason.sections!;
|
final List<SectionItem> sections = ugcSeason.sections!;
|
||||||
for (int i = 0; i < sections.length; i++) {
|
for (int i = 0; i < sections.length; i++) {
|
||||||
@ -497,10 +507,15 @@ class VideoIntroController extends GetxController {
|
|||||||
episodes.indexWhere((e) => e.cid == lastPlayCid.value);
|
episodes.indexWhere((e) => e.cid == lastPlayCid.value);
|
||||||
int nextIndex = currentIndex + 1;
|
int nextIndex = currentIndex + 1;
|
||||||
cover = episodes[nextIndex].cover;
|
cover = episodes[nextIndex].cover;
|
||||||
final VideoDetailController videoDetailCtr =
|
|
||||||
Get.find<VideoDetailController>(tag: heroTag);
|
|
||||||
final PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
final PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||||
|
|
||||||
|
int cid = episodes[nextIndex].cid!;
|
||||||
|
while (cid == -1) {
|
||||||
|
nextIndex += 1;
|
||||||
|
SmartDialog.showToast('当前视频暂不支持播放,自动跳过');
|
||||||
|
cid = episodes[nextIndex].cid!;
|
||||||
|
}
|
||||||
|
|
||||||
// 列表循环
|
// 列表循环
|
||||||
if (nextIndex >= episodes.length) {
|
if (nextIndex >= episodes.length) {
|
||||||
if (platRepeat == PlayRepeat.listCycle) {
|
if (platRepeat == PlayRepeat.listCycle) {
|
||||||
@ -510,7 +525,6 @@ class VideoIntroController extends GetxController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final int cid = episodes[nextIndex].cid!;
|
|
||||||
final String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
|
final String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
|
||||||
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
|
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
|
||||||
changeSeasonOrbangu(rBvid, cid, rAid, cover);
|
changeSeasonOrbangu(rBvid, cid, rAid, cover);
|
||||||
@ -604,4 +618,34 @@ class VideoIntroController extends GetxController {
|
|||||||
).buildShowContent(Get.context!),
|
).buildShowContent(Get.context!),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
oneThreeDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('是否一键三连'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => navigator!.pop(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(Get.context!).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
actionOneThree();
|
||||||
|
navigator!.pop();
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
import 'dart:ffi';
|
|
||||||
|
|
||||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
import 'package:bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:expandable/expandable.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -151,11 +148,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
RxBool isExpand = false.obs;
|
RxBool isExpand = false.obs;
|
||||||
late ExpandableController _expandableCtr;
|
late ExpandableController _expandableCtr;
|
||||||
|
|
||||||
// 一键三连动画
|
|
||||||
late AnimationController _controller;
|
|
||||||
late Animation<double> _scaleTransition;
|
|
||||||
final RxDouble _progress = 0.0.obs;
|
|
||||||
|
|
||||||
void Function()? handleState(Future<dynamic> Function() action) {
|
void Function()? handleState(Future<dynamic> Function() action) {
|
||||||
return isProcessing
|
return isProcessing
|
||||||
? null
|
? null
|
||||||
@ -178,26 +170,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
owner = widget.videoDetail!.owner;
|
owner = widget.videoDetail!.owner;
|
||||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||||
_expandableCtr = ExpandableController(initialExpanded: false);
|
_expandableCtr = ExpandableController(initialExpanded: false);
|
||||||
|
|
||||||
/// 一键三连动画
|
|
||||||
_controller = AnimationController(
|
|
||||||
duration: const Duration(milliseconds: 1500),
|
|
||||||
reverseDuration: const Duration(milliseconds: 300),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
_scaleTransition = Tween<double>(begin: 0.5, end: 1.5).animate(_controller)
|
|
||||||
..addListener(() async {
|
|
||||||
_progress.value =
|
|
||||||
double.parse((_scaleTransition.value - 0.5).toStringAsFixed(3));
|
|
||||||
if (_progress.value == 1) {
|
|
||||||
if (_controller.status == AnimationStatus.completed) {
|
|
||||||
await videoIntroController.actionOneThree();
|
|
||||||
}
|
|
||||||
_progress.value = 0;
|
|
||||||
_scaleTransition.removeListener(() {});
|
|
||||||
_controller.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
@ -279,8 +251,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_expandableCtr.dispose();
|
_expandableCtr.dispose();
|
||||||
_controller.dispose();
|
|
||||||
_scaleTransition.removeListener(() {});
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -573,131 +543,34 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
Widget actionGrid(BuildContext context, videoIntroController) {
|
Widget actionGrid(BuildContext context, videoIntroController) {
|
||||||
final actionTypeSort = GlobalDataCache().actionTypeSort;
|
final actionTypeSort = GlobalDataCache().actionTypeSort;
|
||||||
|
|
||||||
Widget progressWidget(progress) {
|
|
||||||
return SizedBox(
|
|
||||||
width: const IconThemeData.fallback().size! + 5,
|
|
||||||
height: const IconThemeData.fallback().size! + 5,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
value: progress.value,
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Widget> menuListWidgets = {
|
Map<String, Widget> menuListWidgets = {
|
||||||
'like': Obx(
|
'like': Obx(
|
||||||
() {
|
() => ActionItem(
|
||||||
bool likeStatus = videoIntroController.hasLike.value;
|
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||||
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||||
return Stack(
|
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||||
children: [
|
onLongPress: () => videoIntroController.oneThreeDialog(),
|
||||||
Positioned(
|
selectStatus: videoIntroController.hasLike.value,
|
||||||
top: ((Get.size.width - 24) / 5) / 2 -
|
text: widget.videoDetail!.stat!.like!.toString(),
|
||||||
(const IconThemeData.fallback().size!),
|
),
|
||||||
left: ((Get.size.width - 24) / 5) / 2 -
|
|
||||||
(const IconThemeData.fallback().size! + 5) / 2,
|
|
||||||
child: progressWidget(_progress)),
|
|
||||||
InkWell(
|
|
||||||
onTapDown: (details) {
|
|
||||||
feedBack();
|
|
||||||
if (videoIntroController.userInfo == null) {
|
|
||||||
SmartDialog.showToast('账号未登录');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_controller.forward();
|
|
||||||
},
|
|
||||||
onTapUp: (TapUpDetails details) {
|
|
||||||
if (_progress.value == 0) {
|
|
||||||
feedBack();
|
|
||||||
EasyThrottle.throttle(
|
|
||||||
'my-throttler', const Duration(milliseconds: 200), () {
|
|
||||||
videoIntroController.actionLikeVideo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_controller.reverse();
|
|
||||||
},
|
|
||||||
onTapCancel: () {
|
|
||||||
_controller.reverse();
|
|
||||||
},
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
child: SizedBox(
|
|
||||||
width: (Get.size.width - 24) / 5,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
transitionBuilder:
|
|
||||||
(Widget child, Animation<double> animation) {
|
|
||||||
return ScaleTransition(
|
|
||||||
scale: animation, child: child);
|
|
||||||
},
|
|
||||||
child: Icon(
|
|
||||||
key: ValueKey<bool>(likeStatus),
|
|
||||||
likeStatus
|
|
||||||
? FontAwesomeIcons.solidThumbsUp
|
|
||||||
: FontAwesomeIcons.thumbsUp,
|
|
||||||
color: likeStatus
|
|
||||||
? colorScheme.primary
|
|
||||||
: colorScheme.outline,
|
|
||||||
size: 21,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text(
|
|
||||||
widget.videoDetail!.stat!.like!.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: likeStatus ? colorScheme.primary : null,
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.labelSmall!.fontSize,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
'coin': Obx(
|
'coin': Obx(
|
||||||
() => Stack(
|
() => ActionItem(
|
||||||
children: [
|
icon: const Icon(FontAwesomeIcons.b),
|
||||||
Positioned(
|
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||||
top: ((Get.size.width - 24) / 5) / 2 -
|
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||||
(const IconThemeData.fallback().size!),
|
selectStatus: videoIntroController.hasCoin.value,
|
||||||
left: ((Get.size.width - 24) / 5) / 2 -
|
text: widget.videoDetail!.stat!.coin!.toString(),
|
||||||
(const IconThemeData.fallback().size! + 5) / 2,
|
|
||||||
child: progressWidget(_progress)),
|
|
||||||
ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.b),
|
|
||||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
|
||||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
|
||||||
selectStatus: videoIntroController.hasCoin.value,
|
|
||||||
text: widget.videoDetail!.stat!.coin!.toString(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'collect': Obx(
|
'collect': Obx(
|
||||||
() => Stack(
|
() => ActionItem(
|
||||||
children: [
|
icon: const Icon(FontAwesomeIcons.star),
|
||||||
Positioned(
|
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||||
top: ((Get.size.width - 24) / 5) / 2 -
|
onTap: () => showFavBottomSheet(),
|
||||||
(const IconThemeData.fallback().size!),
|
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||||
left: ((Get.size.width - 24) / 5) / 2 -
|
selectStatus: videoIntroController.hasFav.value,
|
||||||
(const IconThemeData.fallback().size! + 5) / 2,
|
text: widget.videoDetail!.stat!.favorite!.toString(),
|
||||||
child: progressWidget(_progress)),
|
|
||||||
ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.star),
|
|
||||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
|
||||||
onTap: () => showFavBottomSheet(),
|
|
||||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
|
||||||
selectStatus: videoIntroController.hasFav.value,
|
|
||||||
text: widget.videoDetail!.stat!.favorite!.toString(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'watchLater': ActionItem(
|
'watchLater': ActionItem(
|
||||||
|
|||||||
@ -153,7 +153,17 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
child: Container(
|
child: Container(
|
||||||
height: 40,
|
height: 40,
|
||||||
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
|
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
|
||||||
color: Theme.of(context).colorScheme.surface,
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
blurRadius: 0.0,
|
||||||
|
spreadRadius: 0.0,
|
||||||
|
offset: const Offset(2, 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/reply.dart';
|
||||||
import 'package:pilipala/models/common/reply_type.dart';
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
import 'package:pilipala/pages/main/index.dart';
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
@ -18,6 +19,7 @@ import 'package:pilipala/plugin/pl_gallery/index.dart';
|
|||||||
import 'package:pilipala/plugin/pl_popup/index.dart';
|
import 'package:pilipala/plugin/pl_popup/index.dart';
|
||||||
import 'package:pilipala/utils/app_scheme.dart';
|
import 'package:pilipala/utils/app_scheme.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/global_data_cache.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/url_utils.dart';
|
import 'package:pilipala/utils/url_utils.dart';
|
||||||
@ -48,6 +50,8 @@ class ReplyItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final bool isOwner = int.parse(replyItem!.member!.mid!) ==
|
||||||
|
(GlobalDataCache().userInfo?.mid ?? -1);
|
||||||
return Material(
|
return Material(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
// 点击整个评论区 评论详情/回复
|
// 点击整个评论区 评论详情/回复
|
||||||
@ -73,6 +77,7 @@ class ReplyItem extends StatelessWidget {
|
|||||||
return MorePanel(
|
return MorePanel(
|
||||||
item: replyItem,
|
item: replyItem,
|
||||||
mainFloor: true,
|
mainFloor: true,
|
||||||
|
isOwner: isOwner,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -195,25 +200,36 @@ class ReplyItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
RichText(
|
||||||
children: <Widget>[
|
text: TextSpan(
|
||||||
Text(
|
children: [
|
||||||
Utils.dateFormat(replyItem!.ctime),
|
TextSpan(
|
||||||
style: TextStyle(
|
text: Utils.dateFormat(replyItem!.ctime),
|
||||||
fontSize: textTheme.labelSmall!.fontSize,
|
|
||||||
color: colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (replyItem!.replyControl != null &&
|
|
||||||
replyItem!.replyControl!.location != '')
|
|
||||||
Text(
|
|
||||||
' • ${replyItem!.replyControl!.location!}',
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: textTheme.labelSmall!.fontSize,
|
fontSize: textTheme.labelSmall!.fontSize,
|
||||||
color: colorScheme.outline),
|
color: colorScheme.outline,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
if (replyItem!.replyControl != null &&
|
||||||
)
|
replyItem!.replyControl!.location != '')
|
||||||
|
TextSpan(
|
||||||
|
text: ' • ${replyItem!.replyControl!.location!}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: textTheme.labelSmall!.fontSize,
|
||||||
|
color: colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (replyItem!.invisible!)
|
||||||
|
TextSpan(
|
||||||
|
text: ' • 隐藏的评论',
|
||||||
|
style: TextStyle(
|
||||||
|
color: colorScheme.outline,
|
||||||
|
fontSize: textTheme.labelSmall!.fontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -698,14 +714,11 @@ InlineSpan buildContent(
|
|||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
} else if (RegExp(r'^cv\d+$').hasMatch(matchStr)) {
|
} else if (RegExp(r'^cv\d+$').hasMatch(matchStr)) {
|
||||||
Get.toNamed(
|
Get.toNamed('/read', parameters: {
|
||||||
'/webview',
|
'title': title,
|
||||||
parameters: {
|
'id': Utils.matchNum(matchStr).first.toString(),
|
||||||
'url': 'https://www.bilibili.com/read/$matchStr',
|
'articleType': 'read',
|
||||||
'type': 'url',
|
});
|
||||||
'pageTitle': title
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
|
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
|
||||||
SchemeEntity scheme = SchemeEntity(
|
SchemeEntity scheme = SchemeEntity(
|
||||||
@ -717,7 +730,7 @@ InlineSpan buildContent(
|
|||||||
source: '',
|
source: '',
|
||||||
dataString: matchStr,
|
dataString: matchStr,
|
||||||
);
|
);
|
||||||
PiliSchame.fullPathPush(scheme);
|
PiliSchame.httpsScheme(scheme);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (appUrlSchema.startsWith('bilibili://search')) {
|
if (appUrlSchema.startsWith('bilibili://search')) {
|
||||||
@ -1004,10 +1017,12 @@ InlineSpan buildContent(
|
|||||||
class MorePanel extends StatelessWidget {
|
class MorePanel extends StatelessWidget {
|
||||||
final dynamic item;
|
final dynamic item;
|
||||||
final bool mainFloor;
|
final bool mainFloor;
|
||||||
|
final bool isOwner;
|
||||||
const MorePanel({
|
const MorePanel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
this.mainFloor = false,
|
this.mainFloor = false,
|
||||||
|
this.isOwner = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<dynamic> menuActionHandler(String type) async {
|
Future<dynamic> menuActionHandler(String type) async {
|
||||||
@ -1043,9 +1058,43 @@ class MorePanel extends StatelessWidget {
|
|||||||
// case 'report':
|
// case 'report':
|
||||||
// SmartDialog.showToast('举报');
|
// SmartDialog.showToast('举报');
|
||||||
// break;
|
// break;
|
||||||
// case 'delete':
|
case 'delete':
|
||||||
// SmartDialog.showToast('删除');
|
// 删除评论提示
|
||||||
// break;
|
await showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('删除评论'),
|
||||||
|
content: const Text('删除评论后,评论下所有回复将被删除,确定删除吗?'),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: Text('取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline)),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Get.back();
|
||||||
|
var result = await ReplyHttp.replyDel(
|
||||||
|
type: item.type!,
|
||||||
|
oid: item.oid!,
|
||||||
|
rpid: item.rpid!,
|
||||||
|
);
|
||||||
|
if (result['status']) {
|
||||||
|
SmartDialog.showToast('评论删除成功,需手动刷新');
|
||||||
|
Get.back();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1054,6 +1103,7 @@ class MorePanel extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
TextTheme textTheme = Theme.of(context).textTheme;
|
TextTheme textTheme = Theme.of(context).textTheme;
|
||||||
|
Color errorColor = colorScheme.error;
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -1106,12 +1156,14 @@ class MorePanel extends StatelessWidget {
|
|||||||
// leading: Icon(Icons.report_outlined, color: errorColor),
|
// leading: Icon(Icons.report_outlined, color: errorColor),
|
||||||
// title: Text('举报', style: TextStyle(color: errorColor)),
|
// title: Text('举报', style: TextStyle(color: errorColor)),
|
||||||
// ),
|
// ),
|
||||||
// ListTile(
|
if (isOwner)
|
||||||
// onTap: () async => await menuActionHandler('del'),
|
ListTile(
|
||||||
// minLeadingWidth: 0,
|
onTap: () async => await menuActionHandler('delete'),
|
||||||
// leading: Icon(Icons.delete_outline, color: errorColor),
|
minLeadingWidth: 0,
|
||||||
// title: Text('删除', style: TextStyle(color: errorColor)),
|
leading: Icon(Icons.delete_outline, color: errorColor),
|
||||||
// ),
|
title: Text('删除评论',
|
||||||
|
style: textTheme.titleSmall!.copyWith(color: errorColor)),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||||
import 'package:floating/floating.dart';
|
import 'package:floating/floating.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
@ -68,10 +69,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
late final AppLifecycleListener _lifecycleListener;
|
late final AppLifecycleListener _lifecycleListener;
|
||||||
late double statusHeight;
|
late double statusHeight;
|
||||||
|
|
||||||
// 稍后再看控制器
|
|
||||||
// late AnimationController _laterCtr;
|
|
||||||
// late Animation<Offset> _laterOffsetAni;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -85,14 +82,16 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
videoIntroController.videoDetail.listen((value) {
|
videoIntroController.videoDetail.listen((value) {
|
||||||
videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value);
|
videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value);
|
||||||
});
|
});
|
||||||
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
if (vdCtr.videoType == SearchType.media_bangumi) {
|
||||||
bangumiIntroController.bangumiDetail.listen((value) {
|
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||||
videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value);
|
bangumiIntroController.bangumiDetail.listen((value) {
|
||||||
});
|
videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value);
|
||||||
vdCtr.cid.listen((p0) {
|
});
|
||||||
videoPlayerServiceHandler.onVideoDetailChange(
|
vdCtr.cid.listen((p0) {
|
||||||
bangumiIntroController.bangumiDetail.value, p0);
|
videoPlayerServiceHandler.onVideoDetailChange(
|
||||||
});
|
bangumiIntroController.bangumiDetail.value, p0);
|
||||||
|
});
|
||||||
|
}
|
||||||
statusBarHeight = localCache.get('statusBarHeight');
|
statusBarHeight = localCache.get('statusBarHeight');
|
||||||
autoExitFullcreen =
|
autoExitFullcreen =
|
||||||
setting.get(SettingBoxKey.enableAutoExit, defaultValue: false);
|
setting.get(SettingBoxKey.enableAutoExit, defaultValue: false);
|
||||||
@ -108,7 +107,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
}
|
}
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
lifecycleListener();
|
lifecycleListener();
|
||||||
// watchLaterControllerInit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取视频资源,初始化播放器
|
// 获取视频资源,初始化播放器
|
||||||
@ -242,8 +240,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
appbarStream.close();
|
appbarStream.close();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_lifecycleListener.dispose();
|
_lifecycleListener.dispose();
|
||||||
// _laterCtr.dispose();
|
|
||||||
// _laterOffsetAni.removeListener(() {});
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,6 +293,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
plPlayerController?.play();
|
plPlayerController?.play();
|
||||||
}
|
}
|
||||||
plPlayerController?.addStatusLister(playerListener);
|
plPlayerController?.addStatusLister(playerListener);
|
||||||
|
appbarStream.add(0);
|
||||||
super.didPopNext();
|
super.didPopNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,21 +487,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 稍后再看控制器初始化
|
|
||||||
// void watchLaterControllerInit() {
|
|
||||||
// _laterCtr = AnimationController(
|
|
||||||
// duration: const Duration(milliseconds: 300),
|
|
||||||
// vsync: this,
|
|
||||||
// );
|
|
||||||
// _laterOffsetAni = Tween<Offset>(
|
|
||||||
// begin: const Offset(0.0, 1.0),
|
|
||||||
// end: Offset.zero,
|
|
||||||
// ).animate(CurvedAnimation(
|
|
||||||
// parent: _laterCtr,
|
|
||||||
// curve: Curves.easeInOut,
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sizeContext = MediaQuery.sizeOf(context);
|
final sizeContext = MediaQuery.sizeOf(context);
|
||||||
@ -529,6 +511,14 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
exitFullScreen();
|
exitFullScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
|
SystemUiOverlayStyle(
|
||||||
|
systemNavigationBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarIconBrightness:
|
||||||
|
Get.isDarkMode ? Brightness.light : Brightness.dark,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Widget buildLoadingWidget() {
|
Widget buildLoadingWidget() {
|
||||||
return Center(child: Lottie.asset('assets/loading.json', width: 200));
|
return Center(child: Lottie.asset('assets/loading.json', width: 200));
|
||||||
}
|
}
|
||||||
@ -615,10 +605,16 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
key: vdCtr.scaffoldKey,
|
key: vdCtr.scaffoldKey,
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(0),
|
preferredSize: const Size.fromHeight(0),
|
||||||
child: AppBar(
|
child: StreamBuilder(
|
||||||
backgroundColor: Colors.black,
|
stream: appbarStream.stream.distinct(),
|
||||||
elevation: 0,
|
initialData: 0,
|
||||||
scrolledUnderElevation: 0,
|
builder: ((context, snapshot) {
|
||||||
|
return AppBar(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: ExtendedNestedScrollView(
|
body: ExtendedNestedScrollView(
|
||||||
|
|||||||
@ -109,7 +109,7 @@ class _MediaListPanelState extends State<MediaListPanel> {
|
|||||||
var item = mediaList[index];
|
var item = mediaList[index];
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
String bvid = item.bvId!;
|
String bvid = item.bvid!;
|
||||||
int? aid = item.id;
|
int? aid = item.id;
|
||||||
String cover = item.cover ?? '';
|
String cover = item.cover ?? '';
|
||||||
final int cid =
|
final int cid =
|
||||||
@ -173,7 +173,7 @@ class _MediaListPanelState extends State<MediaListPanel> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: item.bvId == widget.bvid
|
color: item.bvid == widget.bvid
|
||||||
? Theme.of(context)
|
? Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.primary
|
.primary
|
||||||
|
|||||||
@ -228,7 +228,7 @@ class SessionItem extends StatelessWidget {
|
|||||||
parameters: {
|
parameters: {
|
||||||
'talkerId': sessionItem.talkerId.toString(),
|
'talkerId': sessionItem.talkerId.toString(),
|
||||||
'name': sessionItem.accountInfo.name,
|
'name': sessionItem.accountInfo.name,
|
||||||
'face': sessionItem.accountInfo.face,
|
'face': sessionItem.accountInfo.face ?? '',
|
||||||
'mid': (sessionItem.accountInfo?.mid ?? 0).toString(),
|
'mid': (sessionItem.accountInfo?.mid ?? 0).toString(),
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
},
|
},
|
||||||
@ -244,7 +244,7 @@ class SessionItem extends StatelessWidget {
|
|||||||
width: 45,
|
width: 45,
|
||||||
height: 45,
|
height: 45,
|
||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
src: sessionItem.accountInfo.face,
|
src: sessionItem.accountInfo.face ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1045,6 +1045,14 @@ class PlPlayerController {
|
|||||||
|
|
||||||
/// 缓存本次弹幕选项
|
/// 缓存本次弹幕选项
|
||||||
cacheDanmakuOption() {
|
cacheDanmakuOption() {
|
||||||
|
final cache = GlobalDataCache();
|
||||||
|
cache.blockTypes = blockTypes;
|
||||||
|
cache.showArea = showArea;
|
||||||
|
cache.opacityVal = opacityVal;
|
||||||
|
cache.fontSizeVal = fontSizeVal;
|
||||||
|
cache.danmakuDurationVal = danmakuDurationVal;
|
||||||
|
cache.strokeWidth = strokeWidth;
|
||||||
|
|
||||||
localCache.put(LocalCacheKey.danmakuBlockType, blockTypes);
|
localCache.put(LocalCacheKey.danmakuBlockType, blockTypes);
|
||||||
localCache.put(LocalCacheKey.danmakuShowArea, showArea);
|
localCache.put(LocalCacheKey.danmakuShowArea, showArea);
|
||||||
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);
|
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);
|
||||||
|
|||||||
@ -204,6 +204,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
widget.controller.brightness.value = value;
|
widget.controller.brightness.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isUsingFullScreenGestures(double tapPosition, double sectionWidth) {
|
||||||
|
return fullScreenGestureMode != FullScreenGestureMode.none &&
|
||||||
|
tapPosition < sectionWidth * 2;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
animationController.dispose();
|
animationController.dispose();
|
||||||
@ -638,7 +643,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
onVerticalDragUpdate: (DragUpdateDetails details) async {
|
onVerticalDragUpdate: (DragUpdateDetails details) async {
|
||||||
final double totalWidth = MediaQuery.sizeOf(context).width;
|
final double totalWidth = MediaQuery.sizeOf(context).width;
|
||||||
final double tapPosition = details.localPosition.dx;
|
final double tapPosition = details.localPosition.dx;
|
||||||
final double sectionWidth = totalWidth / 3;
|
final double sectionWidth =
|
||||||
|
fullScreenGestureMode == FullScreenGestureMode.none
|
||||||
|
? totalWidth / 2
|
||||||
|
: totalWidth / 3;
|
||||||
final double delta = details.delta.dy;
|
final double delta = details.delta.dy;
|
||||||
|
|
||||||
/// 锁定时禁用
|
/// 锁定时禁用
|
||||||
@ -660,12 +668,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
_brightnessValue.value - delta / level;
|
_brightnessValue.value - delta / level;
|
||||||
final double result = brightness.clamp(0.0, 1.0);
|
final double result = brightness.clamp(0.0, 1.0);
|
||||||
setBrightness(result);
|
setBrightness(result);
|
||||||
} else if (tapPosition < sectionWidth * 2) {
|
} else if (isUsingFullScreenGestures(tapPosition, sectionWidth)) {
|
||||||
// 全屏
|
// 全屏
|
||||||
final double dy = details.delta.dy;
|
final double dy = details.delta.dy;
|
||||||
const double threshold = 7.0; // 滑动阈值
|
const double threshold = 7.0; // 滑动阈值
|
||||||
final bool flag =
|
final bool flag = fullScreenGestureMode !=
|
||||||
fullScreenGestureMode != FullScreenGestureMode.values.last;
|
FullScreenGestureMode.fromBottomtoTop;
|
||||||
if (dy > _distance.value &&
|
if (dy > _distance.value &&
|
||||||
dy > threshold &&
|
dy > threshold &&
|
||||||
!_.controlsLock.value) {
|
!_.controlsLock.value) {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:appscheme/appscheme.dart';
|
import 'package:appscheme/appscheme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -10,22 +11,29 @@ import 'url_utils.dart';
|
|||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
|
|
||||||
class PiliSchame {
|
class PiliSchame {
|
||||||
|
static late AppLinks appLinks;
|
||||||
static AppScheme appScheme = AppSchemeImpl.getInstance()!;
|
static AppScheme appScheme = AppSchemeImpl.getInstance()!;
|
||||||
static Future<void> init() async {
|
static Future<void> init() async {
|
||||||
///
|
appLinks = AppLinks();
|
||||||
final SchemeEntity? value = await appScheme.getInitScheme();
|
appLinks.uriLinkStream.listen((Uri uri) {
|
||||||
if (value != null) {
|
final String scheme = uri.scheme;
|
||||||
_routePush(value);
|
if (RegExp(r'^pili', caseSensitive: false).hasMatch(scheme)) {
|
||||||
}
|
piliScheme(uri);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appScheme.getInitScheme().then((SchemeEntity? value) {
|
||||||
|
if (value != null) {
|
||||||
|
_routePush(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/// 完整链接进入 b23.无效
|
|
||||||
appScheme.getLatestScheme().then((SchemeEntity? value) {
|
appScheme.getLatestScheme().then((SchemeEntity? value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
_routePush(value);
|
_routePush(value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 注册从外部打开的Scheme监听信息 #
|
|
||||||
appScheme.registerSchemeListener().listen((SchemeEntity? event) {
|
appScheme.registerSchemeListener().listen((SchemeEntity? event) {
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
_routePush(event);
|
_routePush(event);
|
||||||
@ -36,88 +44,11 @@ class PiliSchame {
|
|||||||
/// 路由跳转
|
/// 路由跳转
|
||||||
static void _routePush(value) async {
|
static void _routePush(value) async {
|
||||||
final String scheme = value.scheme;
|
final String scheme = value.scheme;
|
||||||
final String host = value.host;
|
|
||||||
final String path = value.path;
|
|
||||||
if (scheme == 'bilibili') {
|
if (scheme == 'bilibili') {
|
||||||
switch (host) {
|
biliScheme(value);
|
||||||
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},
|
|
||||||
);
|
|
||||||
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('/opus', parameters: {
|
|
||||||
'title': '',
|
|
||||||
'id': opusId,
|
|
||||||
'articleType': 'opus',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'search':
|
|
||||||
Get.toNamed('/searchResult', parameters: {'keyword': ''});
|
|
||||||
break;
|
|
||||||
case 'article':
|
|
||||||
final String id = path.split('/').last.split('?').first;
|
|
||||||
Get.toNamed(
|
|
||||||
'/read',
|
|
||||||
parameters: {
|
|
||||||
'title': 'cv$id',
|
|
||||||
'id': 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') {
|
if (scheme == 'https') {
|
||||||
fullPathPush(value);
|
httpsScheme(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +79,7 @@ class PiliSchame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> fullPathPush(SchemeEntity value) async {
|
static Future<void> httpsScheme(SchemeEntity value) async {
|
||||||
// https://m.bilibili.com/bangumi/play/ss39708
|
// https://m.bilibili.com/bangumi/play/ss39708
|
||||||
// https | m.bilibili.com | /bangumi/play/ss39708
|
// https | m.bilibili.com | /bangumi/play/ss39708
|
||||||
// final String scheme = value.scheme!;
|
// final String scheme = value.scheme!;
|
||||||
@ -175,6 +106,11 @@ class PiliSchame {
|
|||||||
if (lastPathSegment.contains('ep')) {
|
if (lastPathSegment.contains('ep')) {
|
||||||
RoutePush.bangumiPush(null, Utils.matchNum(lastPathSegment).first);
|
RoutePush.bangumiPush(null, Utils.matchNum(lastPathSegment).first);
|
||||||
}
|
}
|
||||||
|
} else if (path.startsWith('/BV')) {
|
||||||
|
final String bvid = path.split('?').first.split('/').last;
|
||||||
|
_videoPush(null, bvid);
|
||||||
|
} else if (path.startsWith('/av')) {
|
||||||
|
_videoPush(Utils.matchNum(path.split('?').first).first, null);
|
||||||
}
|
}
|
||||||
} else if (host.contains('live')) {
|
} else if (host.contains('live')) {
|
||||||
int roomId = int.parse(path!.split('/').last);
|
int roomId = int.parse(path!.split('/').last);
|
||||||
@ -276,6 +212,140 @@ class PiliSchame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> biliScheme(SchemeEntity value) async {
|
||||||
|
final String host = value.host!;
|
||||||
|
final String path = value.path!;
|
||||||
|
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},
|
||||||
|
);
|
||||||
|
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('/opus', parameters: {
|
||||||
|
'title': '',
|
||||||
|
'id': opusId,
|
||||||
|
'articleType': 'opus',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'search':
|
||||||
|
Get.toNamed('/searchResult', parameters: {'keyword': ''});
|
||||||
|
break;
|
||||||
|
case 'article':
|
||||||
|
final String id = path.split('/').last.split('?').first;
|
||||||
|
Get.toNamed(
|
||||||
|
'/read',
|
||||||
|
parameters: {
|
||||||
|
'title': 'cv$id',
|
||||||
|
'id': 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> piliScheme(Uri value) async {
|
||||||
|
final String host = value.host;
|
||||||
|
final String path = value.path;
|
||||||
|
final String arg = path.split('/').last;
|
||||||
|
switch (host) {
|
||||||
|
case 'home':
|
||||||
|
case 'root':
|
||||||
|
Get.toNamed('/');
|
||||||
|
break;
|
||||||
|
case 'member':
|
||||||
|
if (arg != '') {
|
||||||
|
final int? mid = int.tryParse(arg);
|
||||||
|
if (mid == null) {
|
||||||
|
SmartDialog.showToast('用户id有误');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.toNamed<dynamic>(
|
||||||
|
'/member?mid=$mid',
|
||||||
|
arguments: <String, dynamic>{'face': null},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Get.toNamed('/mine');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'search':
|
||||||
|
if (arg != '') {
|
||||||
|
final String encodedArg = Uri.decodeComponent(arg);
|
||||||
|
Get.toNamed('/searchResult', parameters: {'keyword': encodedArg});
|
||||||
|
} else {
|
||||||
|
Get.toNamed('/search');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'setting':
|
||||||
|
Get.toNamed('/setting');
|
||||||
|
break;
|
||||||
|
case 'fav':
|
||||||
|
Get.toNamed('/fav');
|
||||||
|
break;
|
||||||
|
case 'history':
|
||||||
|
Get.toNamed('/history');
|
||||||
|
break;
|
||||||
|
case 'later':
|
||||||
|
Get.toNamed('/later');
|
||||||
|
break;
|
||||||
|
case 'msg':
|
||||||
|
Get.toNamed('/whisper');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Get.toNamed('/');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void _handleEpisodePath(String lastPathSegment, String redirectUrl) {
|
static void _handleEpisodePath(String lastPathSegment, String redirectUrl) {
|
||||||
final String seasonId = _extractIdFromPath(lastPathSegment);
|
final String seasonId = _extractIdFromPath(lastPathSegment);
|
||||||
RoutePush.bangumiPush(null, Utils.matchNum(seasonId).first);
|
RoutePush.bangumiPush(null, Utils.matchNum(seasonId).first);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
|
||||||
@ -50,13 +51,17 @@ class CacheManage {
|
|||||||
Future<double> getTotalSizeOfFilesInDir(final FileSystemEntity file) async {
|
Future<double> getTotalSizeOfFilesInDir(final FileSystemEntity file) async {
|
||||||
if (file is File) {
|
if (file is File) {
|
||||||
int length = await file.length();
|
int length = await file.length();
|
||||||
return double.parse(length.toString());
|
return length.toDouble();
|
||||||
}
|
}
|
||||||
if (file is Directory) {
|
if (file is Directory) {
|
||||||
final List<FileSystemEntity> children = file.listSync();
|
|
||||||
double total = 0;
|
double total = 0;
|
||||||
for (final FileSystemEntity child in children) {
|
try {
|
||||||
total += await getTotalSizeOfFilesInDir(child);
|
await for (final FileSystemEntity child in file.list()) {
|
||||||
|
total += await getTotalSizeOfFilesInDir(child);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 处理错误,例如记录日志或显示错误消息
|
||||||
|
print('读取目录时出错: $e');
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
@ -77,16 +82,17 @@ class CacheManage {
|
|||||||
|
|
||||||
// 清除缓存
|
// 清除缓存
|
||||||
Future<bool> clearCacheAll() async {
|
Future<bool> clearCacheAll() async {
|
||||||
bool cleanStatus = await SmartDialog.show(
|
bool? cleanStatus = await showDialog<bool>(
|
||||||
useSystem: true,
|
context: Get.context!,
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('提示'),
|
title: const Text('提示'),
|
||||||
content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'),
|
content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: (() => {SmartDialog.dismiss()}),
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(false);
|
||||||
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'取消',
|
'取消',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
@ -94,40 +100,45 @@ class CacheManage {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
SmartDialog.dismiss();
|
Navigator.of(context).pop(true);
|
||||||
SmartDialog.showLoading(msg: '正在清除...');
|
|
||||||
try {
|
|
||||||
// 清除缓存 图片缓存
|
|
||||||
await clearLibraryCache();
|
|
||||||
SmartDialog.dismiss().then((res) {
|
|
||||||
SmartDialog.showToast('清除完成');
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
SmartDialog.dismiss();
|
|
||||||
SmartDialog.showToast(err.toString());
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: const Text('确认'),
|
child: const Text('确认'),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).then((res) {
|
);
|
||||||
return true;
|
if (cleanStatus != null && cleanStatus) {
|
||||||
});
|
SmartDialog.showLoading(msg: '正在清除...');
|
||||||
return cleanStatus;
|
try {
|
||||||
|
// 清除缓存 图片缓存
|
||||||
|
await clearLibraryCache();
|
||||||
|
SmartDialog.dismiss().then((res) {
|
||||||
|
SmartDialog.showToast('清除完成');
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
SmartDialog.showToast(err.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleanStatus!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 清除 Documents 目录下的 DioCache.db
|
/// 清除 Documents 目录下的 DioCache.db
|
||||||
Future clearApplicationCache() async {
|
Future<void> clearApplicationCache() async {
|
||||||
Directory directory = await getApplicationDocumentsDirectory();
|
try {
|
||||||
if (directory.existsSync()) {
|
Directory directory = await getApplicationDocumentsDirectory();
|
||||||
String dioCacheFileName =
|
if (directory.existsSync()) {
|
||||||
'${directory.path}${Platform.pathSeparator}DioCache.db';
|
String dioCacheFileName =
|
||||||
var dioCacheFile = File(dioCacheFileName);
|
'${directory.path}${Platform.pathSeparator}DioCache.db';
|
||||||
if (dioCacheFile.existsSync()) {
|
File dioCacheFile = File(dioCacheFileName);
|
||||||
dioCacheFile.delete();
|
if (await dioCacheFile.exists()) {
|
||||||
|
await dioCacheFile.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 处理错误,例如记录日志或显示错误消息
|
||||||
|
print('清除缓存时出错: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ class GlobalDataCache {
|
|||||||
late FullScreenGestureMode fullScreenGestureMode;
|
late FullScreenGestureMode fullScreenGestureMode;
|
||||||
late bool enablePlayerControlAnimation;
|
late bool enablePlayerControlAnimation;
|
||||||
late List<String> actionTypeSort;
|
late List<String> actionTypeSort;
|
||||||
|
String? wWebid;
|
||||||
|
|
||||||
/// 播放器相关
|
/// 播放器相关
|
||||||
// 弹幕开关
|
// 弹幕开关
|
||||||
@ -59,7 +60,7 @@ class GlobalDataCache {
|
|||||||
defaultValue: 10); // 设置全局变量
|
defaultValue: 10); // 设置全局变量
|
||||||
fullScreenGestureMode = FullScreenGestureMode.values[setting.get(
|
fullScreenGestureMode = FullScreenGestureMode.values[setting.get(
|
||||||
SettingBoxKey.fullScreenGestureMode,
|
SettingBoxKey.fullScreenGestureMode,
|
||||||
defaultValue: FullScreenGestureMode.values.last.index) as int];
|
defaultValue: FullScreenGestureMode.fromBottomtoTop.index)];
|
||||||
enablePlayerControlAnimation = setting
|
enablePlayerControlAnimation = setting
|
||||||
.get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true);
|
.get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true);
|
||||||
actionTypeSort = await setting.get(SettingBoxKey.actionTypeSort,
|
actionTypeSort = await setting.get(SettingBoxKey.actionTypeSort,
|
||||||
|
|||||||
@ -1,21 +1,51 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
class SubTitleUtils {
|
class SubTitleUtils {
|
||||||
// 格式整理
|
// 格式整理
|
||||||
static String convertToWebVTT(List jsonData) {
|
static Future<String> convertToWebVTT(List jsonData) async {
|
||||||
String webVTTContent = 'WEBVTT FILE\n\n';
|
final receivePort = ReceivePort();
|
||||||
|
await Isolate.spawn(_convertToWebVTTIsolate, receivePort.sendPort);
|
||||||
|
|
||||||
for (int i = 0; i < jsonData.length; i++) {
|
final sendPort = await receivePort.first as SendPort;
|
||||||
final item = jsonData[i];
|
final response = ReceivePort();
|
||||||
double from = double.parse(item['from'].toString());
|
sendPort.send([jsonData, response.sendPort]);
|
||||||
double to = double.parse(item['to'].toString());
|
|
||||||
int sid = (item['sid'] ?? 0) as int;
|
|
||||||
String content = item['content'] as String;
|
|
||||||
|
|
||||||
webVTTContent += '$sid\n';
|
return await response.first as String;
|
||||||
webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n';
|
}
|
||||||
webVTTContent += '$content\n\n';
|
|
||||||
|
static void _convertToWebVTTIsolate(SendPort sendPort) async {
|
||||||
|
final port = ReceivePort();
|
||||||
|
sendPort.send(port.sendPort);
|
||||||
|
|
||||||
|
await for (final message in port) {
|
||||||
|
final List jsonData = message[0];
|
||||||
|
final SendPort replyTo = message[1];
|
||||||
|
|
||||||
|
String webVTTContent = 'WEBVTT FILE\n\n';
|
||||||
|
int chunkSize = 100; // 每次处理100条数据
|
||||||
|
int totalChunks = (jsonData.length / chunkSize).ceil();
|
||||||
|
|
||||||
|
for (int chunk = 0; chunk < totalChunks; chunk++) {
|
||||||
|
int start = chunk * chunkSize;
|
||||||
|
int end = start + chunkSize;
|
||||||
|
if (end > jsonData.length) end = jsonData.length;
|
||||||
|
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
final item = jsonData[i];
|
||||||
|
double from = double.parse(item['from'].toString());
|
||||||
|
double to = double.parse(item['to'].toString());
|
||||||
|
int sid = (item['sid'] ?? 0) as int;
|
||||||
|
String content = item['content'] as String;
|
||||||
|
|
||||||
|
webVTTContent += '$sid\n';
|
||||||
|
webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n';
|
||||||
|
webVTTContent += '$content\n\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replyTo.send(webVTTContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return webVTTContent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String formatTime(num seconds) {
|
static String formatTime(num seconds) {
|
||||||
|
|||||||
@ -306,7 +306,7 @@ class Utils {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await SmartDialog.dismiss();
|
await SmartDialog.dismiss();
|
||||||
launchUrl(
|
launchUrl(
|
||||||
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
|
Uri.parse('https://www.123684.com/s/9sVqVv-DEZ0A'),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <dynamic_color/dynamic_color_plugin.h>
|
#include <dynamic_color/dynamic_color_plugin.h>
|
||||||
#include <flutter_volume_controller/flutter_volume_controller_plugin.h>
|
#include <flutter_volume_controller/flutter_volume_controller_plugin.h>
|
||||||
|
#include <gtk/gtk_plugin.h>
|
||||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
#include <media_kit_video/media_kit_video_plugin.h>
|
#include <media_kit_video/media_kit_video_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
@ -19,6 +20,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin");
|
||||||
flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar);
|
flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
||||||
|
gtk_plugin_register_with_registrar(gtk_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
||||||
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
dynamic_color
|
dynamic_color
|
||||||
flutter_volume_controller
|
flutter_volume_controller
|
||||||
|
gtk
|
||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
media_kit_video
|
media_kit_video
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import app_links
|
||||||
import audio_service
|
import audio_service
|
||||||
import audio_session
|
import audio_session
|
||||||
import connectivity_plus
|
import connectivity_plus
|
||||||
@ -22,6 +23,7 @@ import url_launcher_macos
|
|||||||
import wakelock_plus
|
import wakelock_plus
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||||
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
||||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||||
|
|||||||
40
pubspec.lock
40
pubspec.lock
@ -17,6 +17,38 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.0"
|
version: "6.2.0"
|
||||||
|
app_links:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: app_links
|
||||||
|
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.2"
|
||||||
|
app_links_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: app_links_linux
|
||||||
|
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
app_links_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: app_links_platform_interface
|
||||||
|
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
app_links_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: app_links_web
|
||||||
|
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
appscheme:
|
appscheme:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -686,6 +718,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
|
gtk:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gtk
|
||||||
|
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
hive:
|
hive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -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.24+1024
|
version: 1.0.25+1025
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
@ -112,6 +112,7 @@ dependencies:
|
|||||||
flutter_displaymode: ^0.6.0
|
flutter_displaymode: ^0.6.0
|
||||||
# scheme跳转
|
# scheme跳转
|
||||||
appscheme: ^1.0.8
|
appscheme: ^1.0.8
|
||||||
|
app_links: ^6.3.2
|
||||||
# 弹幕
|
# 弹幕
|
||||||
ns_danmaku:
|
ns_danmaku:
|
||||||
git:
|
git:
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <app_links/app_links_plugin_c_api.h>
|
||||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||||
#include <flutter_volume_controller/flutter_volume_controller_plugin_c_api.h>
|
#include <flutter_volume_controller/flutter_volume_controller_plugin_c_api.h>
|
||||||
@ -17,6 +18,8 @@
|
|||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
AppLinksPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
||||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
DynamicColorPluginCApiRegisterWithRegistrar(
|
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
app_links
|
||||||
connectivity_plus
|
connectivity_plus
|
||||||
dynamic_color
|
dynamic_color
|
||||||
flutter_volume_controller
|
flutter_volume_controller
|
||||||
|
|||||||
Reference in New Issue
Block a user