Merge branch 'main' into pr/434
This commit is contained in:
107
.gitignore
vendored
107
.gitignore
vendored
@ -21,6 +21,29 @@ migrate_working_dir/
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter repo-specific
|
||||
/bin/cache/
|
||||
/bin/internal/bootstrap.bat
|
||||
/bin/internal/bootstrap.sh
|
||||
/bin/mingit/
|
||||
/dev/benchmarks/mega_gallery/
|
||||
/dev/bots/.recipe_deps
|
||||
/dev/bots/android_tools/
|
||||
/dev/devicelab/ABresults*.json
|
||||
/dev/docs/doc/
|
||||
/dev/docs/api_docs.zip
|
||||
/dev/docs/flutter.docs.zip
|
||||
/dev/docs/lib/
|
||||
/dev/docs/pubspec.yaml
|
||||
/dev/integration_tests/**/xcuserdata
|
||||
/dev/integration_tests/**/Pods
|
||||
/packages/flutter/coverage/
|
||||
version
|
||||
analysis_benchmark.json
|
||||
|
||||
# packages file containing multi-root paths
|
||||
.packages.generated
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
@ -31,15 +54,83 @@ migrate_working_dir/
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
flutter_*.png
|
||||
linked_*.ds
|
||||
unlinked.ds
|
||||
unlinked_spec.ds
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
/*/flutter/[Gg]enerated*[Pp]lugin*
|
||||
# Android related
|
||||
**/android/**/gradle-wrapper.jar
|
||||
.gradle/
|
||||
**/android/captures/
|
||||
**/android/gradlew
|
||||
**/android/gradlew.bat
|
||||
**/android/local.properties
|
||||
**/android/**/GeneratedPluginRegistrant.java
|
||||
**/android/key.properties
|
||||
*.jks
|
||||
|
||||
# iOS/XCode related
|
||||
**/ios/**/*.mode1v3
|
||||
**/ios/**/*.mode2v3
|
||||
**/ios/**/*.moved-aside
|
||||
**/ios/**/*.pbxuser
|
||||
**/ios/**/*.perspectivev3
|
||||
**/ios/**/*sync/
|
||||
**/ios/**/.sconsign.dblite
|
||||
**/ios/**/.tags*
|
||||
**/ios/**/.vagrant/
|
||||
**/ios/**/DerivedData/
|
||||
**/ios/**/Icon?
|
||||
**/ios/**/Pods/
|
||||
**/ios/**/.symlinks/
|
||||
**/ios/**/profile
|
||||
**/ios/**/xcuserdata
|
||||
**/ios/.generated/
|
||||
**/ios/Flutter/.last_build_id
|
||||
**/ios/Flutter/App.framework
|
||||
**/ios/Flutter/Flutter.framework
|
||||
**/ios/Flutter/Flutter.podspec
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/ephemeral
|
||||
**/ios/Flutter/app.flx
|
||||
**/ios/Flutter/app.zip
|
||||
**/ios/Flutter/flutter_assets/
|
||||
**/ios/Flutter/flutter_export_environment.sh
|
||||
**/ios/ServiceDefinitions.json
|
||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# macOS
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
**/macos/Flutter/GeneratedPluginRegistrant.swift
|
||||
**/macos/Flutter/ephemeral
|
||||
**/xcuserdata/
|
||||
|
||||
# Windows
|
||||
**/windows/flutter/generated_plugin_registrant.cc
|
||||
**/windows/flutter/generated_plugin_registrant.h
|
||||
**/windows/flutter/generated_plugins.cmake
|
||||
|
||||
# Linux
|
||||
**/linux/flutter/generated_plugin_registrant.cc
|
||||
**/linux/flutter/generated_plugin_registrant.h
|
||||
**/linux/flutter/generated_plugins.cmake
|
||||
|
||||
# Coverage
|
||||
coverage/
|
||||
|
||||
# Symbols
|
||||
app.*.symbols
|
||||
|
||||
# Exceptions to above rules.
|
||||
!**/ios/**/default.mode1v3
|
||||
!**/ios/**/default.mode2v3
|
||||
!**/ios/**/default.pbxuser
|
||||
!**/ios/**/default.perspectivev3
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
!/dev/ci/**/Gemfile.lock
|
||||
!.vscode/settings.json
|
39
change_log/1.0.17.0125.md
Normal file
39
change_log/1.0.17.0125.md
Normal file
@ -0,0 +1,39 @@
|
||||
## 1.0.17
|
||||
|
||||
|
||||
### 功能
|
||||
+ 视频全屏时隐藏进度条
|
||||
+ 动态内容增加投稿跳转
|
||||
+ 未开启自动播放时点击封面播放
|
||||
+ 弹幕发送标识
|
||||
+ 定时关闭
|
||||
+ 推荐视频卡片拉黑up功能
|
||||
+ 首页tabbar编辑排序
|
||||
|
||||
### 修复
|
||||
+ 连续跳转搜索页未刷新
|
||||
+ 搜索结果为空时页面异常
|
||||
+ 评论区链接解析
|
||||
+ 视频全屏状态栏背景色
|
||||
+ 私信对话气泡位置
|
||||
+ 设置up关注分组样式
|
||||
+ 每次推荐请求数据相同
|
||||
+ iOS代理网络异常
|
||||
+ 双击切换播放状态无声
|
||||
+ 设置自定义倍速白屏
|
||||
+ 免登录查看1080p
|
||||
|
||||
### 优化
|
||||
+ 首页web端推荐观看数展示
|
||||
+ 首页web端推荐接口更新
|
||||
+ 首页样式
|
||||
+ 搜索页跳转
|
||||
+ 弹幕资源优化
|
||||
+ 图片渲染占用内存优化(部分)
|
||||
+ 两次返回退出应用
|
||||
+ schame 补充
|
||||
|
||||
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
@ -1,6 +1,7 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/utils/extension.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
@ -12,55 +13,75 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
this.src,
|
||||
required this.width,
|
||||
required this.height,
|
||||
this.cacheW,
|
||||
this.cacheH,
|
||||
this.type,
|
||||
this.fadeOutDuration,
|
||||
this.fadeInDuration,
|
||||
// 图片质量 默认1%
|
||||
this.quality,
|
||||
this.origAspectRatio,
|
||||
});
|
||||
|
||||
final String? src;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final double? cacheW;
|
||||
final double? cacheH;
|
||||
final double width;
|
||||
final double height;
|
||||
final String? type;
|
||||
final Duration? fadeOutDuration;
|
||||
final Duration? fadeInDuration;
|
||||
final int? quality;
|
||||
final double? origAspectRatio;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double pr = MediaQuery.of(context).devicePixelRatio;
|
||||
final int picQuality =
|
||||
setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10) as int;
|
||||
final String imageUrl =
|
||||
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? 100}q.webp';
|
||||
int? memCacheWidth, memCacheHeight;
|
||||
double aspectRatio = (width / height).toDouble();
|
||||
|
||||
// double pr = 2;
|
||||
return src != ''
|
||||
void setMemCacheSizes() {
|
||||
if (aspectRatio > 1) {
|
||||
memCacheHeight = height.cacheSize(context);
|
||||
} else if (aspectRatio < 1) {
|
||||
memCacheWidth = width.cacheSize(context);
|
||||
} else {
|
||||
if (origAspectRatio != null && origAspectRatio! > 1) {
|
||||
memCacheWidth = width.cacheSize(context);
|
||||
} else if (origAspectRatio != null && origAspectRatio! < 1) {
|
||||
memCacheHeight = height.cacheSize(context);
|
||||
} else {
|
||||
memCacheWidth = width.cacheSize(context);
|
||||
memCacheHeight = height.cacheSize(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setMemCacheSizes();
|
||||
|
||||
if (memCacheWidth == null && memCacheHeight == null) {
|
||||
memCacheWidth = width.toInt();
|
||||
}
|
||||
|
||||
return src != '' && src != null
|
||||
? ClipRRect(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: BorderRadius.circular(type == 'avatar'
|
||||
clipBehavior: Clip.antiAlias,
|
||||
borderRadius: BorderRadius.circular(
|
||||
type == 'avatar'
|
||||
? 50
|
||||
: type == 'emote'
|
||||
? 0
|
||||
: StyleString.imgRadius.x),
|
||||
: StyleString.imgRadius.x,
|
||||
),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? picQuality}q.webp',
|
||||
width: width ?? double.infinity,
|
||||
height: height ?? double.infinity,
|
||||
maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
|
||||
// maxHeightDiskCache: (cacheH ?? height!).toInt(),
|
||||
memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
|
||||
// memCacheHeight: (cacheH ?? height!).toInt(),
|
||||
imageUrl: imageUrl,
|
||||
width: width,
|
||||
height: height,
|
||||
memCacheWidth: memCacheWidth,
|
||||
memCacheHeight: memCacheHeight,
|
||||
fit: BoxFit.cover,
|
||||
fadeOutDuration:
|
||||
fadeOutDuration ?? const Duration(milliseconds: 200),
|
||||
fadeOutDuration ?? const Duration(milliseconds: 120),
|
||||
fadeInDuration:
|
||||
fadeInDuration ?? const Duration(milliseconds: 200),
|
||||
// filterQuality: FilterQuality.high,
|
||||
fadeInDuration ?? const Duration(milliseconds: 120),
|
||||
filterQuality: FilterQuality.high,
|
||||
errorWidget: (BuildContext context, String url, Object error) =>
|
||||
placeholder(context),
|
||||
placeholder: (BuildContext context, String url) =>
|
||||
@ -72,9 +93,9 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
|
||||
Widget placeholder(BuildContext context) {
|
||||
return Container(
|
||||
width: width ?? double.infinity,
|
||||
height: height ?? double.infinity,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
width: width,
|
||||
height: height,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
|
||||
borderRadius: BorderRadius.circular(type == 'avatar'
|
||||
@ -88,9 +109,12 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
type == 'avatar'
|
||||
? 'assets/images/noface.jpeg'
|
||||
: 'assets/images/loading.png',
|
||||
width: 300,
|
||||
height: 300,
|
||||
)),
|
||||
width: width,
|
||||
height: height,
|
||||
cacheWidth: width.cacheSize(context),
|
||||
cacheHeight: height.cacheSize(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ class VideoCardH extends StatelessWidget {
|
||||
final double width = (boxConstraints.maxWidth -
|
||||
StyleString.cardSpace *
|
||||
6 /
|
||||
MediaQuery.of(context).textScaleFactor) /
|
||||
MediaQuery.textScalerOf(context).scale(1.0)) /
|
||||
2;
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minHeight: 88),
|
||||
|
@ -326,7 +326,8 @@ class VideoStat extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
fontSize: MediaQuery.textScalerOf(context)
|
||||
.scale(Theme.of(context).textTheme.labelSmall!.fontSize!),
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
children: [
|
||||
|
@ -185,7 +185,7 @@ class Api {
|
||||
static const String searchDefault = '/x/web-interface/wbi/search/default';
|
||||
|
||||
// 搜索关键词
|
||||
static const String serachSuggest =
|
||||
static const String searchSuggest =
|
||||
'https://s.search.bilibili.com/main/suggest';
|
||||
|
||||
// 分类搜索
|
||||
@ -467,4 +467,7 @@ class Api {
|
||||
/// page_size
|
||||
static const getSeasonDetailApi =
|
||||
'/x/polymer/web-space/seasons_archives_list';
|
||||
|
||||
/// 获取未读动态数
|
||||
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
|
||||
}
|
||||
|
17
lib/http/common.dart
Normal file
17
lib/http/common.dart
Normal file
@ -0,0 +1,17 @@
|
||||
import 'index.dart';
|
||||
|
||||
class CommonHttp {
|
||||
static Future unReadDynamic() async {
|
||||
var res = await Request().get(Api.getUnreadDynamic,
|
||||
data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']['dyn_basic_infos']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import 'package:dio/io.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
// import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'constants.dart';
|
||||
@ -77,10 +78,11 @@ class Request {
|
||||
static setOptionsHeaders(userInfo, bool status) {
|
||||
if (status) {
|
||||
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
|
||||
dio.options.headers['x-bili-aurora-eid'] =
|
||||
IdUtils.genAuroraEid(userInfo.mid);
|
||||
}
|
||||
dio.options.headers['env'] = 'prod';
|
||||
dio.options.headers['app-key'] = 'android64';
|
||||
dio.options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
|
||||
dio.options.headers['x-bili-aurora-zone'] = 'sh001';
|
||||
dio.options.headers['referer'] = 'https://www.bilibili.com/';
|
||||
}
|
||||
|
@ -70,14 +70,14 @@ class ApiInterceptor extends Interceptor {
|
||||
case DioExceptionType.sendTimeout:
|
||||
return '发送请求超时,请检查网络设置';
|
||||
case DioExceptionType.unknown:
|
||||
final String res = await checkConect();
|
||||
final String res = await checkConnect();
|
||||
return '$res \n 网络异常,请稍后重试!';
|
||||
// default:
|
||||
// return 'Dio异常';
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> checkConect() async {
|
||||
static Future<String> checkConnect() async {
|
||||
final ConnectivityResult connectivityResult =
|
||||
await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.mobile) {
|
||||
|
@ -36,7 +36,7 @@ class SearchHttp {
|
||||
|
||||
// 获取搜索建议
|
||||
static Future searchSuggest({required term}) async {
|
||||
var res = await Request().get(Api.serachSuggest,
|
||||
var res = await Request().get(Api.searchSuggest,
|
||||
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
||||
if (res.data is String) {
|
||||
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||
|
@ -158,9 +158,8 @@ class MyApp extends StatelessWidget {
|
||||
return FlutterSmartDialog(
|
||||
toastBuilder: (String msg) => CustomToast(msg: msg),
|
||||
child: MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaleFactor:
|
||||
MediaQuery.of(context).textScaleFactor * textScale),
|
||||
data: MediaQuery.of(context)
|
||||
.copyWith(textScaler: TextScaler.linear(textScale)),
|
||||
child: child!,
|
||||
),
|
||||
);
|
||||
|
@ -9,6 +9,7 @@ enum TabType { live, rcmd, hot, bangumi }
|
||||
|
||||
extension TabTypeDesc on TabType {
|
||||
String get description => ['直播', '推荐', '热门', '番剧'][index];
|
||||
String get id => ['live', 'rcmd', 'hot', 'bangumi'][index];
|
||||
}
|
||||
|
||||
List tabsConfig = [
|
||||
|
@ -1,8 +1,3 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'result.g.dart';
|
||||
|
||||
@HiveType(typeId: 0)
|
||||
class RecVideoItemAppModel {
|
||||
RecVideoItemAppModel({
|
||||
this.id,
|
||||
@ -27,47 +22,27 @@ class RecVideoItemAppModel {
|
||||
this.adInfo,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
int? id;
|
||||
@HiveField(1)
|
||||
int? aid;
|
||||
@HiveField(2)
|
||||
String? bvid;
|
||||
@HiveField(3)
|
||||
int? cid;
|
||||
@HiveField(4)
|
||||
String? pic;
|
||||
@HiveField(5)
|
||||
RcmdStat? stat;
|
||||
@HiveField(6)
|
||||
int? duration;
|
||||
@HiveField(7)
|
||||
String? title;
|
||||
@HiveField(8)
|
||||
int? isFollowed;
|
||||
@HiveField(9)
|
||||
RcmdOwner? owner;
|
||||
@HiveField(10)
|
||||
RcmdReason? rcmdReason;
|
||||
@HiveField(11)
|
||||
String? goto;
|
||||
@HiveField(12)
|
||||
int? param;
|
||||
@HiveField(13)
|
||||
String? uri;
|
||||
@HiveField(14)
|
||||
String? talkBack;
|
||||
// 番剧
|
||||
@HiveField(15)
|
||||
String? bangumiView;
|
||||
@HiveField(16)
|
||||
String? bangumiFollow;
|
||||
@HiveField(17)
|
||||
String? bangumiBadge;
|
||||
|
||||
@HiveField(18)
|
||||
String? cardType;
|
||||
@HiveField(19)
|
||||
Map? adInfo;
|
||||
|
||||
RecVideoItemAppModel.fromJson(Map<String, dynamic> json) {
|
||||
@ -116,18 +91,14 @@ class RecVideoItemAppModel {
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
class RcmdStat {
|
||||
RcmdStat({
|
||||
this.view,
|
||||
this.like,
|
||||
this.danmu,
|
||||
});
|
||||
@HiveField(0)
|
||||
String? view;
|
||||
@HiveField(1)
|
||||
String? like;
|
||||
@HiveField(2)
|
||||
String? danmu;
|
||||
|
||||
RcmdStat.fromJson(Map<String, dynamic> json) {
|
||||
@ -136,13 +107,10 @@ class RcmdStat {
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 2)
|
||||
class RcmdOwner {
|
||||
RcmdOwner({this.name, this.mid});
|
||||
|
||||
@HiveField(0)
|
||||
String? name;
|
||||
@HiveField(1)
|
||||
int? mid;
|
||||
|
||||
RcmdOwner.fromJson(Map<String, dynamic> json) {
|
||||
@ -155,13 +123,11 @@ class RcmdOwner {
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 8)
|
||||
class RcmdReason {
|
||||
RcmdReason({
|
||||
this.content,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
String? content;
|
||||
|
||||
RcmdReason.fromJson(Map<String, dynamic> json) {
|
||||
|
@ -1,209 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'result.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class RecVideoItemAppModelAdapter extends TypeAdapter<RecVideoItemAppModel> {
|
||||
@override
|
||||
final int typeId = 0;
|
||||
|
||||
@override
|
||||
RecVideoItemAppModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return RecVideoItemAppModel(
|
||||
id: fields[0] as int?,
|
||||
aid: fields[1] as int?,
|
||||
bvid: fields[2] as String?,
|
||||
cid: fields[3] as int?,
|
||||
pic: fields[4] as String?,
|
||||
stat: fields[5] as RcmdStat?,
|
||||
duration: fields[6] as int?,
|
||||
title: fields[7] as String?,
|
||||
isFollowed: fields[8] as int?,
|
||||
owner: fields[9] as RcmdOwner?,
|
||||
rcmdReason: fields[10] as RcmdReason?,
|
||||
goto: fields[11] as String?,
|
||||
param: fields[12] as int?,
|
||||
uri: fields[13] as String?,
|
||||
talkBack: fields[14] as String?,
|
||||
bangumiView: fields[15] as String?,
|
||||
bangumiFollow: fields[16] as String?,
|
||||
bangumiBadge: fields[17] as String?,
|
||||
cardType: fields[18] as String?,
|
||||
adInfo: (fields[19] as Map?)?.cast<dynamic, dynamic>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, RecVideoItemAppModel obj) {
|
||||
writer
|
||||
..writeByte(20)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.aid)
|
||||
..writeByte(2)
|
||||
..write(obj.bvid)
|
||||
..writeByte(3)
|
||||
..write(obj.cid)
|
||||
..writeByte(4)
|
||||
..write(obj.pic)
|
||||
..writeByte(5)
|
||||
..write(obj.stat)
|
||||
..writeByte(6)
|
||||
..write(obj.duration)
|
||||
..writeByte(7)
|
||||
..write(obj.title)
|
||||
..writeByte(8)
|
||||
..write(obj.isFollowed)
|
||||
..writeByte(9)
|
||||
..write(obj.owner)
|
||||
..writeByte(10)
|
||||
..write(obj.rcmdReason)
|
||||
..writeByte(11)
|
||||
..write(obj.goto)
|
||||
..writeByte(12)
|
||||
..write(obj.param)
|
||||
..writeByte(13)
|
||||
..write(obj.uri)
|
||||
..writeByte(14)
|
||||
..write(obj.talkBack)
|
||||
..writeByte(15)
|
||||
..write(obj.bangumiView)
|
||||
..writeByte(16)
|
||||
..write(obj.bangumiFollow)
|
||||
..writeByte(17)
|
||||
..write(obj.bangumiBadge)
|
||||
..writeByte(18)
|
||||
..write(obj.cardType)
|
||||
..writeByte(19)
|
||||
..write(obj.adInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RecVideoItemAppModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class RcmdStatAdapter extends TypeAdapter<RcmdStat> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
RcmdStat read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return RcmdStat(
|
||||
view: fields[0] as String?,
|
||||
like: fields[1] as String?,
|
||||
danmu: fields[2] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, RcmdStat obj) {
|
||||
writer
|
||||
..writeByte(3)
|
||||
..writeByte(0)
|
||||
..write(obj.view)
|
||||
..writeByte(1)
|
||||
..write(obj.like)
|
||||
..writeByte(2)
|
||||
..write(obj.danmu);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RcmdStatAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class RcmdOwnerAdapter extends TypeAdapter<RcmdOwner> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
RcmdOwner read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return RcmdOwner(
|
||||
name: fields[0] as String?,
|
||||
mid: fields[1] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, RcmdOwner obj) {
|
||||
writer
|
||||
..writeByte(2)
|
||||
..writeByte(0)
|
||||
..write(obj.name)
|
||||
..writeByte(1)
|
||||
..write(obj.mid);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RcmdOwnerAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class RcmdReasonAdapter extends TypeAdapter<RcmdReason> {
|
||||
@override
|
||||
final int typeId = 8;
|
||||
|
||||
@override
|
||||
RcmdReason read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return RcmdReason(
|
||||
content: fields[0] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, RcmdReason obj) {
|
||||
writer
|
||||
..writeByte(1)
|
||||
..writeByte(0)
|
||||
..write(obj.content);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RcmdReasonAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
@ -86,6 +86,14 @@ class _AboutPageState extends State<AboutPage> {
|
||||
style: subTitleStyle,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.webSiteUrl(),
|
||||
title: const Text('访问官网'),
|
||||
trailing: Text(
|
||||
'https://pilipalanet.mysxl.cn',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.panDownload(),
|
||||
title: const Text('网盘下载'),
|
||||
@ -244,4 +252,12 @@ class AboutController extends GetxController {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 官网
|
||||
webSiteUrl() {
|
||||
launchUrl(
|
||||
Uri.parse('https://pilipalanet.mysxl.cn'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -224,7 +224,7 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
// 列数
|
||||
crossAxisCount: 3,
|
||||
mainAxisExtent: Get.size.width / 3 / 0.65 +
|
||||
32 * MediaQuery.of(context).textScaleFactor,
|
||||
MediaQuery.textScalerOf(context).scale(32.0),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
// 内容
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/pages/preview/index.dart';
|
||||
@ -48,6 +49,13 @@ class _ContentState extends State<Content> {
|
||||
WidgetSpan(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
double maxWidth = box.maxWidth.truncateToDouble();
|
||||
double maxHeight = box.maxWidth * 0.6; // 设置最大高度
|
||||
double height = maxWidth *
|
||||
0.5 *
|
||||
(pictureItem.height != null && pictureItem.width != null
|
||||
? pictureItem.height! / pictureItem.width!
|
||||
: 1);
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
@ -58,18 +66,29 @@ class _ContentState extends State<Content> {
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
constraints: BoxConstraints(maxHeight: maxHeight),
|
||||
width: box.maxWidth / 2,
|
||||
height: height,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: NetworkImgLayer(
|
||||
src: pictureItem.url,
|
||||
width: box.maxWidth / 2,
|
||||
height: box.maxWidth *
|
||||
0.5 *
|
||||
(pictureItem.height != null && pictureItem.width != null
|
||||
? pictureItem.height! / pictureItem.width!
|
||||
: 1),
|
||||
width: maxWidth / 2,
|
||||
height: height,
|
||||
),
|
||||
),
|
||||
height > maxHeight
|
||||
? const PBadge(
|
||||
text: '长图',
|
||||
right: 8,
|
||||
bottom: 8,
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
)),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -83,6 +102,7 @@ class _ContentState extends State<Content> {
|
||||
list.add(
|
||||
LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
double maxWidth = box.maxWidth.truncateToDouble();
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
@ -95,8 +115,10 @@ class _ContentState extends State<Content> {
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
src: pics[i].url,
|
||||
width: box.maxWidth,
|
||||
height: box.maxWidth,
|
||||
width: maxWidth,
|
||||
height: maxWidth,
|
||||
origAspectRatio:
|
||||
pics[i].width!.toInt() / pics[i].height!.toInt(),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -107,7 +129,7 @@ class _ContentState extends State<Content> {
|
||||
WidgetSpan(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
double maxWidth = box.maxWidth;
|
||||
double maxWidth = box.maxWidth.truncateToDouble();
|
||||
double crossCount = len < 3 ? 2 : 3;
|
||||
double height = maxWidth /
|
||||
crossCount *
|
||||
|
@ -5,15 +5,17 @@ import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/common/tab_type.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import '../../http/index.dart';
|
||||
|
||||
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
bool flag = false;
|
||||
late List tabs;
|
||||
late RxList tabs = [].obs;
|
||||
RxInt initialIndex = 1.obs;
|
||||
late TabController tabController;
|
||||
late List tabsCtrList;
|
||||
late List<Widget> tabsPageList;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box settingStorage = GStrorage.setting;
|
||||
RxBool userLogin = false.obs;
|
||||
RxString userFace = ''.obs;
|
||||
var userInfo;
|
||||
@ -21,6 +23,9 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
late final StreamController<bool> searchBarStream =
|
||||
StreamController<bool>.broadcast();
|
||||
late bool hideSearchBar;
|
||||
late List defaultTabs;
|
||||
late List<String> tabbarSort;
|
||||
RxString defaultSearch = ''.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -28,19 +33,13 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin.value = userInfo != null;
|
||||
userFace.value = userInfo != null ? userInfo.face : '';
|
||||
|
||||
// 进行tabs配置
|
||||
tabs = tabsConfig;
|
||||
tabsCtrList = tabsConfig.map((e) => e['ctr']).toList();
|
||||
tabsPageList = tabsConfig.map<Widget>((e) => e['page']).toList();
|
||||
|
||||
tabController = TabController(
|
||||
initialIndex: initialIndex.value,
|
||||
length: tabs.length,
|
||||
vsync: this,
|
||||
);
|
||||
setTabConfig();
|
||||
hideSearchBar =
|
||||
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
|
||||
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
|
||||
searchDefault();
|
||||
}
|
||||
}
|
||||
|
||||
void onRefresh() {
|
||||
@ -62,4 +61,49 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
if (val) return;
|
||||
userFace.value = userInfo != null ? userInfo.face : '';
|
||||
}
|
||||
|
||||
void setTabConfig() async {
|
||||
defaultTabs = tabsConfig;
|
||||
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
|
||||
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
|
||||
|
||||
tabs.value = defaultTabs
|
||||
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
|
||||
.toList();
|
||||
|
||||
if (tabbarSort.contains(TabType.rcmd.id)) {
|
||||
initialIndex.value = tabbarSort.indexOf(TabType.rcmd.id);
|
||||
} else {
|
||||
initialIndex.value = 0;
|
||||
}
|
||||
tabsCtrList = tabs.map((e) => e['ctr']).toList();
|
||||
tabsPageList = tabs.map<Widget>((e) => e['page']).toList();
|
||||
|
||||
tabController = TabController(
|
||||
initialIndex: initialIndex.value,
|
||||
length: tabs.length,
|
||||
vsync: this,
|
||||
);
|
||||
// 监听 tabController 切换
|
||||
tabController.animation!.addListener(() {
|
||||
if (tabController.indexIsChanging) {
|
||||
if (initialIndex.value != tabController.index) {
|
||||
initialIndex.value = tabController.index;
|
||||
}
|
||||
} else {
|
||||
final int temp = tabController.animation!.value.round();
|
||||
if (initialIndex.value != temp) {
|
||||
initialIndex.value = temp;
|
||||
tabController.index = initialIndex.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void searchDefault() async {
|
||||
var res = await Request().get(Api.searchDefault);
|
||||
if (res.data['code'] == 0) {
|
||||
defaultSearch.value = res.data['data']['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/pages/mine/index.dart';
|
||||
import 'package:pilipala/pages/search/index.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import './controller.dart';
|
||||
|
||||
@ -46,6 +46,13 @@ class _HomePageState extends State<HomePage>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
|
||||
// 设置状态栏图标的亮度
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: currentBrightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
));
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
extendBodyBehindAppBar: true,
|
||||
@ -82,7 +89,11 @@ class _HomePageState extends State<HomePage>
|
||||
ctr: _homeController,
|
||||
callback: showUserBottomSheet,
|
||||
),
|
||||
if (_homeController.tabs.length > 1) ...[
|
||||
const CustomTabs(),
|
||||
] else ...[
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _homeController.tabController,
|
||||
@ -129,9 +140,10 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
height: snapshot.data ? top + 52 : top,
|
||||
padding: EdgeInsets.fromLTRB(14, top, 14, 0),
|
||||
padding: EdgeInsets.fromLTRB(14, top + 6, 14, 0),
|
||||
child: UserInfoWidget(
|
||||
top: top,
|
||||
ctr: ctr,
|
||||
userLogin: isUserLoggedIn,
|
||||
userFace: ctr?.userFace.value,
|
||||
callback: () => callback!(),
|
||||
@ -150,18 +162,20 @@ class UserInfoWidget extends StatelessWidget {
|
||||
required this.userLogin,
|
||||
required this.userFace,
|
||||
required this.callback,
|
||||
required this.ctr,
|
||||
}) : super(key: key);
|
||||
|
||||
final double top;
|
||||
final RxBool userLogin;
|
||||
final String? userFace;
|
||||
final VoidCallback? callback;
|
||||
final HomeController? ctr;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
const SearchBar(),
|
||||
SearchBar(ctr: ctr),
|
||||
if (userLogin.value) ...[
|
||||
const SizedBox(width: 4),
|
||||
ClipRect(
|
||||
@ -242,17 +256,6 @@ class CustomTabs extends StatefulWidget {
|
||||
|
||||
class _CustomTabsState extends State<CustomTabs> {
|
||||
final HomeController _homeController = Get.put(HomeController());
|
||||
int currentTabIndex = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_homeController.tabController.addListener(listen);
|
||||
}
|
||||
|
||||
void listen() {
|
||||
_homeController.initialIndex.value = _homeController.tabController.index;
|
||||
}
|
||||
|
||||
void onTap(int index) {
|
||||
feedBack();
|
||||
@ -263,18 +266,13 @@ class _CustomTabsState extends State<CustomTabs> {
|
||||
_homeController.tabController.index = index;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_homeController.tabController.removeListener(listen);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 44,
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
child: ListView.separated(
|
||||
child: Obx(
|
||||
() => ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: _homeController.tabs.length,
|
||||
@ -292,6 +290,7 @@ class _CustomTabsState extends State<CustomTabs> {
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -338,11 +337,15 @@ class CustomChip extends StatelessWidget {
|
||||
}
|
||||
|
||||
class SearchBar extends StatelessWidget {
|
||||
const SearchBar({super.key});
|
||||
const SearchBar({
|
||||
Key? key,
|
||||
required this.ctr,
|
||||
}) : super(key: key);
|
||||
|
||||
final HomeController? ctr;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final SSearchController searchController = Get.put(SSearchController());
|
||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
return Expanded(
|
||||
child: Container(
|
||||
@ -356,7 +359,10 @@ class SearchBar extends StatelessWidget {
|
||||
color: colorScheme.onSecondaryContainer.withOpacity(0.05),
|
||||
child: InkWell(
|
||||
splashColor: colorScheme.primaryContainer.withOpacity(0.3),
|
||||
onTap: () => Get.toNamed('/search'),
|
||||
onTap: () => Get.toNamed(
|
||||
'/search',
|
||||
parameters: {'hintText': ctr!.defaultSearch.value},
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 14),
|
||||
@ -365,16 +371,14 @@ class SearchBar extends StatelessWidget {
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
Obx(
|
||||
() => Text(
|
||||
searchController.defaultSearch.value,
|
||||
ctr!.defaultSearch.value,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: colorScheme.outline),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -162,8 +162,9 @@ class _LivePageState extends State<LivePage>
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisExtent:
|
||||
Get.size.width / crossAxisCount / StyleString.aspectRatio +
|
||||
(crossAxisCount == 1 ? 48 : 68) *
|
||||
MediaQuery.of(context).textScaleFactor,
|
||||
MediaQuery.textScalerOf(context).scale(
|
||||
(crossAxisCount == 1 ? 48 : 68),
|
||||
),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/common.dart';
|
||||
import 'package:pilipala/pages/dynamics/index.dart';
|
||||
import 'package:pilipala/pages/home/view.dart';
|
||||
import 'package:pilipala/pages/media/index.dart';
|
||||
@ -27,6 +29,7 @@ class MainController extends GetxController {
|
||||
size: 21,
|
||||
),
|
||||
'label': "首页",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
@ -38,6 +41,7 @@ class MainController extends GetxController {
|
||||
size: 21,
|
||||
),
|
||||
'label': "动态",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
@ -49,6 +53,7 @@ class MainController extends GetxController {
|
||||
size: 21,
|
||||
),
|
||||
'label': "媒体库",
|
||||
'count': 0,
|
||||
}
|
||||
].obs;
|
||||
final StreamController<bool> bottomBarStream =
|
||||
@ -56,6 +61,10 @@ class MainController extends GetxController {
|
||||
Box setting = GStrorage.setting;
|
||||
DateTime? _lastPressedAt;
|
||||
late bool hideTabBar;
|
||||
late PageController pageController;
|
||||
int selectedIndex = 0;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
RxBool userLogin = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -64,17 +73,47 @@ class MainController extends GetxController {
|
||||
Utils.checkUpdata();
|
||||
}
|
||||
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
|
||||
var userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin.value = userInfo != null;
|
||||
getUnreadDynamic();
|
||||
}
|
||||
|
||||
Future<bool> onBackPressed(BuildContext context) {
|
||||
void onBackPressed(BuildContext context) {
|
||||
if (_lastPressedAt == null ||
|
||||
DateTime.now().difference(_lastPressedAt!) >
|
||||
const Duration(seconds: 2)) {
|
||||
// 两次点击时间间隔超过2秒,重新记录时间戳
|
||||
_lastPressedAt = DateTime.now();
|
||||
SmartDialog.showToast("再按一次退出Pili");
|
||||
return Future.value(false); // 不退出应用
|
||||
if (selectedIndex != 0) {
|
||||
pageController.jumpTo(0);
|
||||
}
|
||||
return Future.value(true); // 退出应用
|
||||
SmartDialog.showToast("再按一次退出Pili");
|
||||
return; // 不退出应用
|
||||
}
|
||||
SystemNavigator.pop(); // 退出应用
|
||||
}
|
||||
|
||||
void getUnreadDynamic() async {
|
||||
if (!userLogin.value) {
|
||||
return;
|
||||
}
|
||||
int dynamicItemIndex =
|
||||
navigationBars.indexWhere((item) => item['label'] == "动态");
|
||||
var res = await CommonHttp.unReadDynamic();
|
||||
var data = res['data'];
|
||||
if (dynamicItemIndex != -1) {
|
||||
navigationBars[dynamicItemIndex]['count'] =
|
||||
data == null ? 0 : data.length; // 修改 count 属性为新的值
|
||||
}
|
||||
navigationBars.refresh();
|
||||
}
|
||||
|
||||
void clearUnread() async {
|
||||
int dynamicItemIndex =
|
||||
navigationBars.indexWhere((item) => item['label'] == "动态");
|
||||
if (dynamicItemIndex != -1) {
|
||||
navigationBars[dynamicItemIndex]['count'] = 0; // 修改 count 属性为新的值
|
||||
}
|
||||
navigationBars.refresh();
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
||||
final MediaController _mediaController = Get.put(MediaController());
|
||||
|
||||
PageController? _pageController;
|
||||
int selectedIndex = 0;
|
||||
int? _lastSelectTime; //上次点击时间
|
||||
Box setting = GStrorage.setting;
|
||||
late bool enableMYBar;
|
||||
@ -34,13 +32,14 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
_pageController = PageController(initialPage: selectedIndex);
|
||||
_mainController.pageController =
|
||||
PageController(initialPage: _mainController.selectedIndex);
|
||||
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
|
||||
}
|
||||
|
||||
void setIndex(int value) async {
|
||||
feedBack();
|
||||
_pageController!.jumpToPage(value);
|
||||
_mainController.pageController.jumpToPage(value);
|
||||
var currentPage = _mainController.pages[value];
|
||||
if (currentPage is HomePage) {
|
||||
if (_homeController.flag) {
|
||||
@ -68,6 +67,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
_dynamicController.flag = true;
|
||||
_mainController.clearUnread();
|
||||
} else {
|
||||
_dynamicController.flag = false;
|
||||
}
|
||||
@ -94,14 +94,17 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
localCache.put('sheetHeight', sheetHeight);
|
||||
localCache.put('statusBarHeight', statusBarHeight);
|
||||
return PopScope(
|
||||
onPopInvoked: (bool status) => _mainController.onBackPressed(context),
|
||||
canPop: false,
|
||||
onPopInvoked: (bool didPop) async {
|
||||
_mainController.onBackPressed(context);
|
||||
},
|
||||
child: Scaffold(
|
||||
extendBody: true,
|
||||
body: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _pageController,
|
||||
controller: _mainController.pageController,
|
||||
onPageChanged: (index) {
|
||||
selectedIndex = index;
|
||||
_mainController.selectedIndex = index;
|
||||
setState(() {});
|
||||
},
|
||||
children: _mainController.pages,
|
||||
@ -116,14 +119,20 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
offset: Offset(0, snapshot.data ? 0 : 1),
|
||||
child: enableMYBar
|
||||
child: Obx(
|
||||
() => enableMYBar
|
||||
? NavigationBar(
|
||||
onDestinationSelected: (value) => setIndex(value),
|
||||
selectedIndex: selectedIndex,
|
||||
selectedIndex: _mainController.selectedIndex,
|
||||
destinations: <Widget>[
|
||||
..._mainController.navigationBars.map((e) {
|
||||
return NavigationDestination(
|
||||
icon: e['icon'],
|
||||
icon: Badge(
|
||||
label: Text(e['count'].toString()),
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||
isLabelVisible: e['count'] > 0,
|
||||
child: e['icon'],
|
||||
),
|
||||
selectedIcon: e['selectIcon'],
|
||||
label: e['label'],
|
||||
);
|
||||
@ -131,7 +140,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
],
|
||||
)
|
||||
: BottomNavigationBar(
|
||||
currentIndex: selectedIndex,
|
||||
currentIndex: _mainController.selectedIndex,
|
||||
onTap: (value) => setIndex(value),
|
||||
iconSize: 16,
|
||||
selectedFontSize: 12,
|
||||
@ -139,13 +148,19 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
items: [
|
||||
..._mainController.navigationBars.map((e) {
|
||||
return BottomNavigationBarItem(
|
||||
icon: e['icon'],
|
||||
icon: Badge(
|
||||
label: Text(e['count'].toString()),
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||
isLabelVisible: e['count'] > 0,
|
||||
child: e['icon'],
|
||||
),
|
||||
activeIcon: e['selectIcon'],
|
||||
label: e['label'],
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -163,7 +163,7 @@ class _MediaPageState extends State<MediaPage>
|
||||
// const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 200 * MediaQuery.of(context).textScaleFactor,
|
||||
height: MediaQuery.textScalerOf(context).scale(200),
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
|
@ -13,7 +13,6 @@ class RcmdController extends GetxController {
|
||||
// RxList<RecVideoItemModel> webVideoList = <RecVideoItemModel>[].obs;
|
||||
bool isLoadingMore = true;
|
||||
OverlayEntry? popupDialog;
|
||||
Box recVideo = GStrorage.recVideo;
|
||||
Box setting = GStrorage.setting;
|
||||
RxInt crossAxisCount = 2.obs;
|
||||
late bool enableSaveLastData;
|
||||
@ -25,15 +24,6 @@ class RcmdController extends GetxController {
|
||||
super.onInit();
|
||||
crossAxisCount.value =
|
||||
setting.get(SettingBoxKey.customRows, defaultValue: 2);
|
||||
// 读取app端缓存内容
|
||||
// if (recVideo.get('cacheList') != null &&
|
||||
// recVideo.get('cacheList').isNotEmpty) {
|
||||
// List<RecVideoItemAppModel> list = [];
|
||||
// for (var i in recVideo.get('cacheList')) {
|
||||
// list.add(i);
|
||||
// }
|
||||
// videoList.value = list;
|
||||
// }
|
||||
enableSaveLastData =
|
||||
setting.get(SettingBoxKey.enableSaveLastData, defaultValue: false);
|
||||
defaultRcmdType =
|
||||
@ -84,10 +74,6 @@ class RcmdController extends GetxController {
|
||||
} else if (type == 'onLoad') {
|
||||
videoList.addAll(res['data']);
|
||||
}
|
||||
// 目前仅支持app端系列保存缓存
|
||||
if (defaultRcmdType != 'web') {
|
||||
recVideo.put('cacheList', res['data']);
|
||||
}
|
||||
_currentPage += 1;
|
||||
// 若videoList数量太小,可能会影响翻页,此时再次请求
|
||||
// 为避免请求到的数据太少时还在反复请求,要求本次返回数据大于1条才触发
|
||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/index.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/search/hot.dart';
|
||||
import 'package:pilipala/models/search/suggest.dart';
|
||||
@ -27,9 +26,6 @@ class SSearchController extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
|
||||
searchDefault();
|
||||
}
|
||||
// 其他页面跳转过来
|
||||
if (Get.parameters.keys.isNotEmpty) {
|
||||
if (Get.parameters['keyword'] != null) {
|
||||
@ -130,12 +126,4 @@ class SSearchController extends GetxController {
|
||||
historyList.refresh();
|
||||
histiryWord.put('cacheList', []);
|
||||
}
|
||||
|
||||
void searchDefault() async {
|
||||
var res = await Request().get(Api.searchDefault);
|
||||
if (res.data['code'] == 0) {
|
||||
searchKeyWord.value =
|
||||
hintText = defaultSearch.value = res.data['data']['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,7 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
||||
double width = (boxConstraints.maxWidth -
|
||||
StyleString.cardSpace *
|
||||
6 /
|
||||
MediaQuery.of(context).textScaleFactor) /
|
||||
2;
|
||||
MediaQuery.textScalerOf(context).scale(2.0));
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minHeight: 88),
|
||||
height: width / StyleString.aspectRatio,
|
||||
|
@ -17,7 +17,7 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
|
||||
mainAxisSpacing: StyleString.cardSpace + 3,
|
||||
mainAxisExtent:
|
||||
MediaQuery.sizeOf(context).width / 2 / StyleString.aspectRatio +
|
||||
66 * MediaQuery.of(context).textScaleFactor),
|
||||
MediaQuery.textScalerOf(context).scale(66.0)),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
return LiveItem(liveItem: list![index]);
|
||||
|
@ -67,11 +67,11 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
||||
TextSpan(
|
||||
text: i['text'],
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
fontSize: MediaQuery.textScalerOf(context)
|
||||
.scale(Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall!
|
||||
.fontSize! *
|
||||
MediaQuery.of(context).textScaleFactor,
|
||||
.fontSize!),
|
||||
fontWeight: FontWeight.bold,
|
||||
color: i['type'] == 'em'
|
||||
? Theme.of(context).colorScheme.primary
|
||||
|
@ -90,7 +90,7 @@ class SearchVideoPanel extends StatelessWidget {
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () => controller.onShowFilterDialog(),
|
||||
onPressed: () => controller.onShowFilterDialog(ctr),
|
||||
icon: Icon(
|
||||
Icons.filter_list_outlined,
|
||||
size: 18,
|
||||
@ -175,7 +175,7 @@ class VideoPanelController extends GetxController {
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
onShowFilterDialog() {
|
||||
onShowFilterDialog(searchPanelCtr) {
|
||||
SmartDialog.show(
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
@ -199,7 +199,8 @@ class VideoPanelController extends GetxController {
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast("「${i['label']}」的筛选结果");
|
||||
SearchPanelController ctr =
|
||||
Get.find<SearchPanelController>(tag: 'video');
|
||||
Get.find<SearchPanelController>(
|
||||
tag: 'video${searchPanelCtr.keyword!}');
|
||||
ctr.duration.value = i['value'];
|
||||
SmartDialog.showLoading(msg: 'loooad');
|
||||
await ctr.onRefresh();
|
||||
|
@ -86,7 +86,8 @@ class _SearchResultPageState extends State<SearchResultPage>
|
||||
onTap: (index) {
|
||||
if (index == _searchResultController!.tabIndex) {
|
||||
Get.find<SearchPanelController>(
|
||||
tag: SearchType.values[index].type)
|
||||
tag: SearchType.values[index].type +
|
||||
_searchResultController!.keyword!)
|
||||
.animateToTop();
|
||||
}
|
||||
|
||||
|
90
lib/pages/setting/pages/home_tabbar_set.dart
Normal file
90
lib/pages/setting/pages/home_tabbar_set.dart
Normal file
@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/common/tab_type.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class TabbarSetPage extends StatefulWidget {
|
||||
const TabbarSetPage({super.key});
|
||||
|
||||
@override
|
||||
State<TabbarSetPage> createState() => _TabbarSetPageState();
|
||||
}
|
||||
|
||||
class _TabbarSetPageState extends State<TabbarSetPage> {
|
||||
Box settingStorage = GStrorage.setting;
|
||||
late List defaultTabs;
|
||||
late List<String> tabbarSort;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
defaultTabs = tabsConfig;
|
||||
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
|
||||
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
|
||||
}
|
||||
|
||||
void saveEdit() {
|
||||
List<String> sortedTabbar = defaultTabs
|
||||
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
|
||||
.map<String>((i) => (i['type'] as TabType).id)
|
||||
.toList();
|
||||
if (sortedTabbar.isEmpty) {
|
||||
SmartDialog.showToast('请至少设置一项!');
|
||||
return;
|
||||
}
|
||||
settingStorage.put(SettingBoxKey.tabbarSort, sortedTabbar);
|
||||
SmartDialog.showToast('保存成功,下次启动时生效');
|
||||
}
|
||||
|
||||
void onReorder(int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
if (newIndex > oldIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final tabsItem = defaultTabs.removeAt(oldIndex);
|
||||
defaultTabs.insert(newIndex, tabsItem);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final listTiles = [
|
||||
for (int i = 0; i < defaultTabs.length; i++) ...[
|
||||
CheckboxListTile(
|
||||
key: Key(defaultTabs[i]['label']),
|
||||
value: tabbarSort.contains((defaultTabs[i]['type'] as TabType).id),
|
||||
onChanged: (bool? newValue) {
|
||||
String tabTypeId = (defaultTabs[i]['type'] as TabType).id;
|
||||
if (!newValue!) {
|
||||
tabbarSort.remove(tabTypeId);
|
||||
} else {
|
||||
tabbarSort.add(tabTypeId);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(defaultTabs[i]['label']),
|
||||
secondary: const Icon(Icons.drag_indicator_rounded),
|
||||
)
|
||||
]
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Tabbar编辑'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => saveEdit(), child: const Text('保存')),
|
||||
const SizedBox(width: 12)
|
||||
],
|
||||
),
|
||||
body: ReorderableListView(
|
||||
onReorder: onReorder,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
footer: SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 30,
|
||||
),
|
||||
children: listTiles,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -254,6 +254,11 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
onTap: () => Get.toNamed('/fontSizeSetting'),
|
||||
title: Text('字体大小', style: titleStyle),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () => Get.toNamed('/tabbarSetting'),
|
||||
title: Text('首页tabbar', style: titleStyle),
|
||||
),
|
||||
if (Platform.isAndroid)
|
||||
ListTile(
|
||||
dense: false,
|
||||
|
@ -9,7 +9,8 @@ import './controller.dart';
|
||||
|
||||
class RelatedVideoPanel extends StatelessWidget {
|
||||
final ReleatedController _releatedController =
|
||||
Get.put(ReleatedController(), tag: Get.arguments['heroTag']);
|
||||
Get.put(ReleatedController(), tag: Get.arguments?['heroTag']);
|
||||
RelatedVideoPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -849,6 +849,13 @@ InlineSpan buildContent(
|
||||
WidgetSpan(
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints box) {
|
||||
double maxHeight = box.maxWidth * 0.6; // 设置最大高度
|
||||
// double width = (box.maxWidth / 2).truncateToDouble();
|
||||
double height = ((box.maxWidth /
|
||||
2 *
|
||||
pictureItem['img_height'] /
|
||||
pictureItem['img_width']))
|
||||
.truncateToDouble();
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
@ -859,15 +866,28 @@ InlineSpan buildContent(
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
constraints: BoxConstraints(maxHeight: maxHeight),
|
||||
width: box.maxWidth / 2,
|
||||
height: height,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: NetworkImgLayer(
|
||||
src: pictureItem['img_src'],
|
||||
width: box.maxWidth / 2,
|
||||
height: box.maxWidth *
|
||||
0.5 *
|
||||
pictureItem['img_height'] /
|
||||
pictureItem['img_width'],
|
||||
height: height,
|
||||
),
|
||||
),
|
||||
height > maxHeight
|
||||
? const PBadge(
|
||||
text: '长图',
|
||||
right: 8,
|
||||
bottom: 8,
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -2,12 +2,10 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class VideoReplyNewDialog extends StatefulWidget {
|
||||
final int? oid;
|
||||
@ -34,25 +32,16 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
final TextEditingController _replyContentController = TextEditingController();
|
||||
final FocusNode replyContentFocusNode = FocusNode();
|
||||
final GlobalKey _formKey = GlobalKey<FormState>();
|
||||
double _keyboardHeight = 0.0; // 键盘高度
|
||||
final _debouncer = Debouncer(milliseconds: 100); // 设置延迟时间
|
||||
bool ableClean = false;
|
||||
Timer? timer;
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 监听输入框聚焦
|
||||
// replyContentFocusNode.addListener(_onFocus);
|
||||
_replyContentController.addListener(_printLatestValue);
|
||||
// 界面观察者 必须
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
// 自动聚焦
|
||||
_autoFocus();
|
||||
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
}
|
||||
|
||||
_autoFocus() async {
|
||||
@ -62,12 +51,6 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
}
|
||||
}
|
||||
|
||||
_printLatestValue() {
|
||||
setState(() {
|
||||
ableClean = _replyContentController.text != '';
|
||||
});
|
||||
}
|
||||
|
||||
Future submitReplyAdd() async {
|
||||
feedBack();
|
||||
String message = _replyContentController.text;
|
||||
@ -90,24 +73,6 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
super.didChangeMetrics();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// 键盘高度
|
||||
final viewInsets = EdgeInsets.fromViewPadding(
|
||||
View.of(context).viewInsets, View.of(context).devicePixelRatio);
|
||||
_debouncer.run(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_keyboardHeight =
|
||||
_keyboardHeight == 0.0 ? viewInsets.bottom : _keyboardHeight;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
@ -117,8 +82,10 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double keyboardHeight = EdgeInsets.fromViewPadding(
|
||||
View.of(context).viewInsets, View.of(context).devicePixelRatio)
|
||||
.bottom;
|
||||
return Container(
|
||||
height: 500,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
@ -130,10 +97,15 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 200,
|
||||
minHeight: 120,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 6, right: 15, left: 15, bottom: 10),
|
||||
top: 12, right: 15, left: 15, bottom: 10),
|
||||
child: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
@ -154,6 +126,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
@ -183,7 +156,8 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
MaterialStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).highlightColor;
|
||||
}),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
@ -196,7 +170,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: _keyboardHeight,
|
||||
height: keyboardHeight,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -204,22 +178,3 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef DebounceCallback = void Function();
|
||||
|
||||
class Debouncer {
|
||||
DebounceCallback? callback;
|
||||
final int? milliseconds;
|
||||
Timer? _timer;
|
||||
|
||||
Debouncer({this.milliseconds});
|
||||
|
||||
run(DebounceCallback callback) {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(Duration(milliseconds: milliseconds!), () {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
plPlayerController!.triggerFullScreen(status: false);
|
||||
}
|
||||
shutdownTimerService.handleWaitingFinished();
|
||||
|
||||
/// 顺序播放 列表循环
|
||||
if (plPlayerController!.playRepeat != PlayRepeat.pause &&
|
||||
plPlayerController!.playRepeat != PlayRepeat.singleCycle) {
|
||||
@ -234,7 +235,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
videoIntroController.isPaused = false;
|
||||
if (_extendNestCtr.position.pixels == 0 && autoplay) {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
plPlayerController!.seekTo(videoDetailController.defaultST);
|
||||
plPlayerController?.seekTo(videoDetailController.defaultST);
|
||||
plPlayerController?.play();
|
||||
}
|
||||
plPlayerController?.addStatusLister(playerListener);
|
||||
@ -308,7 +309,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
body: ExtendedNestedScrollView(
|
||||
controller: _extendNestCtr,
|
||||
headerSliverBuilder:
|
||||
(BuildContext _context, bool innerBoxIsScrolled) {
|
||||
(BuildContext context, bool innerBoxIsScrolled) {
|
||||
return <Widget>[
|
||||
Obx(
|
||||
() => SliverAppBar(
|
||||
@ -418,7 +419,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: videoDetailController
|
||||
|
@ -621,7 +621,7 @@ class PlPlayerController {
|
||||
if (duration.value.inSeconds != 0) {
|
||||
if (type != 'slider') {
|
||||
/// 拖动进度条调节时,不等待第一帧,防止抖动
|
||||
await _videoPlayerController!.stream.buffer.first;
|
||||
await _videoPlayerController?.stream.buffer.first;
|
||||
}
|
||||
await _videoPlayerController?.seek(position);
|
||||
// if (playerStatus.stopped) {
|
||||
@ -786,7 +786,7 @@ class PlPlayerController {
|
||||
volume.value = volumeNew;
|
||||
|
||||
try {
|
||||
FlutterVolumeController.showSystemUI = false;
|
||||
FlutterVolumeController.updateShowSystemUI(false);
|
||||
await FlutterVolumeController.setVolume(volumeNew);
|
||||
} catch (err) {
|
||||
print(err);
|
||||
@ -1086,12 +1086,13 @@ class PlPlayerController {
|
||||
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);
|
||||
localCache.put(LocalCacheKey.danmakuFontScale, fontSizeVal);
|
||||
localCache.put(LocalCacheKey.danmakuDuration, danmakuDurationVal);
|
||||
|
||||
if (_videoPlayerController != null) {
|
||||
var pp = _videoPlayerController!.platform as NativePlayer;
|
||||
await pp.setProperty('audio-files', '');
|
||||
removeListeners();
|
||||
await _videoPlayerController?.dispose();
|
||||
_videoPlayerController = null;
|
||||
}
|
||||
_instance = null;
|
||||
// 关闭所有视频页面恢复亮度
|
||||
resetBrightness();
|
||||
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -130,7 +129,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
setting.get(SettingBoxKey.enableBackgroundPlay, defaultValue: false);
|
||||
Future.microtask(() async {
|
||||
try {
|
||||
FlutterVolumeController.showSystemUI = true;
|
||||
FlutterVolumeController.updateShowSystemUI(true);
|
||||
_ctr.volumeValue.value = (await FlutterVolumeController.getVolume())!;
|
||||
FlutterVolumeController.addListener((double value) {
|
||||
if (mounted && !_ctr.volumeInterceptEventStream.value) {
|
||||
@ -154,7 +153,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
|
||||
Future<void> setVolume(double value) async {
|
||||
try {
|
||||
FlutterVolumeController.showSystemUI = false;
|
||||
FlutterVolumeController.updateShowSystemUI(false);
|
||||
await FlutterVolumeController.setVolume(value);
|
||||
} catch (_) {}
|
||||
_ctr.volumeValue.value = value;
|
||||
@ -703,7 +702,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
}
|
||||
}),
|
||||
|
||||
|
@ -36,6 +36,7 @@ import '../pages/setting/index.dart';
|
||||
import '../pages/setting/pages/color_select.dart';
|
||||
import '../pages/setting/pages/display_mode.dart';
|
||||
import '../pages/setting/pages/font_size_select.dart';
|
||||
import '../pages/setting/pages/home_tabbar_set.dart';
|
||||
import '../pages/setting/pages/play_speed_set.dart';
|
||||
import '../pages/setting/recommend_setting.dart';
|
||||
import '../pages/setting/play_setting.dart';
|
||||
@ -114,6 +115,8 @@ class Routes {
|
||||
//
|
||||
CustomGetPage(name: '/blackListPage', page: () => const BlackListPage()),
|
||||
CustomGetPage(name: '/colorSetting', page: () => const ColorSelectPage()),
|
||||
// 首页tabbar
|
||||
CustomGetPage(name: '/tabbarSetting', page: () => const TabbarSetPage()),
|
||||
CustomGetPage(
|
||||
name: '/fontSizeSetting', page: () => const FontSizeSelectPage()),
|
||||
// 屏幕帧率
|
||||
|
7
lib/utils/extension.dart
Normal file
7
lib/utils/extension.dart
Normal file
@ -0,0 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension ImageExtension on num {
|
||||
int cacheSize(BuildContext context) {
|
||||
return (this * MediaQuery.of(context).devicePixelRatio).round();
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@ -72,4 +73,19 @@ class IdUtils {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// eid生成
|
||||
static String? genAuroraEid(int uid) {
|
||||
if (uid == 0) {
|
||||
return null;
|
||||
}
|
||||
String uidString = uid.toString();
|
||||
List<int> resultBytes = List.generate(
|
||||
uidString.length,
|
||||
(i) => uidString.codeUnitAt(i) ^ "ad1va46a7lza".codeUnitAt(i % 12),
|
||||
);
|
||||
String auroraEid = base64Url.encode(resultBytes);
|
||||
auroraEid = auroraEid.replaceAll(RegExp(r'=*$', multiLine: true), '');
|
||||
return auroraEid;
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,11 @@ import 'dart:io';
|
||||
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:pilipala/models/home/rcmd/result.dart';
|
||||
import 'package:pilipala/models/model_owner.dart';
|
||||
import 'package:pilipala/models/search/hot.dart';
|
||||
import 'package:pilipala/models/user/info.dart';
|
||||
|
||||
class GStrorage {
|
||||
static late final Box<dynamic> recVideo;
|
||||
static late final Box<dynamic> userInfo;
|
||||
static late final Box<dynamic> historyword;
|
||||
static late final Box<dynamic> localCache;
|
||||
@ -21,13 +19,6 @@ class GStrorage {
|
||||
final String path = dir.path;
|
||||
await Hive.initFlutter('$path/hive');
|
||||
regAdapter();
|
||||
// 首页推荐视频
|
||||
recVideo = await Hive.openBox(
|
||||
'recVideo',
|
||||
compactionStrategy: (int entries, int deletedEntries) {
|
||||
return deletedEntries > 12;
|
||||
},
|
||||
);
|
||||
// 登录用户信息
|
||||
userInfo = await Hive.openBox(
|
||||
'userInfo',
|
||||
@ -54,10 +45,6 @@ class GStrorage {
|
||||
}
|
||||
|
||||
static void regAdapter() {
|
||||
Hive.registerAdapter(RecVideoItemAppModelAdapter());
|
||||
Hive.registerAdapter(RcmdReasonAdapter());
|
||||
Hive.registerAdapter(RcmdStatAdapter());
|
||||
Hive.registerAdapter(RcmdOwnerAdapter());
|
||||
Hive.registerAdapter(OwnerAdapter());
|
||||
Hive.registerAdapter(UserInfoDataAdapter());
|
||||
Hive.registerAdapter(LevelInfoAdapter());
|
||||
@ -73,8 +60,6 @@ class GStrorage {
|
||||
static Future<void> close() async {
|
||||
// user.compact();
|
||||
// user.close();
|
||||
recVideo.compact();
|
||||
recVideo.close();
|
||||
userInfo.compact();
|
||||
userInfo.close();
|
||||
historyword.compact();
|
||||
@ -151,7 +136,8 @@ class SettingBoxKey {
|
||||
customRows = 'customRows', // 自定义列
|
||||
enableMYBar = 'enableMYBar',
|
||||
hideSearchBar = 'hideSearchBar', // 收起顶栏
|
||||
hideTabBar = 'hideTabBar'; // 收起底栏
|
||||
hideTabBar = 'hideTabBar', // 收起底栏
|
||||
tabbarSort = 'tabbarSort'; // 首页tabbar
|
||||
}
|
||||
|
||||
class LocalCacheKey {
|
||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.16+1016
|
||||
version: 1.0.17+1017
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.6 <3.0.0"
|
||||
|
Reference in New Issue
Block a user