Merge branch 'main' into feature-minePage

This commit is contained in:
guozhigq
2024-10-16 14:17:24 +08:00
parent 676b2f18eb
commit 174eff7151
62 changed files with 1738 additions and 1371 deletions

View File

@ -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: 下载项目依赖

View File

@ -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: |

View File

@ -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
View File

@ -0,0 +1,39 @@
## 1.0.25
### 功能
+ 直播弹幕
+ 稍后再看、收藏夹播放全部
+ 收藏夹新建、编辑
+ 评论删除
+ 评论保存为图片
+ 动态页滑动切换up
+ up投稿筛选充电视频
+ 直播tab展示关注up
+ up主页专栏展示
### 优化
+ 视频详情页一键三连
+ 动态页标识充电视频
+ 播放器亮度、音量调整百分比展示
+ 封面预览时视频标题可复制
+ 竖屏直播布局
+ 图片预览
+ 专栏渲染优化
+ 私信图片查看
### 修复
+ 收藏夹点击异常
+ 搜索up异常
+ 系统通知已读异常
+ [赞了我的]展示错误
+ 部分up合集无法打开
+ 切换合集视频投币个数未重置
+ 搜索条件筛选面板无法滚动
+ 部分机型导航条未沉浸
+ 专栏图片渲染问题
+ 专栏浏览历史记录
+ 直播间历史记录
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -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

View File

@ -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>

View File

@ -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);
} }

View File

@ -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(

View File

@ -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';
} }

View File

@ -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;
} }
} }

View File

@ -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,

View File

@ -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']};
}
}
} }

View File

@ -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};
} }

View File

@ -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,
)); ));
} }

View File

@ -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];
} }

View File

@ -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];
}

View File

@ -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"],

View File

@ -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,
); );
} }

View File

@ -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,

View File

@ -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) {

View File

@ -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();
} }

View File

@ -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;

View File

@ -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;

View File

@ -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);
} }

View File

@ -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,
),
],
), ),
), ),
); ),
}, );
), },
), ),
), ),
), ),

View File

@ -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

View File

@ -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 {

View File

@ -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');

View File

@ -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),
),
),
);
}
} }

View 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),
),
),
);
}
}

View File

@ -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('未登录'),
); );
} }
} }

View File

@ -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;

View File

@ -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,

View File

@ -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(

View File

@ -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();

View File

@ -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);

View File

@ -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,
); );

View File

@ -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();
} }

View File

@ -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();
}
}

View File

@ -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(() {});
} }
}, },

View File

@ -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();
}); });

View File

@ -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('确认'),
)
],
);
},
);
}
} }

View File

@ -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(

View File

@ -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: [

View File

@ -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)),
),
], ],
), ),
); );

View File

@ -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(

View File

@ -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

View File

@ -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 ?? '',
), ),
), ),
), ),

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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');
} }
} }

View File

@ -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,

View File

@ -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) {

View File

@ -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,
); );
}, },

View File

@ -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);

View File

@ -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

View File

@ -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"))

View File

@ -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:

View File

@ -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:

View File

@ -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(

View File

@ -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