mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-17 23:56:13 +08:00
tweaks (#1187)
* opt: marquee * fix: bangumi seek * opt: post panel * opt: remove deprecated code * opt: singleton dynController * fix: music scheme * feat: MemberVideo jump keep position * tweak
This commit is contained in:
committed by
GitHub
parent
e8a674ca2a
commit
172389b12b
@@ -15,7 +15,7 @@ class ColorPalette extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final Hct hct = Hct.fromInt(color.value);
|
final Hct hct = Hct.fromInt(color.toARGB32());
|
||||||
final primary = Color(Hct.from(hct.hue, 20.0, 90.0).toInt());
|
final primary = Color(Hct.from(hct.hue, 20.0, 90.0).toInt());
|
||||||
final tertiary = Color(Hct.from(hct.hue + 50, 20.0, 85.0).toInt());
|
final tertiary = Color(Hct.from(hct.hue + 50, 20.0, 85.0).toInt());
|
||||||
final primaryContainer = Color(Hct.from(hct.hue, 30.0, 50.0).toInt());
|
final primaryContainer = Color(Hct.from(hct.hue, 30.0, 50.0).toInt());
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class LoadingWidget extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.dialogBackgroundColor,
|
color: theme.dialogTheme.backgroundColor,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
void autoWrapReportDialog(
|
Future<void> autoWrapReportDialog(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Map<String, Map<int, String>> options,
|
Map<String, Map<int, String>> options,
|
||||||
Future<Map> Function(int reasonType, String? reasonDesc, bool banUid)
|
Future<Map> Function(int reasonType, String? reasonDesc, bool banUid)
|
||||||
@@ -14,30 +14,30 @@ void autoWrapReportDialog(
|
|||||||
String? reasonDesc;
|
String? reasonDesc;
|
||||||
bool banUid = false;
|
bool banUid = false;
|
||||||
late final key = GlobalKey<FormState>();
|
late final key = GlobalKey<FormState>();
|
||||||
showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => StatefulBuilder(
|
builder: (context) {
|
||||||
builder: (context, setState) {
|
return AlertDialog(
|
||||||
return AlertDialog(
|
title: const Text('举报'),
|
||||||
title: const Text('举报'),
|
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
|
||||||
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
|
contentPadding: const EdgeInsets.symmetric(vertical: 5),
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 5),
|
actionsPadding: const EdgeInsets.only(
|
||||||
actionsPadding: const EdgeInsets.only(
|
left: 16,
|
||||||
left: 16,
|
right: 16,
|
||||||
right: 16,
|
bottom: 10,
|
||||||
bottom: 10,
|
),
|
||||||
),
|
content: Form(
|
||||||
content: Form(
|
key: key,
|
||||||
key: key,
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Flexible(
|
||||||
Flexible(
|
child: SingleChildScrollView(
|
||||||
child: SingleChildScrollView(
|
child: AnimatedSize(
|
||||||
child: AnimatedSize(
|
duration: const Duration(milliseconds: 200),
|
||||||
duration: const Duration(milliseconds: 200),
|
child: Builder(
|
||||||
child: Column(
|
builder: (context) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Padding(
|
const Padding(
|
||||||
@@ -48,13 +48,20 @@ void autoWrapReportDialog(
|
|||||||
),
|
),
|
||||||
child: Text('请选择举报的理由:'),
|
child: Text('请选择举报的理由:'),
|
||||||
),
|
),
|
||||||
...options.entries.map(
|
RadioGroup(
|
||||||
(entry) => WrapRadioOptionsGroup<int>(
|
onChanged: (value) {
|
||||||
groupTitle: entry.key,
|
reasonType = value;
|
||||||
options: entry.value,
|
(context as Element).markNeedsBuild();
|
||||||
selectedValue: reasonType,
|
},
|
||||||
onChanged: (value) =>
|
groupValue: reasonType,
|
||||||
setState(() => reasonType = value),
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: options.entries.map((entry) {
|
||||||
|
return WrapRadioOptionsGroup<int>(
|
||||||
|
groupTitle: entry.key,
|
||||||
|
options: entry.value,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (reasonType == 0)
|
if (reasonType == 0)
|
||||||
@@ -66,51 +73,51 @@ void autoWrapReportDialog(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.only(left: 14, top: 6),
|
Padding(
|
||||||
child: CheckBoxText(
|
padding: const EdgeInsets.only(left: 14, top: 6),
|
||||||
text: '拉黑该用户',
|
child: CheckBoxText(
|
||||||
onChanged: (value) => banUid = value,
|
text: '拉黑该用户',
|
||||||
),
|
onChanged: (value) => banUid = value,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
TextButton(
|
||||||
TextButton(
|
onPressed: () async {
|
||||||
onPressed: Get.back,
|
if (reasonType == null ||
|
||||||
child: Text(
|
(reasonType == 0 && key.currentState?.validate() != true)) {
|
||||||
'取消',
|
return;
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
}
|
||||||
),
|
SmartDialog.showLoading();
|
||||||
),
|
try {
|
||||||
TextButton(
|
final data = await onSuccess(reasonType!, reasonDesc, banUid);
|
||||||
onPressed: () async {
|
SmartDialog.dismiss();
|
||||||
if (reasonType == null ||
|
if (data['code'] == 0) {
|
||||||
(reasonType == 0 && key.currentState?.validate() != true)) {
|
Get.back();
|
||||||
return;
|
SmartDialog.showToast('举报成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(data['message']);
|
||||||
}
|
}
|
||||||
SmartDialog.showLoading();
|
} catch (e) {
|
||||||
try {
|
SmartDialog.dismiss();
|
||||||
final data = await onSuccess(reasonType!, reasonDesc, banUid);
|
SmartDialog.showToast('提交失败:$e');
|
||||||
SmartDialog.dismiss();
|
}
|
||||||
if (data['code'] == 0) {
|
},
|
||||||
Get.back();
|
child: const Text('确定'),
|
||||||
SmartDialog.showToast('举报成功');
|
),
|
||||||
} else {
|
],
|
||||||
SmartDialog.showToast(data['message']);
|
);
|
||||||
}
|
},
|
||||||
} catch (e) {
|
|
||||||
SmartDialog.dismiss();
|
|
||||||
SmartDialog.showToast('提交失败:$e');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('确定'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +193,8 @@ class _CheckBoxTextState extends State<CheckBoxText> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selected = !_selected;
|
_selected = !_selected;
|
||||||
|
widget.onChanged(_selected);
|
||||||
});
|
});
|
||||||
widget.onChanged(_selected);
|
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
|
|||||||
@@ -1,126 +1,124 @@
|
|||||||
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
|
||||||
import 'package:PiliPlus/http/member.dart';
|
import 'package:PiliPlus/http/member.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:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class MemberReportPanel extends StatefulWidget {
|
Future<void> showMemberReportDialog(
|
||||||
const MemberReportPanel({
|
BuildContext context, {
|
||||||
super.key,
|
required Object? name,
|
||||||
required this.name,
|
required Object mid,
|
||||||
required this.mid,
|
}) {
|
||||||
});
|
final List<bool> reasonList = List.generate(3, (_) => false);
|
||||||
|
final Set<int> reason = {};
|
||||||
|
int? reasonV2;
|
||||||
|
|
||||||
final dynamic name;
|
return showDialog(
|
||||||
final dynamic mid;
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
@override
|
final theme = Theme.of(context);
|
||||||
State<MemberReportPanel> createState() => _MemberReportPanelState();
|
return AlertDialog(
|
||||||
}
|
clipBehavior: Clip.hardEdge,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
class _MemberReportPanelState extends State<MemberReportPanel> {
|
horizontal: 20,
|
||||||
final List<bool> _reasonList = List.generate(3, (_) => false);
|
vertical: 16,
|
||||||
final Set<int> _reason = {};
|
),
|
||||||
int? _reasonV2;
|
titleTextStyle: theme.textTheme.bodyMedium,
|
||||||
|
title: Column(
|
||||||
@override
|
spacing: 4,
|
||||||
Widget build(BuildContext context) {
|
children: [
|
||||||
final theme = Theme.of(context);
|
Text(
|
||||||
return SingleChildScrollView(
|
'举报: $name',
|
||||||
child: Column(
|
style: const TextStyle(fontSize: 18),
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'举报: ${widget.name}',
|
|
||||||
style: const TextStyle(fontSize: 18),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text('uid: ${widget.mid}'),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
const Text('举报内容(必选,可多选)'),
|
|
||||||
...List.generate(
|
|
||||||
3,
|
|
||||||
(index) => _checkBoxWidget(
|
|
||||||
_reasonList[index],
|
|
||||||
(value) {
|
|
||||||
setState(() => _reasonList[index] = value);
|
|
||||||
if (value) {
|
|
||||||
_reason.add(index + 1);
|
|
||||||
} else {
|
|
||||||
_reason.remove(index + 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
const ['头像违规', '昵称违规', '签名违规'][index],
|
|
||||||
),
|
),
|
||||||
),
|
Text('uid: $mid'),
|
||||||
const Text('举报理由(单选,非必选)'),
|
],
|
||||||
...List.generate(
|
),
|
||||||
5,
|
content: SingleChildScrollView(
|
||||||
(index) => RadioWidget<int>(
|
child: Column(
|
||||||
value: index,
|
mainAxisSize: MainAxisSize.min,
|
||||||
groupValue: _reasonV2,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
onChanged: (value) {
|
|
||||||
setState(() => _reasonV2 = value);
|
|
||||||
},
|
|
||||||
title: const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
const Text('举报内容(必选,可多选)'),
|
||||||
onPressed: Get.back,
|
...List.generate(
|
||||||
child: Text(
|
3,
|
||||||
'取消',
|
(index) => Builder(
|
||||||
style: TextStyle(color: theme.colorScheme.outline),
|
builder: (context) => CheckboxListTile(
|
||||||
|
dense: true,
|
||||||
|
value: reasonList[index],
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
onChanged: (value) {
|
||||||
|
reasonList[index] = value!;
|
||||||
|
if (value) {
|
||||||
|
reason.add(index + 1);
|
||||||
|
} else {
|
||||||
|
reason.remove(index + 1);
|
||||||
|
}
|
||||||
|
(context as Element).markNeedsBuild();
|
||||||
|
},
|
||||||
|
title: Text(const ['头像违规', '昵称违规', '签名违规'][index]),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
const Text('举报理由(单选,非必选)'),
|
||||||
onPressed: () async {
|
Builder(
|
||||||
if (_reason.isEmpty) {
|
builder: (context) => RadioGroup<int>(
|
||||||
SmartDialog.showToast('至少选择一项作为举报内容');
|
onChanged: (v) {
|
||||||
} else {
|
reasonV2 = v;
|
||||||
Get.back();
|
(context as Element).markNeedsBuild();
|
||||||
var result = await MemberHttp.reportMember(
|
},
|
||||||
widget.mid,
|
groupValue: reasonV2,
|
||||||
reason: _reason.join(','),
|
child: Column(
|
||||||
reasonV2: _reasonV2 != null ? _reasonV2! + 1 : null,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
);
|
children: List.generate(
|
||||||
if (result['msg'] is String && result['msg'].isNotEmpty) {
|
5,
|
||||||
SmartDialog.showToast(result['msg']);
|
(index) => RadioListTile<int>(
|
||||||
} else {
|
toggleable: true,
|
||||||
SmartDialog.showToast('举报失败');
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
}
|
contentPadding: const EdgeInsets.only(left: 4),
|
||||||
}
|
dense: true,
|
||||||
},
|
value: index,
|
||||||
child: const Text('确定'),
|
title: Text(
|
||||||
|
const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _checkBoxWidget(
|
|
||||||
bool defValue,
|
|
||||||
ValueChanged onChanged,
|
|
||||||
String title,
|
|
||||||
) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => onChanged(!defValue),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Checkbox(
|
|
||||||
value: defValue,
|
|
||||||
onChanged: onChanged,
|
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
),
|
),
|
||||||
Text(title),
|
actions: [
|
||||||
],
|
TextButton(
|
||||||
),
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: theme.colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (reason.isEmpty) {
|
||||||
|
SmartDialog.showToast('至少选择一项作为举报内容');
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
var result = await MemberHttp.reportMember(
|
||||||
|
mid,
|
||||||
|
reason: reason.join(','),
|
||||||
|
reasonV2: reasonV2 != null ? reasonV2! + 1 : null,
|
||||||
|
);
|
||||||
|
if (result['msg'] is String && result['msg'].isNotEmpty) {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast('举报失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
@@ -7,7 +8,7 @@ class MarqueeText extends StatelessWidget {
|
|||||||
final TextStyle? style;
|
final TextStyle? style;
|
||||||
final double spacing;
|
final double spacing;
|
||||||
final double velocity;
|
final double velocity;
|
||||||
final MarqueeController? controller;
|
final ContextSingleTicker? provider;
|
||||||
|
|
||||||
const MarqueeText(
|
const MarqueeText(
|
||||||
this.text, {
|
this.text, {
|
||||||
@@ -15,7 +16,7 @@ class MarqueeText extends StatelessWidget {
|
|||||||
this.style,
|
this.style,
|
||||||
this.spacing = 0,
|
this.spacing = 0,
|
||||||
this.velocity = 25,
|
this.velocity = 25,
|
||||||
this.controller,
|
this.provider,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -23,7 +24,7 @@ class MarqueeText extends StatelessWidget {
|
|||||||
return NormalMarquee(
|
return NormalMarquee(
|
||||||
velocity: velocity,
|
velocity: velocity,
|
||||||
spacing: spacing,
|
spacing: spacing,
|
||||||
controller: controller,
|
provider: provider,
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: style,
|
style: style,
|
||||||
@@ -39,7 +40,7 @@ abstract class Marquee extends SingleChildRenderObjectWidget {
|
|||||||
final Clip clipBehavior;
|
final Clip clipBehavior;
|
||||||
final double spacing;
|
final double spacing;
|
||||||
final double velocity;
|
final double velocity;
|
||||||
final MarqueeController? controller;
|
final ContextSingleTicker? provider;
|
||||||
|
|
||||||
const Marquee({
|
const Marquee({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -48,7 +49,7 @@ abstract class Marquee extends SingleChildRenderObjectWidget {
|
|||||||
this.direction = Axis.horizontal,
|
this.direction = Axis.horizontal,
|
||||||
this.clipBehavior = Clip.hardEdge,
|
this.clipBehavior = Clip.hardEdge,
|
||||||
this.spacing = 0,
|
this.spacing = 0,
|
||||||
this.controller,
|
this.provider,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -61,6 +62,10 @@ abstract class Marquee extends SingleChildRenderObjectWidget {
|
|||||||
..clipBehavior = clipBehavior
|
..clipBehavior = clipBehavior
|
||||||
..velocity = velocity
|
..velocity = velocity
|
||||||
..spacing = spacing;
|
..spacing = spacing;
|
||||||
|
|
||||||
|
if (provider != null) {
|
||||||
|
renderObject.provider = provider!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +77,7 @@ class NormalMarquee extends Marquee {
|
|||||||
super.direction,
|
super.direction,
|
||||||
super.clipBehavior,
|
super.clipBehavior,
|
||||||
super.spacing,
|
super.spacing,
|
||||||
super.controller,
|
super.provider,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -81,7 +86,7 @@ class NormalMarquee extends Marquee {
|
|||||||
velocity: velocity,
|
velocity: velocity,
|
||||||
clipBehavior: clipBehavior,
|
clipBehavior: clipBehavior,
|
||||||
spacing: spacing,
|
spacing: spacing,
|
||||||
controller: controller,
|
provider: provider ?? ContextSingleTicker(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +98,7 @@ class BounceMarquee extends Marquee {
|
|||||||
super.direction,
|
super.direction,
|
||||||
super.clipBehavior,
|
super.clipBehavior,
|
||||||
super.spacing,
|
super.spacing,
|
||||||
|
super.provider,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -101,6 +107,7 @@ class BounceMarquee extends Marquee {
|
|||||||
velocity: velocity,
|
velocity: velocity,
|
||||||
clipBehavior: clipBehavior,
|
clipBehavior: clipBehavior,
|
||||||
spacing: spacing,
|
spacing: spacing,
|
||||||
|
provider: provider ?? ContextSingleTicker(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,16 +118,15 @@ abstract class MarqueeRender extends RenderBox
|
|||||||
required double velocity,
|
required double velocity,
|
||||||
required double spacing,
|
required double spacing,
|
||||||
required this.clipBehavior,
|
required this.clipBehavior,
|
||||||
this.controller,
|
required ContextSingleTicker provider,
|
||||||
}) : _spacing = spacing,
|
}) : _ticker = provider,
|
||||||
|
_spacing = spacing,
|
||||||
_velocity = velocity,
|
_velocity = velocity,
|
||||||
_direction = direction,
|
_direction = direction,
|
||||||
assert(spacing.isFinite && !spacing.isNaN);
|
assert(spacing.isFinite && !spacing.isNaN);
|
||||||
|
|
||||||
Clip clipBehavior;
|
Clip clipBehavior;
|
||||||
|
|
||||||
MarqueeController? controller;
|
|
||||||
|
|
||||||
Axis _direction;
|
Axis _direction;
|
||||||
Axis get direction => _direction;
|
Axis get direction => _direction;
|
||||||
set direction(Axis value) {
|
set direction(Axis value) {
|
||||||
@@ -129,12 +135,26 @@ abstract class MarqueeRender extends RenderBox
|
|||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContextSingleTicker _ticker;
|
||||||
|
set provider(ContextSingleTicker value) {
|
||||||
|
if (_ticker == value) return;
|
||||||
|
if (_ticker._ticker != null) {
|
||||||
|
if (value._ticker != null) {
|
||||||
|
value._ticker!.absorbTicker(_ticker._ticker!);
|
||||||
|
} else {
|
||||||
|
value.createTicker(_onTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ticker.cancel();
|
||||||
|
_ticker = value;
|
||||||
|
}
|
||||||
|
|
||||||
double _velocity;
|
double _velocity;
|
||||||
set velocity(double value) {
|
set velocity(double value) {
|
||||||
if (_velocity == value) return;
|
if (_velocity == value) return;
|
||||||
_velocity = value;
|
_velocity = value;
|
||||||
_simulation = _simulation?.copyWith(initialValue: _delta, velocity: value);
|
_simulation = _simulation?.copyWith(initialValue: _delta, velocity: value);
|
||||||
controller?.reset();
|
_ticker.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
double _spacing;
|
double _spacing;
|
||||||
@@ -149,7 +169,7 @@ abstract class MarqueeRender extends RenderBox
|
|||||||
addSize: value - _spacing,
|
addSize: value - _spacing,
|
||||||
);
|
);
|
||||||
_spacing = value;
|
_spacing = value;
|
||||||
controller?.reset();
|
_ticker.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
double _delta = 0;
|
double _delta = 0;
|
||||||
@@ -160,14 +180,14 @@ abstract class MarqueeRender extends RenderBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void detach() {
|
void attach(PipelineOwner owner) {
|
||||||
controller?.dispose();
|
super.attach(owner);
|
||||||
super.detach();
|
_ticker.updateTicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
controller?.dispose();
|
_ticker.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,11 +223,9 @@ abstract class MarqueeRender extends RenderBox
|
|||||||
|
|
||||||
if (_distance > 0) {
|
if (_distance > 0) {
|
||||||
updateSize();
|
updateSize();
|
||||||
(controller ??= MarqueeController())
|
_ticker.createTicker(_onTick);
|
||||||
..ticker ??= Ticker(_onTick)
|
|
||||||
..initStart();
|
|
||||||
} else {
|
} else {
|
||||||
controller?.dispose();
|
_ticker.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +255,7 @@ class _BounceMarqueeRender extends MarqueeRender {
|
|||||||
required super.velocity,
|
required super.velocity,
|
||||||
required super.clipBehavior,
|
required super.clipBehavior,
|
||||||
required super.spacing,
|
required super.spacing,
|
||||||
|
required super.provider,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -278,7 +297,7 @@ class _NormalMarqueeRender extends MarqueeRender {
|
|||||||
required super.velocity,
|
required super.velocity,
|
||||||
required super.clipBehavior,
|
required super.clipBehavior,
|
||||||
required super.spacing,
|
required super.spacing,
|
||||||
super.controller,
|
required super.provider,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -375,44 +394,56 @@ class _MarqueeSimulation extends Simulation {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on Ticker {
|
class ContextSingleTicker {
|
||||||
|
Ticker? _ticker;
|
||||||
|
BuildContext context;
|
||||||
|
|
||||||
|
ContextSingleTicker(this.context);
|
||||||
|
|
||||||
|
void createTicker(TickerCallback onTick) {
|
||||||
|
assert(() {
|
||||||
|
if (_ticker == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary(
|
||||||
|
'$runtimeType is a SingleTickerProviderStateMixin but multiple tickers were created.',
|
||||||
|
),
|
||||||
|
ErrorDescription(
|
||||||
|
'A SingleTickerProviderStateMixin can only be used as a TickerProvider once.',
|
||||||
|
),
|
||||||
|
ErrorHint(
|
||||||
|
'If a State is used for multiple AnimationController objects, or if it is passed to other '
|
||||||
|
'objects and those objects might use it more than one time in total, then instead of '
|
||||||
|
'mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}());
|
||||||
|
_ticker = Ticker(
|
||||||
|
onTick,
|
||||||
|
debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null,
|
||||||
|
)..start();
|
||||||
|
_tickerModeNotifier = TickerMode.getNotifier(context)
|
||||||
|
..addListener(updateTicker);
|
||||||
|
updateTicker(); // Sets _ticker.mute correctly.
|
||||||
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
this
|
_ticker
|
||||||
..stop()
|
?..stop()
|
||||||
..start();
|
..start();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
void cancel() {
|
||||||
class MarqueeController {
|
_ticker?.dispose();
|
||||||
MarqueeController({this.autoStart = true});
|
_ticker = null;
|
||||||
bool autoStart;
|
_tickerModeNotifier?.removeListener(updateTicker);
|
||||||
|
_tickerModeNotifier = null;
|
||||||
Ticker? ticker;
|
}
|
||||||
|
|
||||||
void initStart() {
|
ValueListenable<bool>? _tickerModeNotifier;
|
||||||
if (autoStart) {
|
|
||||||
start();
|
void updateTicker() => _ticker?.muted = !_tickerModeNotifier!.value;
|
||||||
}
|
|
||||||
}
|
set muted(bool value) => _ticker?.muted = value;
|
||||||
|
|
||||||
void start() {
|
|
||||||
if (ticker != null) {
|
|
||||||
if (!ticker!.isTicking) {
|
|
||||||
ticker!.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
ticker?.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
ticker?.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
ticker?.dispose();
|
|
||||||
ticker = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,88 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class RadioWidget<T> extends StatelessWidget {
|
class RadioWidget<T> extends StatefulWidget {
|
||||||
final T value;
|
final T value;
|
||||||
final T? groupValue;
|
|
||||||
final ValueChanged<T?> onChanged;
|
|
||||||
final String title;
|
final String title;
|
||||||
|
final bool tristate;
|
||||||
final EdgeInsetsGeometry? padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
|
final MainAxisSize mainAxisSize;
|
||||||
|
|
||||||
const RadioWidget({
|
const RadioWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.value,
|
required this.value,
|
||||||
this.groupValue,
|
|
||||||
required this.onChanged,
|
|
||||||
required this.title,
|
required this.title,
|
||||||
|
this.tristate = false,
|
||||||
this.padding,
|
this.padding,
|
||||||
|
this.mainAxisSize = MainAxisSize.min,
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget _child() => Row(
|
@override
|
||||||
children: [
|
State<RadioWidget<T>> createState() => RadioWidgetState<T>();
|
||||||
Radio<T>(
|
}
|
||||||
value: value,
|
|
||||||
groupValue: groupValue,
|
class RadioWidgetState<T> extends State<RadioWidget<T>> with RadioClient<T> {
|
||||||
onChanged: onChanged,
|
late final _RadioRegistry<T> _radioRegistry = _RadioRegistry<T>(this);
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
),
|
@override
|
||||||
Text(title),
|
final focusNode = FocusNode();
|
||||||
],
|
|
||||||
);
|
@override
|
||||||
|
T get radioValue => widget.value;
|
||||||
|
|
||||||
|
bool get checked => radioValue == registry!.groupValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get tristate => widget.tristate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
registry = null;
|
||||||
|
focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
registry = RadioGroup.maybeOf(context);
|
||||||
|
assert(registry != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTap() {
|
||||||
|
if (checked) {
|
||||||
|
if (tristate) registry!.onChanged(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
registry!.onChanged(radioValue);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final child = Row(
|
||||||
|
mainAxisSize: widget.mainAxisSize,
|
||||||
|
children: [
|
||||||
|
Focus(
|
||||||
|
parentNode: focusNode,
|
||||||
|
canRequestFocus: false,
|
||||||
|
skipTraversal: true,
|
||||||
|
includeSemantics: true,
|
||||||
|
descendantsAreFocusable: false,
|
||||||
|
descendantsAreTraversable: false,
|
||||||
|
child: Radio<T>(
|
||||||
|
value: radioValue,
|
||||||
|
groupRegistry: _radioRegistry,
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(widget.title),
|
||||||
|
],
|
||||||
|
);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => onChanged(value),
|
onTap: _handleTap,
|
||||||
child: padding != null
|
focusNode: focusNode,
|
||||||
? Padding(
|
child: widget.padding == null
|
||||||
padding: padding!,
|
? child
|
||||||
child: _child(),
|
: Padding(padding: widget.padding!, child: child),
|
||||||
)
|
|
||||||
: _child(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,16 +90,12 @@ class RadioWidget<T> extends StatelessWidget {
|
|||||||
class WrapRadioOptionsGroup<T> extends StatelessWidget {
|
class WrapRadioOptionsGroup<T> extends StatelessWidget {
|
||||||
final String groupTitle;
|
final String groupTitle;
|
||||||
final Map<T, String> options;
|
final Map<T, String> options;
|
||||||
final T? selectedValue;
|
|
||||||
final ValueChanged<T?> onChanged;
|
|
||||||
final EdgeInsetsGeometry? itemPadding;
|
final EdgeInsetsGeometry? itemPadding;
|
||||||
|
|
||||||
const WrapRadioOptionsGroup({
|
const WrapRadioOptionsGroup({
|
||||||
super.key,
|
super.key,
|
||||||
required this.groupTitle,
|
required this.groupTitle,
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.selectedValue,
|
|
||||||
required this.onChanged,
|
|
||||||
this.itemPadding,
|
this.itemPadding,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,14 +116,10 @@ class WrapRadioOptionsGroup<T> extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: options.entries.map((entry) {
|
children: options.entries.map((entry) {
|
||||||
return IntrinsicWidth(
|
return RadioWidget<T>(
|
||||||
child: RadioWidget<T>(
|
value: entry.key,
|
||||||
value: entry.key,
|
title: entry.value,
|
||||||
groupValue: selectedValue,
|
padding: itemPadding ?? const EdgeInsets.only(right: 10),
|
||||||
onChanged: onChanged,
|
|
||||||
title: entry.value,
|
|
||||||
padding: itemPadding ?? const EdgeInsets.only(right: 10),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
@@ -91,3 +128,27 @@ class WrapRadioOptionsGroup<T> extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A registry to controls internal [Radio] and hides it from [RadioGroup]
|
||||||
|
/// ancestor.
|
||||||
|
///
|
||||||
|
/// [RadioListTile] implements the [RadioClient] directly to register to
|
||||||
|
/// [RadioGroup] ancestor. Therefore, it has to hide the internal [Radio] from
|
||||||
|
/// participate in the [RadioGroup] ancestor.
|
||||||
|
class _RadioRegistry<T> extends RadioGroupRegistry<T> {
|
||||||
|
_RadioRegistry(this.state);
|
||||||
|
|
||||||
|
final RadioWidgetState<T> state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
T? get groupValue => state.registry!.groupValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ValueChanged<T?> get onChanged => state.registry!.onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void registerClient(RadioClient<T> radio) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void unregisterClient(RadioClient<T> radio) {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ class DynamicsHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
static Future dynamicDetail({
|
static Future<LoadingState<DynamicItemModel>> dynamicDetail({
|
||||||
dynamic id,
|
dynamic id,
|
||||||
dynamic rid,
|
dynamic rid,
|
||||||
dynamic type,
|
dynamic type,
|
||||||
@@ -236,21 +236,12 @@ class DynamicsHttp {
|
|||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
try {
|
try {
|
||||||
return {
|
return Success(DynamicItemModel.fromJson(res.data['data']['item']));
|
||||||
'status': true,
|
|
||||||
'data': DynamicItemModel.fromJson(res.data['data']['item']),
|
|
||||||
};
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return Error(err.toString());
|
||||||
'status': false,
|
|
||||||
'msg': err.toString(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return Error(res.data['message']);
|
||||||
'status': false,
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class MemberHttp {
|
|||||||
int? next,
|
int? next,
|
||||||
int? seasonId,
|
int? seasonId,
|
||||||
int? seriesId,
|
int? seriesId,
|
||||||
includeCursor,
|
bool? includeCursor,
|
||||||
}) async {
|
}) async {
|
||||||
final params = {
|
final params = {
|
||||||
'aid': ?aid,
|
'aid': ?aid,
|
||||||
|
|||||||
@@ -33,38 +33,31 @@ void main() async {
|
|||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
MediaKit.ensureInitialized();
|
MediaKit.ensureInitialized();
|
||||||
await GStorage.init();
|
await GStorage.init();
|
||||||
Get.put(AccountService());
|
Get.lazyPut(AccountService.new);
|
||||||
if (Pref.autoClearCache) {
|
|
||||||
await CacheManage.clearLibraryCache();
|
|
||||||
} else {
|
|
||||||
final num maxCacheSize = Pref.maxCacheSize;
|
|
||||||
if (maxCacheSize != 0) {
|
|
||||||
final double currCache = await CacheManage().loadApplicationCache();
|
|
||||||
if (currCache >= maxCacheSize) {
|
|
||||||
await CacheManage.clearLibraryCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Pref.horizontalScreen) {
|
|
||||||
await SystemChrome.setPreferredOrientations(
|
|
||||||
//支持竖屏与横屏
|
|
||||||
[
|
|
||||||
DeviceOrientation.portraitUp,
|
|
||||||
// DeviceOrientation.portraitDown,
|
|
||||||
DeviceOrientation.landscapeLeft,
|
|
||||||
DeviceOrientation.landscapeRight,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await SystemChrome.setPreferredOrientations(
|
|
||||||
//支持竖屏
|
|
||||||
[
|
|
||||||
DeviceOrientation.portraitUp,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
HttpOverrides.global = _CustomHttpOverrides();
|
HttpOverrides.global = _CustomHttpOverrides();
|
||||||
await setupServiceLocator();
|
|
||||||
|
await Future.wait([
|
||||||
|
CacheManage.autoClearCache(),
|
||||||
|
if (Pref.horizontalScreen)
|
||||||
|
SystemChrome.setPreferredOrientations(
|
||||||
|
//支持竖屏与横屏
|
||||||
|
[
|
||||||
|
DeviceOrientation.portraitUp,
|
||||||
|
// DeviceOrientation.portraitDown,
|
||||||
|
DeviceOrientation.landscapeLeft,
|
||||||
|
DeviceOrientation.landscapeRight,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
else
|
||||||
|
SystemChrome.setPreferredOrientations(
|
||||||
|
//支持竖屏
|
||||||
|
[
|
||||||
|
DeviceOrientation.portraitUp,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
setupServiceLocator(),
|
||||||
|
]);
|
||||||
|
|
||||||
Request();
|
Request();
|
||||||
Request.setCookie();
|
Request.setCookie();
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,16 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
getCurrentApp();
|
getCurrentApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
currentVersion.close();
|
||||||
|
cacheSize.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getCacheSize() async {
|
Future<void> getCacheSize() async {
|
||||||
cacheSize.value = CacheManage.formatSize(
|
cacheSize.value = CacheManage.formatSize(
|
||||||
await CacheManage().loadApplicationCache(),
|
await CacheManage.loadApplicationCache(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class ArticleController extends CommonDynController {
|
|||||||
id = opusId;
|
id = opusId;
|
||||||
type = 'opus';
|
type = 'opus';
|
||||||
}
|
}
|
||||||
|
Get.putOrFind(() => this, tag: type + id);
|
||||||
}
|
}
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import 'package:PiliPlus/pages/common/dyn/common_dyn_page.dart';
|
|||||||
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
||||||
import 'package:PiliPlus/utils/date_util.dart';
|
import 'package:PiliPlus/utils/date_util.dart';
|
||||||
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/grid.dart';
|
import 'package:PiliPlus/utils/grid.dart';
|
||||||
import 'package:PiliPlus/utils/image_util.dart';
|
import 'package:PiliPlus/utils/image_util.dart';
|
||||||
import 'package:PiliPlus/utils/num_util.dart';
|
import 'package:PiliPlus/utils/num_util.dart';
|
||||||
@@ -42,9 +43,9 @@ class ArticlePage extends StatefulWidget {
|
|||||||
|
|
||||||
class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
||||||
@override
|
@override
|
||||||
final ArticleController controller = Get.put(
|
final ArticleController controller = Get.putOrFind(
|
||||||
ArticleController(),
|
ArticleController.new,
|
||||||
tag: Utils.generateRandomString(8),
|
tag: Get.parameters['type']! + Get.parameters['id']!,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -56,9 +57,9 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
|||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (controller.scrollController.hasClients) {
|
if (scrollController.hasClients) {
|
||||||
controller.showTitle.value =
|
controller.showTitle.value =
|
||||||
controller.scrollController.positions.last.pixels >= 45;
|
scrollController.positions.last.pixels >= 45;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -88,7 +89,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
_buildContent(
|
_buildContent(
|
||||||
@@ -117,7 +118,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: flex,
|
flex: flex,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
@@ -150,7 +151,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
|||||||
body: refreshIndicator(
|
body: refreshIndicator(
|
||||||
onRefresh: controller.onRefresh,
|
onRefresh: controller.onRefresh,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
buildReplyHeader(theme),
|
buildReplyHeader(theme),
|
||||||
@@ -554,7 +555,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
position: controller.fabAnim,
|
position: fabAnim,
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
if (!controller.showDynActionBar) {
|
if (!controller.showDynActionBar) {
|
||||||
|
|||||||
@@ -3,84 +3,17 @@ import 'package:PiliPlus/grpc/reply.dart';
|
|||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/rendering.dart' show ScrollDirection;
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
abstract class CommonDynController extends ReplyController<MainListReply>
|
abstract class CommonDynController extends ReplyController<MainListReply> {
|
||||||
with GetSingleTickerProviderStateMixin {
|
|
||||||
int get oid;
|
int get oid;
|
||||||
int get replyType;
|
int get replyType;
|
||||||
|
|
||||||
bool _showFab = true;
|
|
||||||
late final AnimationController fabAnimationCtr;
|
|
||||||
late final Animation<Offset> fabAnim;
|
|
||||||
|
|
||||||
late final RxBool showTitle = false.obs;
|
late final RxBool showTitle = false.obs;
|
||||||
|
|
||||||
late final horizontalPreview = Pref.horizontalPreview;
|
late final horizontalPreview = Pref.horizontalPreview;
|
||||||
late final List<double> ratio = Pref.dynamicDetailRatio;
|
late final List<double> ratio = Pref.dynamicDetailRatio;
|
||||||
|
|
||||||
final fabOffset = const Offset(0, 1);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
fabAnimationCtr = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
);
|
|
||||||
fabAnim =
|
|
||||||
Tween<Offset>(
|
|
||||||
begin: fabOffset,
|
|
||||||
end: Offset.zero,
|
|
||||||
).animate(
|
|
||||||
CurvedAnimation(
|
|
||||||
parent: fabAnimationCtr,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
fabAnimationCtr.forward();
|
|
||||||
scrollController.addListener(listener);
|
|
||||||
super.onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void listener() {
|
|
||||||
showTitle.value = scrollController.positions.first.pixels > 55;
|
|
||||||
|
|
||||||
final ScrollDirection direction1 =
|
|
||||||
scrollController.positions.first.userScrollDirection;
|
|
||||||
late final ScrollDirection direction2 =
|
|
||||||
scrollController.positions.last.userScrollDirection;
|
|
||||||
if (direction1 == ScrollDirection.forward ||
|
|
||||||
direction2 == ScrollDirection.forward) {
|
|
||||||
showFab();
|
|
||||||
} else if (direction1 == ScrollDirection.reverse ||
|
|
||||||
direction2 == ScrollDirection.reverse) {
|
|
||||||
hideFab();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void showFab() {
|
|
||||||
if (!_showFab) {
|
|
||||||
_showFab = true;
|
|
||||||
fabAnimationCtr.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void hideFab() {
|
|
||||||
if (_showFab) {
|
|
||||||
_showFab = false;
|
|
||||||
fabAnimationCtr.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
fabAnimationCtr.dispose();
|
|
||||||
scrollController.removeListener(listener);
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
|
Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
|
||||||
type: replyType,
|
type: replyType,
|
||||||
|
|||||||
@@ -18,12 +18,15 @@ import 'package:PiliPlus/utils/storage.dart';
|
|||||||
import 'package:PiliPlus/utils/storage_key.dart';
|
import 'package:PiliPlus/utils/storage_key.dart';
|
||||||
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:flutter/rendering.dart';
|
||||||
import 'package:get/get.dart' hide ContextExtensionss;
|
import 'package:get/get.dart' hide ContextExtensionss;
|
||||||
|
|
||||||
abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
CommonDynController get controller;
|
CommonDynController get controller;
|
||||||
|
|
||||||
|
late final scrollController = ScrollController()..addListener(listener);
|
||||||
|
|
||||||
late final scaffoldKey = GlobalKey<ScaffoldState>();
|
late final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
bool get horizontalPreview => !isPortrait && controller.horizontalPreview;
|
bool get horizontalPreview => !isPortrait && controller.horizontalPreview;
|
||||||
@@ -35,6 +38,49 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
|||||||
late bool isPortrait;
|
late bool isPortrait;
|
||||||
late double maxWidth;
|
late double maxWidth;
|
||||||
|
|
||||||
|
bool _showFab = true;
|
||||||
|
|
||||||
|
final fabOffset = const Offset(0, 1);
|
||||||
|
|
||||||
|
late final AnimationController fabAnimationCtr = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
)..forward();
|
||||||
|
|
||||||
|
late final Animation<Offset> fabAnim = Tween<Offset>(
|
||||||
|
begin: fabOffset,
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(CurvedAnimation(parent: fabAnimationCtr, curve: Curves.easeInOut));
|
||||||
|
|
||||||
|
void listener() {
|
||||||
|
final pos = scrollController.positions;
|
||||||
|
controller.showTitle.value = pos.first.pixels > 55;
|
||||||
|
|
||||||
|
final direction1 = pos.first.userScrollDirection;
|
||||||
|
late final direction2 = pos.last.userScrollDirection;
|
||||||
|
if (direction1 == ScrollDirection.forward ||
|
||||||
|
direction2 == ScrollDirection.forward) {
|
||||||
|
showFab();
|
||||||
|
} else if (direction1 == ScrollDirection.reverse ||
|
||||||
|
direction2 == ScrollDirection.reverse) {
|
||||||
|
hideFab();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showFab() {
|
||||||
|
if (!_showFab) {
|
||||||
|
_showFab = true;
|
||||||
|
fabAnimationCtr.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hideFab() {
|
||||||
|
if (_showFab) {
|
||||||
|
_showFab = false;
|
||||||
|
fabAnimationCtr.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
@@ -43,7 +89,7 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
|||||||
isPortrait = size.isPortrait;
|
isPortrait = size.isPortrait;
|
||||||
imageCallback = horizontalPreview
|
imageCallback = horizontalPreview
|
||||||
? (imgList, index) {
|
? (imgList, index) {
|
||||||
controller.hideFab();
|
hideFab();
|
||||||
PageUtils.onHorizontalPreview(
|
PageUtils.onHorizontalPreview(
|
||||||
scaffoldKey,
|
scaffoldKey,
|
||||||
this,
|
this,
|
||||||
@@ -55,6 +101,12 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
|||||||
padding = MediaQuery.viewPaddingOf(context);
|
padding = MediaQuery.viewPaddingOf(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildReplyHeader(ThemeData theme) {
|
Widget buildReplyHeader(ThemeData theme) {
|
||||||
final secondary = theme.colorScheme.secondary;
|
final secondary = theme.colorScheme.secondary;
|
||||||
return SliverPersistentHeader(
|
return SliverPersistentHeader(
|
||||||
@@ -220,7 +272,7 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
|||||||
} else {
|
} else {
|
||||||
ScaffoldState? scaffoldState = Scaffold.maybeOf(context);
|
ScaffoldState? scaffoldState = Scaffold.maybeOf(context);
|
||||||
if (scaffoldState != null) {
|
if (scaffoldState != null) {
|
||||||
controller.hideFab();
|
hideFab();
|
||||||
scaffoldState.showBottomSheet(
|
scaffoldState.showBottomSheet(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
(context) => replyReplyPage(showBackBtn: false),
|
(context) => replyReplyPage(showBackBtn: false),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:PiliPlus/models/dynamics/result.dart';
|
|||||||
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
||||||
import 'package:PiliPlus/utils/id_utils.dart';
|
import 'package:PiliPlus/utils/id_utils.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class DynamicDetailController extends CommonDynController {
|
class DynamicDetailController extends CommonDynController {
|
||||||
@@ -30,11 +29,11 @@ class DynamicDetailController extends CommonDynController {
|
|||||||
_init(commentIdStr!, commentType);
|
_init(commentIdStr!, commentType);
|
||||||
} else {
|
} else {
|
||||||
DynamicsHttp.dynamicDetail(id: dynItem.idStr).then((res) {
|
DynamicsHttp.dynamicDetail(id: dynItem.idStr).then((res) {
|
||||||
if (res['status']) {
|
if (res.isSuccess) {
|
||||||
DynamicItemModel data = res['data'];
|
final data = res.data;
|
||||||
_init(data.basic!.commentIdStr!, data.basic!.commentType!);
|
_init(data.basic!.commentIdStr!, data.basic!.commentType!);
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(res['msg']);
|
res.toast();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
|
|||||||
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
|
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_detail/controller.dart';
|
import 'package:PiliPlus/pages/dynamics_detail/controller.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
||||||
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/grid.dart';
|
import 'package:PiliPlus/utils/grid.dart';
|
||||||
import 'package:PiliPlus/utils/num_util.dart';
|
import 'package:PiliPlus/utils/num_util.dart';
|
||||||
import 'package:PiliPlus/utils/request_utils.dart';
|
import 'package:PiliPlus/utils/request_utils.dart';
|
||||||
@@ -26,9 +27,9 @@ class DynamicDetailPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
||||||
@override
|
@override
|
||||||
final DynamicDetailController controller = Get.put(
|
final DynamicDetailController controller = Get.putOrFind(
|
||||||
DynamicDetailController(),
|
DynamicDetailController.new,
|
||||||
tag: Utils.generateRandomString(8),
|
tag: (Get.arguments['item'] as DynamicItemModel).idStr.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -40,9 +41,9 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (controller.scrollController.hasClients) {
|
if (scrollController.hasClients) {
|
||||||
controller.showTitle.value =
|
controller.showTitle.value =
|
||||||
controller.scrollController.positions.first.pixels > 55;
|
scrollController.positions.first.pixels > 55;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -100,7 +101,7 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
child = Padding(
|
child = Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
@@ -126,7 +127,7 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: flex,
|
flex: flex,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
@@ -161,7 +162,7 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
body: refreshIndicator(
|
body: refreshIndicator(
|
||||||
onRefresh: controller.onRefresh,
|
onRefresh: controller.onRefresh,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
buildReplyHeader(theme),
|
buildReplyHeader(theme),
|
||||||
@@ -192,7 +193,7 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
position: controller.fabAnim,
|
position: fabAnim,
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
if (!controller.showDynActionBar) {
|
if (!controller.showDynActionBar) {
|
||||||
@@ -282,8 +283,8 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
int count = forward.count ?? 0;
|
int count = forward.count ?? 0;
|
||||||
forward.count = count + 1;
|
forward.count = count + 1;
|
||||||
if (btnContext.mounted) {
|
if (btnContext.mounted) {
|
||||||
(btnContext as Element?)
|
(btnContext as Element)
|
||||||
?.markNeedsBuild();
|
.markNeedsBuild();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -315,7 +316,7 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
controller.dynItem,
|
controller.dynItem,
|
||||||
() {
|
() {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
(context as Element?)?.markNeedsBuild();
|
(context as Element).markNeedsBuild();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -708,56 +708,59 @@ class LoginPageController extends GetxController
|
|||||||
};
|
};
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => StatefulBuilder(
|
builder: (context) => AlertDialog(
|
||||||
builder: (context, setState) {
|
title: const Text('选择账号mid, 为0时使用匿名'),
|
||||||
return AlertDialog(
|
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
|
||||||
title: const Text('选择账号mid, 为0时使用匿名'),
|
contentPadding: const EdgeInsets.symmetric(vertical: 5),
|
||||||
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
|
actionsPadding: const EdgeInsets.only(
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 5),
|
left: 16,
|
||||||
actionsPadding: const EdgeInsets.only(
|
right: 16,
|
||||||
left: 16,
|
bottom: 10,
|
||||||
right: 16,
|
),
|
||||||
bottom: 10,
|
content: SingleChildScrollView(
|
||||||
),
|
child: Column(
|
||||||
content: SingleChildScrollView(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
children: AccountType.values
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
.map(
|
||||||
children: AccountType.values
|
(e) => Builder(
|
||||||
.map(
|
builder: (context) => RadioGroup(
|
||||||
(e) => WrapRadioOptionsGroup<Account>(
|
groupValue: selectAccount[e],
|
||||||
|
onChanged: (v) {
|
||||||
|
selectAccount[e] = v!;
|
||||||
|
(context as Element).markNeedsBuild();
|
||||||
|
},
|
||||||
|
child: WrapRadioOptionsGroup<Account>(
|
||||||
groupTitle: e.title,
|
groupTitle: e.title,
|
||||||
options: options,
|
options: options,
|
||||||
selectedValue: selectAccount[e],
|
|
||||||
onChanged: (v) => setState(() => selectAccount[e] = v!),
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.toList(),
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: Get.back,
|
onPressed: () {
|
||||||
child: Text(
|
for (var i in selectAccount.entries) {
|
||||||
'取消',
|
if (i.value != Accounts.get(i.key)) {
|
||||||
style: TextStyle(
|
Accounts.set(i.key, i.value);
|
||||||
color: Theme.of(context).colorScheme.outline,
|
}
|
||||||
),
|
}
|
||||||
),
|
Get.back();
|
||||||
),
|
},
|
||||||
TextButton(
|
child: const Text('确定'),
|
||||||
onPressed: () {
|
),
|
||||||
for (var i in selectAccount.entries) {
|
],
|
||||||
if (i.value != Accounts.get(i.key)) {
|
|
||||||
Accounts.set(i.key, i.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: const Text('确定'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import 'package:PiliPlus/pages/match_info/controller.dart';
|
|||||||
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
||||||
import 'package:PiliPlus/utils/date_util.dart';
|
import 'package:PiliPlus/utils/date_util.dart';
|
||||||
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/page_utils.dart';
|
import 'package:PiliPlus/utils/page_utils.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
|
||||||
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';
|
||||||
@@ -31,14 +31,17 @@ class MatchInfoPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _MatchInfoPageState extends CommonDynPageState<MatchInfoPage> {
|
class _MatchInfoPageState extends CommonDynPageState<MatchInfoPage> {
|
||||||
@override
|
@override
|
||||||
final MatchInfoController controller = Get.put(
|
final MatchInfoController controller = Get.putOrFind(
|
||||||
MatchInfoController(),
|
MatchInfoController.new,
|
||||||
tag: Utils.generateRandomString(8),
|
tag: Get.parameters['cid']!,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
dynamic get arguments => null;
|
dynamic get arguments => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset get fabOffset => const Offset(0, 2);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
@@ -48,7 +51,7 @@ class _MatchInfoPageState extends CommonDynPageState<MatchInfoPage> {
|
|||||||
body: refreshIndicator(
|
body: refreshIndicator(
|
||||||
onRefresh: controller.onRefresh,
|
onRefresh: controller.onRefresh,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
Obx(() => _buildInfo(theme, controller.infoState.value)),
|
Obx(() => _buildInfo(theme, controller.infoState.value)),
|
||||||
@@ -61,7 +64,7 @@ class _MatchInfoPageState extends CommonDynPageState<MatchInfoPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: SlideTransition(
|
floatingActionButton: SlideTransition(
|
||||||
position: controller.fabAnim,
|
position: fabAnim,
|
||||||
child: replyButton,
|
child: replyButton,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class MemberController extends CommonDataController<SpaceData, SpaceData?>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool customHandleResponse(bool isRefresh, Success<SpaceData> response) {
|
bool customHandleResponse(bool isRefresh, Success<SpaceData> response) {
|
||||||
SpaceData data = response.response;
|
final data = response.response;
|
||||||
username = data.card?.name ?? '';
|
username = data.card?.name ?? '';
|
||||||
isFollowed = data.card?.relation?.isFollowed;
|
isFollowed = data.card?.relation?.isFollowed;
|
||||||
if (data.relation == -1) {
|
if (data.relation == -1) {
|
||||||
@@ -215,11 +215,7 @@ class MemberController extends CommonDataController<SpaceData, SpaceData?>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onRemoveFan() async {
|
Future<void> onRemoveFan() async {
|
||||||
final res = await VideoHttp.relationMod(
|
final res = await VideoHttp.relationMod(mid: mid, act: 7, reSrc: 11);
|
||||||
mid: mid,
|
|
||||||
act: 7,
|
|
||||||
reSrc: 11,
|
|
||||||
);
|
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
isFollowed = null;
|
isFollowed = null;
|
||||||
if (relation.value == 4) {
|
if (relation.value == 4) {
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ class _MemberPageState extends State<MemberPage> {
|
|||||||
if (_userController.loadingState.value.isSuccess) {
|
if (_userController.loadingState.value.isSuccess) {
|
||||||
return ExtendedNestedScrollView(
|
return ExtendedNestedScrollView(
|
||||||
key: _userController.key,
|
key: _userController.key,
|
||||||
controller: _userController.scrollController,
|
|
||||||
onlyOneScrollInBody: true,
|
onlyOneScrollInBody: true,
|
||||||
pinnedHeaderSliverHeightBuilder: () =>
|
pinnedHeaderSliverHeightBuilder: () =>
|
||||||
kToolbarHeight + MediaQuery.viewPaddingOf(context).top,
|
kToolbarHeight + MediaQuery.viewPaddingOf(context).top,
|
||||||
@@ -259,19 +258,10 @@ class _MemberPageState extends State<MemberPage> {
|
|||||||
] else ...[
|
] else ...[
|
||||||
const PopupMenuDivider(),
|
const PopupMenuDivider(),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () => showDialog(
|
onTap: () => showMemberReportDialog(
|
||||||
context: context,
|
context,
|
||||||
builder: (context) => AlertDialog(
|
name: _userController.username,
|
||||||
clipBehavior: Clip.hardEdge,
|
mid: _mid,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
content: MemberReportPanel(
|
|
||||||
name: _userController.username,
|
|
||||||
mid: _mid,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import 'package:PiliPlus/common/constants.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/image/image_save.dart';
|
import 'package:PiliPlus/common/widgets/image/image_save.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
||||||
|
import 'package:PiliPlus/http/search.dart';
|
||||||
import 'package:PiliPlus/models/common/stat_type.dart';
|
import 'package:PiliPlus/models/common/stat_type.dart';
|
||||||
import 'package:PiliPlus/models_new/space/space_audio/item.dart';
|
import 'package:PiliPlus/models_new/space/space_audio/item.dart';
|
||||||
import 'package:PiliPlus/utils/date_util.dart';
|
import 'package:PiliPlus/utils/date_util.dart';
|
||||||
|
import 'package:PiliPlus/utils/page_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
|
||||||
class MemberAudioItem extends StatelessWidget {
|
class MemberAudioItem extends StatelessWidget {
|
||||||
const MemberAudioItem({super.key, required this.item});
|
const MemberAudioItem({super.key, required this.item});
|
||||||
@@ -19,8 +22,18 @@ class MemberAudioItem extends StatelessWidget {
|
|||||||
return Material(
|
return Material(
|
||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
// TODO
|
// TODO music play
|
||||||
|
final aid = item.aid;
|
||||||
|
if (aid != null) {
|
||||||
|
final cid = await SearchHttp.ab2c(aid: aid);
|
||||||
|
if (cid != null) {
|
||||||
|
PageUtils.toVideoPage(cid: cid, aid: aid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('没有MV');
|
||||||
|
return;
|
||||||
},
|
},
|
||||||
onLongPress: () =>
|
onLongPress: () =>
|
||||||
imageSaveDialog(title: item.title, cover: item.cover),
|
imageSaveDialog(title: item.title, cover: item.cover),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:PiliPlus/pages/member_contribute/controller.dart';
|
|||||||
import 'package:PiliPlus/pages/member_opus/view.dart';
|
import 'package:PiliPlus/pages/member_opus/view.dart';
|
||||||
import 'package:PiliPlus/pages/member_season_series/view.dart';
|
import 'package:PiliPlus/pages/member_season_series/view.dart';
|
||||||
import 'package:PiliPlus/pages/member_video/view.dart';
|
import 'package:PiliPlus/pages/member_video/view.dart';
|
||||||
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -31,8 +32,8 @@ class _MemberContributeState extends State<MemberContribute>
|
|||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
late final _controller = Get.put(
|
late final _controller = Get.putOrFind(
|
||||||
MemberContributeCtr(
|
() => MemberContributeCtr(
|
||||||
heroTag: widget.heroTag,
|
heroTag: widget.heroTag,
|
||||||
initialIndex: widget.initialIndex,
|
initialIndex: widget.initialIndex,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -40,18 +40,19 @@ class MemberVideoCtr
|
|||||||
String? firstAid;
|
String? firstAid;
|
||||||
String? lastAid;
|
String? lastAid;
|
||||||
String? fromViewAid;
|
String? fromViewAid;
|
||||||
Rx<bool?> isLocating = Rx<bool?>(null);
|
RxBool isLocating = false.obs;
|
||||||
bool? isLoadPrevious;
|
bool isLoadPrevious = false;
|
||||||
bool? hasPrev;
|
bool? hasPrev;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onRefresh() async {
|
Future<void> onRefresh() async {
|
||||||
if (isLocating.value == true) {
|
if (isLocating.value) {
|
||||||
if (hasPrev == true) {
|
if (hasPrev == true) {
|
||||||
isLoadPrevious = true;
|
isLoadPrevious = true;
|
||||||
await queryData();
|
await queryData();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
isLoadPrevious = false;
|
||||||
firstAid = null;
|
firstAid = null;
|
||||||
lastAid = null;
|
lastAid = null;
|
||||||
next = null;
|
next = null;
|
||||||
@@ -76,15 +77,15 @@ class MemberVideoCtr
|
|||||||
bool isRefresh,
|
bool isRefresh,
|
||||||
Success<SpaceArchiveData> response,
|
Success<SpaceArchiveData> response,
|
||||||
) {
|
) {
|
||||||
SpaceArchiveData data = response.response;
|
final data = response.response;
|
||||||
episodicButton
|
episodicButton
|
||||||
..value = data.episodicButton ?? EpisodicButton()
|
..value = data.episodicButton ?? EpisodicButton()
|
||||||
..refresh();
|
..refresh();
|
||||||
next = data.next;
|
next = data.next;
|
||||||
if (page == 0 || isLoadPrevious == true) {
|
if (page == 0 || isLoadPrevious) {
|
||||||
hasPrev = data.hasPrev;
|
hasPrev = data.hasPrev;
|
||||||
}
|
}
|
||||||
if (page == 0 || isLoadPrevious != true) {
|
if (page == 0 || !isLoadPrevious) {
|
||||||
if ((type == ContributeType.video
|
if ((type == ContributeType.video
|
||||||
? data.hasNext == false
|
? data.hasNext == false
|
||||||
: data.next == 0) ||
|
: data.next == 0) ||
|
||||||
@@ -97,7 +98,7 @@ class MemberVideoCtr
|
|||||||
: (data.count ?? -1);
|
: (data.count ?? -1);
|
||||||
if (page != 0 && loadingState.value.isSuccess) {
|
if (page != 0 && loadingState.value.isSuccess) {
|
||||||
data.item ??= <SpaceArchiveItem>[];
|
data.item ??= <SpaceArchiveItem>[];
|
||||||
if (isLoadPrevious == true) {
|
if (isLoadPrevious) {
|
||||||
data.item!.addAll(loadingState.value.data!);
|
data.item!.addAll(loadingState.value.data!);
|
||||||
} else {
|
} else {
|
||||||
data.item!.insertAll(0, loadingState.value.data!);
|
data.item!.insertAll(0, loadingState.value.data!);
|
||||||
@@ -105,7 +106,6 @@ class MemberVideoCtr
|
|||||||
}
|
}
|
||||||
firstAid = data.item?.firstOrNull?.param;
|
firstAid = data.item?.firstOrNull?.param;
|
||||||
lastAid = data.item?.lastOrNull?.param;
|
lastAid = data.item?.lastOrNull?.param;
|
||||||
isLoadPrevious = null;
|
|
||||||
loadingState.value = Success(data.item);
|
loadingState.value = Success(data.item);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -116,13 +116,13 @@ class MemberVideoCtr
|
|||||||
type: type,
|
type: type,
|
||||||
mid: mid,
|
mid: mid,
|
||||||
aid: type == ContributeType.video
|
aid: type == ContributeType.video
|
||||||
? isLoadPrevious == true
|
? isLoadPrevious
|
||||||
? firstAid
|
? firstAid
|
||||||
: lastAid
|
: lastAid
|
||||||
: null,
|
: null,
|
||||||
order: type == ContributeType.video ? order.value : null,
|
order: type == ContributeType.video ? order.value : null,
|
||||||
sort: type == ContributeType.video
|
sort: type == ContributeType.video
|
||||||
? isLoadPrevious == true
|
? isLoadPrevious
|
||||||
? 'asc'
|
? 'asc'
|
||||||
: null
|
: null
|
||||||
: sort.value,
|
: sort.value,
|
||||||
@@ -130,12 +130,12 @@ class MemberVideoCtr
|
|||||||
next: next,
|
next: next,
|
||||||
seasonId: seasonId,
|
seasonId: seasonId,
|
||||||
seriesId: seriesId,
|
seriesId: seriesId,
|
||||||
includeCursor: isLocating.value == true && page == 0 ? true : null,
|
includeCursor: isLocating.value && page == 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
void queryBySort() {
|
void queryBySort() {
|
||||||
if (type == ContributeType.video) {
|
if (type == ContributeType.video) {
|
||||||
isLocating.value = null;
|
isLocating.value = false;
|
||||||
order.value = order.value == 'pubdate' ? 'click' : 'pubdate';
|
order.value = order.value == 'pubdate' ? 'click' : 'pubdate';
|
||||||
} else {
|
} else {
|
||||||
sort.value = sort.value == 'desc' ? 'asc' : 'desc';
|
sort.value = sort.value == 'desc' ? 'asc' : 'desc';
|
||||||
@@ -223,7 +223,7 @@ class MemberVideoCtr
|
|||||||
@override
|
@override
|
||||||
Future<void> onReload() {
|
Future<void> onReload() {
|
||||||
reload = true;
|
reload = true;
|
||||||
isLocating.value = null;
|
isLocating.value = false;
|
||||||
return super.onReload();
|
return super.onReload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:PiliPlus/pages/member_video/controller.dart';
|
|||||||
import 'package:PiliPlus/pages/member_video/widgets/video_card_h_member_video.dart';
|
import 'package:PiliPlus/pages/member_video/widgets/video_card_h_member_video.dart';
|
||||||
import 'package:PiliPlus/utils/grid.dart';
|
import 'package:PiliPlus/utils/grid.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class MemberVideo extends StatefulWidget {
|
class MemberVideo extends StatefulWidget {
|
||||||
@@ -59,8 +60,25 @@ class _MemberVideoState extends State<MemberVideo>
|
|||||||
super.build(context);
|
super.build(context);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final padding = MediaQuery.viewPaddingOf(context);
|
final padding = MediaQuery.viewPaddingOf(context);
|
||||||
Widget child = refreshIndicator(
|
final child = refreshIndicator(
|
||||||
onRefresh: _controller.onRefresh,
|
onRefresh: () async {
|
||||||
|
final count = _controller.loadingState.value.dataOrNull?.length;
|
||||||
|
await _controller.onRefresh();
|
||||||
|
if (_controller.isLoadPrevious) {
|
||||||
|
if (mounted) {
|
||||||
|
final newCount = _controller.loadingState.value.dataOrNull?.length;
|
||||||
|
if (count != null && newCount != null && newCount > count) {
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
|
PrimaryScrollController.of(this.context).jumpTo(
|
||||||
|
gridDelegate.layoutCache!
|
||||||
|
.getGeometryForChildIndex(newCount - count)
|
||||||
|
.scrollOffset,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
physics: ReloadScrollPhysics(controller: _controller),
|
physics: ReloadScrollPhysics(controller: _controller),
|
||||||
slivers: [
|
slivers: [
|
||||||
@@ -80,23 +98,21 @@ class _MemberVideoState extends State<MemberVideo>
|
|||||||
children: [
|
children: [
|
||||||
child,
|
child,
|
||||||
Obx(
|
Obx(
|
||||||
() => _controller.isLocating.value != true
|
() => !_controller.isLocating.value
|
||||||
? Positioned(
|
? Positioned(
|
||||||
right: 15 + padding.right,
|
right: 15 + padding.right,
|
||||||
bottom: 15 + padding.bottom,
|
bottom: 15 + padding.bottom,
|
||||||
child: FloatingActionButton.extended(
|
child: FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final fromViewAid = _controller.fromViewAid;
|
final fromViewAid = _controller.fromViewAid;
|
||||||
_controller
|
_controller.isLocating.value = true;
|
||||||
..isLocating.value = true
|
final locatedIndex =
|
||||||
..lastAid = fromViewAid;
|
_controller.loadingState.value.dataOrNull
|
||||||
final locatedIndex = _controller
|
?.indexWhere((i) => i.param == fromViewAid) ??
|
||||||
.loadingState
|
-1;
|
||||||
.value
|
if (locatedIndex == -1) {
|
||||||
.dataOrNull
|
|
||||||
?.indexWhere((i) => i.param == fromViewAid);
|
|
||||||
if (locatedIndex == null || locatedIndex == -1) {
|
|
||||||
_controller
|
_controller
|
||||||
|
..lastAid = fromViewAid
|
||||||
..reload = true
|
..reload = true
|
||||||
..page = 0
|
..page = 0
|
||||||
..loadingState.value = LoadingState.loading()
|
..loadingState.value = LoadingState.loading()
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ class MusicDetailController extends CommonDynController {
|
|||||||
|
|
||||||
bool get showDynActionBar => Pref.showDynActionBar;
|
bool get showDynActionBar => Pref.showDynActionBar;
|
||||||
|
|
||||||
|
String get shareUrl =>
|
||||||
|
'https://music.bilibili.com/h5/music-detail?music_id=${musicId}';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import 'package:PiliPlus/models_new/music/bgm_recommend_list.dart';
|
|||||||
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
typedef MusicRecommendArgs = ({String id, MusicDetail item});
|
||||||
|
|
||||||
class MusicRecommendController
|
class MusicRecommendController
|
||||||
extends CommonListController<List<BgmRecommend>?, BgmRecommend> {
|
extends CommonListController<List<BgmRecommend>?, BgmRecommend> {
|
||||||
late final String musicId;
|
late final String musicId;
|
||||||
@@ -13,9 +15,9 @@ class MusicRecommendController
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
final Map<String, dynamic> args = Get.arguments;
|
final MusicRecommendArgs args = Get.arguments;
|
||||||
musicId = args['id'];
|
musicId = args.id;
|
||||||
musicDetail = args['detail'];
|
musicDetail = args.item;
|
||||||
queryData();
|
queryData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import 'package:PiliPlus/models/common/image_type.dart';
|
|||||||
import 'package:PiliPlus/models_new/music/bgm_recommend_list.dart';
|
import 'package:PiliPlus/models_new/music/bgm_recommend_list.dart';
|
||||||
import 'package:PiliPlus/pages/music/video/controller.dart';
|
import 'package:PiliPlus/pages/music/video/controller.dart';
|
||||||
import 'package:PiliPlus/pages/music/widget/music_video_card_h.dart';
|
import 'package:PiliPlus/pages/music/widget/music_video_card_h.dart';
|
||||||
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/grid.dart';
|
import 'package:PiliPlus/utils/grid.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -20,9 +20,9 @@ class MusicRecommandPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _MusicRecommandPageState extends State<MusicRecommandPage>
|
class _MusicRecommandPageState extends State<MusicRecommandPage>
|
||||||
with GridMixin {
|
with GridMixin {
|
||||||
late final _controller = Get.put(
|
late final MusicRecommendController _controller = Get.putOrFind(
|
||||||
MusicRecommendController(),
|
MusicRecommendController.new,
|
||||||
tag: Utils.generateRandomString(8),
|
tag: (Get.arguments as MusicRecommendArgs).id,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -34,7 +34,6 @@ class _MusicRecommandPageState extends State<MusicRecommandPage>
|
|||||||
child: refreshIndicator(
|
child: refreshIndicator(
|
||||||
onRefresh: _controller.onRefresh,
|
onRefresh: _controller.onRefresh,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: _controller.scrollController,
|
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
_buildAppBar(theme, padding),
|
_buildAppBar(theme, padding),
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ class MusicDetailPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
||||||
@override
|
@override
|
||||||
final MusicDetailController controller = Get.put(
|
late final MusicDetailController controller = Get.putOrFind(
|
||||||
MusicDetailController(),
|
MusicDetailController.new,
|
||||||
tag: Utils.generateRandomString(8),
|
tag: Get.parameters['musicId']!,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -110,7 +110,7 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
child = Padding(
|
child = Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
@@ -136,7 +136,7 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: flex,
|
flex: flex,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller.scrollController,
|
controller: scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
@@ -235,7 +235,7 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
position: controller.fabAnim,
|
position: fabAnim,
|
||||||
child: controller.showDynActionBar
|
child: controller.showDynActionBar
|
||||||
? Column(
|
? Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -288,9 +288,8 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
child: textIconButton(
|
child: textIconButton(
|
||||||
icon: CustomIcon.share_node,
|
icon: CustomIcon.share_node,
|
||||||
text: '分享',
|
text: '分享',
|
||||||
onPressed: () => Utils.shareText(
|
onPressed: () =>
|
||||||
'https://music.bilibili.com/h5/music-detail?music_id=${controller.musicId}',
|
Utils.shareText(controller.shareUrl),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -565,7 +564,7 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
theme,
|
theme,
|
||||||
() => Get.to(
|
() => Get.to(
|
||||||
const MusicRecommandPage(),
|
const MusicRecommandPage(),
|
||||||
arguments: {'id': controller.musicId, 'detail': item},
|
arguments: (id: controller.musicId, item: item),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
|||||||
import 'package:PiliPlus/models/common/video/video_type.dart';
|
import 'package:PiliPlus/models/common/video/video_type.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
|
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
|
||||||
|
import 'package:PiliPlus/pages/music/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart';
|
import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart';
|
import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
||||||
@@ -74,8 +75,10 @@ class _SavePanelState extends State<SavePanel> {
|
|||||||
|
|
||||||
//reply
|
//reply
|
||||||
String? cover;
|
String? cover;
|
||||||
|
_CoverType coverType = _CoverType.def16_9;
|
||||||
String? title;
|
String? title;
|
||||||
int? pubdate;
|
int? pubdate;
|
||||||
|
DateFormat dateFormat = DateUtil.longFormatDs;
|
||||||
String? uname;
|
String? uname;
|
||||||
|
|
||||||
String uri = '';
|
String uri = '';
|
||||||
@@ -159,14 +162,42 @@ class _SavePanelState extends State<SavePanel> {
|
|||||||
} else if (currentRoute.startsWith('/articlePage')) {
|
} else if (currentRoute.startsWith('/articlePage')) {
|
||||||
try {
|
try {
|
||||||
final type = reply.type.toInt();
|
final type = reply.type.toInt();
|
||||||
late final oid = reply.oid;
|
final oid = reply.oid;
|
||||||
late final rootId = hasRoot ? reply.root : reply.id;
|
final rootId = hasRoot ? reply.root : reply.id;
|
||||||
late final anchor = hasRoot ? 'anchor=${reply.id}&' : '';
|
final anchor = hasRoot ? 'anchor=${reply.id}&' : '';
|
||||||
late final enterUri =
|
final enterUri =
|
||||||
'bilibili://following/detail/${Get.parameters['id'] ?? Get.arguments?['id']}';
|
'bilibili://following/detail/${Get.parameters['id'] ?? Get.arguments?['id']}';
|
||||||
uri =
|
uri =
|
||||||
'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri';
|
'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri';
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
} else if (currentRoute.startsWith('/musicDetail')) {
|
||||||
|
final type = reply.type.toInt();
|
||||||
|
final oid = reply.oid;
|
||||||
|
final rootId = hasRoot ? reply.root : reply.id;
|
||||||
|
final anchor = hasRoot ? 'anchor=${reply.id}&' : '';
|
||||||
|
String enterUri = '';
|
||||||
|
try {
|
||||||
|
final ctr = Get.find<MusicDetailController>(
|
||||||
|
tag: Get.parameters['musicId'],
|
||||||
|
);
|
||||||
|
// enterUri = 'enterUri=${Uri.encodeComponent(ctr.shareUrl)}'; // official client cannot parse it
|
||||||
|
final data = ctr.infoState.value.dataOrNull;
|
||||||
|
if (data != null) {
|
||||||
|
coverType = _CoverType.square;
|
||||||
|
cover = data.mvCover;
|
||||||
|
title = data.musicTitle;
|
||||||
|
if (data.musicPublish != null) {
|
||||||
|
final time = DateTime.tryParse(
|
||||||
|
data.musicPublish!,
|
||||||
|
)?.millisecondsSinceEpoch;
|
||||||
|
if (time != null) {
|
||||||
|
pubdate = time ~/ 1000;
|
||||||
|
dateFormat = DateUtil.longFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
uri = 'bilibili://comment/detail/$type/$oid/$rootId/?$anchor$enterUri';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kDebugMode) debugPrint(uri);
|
if (kDebugMode) debugPrint(uri);
|
||||||
@@ -296,6 +327,7 @@ class _SavePanelState extends State<SavePanel> {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final padding = MediaQuery.viewPaddingOf(context);
|
final padding = MediaQuery.viewPaddingOf(context);
|
||||||
final maxWidth = context.mediaQueryShortestSide;
|
final maxWidth = context.mediaQueryShortestSide;
|
||||||
|
late final coverSize = MediaQuery.textScalerOf(context).scale(65);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: Get.back,
|
onTap: Get.back,
|
||||||
@@ -367,15 +399,10 @@ class _SavePanelState extends State<SavePanel> {
|
|||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
radius: 6,
|
radius: 6,
|
||||||
src: cover!,
|
src: cover!,
|
||||||
height: MediaQuery.textScalerOf(
|
height: coverSize,
|
||||||
context,
|
width: coverType == _CoverType.def16_9
|
||||||
).scale(65),
|
? coverSize * 16 / 9
|
||||||
width:
|
: coverSize,
|
||||||
MediaQuery.textScalerOf(
|
|
||||||
context,
|
|
||||||
).scale(65) *
|
|
||||||
16 /
|
|
||||||
9,
|
|
||||||
quality: 100,
|
quality: 100,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
@@ -394,7 +421,7 @@ class _SavePanelState extends State<SavePanel> {
|
|||||||
Text(
|
Text(
|
||||||
DateUtil.format(
|
DateUtil.format(
|
||||||
pubdate,
|
pubdate,
|
||||||
format: DateUtil.longFormatDs,
|
format: dateFormat,
|
||||||
),
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.outline,
|
color: theme.colorScheme.outline,
|
||||||
@@ -577,3 +604,5 @@ class _SavePanelState extends State<SavePanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum _CoverType { def16_9, square }
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'dart:math' show pi, max;
|
|||||||
import 'package:PiliPlus/common/widgets/image/custom_grid_view.dart'
|
import 'package:PiliPlus/common/widgets/image/custom_grid_view.dart'
|
||||||
show ImageModel;
|
show ImageModel;
|
||||||
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
|
||||||
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
|
||||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||||
import 'package:PiliPlus/grpc/reply.dart';
|
import 'package:PiliPlus/grpc/reply.dart';
|
||||||
import 'package:PiliPlus/http/fav.dart';
|
import 'package:PiliPlus/http/fav.dart';
|
||||||
@@ -25,7 +24,6 @@ import 'package:PiliPlus/pages/setting/widgets/slide_dialog.dart';
|
|||||||
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
||||||
import 'package:PiliPlus/utils/accounts.dart';
|
import 'package:PiliPlus/utils/accounts.dart';
|
||||||
import 'package:PiliPlus/utils/cache_manage.dart';
|
import 'package:PiliPlus/utils/cache_manage.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
|
||||||
import 'package:PiliPlus/utils/feed_back.dart';
|
import 'package:PiliPlus/utils/feed_back.dart';
|
||||||
import 'package:PiliPlus/utils/image_util.dart';
|
import 'package:PiliPlus/utils/image_util.dart';
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
@@ -799,7 +797,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
final res = await FavHttp.allFavFolders(Accounts.main.mid);
|
final res = await FavHttp.allFavFolders(Accounts.main.mid);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
final list = res.data.list;
|
final list = res.data.list;
|
||||||
if (list.isNullOrEmpty) {
|
if (list == null || list.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final quickFavId = Pref.quickFavId;
|
final quickFavId = Pref.quickFavId;
|
||||||
@@ -809,22 +807,22 @@ List<SettingsModel> get extraSettings => [
|
|||||||
title: const Text('选择默认收藏夹'),
|
title: const Text('选择默认收藏夹'),
|
||||||
contentPadding: const EdgeInsets.only(top: 5, bottom: 18),
|
contentPadding: const EdgeInsets.only(top: 5, bottom: 18),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Builder(
|
child: RadioGroup(
|
||||||
builder: (context) => Column(
|
onChanged: (value) {
|
||||||
children: List.generate(list!.length, (index) {
|
Get.back();
|
||||||
final item = list[index];
|
GStorage.setting.put(SettingBoxKey.quickFavId, value);
|
||||||
return RadioWidget(
|
SmartDialog.showToast('设置成功');
|
||||||
padding: const EdgeInsets.only(left: 14),
|
},
|
||||||
title: item.title,
|
groupValue: quickFavId,
|
||||||
groupValue: quickFavId,
|
child: Column(
|
||||||
|
children: list.map((item) {
|
||||||
|
return RadioListTile(
|
||||||
|
toggleable: true,
|
||||||
|
dense: true,
|
||||||
|
title: Text(item.title),
|
||||||
value: item.id,
|
value: item.id,
|
||||||
onChanged: (value) {
|
|
||||||
Get.back();
|
|
||||||
GStorage.setting.put(SettingBoxKey.quickFavId, value);
|
|
||||||
SmartDialog.showToast('设置成功');
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}),
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -577,7 +577,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
leading: const Icon(Icons.color_lens_outlined),
|
leading: const Icon(Icons.color_lens_outlined),
|
||||||
title: '应用主题',
|
title: '应用主题',
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'当前主题:${Get.put(ColorSelectController()).type.value == 0 ? '动态取色' : '指定颜色'}',
|
'当前主题:${Get.put(ColorSelectController()).dynamicColor.value ? '动态取色' : '指定颜色'}',
|
||||||
),
|
),
|
||||||
SettingsModel(
|
SettingsModel(
|
||||||
settingsType: SettingsType.normal,
|
settingsType: SettingsType.normal,
|
||||||
|
|||||||
@@ -98,13 +98,13 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
),
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => ListTile(
|
() => ListTile(
|
||||||
enabled: ctr.type.value != 0,
|
enabled: !ctr.dynamicColor.value,
|
||||||
title: Row(
|
title: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text('调色板风格'),
|
const Text('调色板风格'),
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
enabled: ctr.type.value != 0,
|
enabled: !ctr.dynamicColor.value,
|
||||||
initialValue: _dynamicSchemeVariant,
|
initialValue: _dynamicSchemeVariant,
|
||||||
onSelected: (item) {
|
onSelected: (item) {
|
||||||
_dynamicSchemeVariant = item;
|
_dynamicSchemeVariant = item;
|
||||||
@@ -130,7 +130,7 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
height: 1,
|
height: 1,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: ctr.type.value == 0
|
color: ctr.dynamicColor.value
|
||||||
? theme.colorScheme.outline.withValues(
|
? theme.colorScheme.outline.withValues(
|
||||||
alpha: 0.8,
|
alpha: 0.8,
|
||||||
)
|
)
|
||||||
@@ -141,7 +141,7 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
Icon(
|
Icon(
|
||||||
size: 20,
|
size: 20,
|
||||||
Icons.keyboard_arrow_right,
|
Icons.keyboard_arrow_right,
|
||||||
color: ctr.type.value == 0
|
color: ctr.dynamicColor.value
|
||||||
? theme.colorScheme.outline.withValues(
|
? theme.colorScheme.outline.withValues(
|
||||||
alpha: 0.8,
|
alpha: 0.8,
|
||||||
)
|
)
|
||||||
@@ -164,27 +164,14 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => RadioListTile(
|
() => CheckboxListTile(
|
||||||
value: 0,
|
|
||||||
title: const Text('动态取色'),
|
title: const Text('动态取色'),
|
||||||
groupValue: ctr.type.value,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
onChanged: (dynamic val) {
|
value: ctr.dynamicColor.value,
|
||||||
|
onChanged: (val) {
|
||||||
ctr
|
ctr
|
||||||
..type.value = 0
|
..dynamicColor.value = val!
|
||||||
..setting.put(SettingBoxKey.dynamicColor, true);
|
..setting.put(SettingBoxKey.dynamicColor, val);
|
||||||
Get.forceAppUpdate();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Obx(
|
|
||||||
() => RadioListTile(
|
|
||||||
value: 1,
|
|
||||||
title: const Text('指定颜色'),
|
|
||||||
groupValue: ctr.type.value,
|
|
||||||
onChanged: (dynamic val) {
|
|
||||||
ctr
|
|
||||||
..type.value = 1
|
|
||||||
..setting.put(SettingBoxKey.dynamicColor, false);
|
|
||||||
Get.forceAppUpdate();
|
Get.forceAppUpdate();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -196,78 +183,79 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => SizedBox(
|
() => ctr.dynamicColor.value
|
||||||
height: ctr.type.value == 0 ? 0 : null,
|
? const SizedBox.shrink(key: ValueKey(false))
|
||||||
child: Padding(
|
: Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
key: const ValueKey(true),
|
||||||
child: Wrap(
|
padding: const EdgeInsets.all(12),
|
||||||
alignment: WrapAlignment.center,
|
child: Wrap(
|
||||||
spacing: 22,
|
alignment: WrapAlignment.center,
|
||||||
runSpacing: 18,
|
spacing: 22,
|
||||||
children: colorThemeTypes.indexed.map(
|
runSpacing: 18,
|
||||||
(e) {
|
children: colorThemeTypes.indexed.map(
|
||||||
final index = e.$1;
|
(e) {
|
||||||
final item = e.$2;
|
final index = e.$1;
|
||||||
return GestureDetector(
|
final item = e.$2;
|
||||||
behavior: HitTestBehavior.opaque,
|
return GestureDetector(
|
||||||
onTap: () {
|
behavior: HitTestBehavior.opaque,
|
||||||
ctr
|
onTap: () {
|
||||||
..currentColor.value = index
|
ctr
|
||||||
..setting.put(SettingBoxKey.customColor, index);
|
..currentColor.value = index
|
||||||
Get.forceAppUpdate();
|
..setting.put(
|
||||||
|
SettingBoxKey.customColor,
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
Get.forceAppUpdate();
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
spacing: 3,
|
||||||
|
children: [
|
||||||
|
ColorPalette(
|
||||||
|
color: item.color,
|
||||||
|
selected: ctr.currentColor.value == index,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item.label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ctr.currentColor.value != index
|
||||||
|
? theme.colorScheme.outline
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Column(
|
).toList(),
|
||||||
spacing: 3,
|
),
|
||||||
children: [
|
),
|
||||||
ColorPalette(
|
|
||||||
color: item.color,
|
|
||||||
selected: ctr.currentColor.value == index,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
item.label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: ctr.currentColor.value != index
|
|
||||||
? theme.colorScheme.outline
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...[
|
Padding(
|
||||||
Padding(
|
padding: padding,
|
||||||
padding: padding,
|
child: IgnorePointer(
|
||||||
child: IgnorePointer(
|
child: Container(
|
||||||
child: Container(
|
height: size.height / 2,
|
||||||
height: size.height / 2,
|
width: size.width,
|
||||||
width: size.width,
|
color: theme.colorScheme.surface,
|
||||||
color: theme.colorScheme.surface,
|
child: const HomePage(),
|
||||||
child: const HomePage(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IgnorePointer(
|
),
|
||||||
child: NavigationBar(
|
IgnorePointer(
|
||||||
destinations: NavigationBarType.values
|
child: NavigationBar(
|
||||||
.map(
|
destinations: NavigationBarType.values
|
||||||
(item) => NavigationDestination(
|
.map(
|
||||||
icon: item.icon,
|
(item) => NavigationDestination(
|
||||||
label: item.label,
|
icon: item.icon,
|
||||||
),
|
label: item.label,
|
||||||
)
|
),
|
||||||
.toList(),
|
)
|
||||||
),
|
.toList(),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -276,7 +264,6 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
|
|
||||||
class ColorSelectController extends GetxController {
|
class ColorSelectController extends GetxController {
|
||||||
final RxBool dynamicColor = Pref.dynamicColor.obs;
|
final RxBool dynamicColor = Pref.dynamicColor.obs;
|
||||||
late final RxInt type = (dynamicColor.value ? 0 : 1).obs;
|
|
||||||
final RxInt currentColor = Pref.customColor.obs;
|
final RxInt currentColor = Pref.customColor.obs;
|
||||||
final RxDouble currentTextScale = Pref.defaultTextScale.obs;
|
final RxDouble currentTextScale = Pref.defaultTextScale.obs;
|
||||||
final Rx<ThemeType> themeType = Pref.themeType.obs;
|
final Rx<ThemeType> themeType = Pref.themeType.obs;
|
||||||
|
|||||||
@@ -75,28 +75,30 @@ class _SetDisplayModeState extends State<SetDisplayMode> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: RadioGroup(
|
||||||
itemCount: modes.length,
|
onChanged: (DisplayMode? newMode) {
|
||||||
itemBuilder: (context, index) {
|
FlutterDisplayMode.setPreferredMode(
|
||||||
final DisplayMode mode = modes[index];
|
newMode!,
|
||||||
return RadioListTile<DisplayMode>(
|
).whenComplete(
|
||||||
value: mode,
|
() => Future.delayed(
|
||||||
title: mode == DisplayMode.auto
|
const Duration(milliseconds: 100),
|
||||||
? const Text('自动')
|
fetchAll,
|
||||||
: Text('$mode${mode == active ? ' [系统]' : ''}'),
|
),
|
||||||
groupValue: preferred,
|
|
||||||
onChanged: (DisplayMode? newMode) {
|
|
||||||
FlutterDisplayMode.setPreferredMode(
|
|
||||||
newMode!,
|
|
||||||
).whenComplete(
|
|
||||||
() => Future.delayed(
|
|
||||||
const Duration(milliseconds: 100),
|
|
||||||
fetchAll,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
groupValue: preferred,
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: modes.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final DisplayMode mode = modes[index];
|
||||||
|
return RadioListTile<DisplayMode>(
|
||||||
|
value: mode,
|
||||||
|
title: mode == DisplayMode.auto
|
||||||
|
? const Text('自动')
|
||||||
|
: Text('$mode${mode == active ? ' [系统]' : ''}'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ class _SlideColorPickerState extends State<SlideColorPicker> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
String get _convert => Color.fromARGB(
|
String get _convert => Color.fromRGBO(
|
||||||
255,
|
|
||||||
_r,
|
_r,
|
||||||
_g,
|
_g,
|
||||||
_b,
|
_b,
|
||||||
).value.toRadixString(16).substring(2).toUpperCase();
|
1,
|
||||||
|
).toARGB32().toRadixString(16).substring(2).toUpperCase();
|
||||||
|
|
||||||
Widget _slider({
|
Widget _slider({
|
||||||
required String title,
|
required String title,
|
||||||
|
|||||||
@@ -33,24 +33,26 @@ class SelectDialog<T> extends StatelessWidget {
|
|||||||
title: Text(title),
|
title: Text(title),
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: RadioGroup<T>(
|
||||||
mainAxisSize: MainAxisSize.min,
|
onChanged: Navigator.of(context).pop,
|
||||||
children: List.generate(
|
groupValue: value,
|
||||||
values.length,
|
child: Column(
|
||||||
(index) {
|
mainAxisSize: MainAxisSize.min,
|
||||||
final item = values[index];
|
children: List.generate(
|
||||||
return RadioListTile<T>(
|
values.length,
|
||||||
dense: true,
|
(index) {
|
||||||
value: item.$1,
|
final item = values[index];
|
||||||
title: Text(
|
return RadioListTile<T>(
|
||||||
item.$2,
|
dense: true,
|
||||||
style: titleMedium,
|
value: item.$1,
|
||||||
),
|
title: Text(
|
||||||
subtitle: subtitleBuilder?.call(context, index),
|
item.$2,
|
||||||
groupValue: value,
|
style: titleMedium,
|
||||||
onChanged: Navigator.of(context).pop,
|
),
|
||||||
);
|
subtitle: subtitleBuilder?.call(context, index),
|
||||||
},
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
|||||||
setting.put(
|
setting.put(
|
||||||
SettingBoxKey.blockColor,
|
SettingBoxKey.blockColor,
|
||||||
_blockColor
|
_blockColor
|
||||||
.map((item) => item.value.toRadixString(16).substring(2))
|
.map((item) => item.toARGB32().toRadixString(16).substring(2))
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
(context as Element).markNeedsBuild();
|
(context as Element).markNeedsBuild();
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ class VideoDetailController extends GetxController
|
|||||||
bool get showVideoSheet => !horizontalScreen && !isPortrait;
|
bool get showVideoSheet => !horizontalScreen && !isPortrait;
|
||||||
|
|
||||||
int? _lastPos;
|
int? _lastPos;
|
||||||
List<PostSegmentModel>? postList;
|
List<PostSegmentModel> postList = [];
|
||||||
RxList<SegmentModel> segmentList = <SegmentModel>[].obs;
|
RxList<SegmentModel> segmentList = <SegmentModel>[].obs;
|
||||||
List<Segment> viewPointList = <Segment>[];
|
List<Segment> viewPointList = <Segment>[];
|
||||||
List<Segment>? segmentProgressList;
|
List<Segment>? segmentProgressList;
|
||||||
@@ -1316,9 +1316,8 @@ class VideoDetailController extends GetxController
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onBlock(BuildContext context) {
|
void onBlock(BuildContext context) {
|
||||||
postList ??= <PostSegmentModel>[];
|
if (postList.isEmpty) {
|
||||||
if (postList!.isEmpty) {
|
postList.add(
|
||||||
postList!.add(
|
|
||||||
PostSegmentModel(
|
PostSegmentModel(
|
||||||
segment: Pair(
|
segment: Pair(
|
||||||
first: 0,
|
first: 0,
|
||||||
|
|||||||
70
lib/pages/video/post_panel/popup_menu_text.dart
Normal file
70
lib/pages/video/post_panel/popup_menu_text.dart
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||||
|
|
||||||
|
class PopupMenuText<T> extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final T initialValue;
|
||||||
|
final PopupMenuItemSelected<T> onSelected;
|
||||||
|
final PopupMenuItemBuilder<T> itemBuilder;
|
||||||
|
final String Function(T) getSelectTitle;
|
||||||
|
|
||||||
|
const PopupMenuText({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.initialValue,
|
||||||
|
required this.onSelected,
|
||||||
|
required this.itemBuilder,
|
||||||
|
required this.getSelectTitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PopupMenuText<T>> createState() => _PopupMenuTextState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
|
||||||
|
late T select = widget.initialValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('${widget.title}: '),
|
||||||
|
PopupMenuButton<T>(
|
||||||
|
initialValue: select,
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == select) return;
|
||||||
|
setState(() {
|
||||||
|
select = value;
|
||||||
|
widget.onSelected(value);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
itemBuilder: widget.itemBuilder,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.getSelectTitle(select),
|
||||||
|
style: TextStyle(
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
color: theme.colorScheme.secondary,
|
||||||
|
),
|
||||||
|
strutStyle: const StrutStyle(
|
||||||
|
height: 1,
|
||||||
|
leading: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
MdiIcons.unfoldMoreHorizontal,
|
||||||
|
size: MediaQuery.textScalerOf(context).scale(14),
|
||||||
|
color: theme.colorScheme.secondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart';
|
|||||||
import 'package:PiliPlus/models_new/sponsor_block/segment_item.dart';
|
import 'package:PiliPlus/models_new/sponsor_block/segment_item.dart';
|
||||||
import 'package:PiliPlus/pages/common/slide/common_collapse_slide_page.dart';
|
import 'package:PiliPlus/pages/common/slide/common_collapse_slide_page.dart';
|
||||||
import 'package:PiliPlus/pages/video/controller.dart';
|
import 'package:PiliPlus/pages/video/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/video/post_panel/popup_menu_text.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||||
import 'package:PiliPlus/utils/duration_util.dart';
|
import 'package:PiliPlus/utils/duration_util.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
@@ -22,7 +23,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart' show FilteringTextInputFormatter;
|
import 'package:flutter/services.dart' show FilteringTextInputFormatter;
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart' hide Response;
|
import 'package:get/get.dart' hide Response;
|
||||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
|
||||||
|
|
||||||
class PostPanel extends CommonCollapseSlidePage {
|
class PostPanel extends CommonCollapseSlidePage {
|
||||||
const PostPanel({
|
const PostPanel({
|
||||||
@@ -60,130 +60,122 @@ class PostPanel extends CommonCollapseSlidePage {
|
|||||||
required double currentPos,
|
required double currentPos,
|
||||||
required double videoDuration,
|
required double videoDuration,
|
||||||
}) {
|
}) {
|
||||||
List<Widget> segment(BuildContext context, bool isFirst) {
|
Widget segment(bool isFirst) => Builder(
|
||||||
String value = DurationUtil.formatDuration(
|
builder: (context) {
|
||||||
isFirst ? item.segment.first : item.segment.second,
|
String value = DurationUtil.formatDuration(
|
||||||
);
|
isFirst ? item.segment.first : item.segment.second,
|
||||||
return [
|
);
|
||||||
Text(
|
return Row(
|
||||||
'${isFirst ? '开始' : '结束'}: $value',
|
spacing: 5,
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
iconButton(
|
children: [
|
||||||
context: context,
|
Text(
|
||||||
size: 26,
|
'${isFirst ? '开始' : '结束'}: $value',
|
||||||
tooltip: '设为当前',
|
),
|
||||||
icon: Icons.my_location,
|
iconButton(
|
||||||
onPressed: () {
|
|
||||||
updateSegment(
|
|
||||||
isFirst: isFirst,
|
|
||||||
item: item,
|
|
||||||
value: currentPos,
|
|
||||||
);
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
iconButton(
|
|
||||||
context: context,
|
|
||||||
size: 26,
|
|
||||||
tooltip: isFirst ? '视频开头' : '视频结尾',
|
|
||||||
icon: isFirst ? Icons.first_page : Icons.last_page,
|
|
||||||
onPressed: () {
|
|
||||||
updateSegment(
|
|
||||||
isFirst: isFirst,
|
|
||||||
item: item,
|
|
||||||
value: isFirst ? 0 : videoDuration,
|
|
||||||
);
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
iconButton(
|
|
||||||
context: context,
|
|
||||||
size: 26,
|
|
||||||
tooltip: '编辑',
|
|
||||||
icon: Icons.edit,
|
|
||||||
onPressed: () {
|
|
||||||
showDialog<String>(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
size: 26,
|
||||||
String initV = value;
|
tooltip: '设为当前',
|
||||||
return AlertDialog(
|
icon: Icons.my_location,
|
||||||
content: TextFormField(
|
onPressed: () {
|
||||||
initialValue: value,
|
updateSegment(
|
||||||
autofocus: true,
|
isFirst: isFirst,
|
||||||
onChanged: (value) => initV = value,
|
item: item,
|
||||||
inputFormatters: [
|
value: currentPos,
|
||||||
FilteringTextInputFormatter.allow(RegExp(r'[\d:.]+')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: Get.back,
|
|
||||||
child: Text(
|
|
||||||
'取消',
|
|
||||||
style: TextStyle(color: theme.colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Get.back(result: initV),
|
|
||||||
child: const Text('确定'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
(context as Element).markNeedsBuild();
|
||||||
},
|
},
|
||||||
).then((res) {
|
),
|
||||||
if (res != null) {
|
iconButton(
|
||||||
try {
|
context: context,
|
||||||
List<num> split = res
|
size: 26,
|
||||||
.split(':')
|
tooltip: isFirst ? '视频开头' : '视频结尾',
|
||||||
.reversed
|
icon: isFirst ? Icons.first_page : Icons.last_page,
|
||||||
.map(num.parse)
|
onPressed: () {
|
||||||
.toList();
|
updateSegment(
|
||||||
double duration = 0;
|
isFirst: isFirst,
|
||||||
for (int i = 0; i < split.length; i++) {
|
item: item,
|
||||||
duration += split[i] * pow(60, i);
|
value: isFirst ? 0 : videoDuration,
|
||||||
}
|
);
|
||||||
if (duration <= videoDuration) {
|
(context as Element).markNeedsBuild();
|
||||||
updateSegment(
|
},
|
||||||
isFirst: isFirst,
|
),
|
||||||
item: item,
|
iconButton(
|
||||||
value: duration,
|
context: context,
|
||||||
|
size: 26,
|
||||||
|
tooltip: '编辑',
|
||||||
|
icon: Icons.edit,
|
||||||
|
onPressed: () async {
|
||||||
|
final res = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
String initV = value;
|
||||||
|
return AlertDialog(
|
||||||
|
content: TextFormField(
|
||||||
|
initialValue: value,
|
||||||
|
autofocus: true,
|
||||||
|
onChanged: (value) => initV = value,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[\d:.]+')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(result: initV),
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
(context as Element).markNeedsBuild();
|
},
|
||||||
}
|
);
|
||||||
} catch (e) {
|
|
||||||
if (kDebugMode) debugPrint(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
final child = Builder(
|
if (res != null) {
|
||||||
builder: (context) => Row(
|
try {
|
||||||
spacing: 5,
|
List<num> split = res
|
||||||
mainAxisSize: MainAxisSize.min,
|
.split(':')
|
||||||
children: segment(context, true),
|
.reversed
|
||||||
),
|
.map(num.parse)
|
||||||
|
.toList();
|
||||||
|
double duration = 0;
|
||||||
|
for (int i = 0; i < split.length; i++) {
|
||||||
|
duration += split[i] * pow(60, i);
|
||||||
|
}
|
||||||
|
if (duration <= videoDuration) {
|
||||||
|
updateSegment(
|
||||||
|
isFirst: isFirst,
|
||||||
|
item: item,
|
||||||
|
value: duration,
|
||||||
|
);
|
||||||
|
(context as Element).markNeedsBuild();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) debugPrint(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (item.category != SegmentType.poi_highlight) {
|
if (item.category != SegmentType.poi_highlight) {
|
||||||
return Wrap(
|
return Wrap(
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
children: [
|
children: [segment(true), segment(false)],
|
||||||
child,
|
|
||||||
Builder(
|
|
||||||
builder: (context) => Row(
|
|
||||||
spacing: 5,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: segment(context, false),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return child;
|
return segment(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +183,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
|||||||
late final VideoDetailController videoDetailController =
|
late final VideoDetailController videoDetailController =
|
||||||
widget.videoDetailController;
|
widget.videoDetailController;
|
||||||
late final PlPlayerController plPlayerController = widget.plPlayerController;
|
late final PlPlayerController plPlayerController = widget.plPlayerController;
|
||||||
late final List<PostSegmentModel>? list = videoDetailController.postList;
|
late final List<PostSegmentModel> list = videoDetailController.postList;
|
||||||
|
|
||||||
late final double videoDuration =
|
late final double videoDuration =
|
||||||
plPlayerController.durationSeconds.value.inMilliseconds / 1000;
|
plPlayerController.durationSeconds.value.inMilliseconds / 1000;
|
||||||
@@ -224,7 +216,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
|||||||
tooltip: '添加片段',
|
tooltip: '添加片段',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
list?.insert(
|
list.insert(
|
||||||
0,
|
0,
|
||||||
PostSegmentModel(
|
PostSegmentModel(
|
||||||
segment: Pair(
|
segment: Pair(
|
||||||
@@ -263,18 +255,14 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
|||||||
return Stack(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
SingleChildScrollView(
|
ListView.builder(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: EdgeInsets.only(bottom: 88 + bottom),
|
padding: EdgeInsets.only(bottom: 88 + bottom),
|
||||||
child: Column(
|
itemCount: list.length,
|
||||||
children: List.generate(
|
itemBuilder: (context, index) {
|
||||||
list!.length,
|
return _buildItem(theme, index, list[index]);
|
||||||
(index) {
|
},
|
||||||
return _buildItem(theme, index, list![index]);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 16,
|
right: 16,
|
||||||
@@ -294,10 +282,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: _onPost,
|
||||||
Get.back();
|
|
||||||
_onPost();
|
|
||||||
},
|
|
||||||
child: const Text('确定提交'),
|
child: const Text('确定提交'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -310,59 +295,56 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPost() {
|
Future<void> _onPost() async {
|
||||||
Request()
|
Get.back();
|
||||||
.post(
|
final res = await Request().post(
|
||||||
'${widget.videoDetailController.blockServer}/api/skipSegments',
|
'${widget.videoDetailController.blockServer}/api/skipSegments',
|
||||||
data: {
|
data: {
|
||||||
'videoID': videoDetailController.bvid,
|
'videoID': videoDetailController.bvid,
|
||||||
'cid': videoDetailController.cid.value.toString(),
|
'cid': videoDetailController.cid.value.toString(),
|
||||||
'userID': Pref.blockUserID.toString(),
|
'userID': Pref.blockUserID.toString(),
|
||||||
'userAgent': Constants.userAgent,
|
'userAgent': Constants.userAgent,
|
||||||
'videoDuration': videoDuration,
|
'videoDuration': videoDuration,
|
||||||
'segments': list!
|
'segments': list
|
||||||
.map(
|
.map(
|
||||||
(item) => {
|
(item) => {
|
||||||
'segment': [
|
'segment': [
|
||||||
item.segment.first,
|
item.segment.first,
|
||||||
item.segment.second,
|
item.segment.second,
|
||||||
],
|
],
|
||||||
'category': item.category.name,
|
'category': item.category.name,
|
||||||
'actionType': item.actionType.name,
|
'actionType': item.actionType.name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
},
|
},
|
||||||
options: Options(
|
options: Options(
|
||||||
followRedirects: true, // Defaults to true.
|
followRedirects: true, // Defaults to true.
|
||||||
validateStatus: (int? status) {
|
validateStatus: (int? status) {
|
||||||
return (status! >= 200 && status < 300) ||
|
return (status! >= 200 && status < 300) ||
|
||||||
const [400, 403, 429, 409] // reduce extra toast
|
const [400, 403, 429, 409] // reduce extra toast
|
||||||
.contains(status);
|
.contains(status);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
.then(
|
|
||||||
(res) {
|
if (res.statusCode == 200) {
|
||||||
if (res.statusCode == 200) {
|
Get.back();
|
||||||
Get.back();
|
SmartDialog.showToast('提交成功');
|
||||||
SmartDialog.showToast('提交成功');
|
list.clear();
|
||||||
list?.clear();
|
if (res.data case List list) {
|
||||||
if (res.data case List list) {
|
videoDetailController.handleSBData(
|
||||||
videoDetailController.handleSBData(
|
list.map((e) => SegmentItemModel.fromJson(e)).toList(),
|
||||||
list.map((e) => SegmentItemModel.fromJson(e)).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
plPlayerController.segmentList.value =
|
|
||||||
videoDetailController.segmentProgressList ?? <Segment>[];
|
|
||||||
if (videoDetailController.positionSubscription == null) {
|
|
||||||
videoDetailController.initSkip();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast('提交失败: ${_errMsg(res)}');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
plPlayerController.segmentList.value =
|
||||||
|
videoDetailController.segmentProgressList ?? <Segment>[];
|
||||||
|
if (videoDetailController.positionSubscription == null) {
|
||||||
|
videoDetailController.initSkip();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast('提交失败: ${_errMsg(res)}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _errMsg(Response res) {
|
String _errMsg(Response res) {
|
||||||
@@ -381,216 +363,142 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildItem(ThemeData theme, int index, PostSegmentModel item) {
|
Widget _buildItem(ThemeData theme, int index, PostSegmentModel item) {
|
||||||
return Builder(
|
return Stack(
|
||||||
builder: (context) {
|
clipBehavior: Clip.none,
|
||||||
return Stack(
|
children: [
|
||||||
clipBehavior: Clip.none,
|
Container(
|
||||||
children: [
|
width: double.infinity,
|
||||||
Container(
|
margin: const EdgeInsets.symmetric(
|
||||||
margin: const EdgeInsets.symmetric(
|
horizontal: 16,
|
||||||
horizontal: 16,
|
vertical: 5,
|
||||||
vertical: 5,
|
),
|
||||||
),
|
padding: const EdgeInsets.all(12),
|
||||||
padding: const EdgeInsets.all(12),
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
color: theme.colorScheme.onInverseSurface,
|
||||||
color: theme.colorScheme.onInverseSurface,
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
),
|
||||||
),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
|
if (item.actionType != ActionType.full)
|
||||||
|
PostPanel.segmentWidget(
|
||||||
|
theme,
|
||||||
|
item: item,
|
||||||
|
currentPos: currentPos,
|
||||||
|
videoDuration: videoDuration,
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
runSpacing: 8,
|
||||||
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
if (item.actionType != ActionType.full)
|
PopupMenuText(
|
||||||
PostPanel.segmentWidget(
|
title: '分类',
|
||||||
theme,
|
initialValue: item.category,
|
||||||
item: item,
|
onSelected: (e) {
|
||||||
currentPos: currentPos,
|
item.category = e;
|
||||||
videoDuration: videoDuration,
|
List<ActionType> constraintList = e.toActionType;
|
||||||
),
|
if (!constraintList.contains(item.actionType)) {
|
||||||
Wrap(
|
item.actionType = constraintList.first;
|
||||||
runSpacing: 8,
|
}
|
||||||
spacing: 16,
|
switch (e) {
|
||||||
children: [
|
case SegmentType.poi_highlight:
|
||||||
Row(
|
PostPanel.updateSegment(
|
||||||
mainAxisSize: MainAxisSize.min,
|
isFirst: false,
|
||||||
children: [
|
item: item,
|
||||||
const Text('分类: '),
|
value: item.segment.first,
|
||||||
PopupMenuButton<SegmentType>(
|
);
|
||||||
initialValue: item.category,
|
break;
|
||||||
onSelected: (e) {
|
case SegmentType.exclusive_access:
|
||||||
item.category = e;
|
PostPanel.updateSegment(
|
||||||
List<ActionType> constraintList = e.toActionType;
|
isFirst: true,
|
||||||
if (!constraintList.contains(item.actionType)) {
|
item: item,
|
||||||
item.actionType = constraintList.first;
|
value: 0,
|
||||||
}
|
);
|
||||||
switch (e) {
|
break;
|
||||||
case SegmentType.poi_highlight:
|
default:
|
||||||
PostPanel.updateSegment(
|
}
|
||||||
isFirst: false,
|
},
|
||||||
item: item,
|
itemBuilder: (context) => SegmentType.values
|
||||||
value: item.segment.first,
|
.map(
|
||||||
);
|
(e) => PopupMenuItem(value: e, child: Text(e.title)),
|
||||||
break;
|
)
|
||||||
case SegmentType.exclusive_access:
|
.toList(),
|
||||||
PostPanel.updateSegment(
|
getSelectTitle: (category) => category.title,
|
||||||
isFirst: true,
|
),
|
||||||
item: item,
|
PopupMenuText(
|
||||||
value: 0,
|
title: '行为类别',
|
||||||
);
|
initialValue: item.actionType,
|
||||||
break;
|
onSelected: (e) {
|
||||||
default:
|
item.actionType = e;
|
||||||
}
|
if (e == ActionType.full) {
|
||||||
(context as Element).markNeedsBuild();
|
PostPanel.updateSegment(
|
||||||
},
|
isFirst: true,
|
||||||
itemBuilder: (context) => SegmentType.values
|
item: item,
|
||||||
.map(
|
value: 0,
|
||||||
(e) => PopupMenuItem<SegmentType>(
|
);
|
||||||
value: e,
|
}
|
||||||
child: Text(e.title),
|
},
|
||||||
),
|
itemBuilder: (context) => ActionType.values
|
||||||
)
|
.map(
|
||||||
.toList(),
|
(e) => PopupMenuItem(
|
||||||
child: Row(
|
enabled: item.category.toActionType.contains(e),
|
||||||
mainAxisSize: MainAxisSize.min,
|
value: e,
|
||||||
children: [
|
child: Text(e.title),
|
||||||
Text(
|
|
||||||
item.category.title,
|
|
||||||
style: TextStyle(
|
|
||||||
height: 1,
|
|
||||||
fontSize: 14,
|
|
||||||
color: theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
strutStyle: const StrutStyle(
|
|
||||||
height: 1,
|
|
||||||
leading: 0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
MdiIcons.unfoldMoreHorizontal,
|
|
||||||
size: MediaQuery.textScalerOf(
|
|
||||||
context,
|
|
||||||
).scale(14),
|
|
||||||
color: theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
),
|
.toList(),
|
||||||
Row(
|
getSelectTitle: (i) => i.title,
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Text('行为类别: '),
|
|
||||||
PopupMenuButton<ActionType>(
|
|
||||||
initialValue: item.actionType,
|
|
||||||
onSelected: (e) {
|
|
||||||
item.actionType = e;
|
|
||||||
if (e == ActionType.full) {
|
|
||||||
PostPanel.updateSegment(
|
|
||||||
isFirst: true,
|
|
||||||
item: item,
|
|
||||||
value: 0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
itemBuilder: (context) => ActionType.values
|
|
||||||
.map(
|
|
||||||
(e) => PopupMenuItem<ActionType>(
|
|
||||||
enabled: item.category.toActionType
|
|
||||||
.contains(e),
|
|
||||||
value: e,
|
|
||||||
child: Text(e.title),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
item.actionType.title,
|
|
||||||
style: TextStyle(
|
|
||||||
height: 1,
|
|
||||||
fontSize: 14,
|
|
||||||
color: theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
strutStyle: const StrutStyle(
|
|
||||||
height: 1,
|
|
||||||
leading: 0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
MdiIcons.unfoldMoreHorizontal,
|
|
||||||
size: MediaQuery.textScalerOf(
|
|
||||||
context,
|
|
||||||
).scale(14),
|
|
||||||
color: theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
Positioned(
|
),
|
||||||
top: 0,
|
),
|
||||||
right: 4,
|
Positioned(
|
||||||
child: iconButton(
|
top: 0,
|
||||||
context: context,
|
right: 4,
|
||||||
size: 26,
|
child: iconButton(
|
||||||
tooltip: '移除',
|
context: context,
|
||||||
icon: Icons.clear,
|
size: 26,
|
||||||
onPressed: () {
|
tooltip: '移除',
|
||||||
setState(() {
|
icon: Icons.clear,
|
||||||
list!.removeAt(index);
|
onPressed: () {
|
||||||
});
|
setState(() {
|
||||||
},
|
list.removeAt(index);
|
||||||
),
|
});
|
||||||
),
|
},
|
||||||
Positioned(
|
),
|
||||||
top: 0,
|
),
|
||||||
left: 4,
|
Positioned(
|
||||||
child: iconButton(
|
top: 0,
|
||||||
context: context,
|
left: 4,
|
||||||
size: 26,
|
child: iconButton(
|
||||||
tooltip: '预览',
|
context: context,
|
||||||
icon: Icons.preview_outlined,
|
size: 26,
|
||||||
onPressed: () async {
|
tooltip: '预览',
|
||||||
if (widget.plPlayerController.videoPlayerController != null) {
|
icon: Icons.preview_outlined,
|
||||||
int start = max(
|
onPressed: () async {
|
||||||
0,
|
final videoCtr = widget.plPlayerController.videoPlayerController;
|
||||||
(item.segment.first * 1000).round() - 2000,
|
if (videoCtr != null) {
|
||||||
);
|
final start = (item.segment.first * 1000).round();
|
||||||
await widget.plPlayerController.videoPlayerController!.seek(
|
final seek = max(0, start - 2000);
|
||||||
Duration(milliseconds: start),
|
await videoCtr.seek(Duration(milliseconds: seek));
|
||||||
);
|
if (!videoCtr.state.playing) {
|
||||||
if (!widget
|
await videoCtr.play();
|
||||||
.plPlayerController
|
}
|
||||||
.videoPlayerController!
|
final delay = start - seek;
|
||||||
.state
|
if (delay > 0) {
|
||||||
.playing) {
|
await Future.delayed(Duration(milliseconds: delay));
|
||||||
await widget.plPlayerController.videoPlayerController!
|
}
|
||||||
.play();
|
videoCtr.seek(
|
||||||
}
|
Duration(milliseconds: (item.segment.second * 1000).round()),
|
||||||
if (start != 0) {
|
);
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
}
|
||||||
}
|
},
|
||||||
widget.plPlayerController.videoPlayerController!.seek(
|
),
|
||||||
Duration(
|
),
|
||||||
milliseconds: (item.segment.second * 1000).round(),
|
],
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ class _SendDanmakuPanelState extends CommonTextPubPageState<SendDanmakuPanel> {
|
|||||||
msg: editController.text,
|
msg: editController.text,
|
||||||
mode: _mode.value,
|
mode: _mode.value,
|
||||||
fontsize: _fontsize.value,
|
fontsize: _fontsize.value,
|
||||||
color: isColorful ? null : _color.value.value & 0xFFFFFF,
|
color: isColorful ? null : _color.value.toARGB32() & 0xFFFFFF,
|
||||||
colorful: isColorful,
|
colorful: isColorful,
|
||||||
);
|
);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
|
|||||||
@@ -83,9 +83,7 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
Timer? clock;
|
Timer? clock;
|
||||||
bool get isFullScreen => plPlayerController.isFullScreen.value;
|
bool get isFullScreen => plPlayerController.isFullScreen.value;
|
||||||
Box setting = GStorage.setting;
|
Box setting = GStorage.setting;
|
||||||
MarqueeController? marqueeController;
|
late final provider = ContextSingleTicker(context);
|
||||||
MarqueeController get _marqueeController =>
|
|
||||||
marqueeController ??= MarqueeController(autoStart: false);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -100,8 +98,6 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
clock?.cancel();
|
clock?.cancel();
|
||||||
marqueeController?.dispose();
|
|
||||||
marqueeController = null;
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1934,11 +1930,11 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
title,
|
title,
|
||||||
spacing: 30,
|
spacing: 30,
|
||||||
velocity: 30,
|
velocity: 30,
|
||||||
controller: _marqueeController,
|
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
|
provider: provider,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import 'package:PiliPlus/models_new/msg/msg_dnd/uid_setting.dart';
|
|||||||
import 'package:PiliPlus/models_new/msg/session_ss/data.dart';
|
import 'package:PiliPlus/models_new/msg/session_ss/data.dart';
|
||||||
import 'package:PiliPlus/utils/accounts.dart';
|
import 'package:PiliPlus/utils/accounts.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.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:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -154,20 +153,9 @@ class WhisperLinkSettingController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void report() {
|
void report() => showMemberReportDialog(
|
||||||
showDialog(
|
Get.context!,
|
||||||
context: Get.context!,
|
name: userState.value.dataOrNull?.firstOrNull?.name,
|
||||||
builder: (context) => AlertDialog(
|
mid: talkerUid,
|
||||||
clipBehavior: Clip.hardEdge,
|
);
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
content: MemberReportPanel(
|
|
||||||
name: userState.value.dataOrNull?.firstOrNull?.name ?? '',
|
|
||||||
mid: talkerUid,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import 'package:PiliPlus/models_new/video/video_shot/data.dart';
|
|||||||
import 'package:PiliPlus/pages/common/common_intro_controller.dart';
|
import 'package:PiliPlus/pages/common/common_intro_controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/controller.dart';
|
import 'package:PiliPlus/pages/video/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart';
|
import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/video/post_panel/popup_menu_text.dart';
|
||||||
import 'package:PiliPlus/pages/video/post_panel/view.dart';
|
import 'package:PiliPlus/pages/video/post_panel/view.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/models/bottom_control_type.dart';
|
import 'package:PiliPlus/plugin/pl_player/models/bottom_control_type.dart';
|
||||||
@@ -187,22 +188,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
super.initState();
|
super.initState();
|
||||||
_controlsListener = plPlayerController.showControls.listen((bool val) {
|
_controlsListener = plPlayerController.showControls.listen((bool val) {
|
||||||
final visible = val && !plPlayerController.controlsLock.value;
|
final visible = val && !plPlayerController.controlsLock.value;
|
||||||
|
widget.videoDetailController?.headerCtrKey.currentState?.provider.muted =
|
||||||
|
!visible;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
animationController.forward();
|
animationController.forward();
|
||||||
widget
|
|
||||||
.videoDetailController
|
|
||||||
?.headerCtrKey
|
|
||||||
.currentState
|
|
||||||
?.marqueeController
|
|
||||||
?.start();
|
|
||||||
} else {
|
} else {
|
||||||
animationController.reverse();
|
animationController.reverse();
|
||||||
widget
|
|
||||||
.videoDetailController
|
|
||||||
?.headerCtrKey
|
|
||||||
.currentState
|
|
||||||
?.marqueeController
|
|
||||||
?.stop();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
animationController = AnimationController(
|
animationController = AnimationController(
|
||||||
@@ -1876,9 +1867,13 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
late final segment = Pair(
|
||||||
|
first: plPlayerController.position.value.inMilliseconds / 1000.0,
|
||||||
|
second: plPlayerController.position.value.inMilliseconds / 1000.0,
|
||||||
|
);
|
||||||
Future<void> screenshotWebp() async {
|
Future<void> screenshotWebp() async {
|
||||||
final videoCtr = widget.videoDetailController!;
|
final videoCtr = widget.videoDetailController!;
|
||||||
final videoInfo = widget.videoDetailController!.data;
|
final videoInfo = videoCtr.data;
|
||||||
final ids = videoInfo.dash!.video!.map((i) => i.id!).toSet();
|
final ids = videoInfo.dash!.video!.map((i) => i.id!).toSet();
|
||||||
final video = videoCtr.findVideoByQa(ids.reduce((p, n) => p < n ? p : n));
|
final video = videoCtr.findVideoByQa(ids.reduce((p, n) => p < n ? p : n));
|
||||||
|
|
||||||
@@ -1890,7 +1885,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final currentPos = ctr.position.value.inMilliseconds / 1000.0;
|
final currentPos = ctr.position.value.inMilliseconds / 1000.0;
|
||||||
final duration = ctr.durationSeconds.value.inMilliseconds / 1000.0;
|
final duration = ctr.durationSeconds.value.inMilliseconds / 1000.0;
|
||||||
final segment = Pair(first: currentPos, second: currentPos + 10.0);
|
|
||||||
final model = PostSegmentModel(
|
final model = PostSegmentModel(
|
||||||
segment: segment,
|
segment: segment,
|
||||||
category: SegmentType.sponsor,
|
category: SegmentType.sponsor,
|
||||||
@@ -1915,43 +1909,37 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
currentPos: currentPos,
|
currentPos: currentPos,
|
||||||
videoDuration: duration,
|
videoDuration: duration,
|
||||||
),
|
),
|
||||||
Builder(
|
PopupMenuText(
|
||||||
builder: (context) => PopupMenuButton(
|
title: '选择画质',
|
||||||
initialValue: qa.code,
|
initialValue: qa.code,
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (value == qa.code) return;
|
final video = videoCtr.findVideoByQa(value);
|
||||||
final video = videoCtr.findVideoByQa(value);
|
url = video.baseUrl;
|
||||||
url = video.baseUrl;
|
qa = video.quality;
|
||||||
qa = video.quality;
|
},
|
||||||
(context as Element).markNeedsBuild();
|
itemBuilder: (context) => videoInfo.supportFormats!
|
||||||
},
|
.map(
|
||||||
itemBuilder: (_) => videoInfo.supportFormats!
|
(i) => PopupMenuItem(
|
||||||
.map(
|
enabled: ids.contains(i.quality),
|
||||||
(i) => PopupMenuItem<int>(
|
value: i.quality,
|
||||||
enabled: ids.contains(i.quality),
|
child: Text(i.newDesc ?? ''),
|
||||||
value: i.quality,
|
),
|
||||||
child: Text(i.newDesc ?? ''),
|
)
|
||||||
),
|
.toList(),
|
||||||
)
|
getSelectTitle: (_) => qa.shortDesc,
|
||||||
.toList(),
|
|
||||||
child: Text('转码画质:${qa.shortDesc}'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Builder(
|
PopupMenuText(
|
||||||
builder: (context) => PopupMenuButton(
|
title: 'webp预设',
|
||||||
initialValue: preset,
|
initialValue: preset,
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (preset == value) return;
|
if (preset == value) return;
|
||||||
preset = value;
|
preset = value;
|
||||||
(context as Element).markNeedsBuild();
|
(context as Element).markNeedsBuild();
|
||||||
},
|
},
|
||||||
itemBuilder: (_) => WebpPreset.values
|
itemBuilder: (context) => WebpPreset.values
|
||||||
.map(
|
.map((i) => PopupMenuItem(value: i, child: Text(i.name)))
|
||||||
(i) => PopupMenuItem(value: i, child: Text(i.name)),
|
.toList(),
|
||||||
)
|
getSelectTitle: (i) => '${i.name}(${i.desc})',
|
||||||
.toList(),
|
|
||||||
child: Text('webp预设:${preset.name}(${preset.desc})'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'*转码使用软解,速度可能慢于播放,请不要选择过长的时间段或过高画质',
|
'*转码使用软解,速度可能慢于播放,请不要选择过长的时间段或过高画质',
|
||||||
|
|||||||
@@ -242,12 +242,12 @@ class PiliScheme {
|
|||||||
case 'comment':
|
case 'comment':
|
||||||
if (path.startsWith("/detail/")) {
|
if (path.startsWith("/detail/")) {
|
||||||
// bilibili://comment/detail/17/832703053858603029/238686570016/?subType=0&anchor=238686628816&showEnter=1&extraIntentId=0&scene=1&enterName=%E6%9F%A5%E7%9C%8B%E5%8A%A8%E6%80%81%E8%AF%A6%E6%83%85&enterUri=bilibili://following/detail/832703053858603029
|
// bilibili://comment/detail/17/832703053858603029/238686570016/?subType=0&anchor=238686628816&showEnter=1&extraIntentId=0&scene=1&enterName=%E6%9F%A5%E7%9C%8B%E5%8A%A8%E6%80%81%E8%AF%A6%E6%83%85&enterUri=bilibili://following/detail/832703053858603029
|
||||||
List<String> pathSegments = uri.pathSegments;
|
final pathSegments = uri.pathSegments;
|
||||||
Map<String, String> queryParameters = uri.queryParameters;
|
final queryParameters = uri.queryParameters;
|
||||||
int type = int.parse(pathSegments[1]); // business_id
|
final type = int.parse(pathSegments[1]); // business_id
|
||||||
int oid = int.parse(pathSegments[2]); // subject_id
|
final oid = int.parse(pathSegments[2]); // subject_id
|
||||||
int rootId = int.parse(pathSegments[3]); // root_id // target_id
|
final rootId = int.parse(pathSegments[3]); // root_id // target_id
|
||||||
int? rpId =
|
final rpId =
|
||||||
queryParameters['anchor'] !=
|
queryParameters['anchor'] !=
|
||||||
null // source_id
|
null // source_id
|
||||||
? int.tryParse(queryParameters['anchor']!)
|
? int.tryParse(queryParameters['anchor']!)
|
||||||
@@ -255,34 +255,39 @@ class PiliScheme {
|
|||||||
// int subType = int.parse(queryParameters['subType'] ?? '0');
|
// int subType = int.parse(queryParameters['subType'] ?? '0');
|
||||||
// int extraIntentId =
|
// int extraIntentId =
|
||||||
// int.parse(queryParameters['extraIntentId'] ?? '0');
|
// int.parse(queryParameters['extraIntentId'] ?? '0');
|
||||||
|
final enterUri = queryParameters['enterUri'];
|
||||||
Get.to(
|
Get.to(
|
||||||
arguments: {
|
arguments: {
|
||||||
'oid': oid,
|
'oid': oid,
|
||||||
'rpid': rootId,
|
'rpid': rootId,
|
||||||
'id': rpId,
|
'id': rpId,
|
||||||
'type': type,
|
'type': type,
|
||||||
'enterUri': queryParameters['enterUri'],
|
'enterUri': enterUri,
|
||||||
},
|
},
|
||||||
() => Scaffold(
|
() => Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('评论详情'),
|
title: const Text('评论详情'),
|
||||||
actions: [
|
actions:
|
||||||
IconButton(
|
enterUri != null || const [11, 16, 17].contains(type)
|
||||||
tooltip: '前往',
|
? [
|
||||||
onPressed: () {
|
IconButton(
|
||||||
String? enterUri = queryParameters['enterUri'];
|
tooltip: '前往',
|
||||||
if (enterUri != null) {
|
onPressed: () {
|
||||||
routePush(Uri.parse(enterUri));
|
if (enterUri != null) {
|
||||||
} else {
|
routePush(Uri.parse(enterUri));
|
||||||
routePush(
|
} else {
|
||||||
Uri.parse('bilibili://following/detail/$oid'),
|
routePush(
|
||||||
);
|
Uri.parse(
|
||||||
}
|
'bilibili://following/detail/$oid',
|
||||||
},
|
),
|
||||||
icon: const Icon(Icons.open_in_new),
|
);
|
||||||
),
|
}
|
||||||
],
|
},
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
body: ViewSafeArea(
|
body: ViewSafeArea(
|
||||||
child: VideoReplyReplyPanel(
|
child: VideoReplyReplyPanel(
|
||||||
@@ -729,9 +734,12 @@ class PiliScheme {
|
|||||||
case 'bangumi':
|
case 'bangumi':
|
||||||
// www.bilibili.com/bangumi/play/ep{eid}?start_progress={offset}&thumb_up_dm_id={dmid}
|
// www.bilibili.com/bangumi/play/ep{eid}?start_progress={offset}&thumb_up_dm_id={dmid}
|
||||||
// if (kDebugMode) debugPrint('番剧');
|
// if (kDebugMode) debugPrint('番剧');
|
||||||
|
final queryParameters = uri.queryParameters;
|
||||||
bool hasMatch = PageUtils.viewPgcFromUri(
|
bool hasMatch = PageUtils.viewPgcFromUri(
|
||||||
path,
|
path,
|
||||||
progress: uri.queryParameters['start_progress'],
|
progress:
|
||||||
|
queryParameters['start_progress'] ??
|
||||||
|
queryParameters['dm_progress'],
|
||||||
);
|
);
|
||||||
if (hasMatch) {
|
if (hasMatch) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -2,17 +2,12 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
class CacheManage {
|
abstract class CacheManage {
|
||||||
CacheManage._internal();
|
|
||||||
|
|
||||||
static final CacheManage cacheManage = CacheManage._internal();
|
|
||||||
|
|
||||||
factory CacheManage() => cacheManage;
|
|
||||||
|
|
||||||
// 获取缓存目录
|
// 获取缓存目录
|
||||||
Future<double> loadApplicationCache() async {
|
static Future<double> loadApplicationCache() async {
|
||||||
/// clear all of image in memory
|
/// clear all of image in memory
|
||||||
// clearMemoryImageCache();
|
// clearMemoryImageCache();
|
||||||
/// get ImageCache
|
/// get ImageCache
|
||||||
@@ -47,7 +42,9 @@ class CacheManage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 循环计算文件的大小(递归)
|
// 循环计算文件的大小(递归)
|
||||||
Future<double> getTotalSizeOfFilesInDir(final FileSystemEntity file) async {
|
static 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 double.parse(length.toString());
|
||||||
@@ -76,7 +73,7 @@ class CacheManage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 清除 Documents 目录下的 DioCache.db
|
/// 清除 Documents 目录下的 DioCache.db
|
||||||
Future<void> clearApplicationCache() async {
|
static Future<void> clearApplicationCache() async {
|
||||||
Directory directory = await getApplicationDocumentsDirectory();
|
Directory directory = await getApplicationDocumentsDirectory();
|
||||||
if (directory.existsSync()) {
|
if (directory.existsSync()) {
|
||||||
String dioCacheFileName =
|
String dioCacheFileName =
|
||||||
@@ -103,7 +100,7 @@ class CacheManage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 递归方式删除目录及文件
|
/// 递归方式删除目录及文件
|
||||||
Future<void> deleteDirectory(FileSystemEntity file) async {
|
static Future<void> deleteDirectory(FileSystemEntity file) async {
|
||||||
if (file is Directory) {
|
if (file is Directory) {
|
||||||
final List<FileSystemEntity> children = file.listSync();
|
final List<FileSystemEntity> children = file.listSync();
|
||||||
for (final FileSystemEntity child in children) {
|
for (final FileSystemEntity child in children) {
|
||||||
@@ -112,4 +109,18 @@ class CacheManage {
|
|||||||
}
|
}
|
||||||
await file.delete();
|
await file.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> autoClearCache() async {
|
||||||
|
if (Pref.autoClearCache) {
|
||||||
|
await CacheManage.clearLibraryCache();
|
||||||
|
} else {
|
||||||
|
final maxCacheSize = Pref.maxCacheSize;
|
||||||
|
if (maxCacheSize != 0) {
|
||||||
|
final currCache = await loadApplicationCache();
|
||||||
|
if (currCache >= maxCacheSize) {
|
||||||
|
await CacheManage.clearLibraryCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ extension ContextExtensions on BuildContext {
|
|||||||
double get devicePixelRatio => MediaQuery.devicePixelRatioOf(this);
|
double get devicePixelRatio => MediaQuery.devicePixelRatioOf(this);
|
||||||
|
|
||||||
/// similar to [MediaQuery.of(this).textScaleFactor]
|
/// similar to [MediaQuery.of(this).textScaleFactor]
|
||||||
double get textScaleFactor => MediaQuery.textScaleFactorOf(this);
|
TextScaler get textScaler => MediaQuery.textScalerOf(this);
|
||||||
|
|
||||||
/// get the shortestSide from screen
|
/// get the shortestSide from screen
|
||||||
double get mediaQueryShortestSide => mediaQuerySize.shortestSide;
|
double get mediaQueryShortestSide => mediaQuerySize.shortestSide;
|
||||||
|
|||||||
@@ -223,3 +223,8 @@ extension FileExt on File {
|
|||||||
extension SizeExt on Size {
|
extension SizeExt on Size {
|
||||||
bool get isPortrait => width < 600 || height >= width;
|
bool get isPortrait => width < 600 || height >= width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension GetExt on GetInterface {
|
||||||
|
S putOrFind<S>(InstanceBuilderCallback<S> dep, {String? tag}) =>
|
||||||
|
GetInstance().putOrFind(dep, tag: tag);
|
||||||
|
}
|
||||||
|
|||||||
@@ -292,14 +292,14 @@ class PageUtils {
|
|||||||
|
|
||||||
static Future<void> pushDynFromId({id, rid, bool off = false}) async {
|
static Future<void> pushDynFromId({id, rid, bool off = false}) async {
|
||||||
SmartDialog.showLoading();
|
SmartDialog.showLoading();
|
||||||
var res = await DynamicsHttp.dynamicDetail(
|
final res = await DynamicsHttp.dynamicDetail(
|
||||||
id: id,
|
id: id,
|
||||||
rid: rid,
|
rid: rid,
|
||||||
type: rid != null ? 2 : null,
|
type: rid != null ? 2 : null,
|
||||||
);
|
);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (res['status']) {
|
if (res.isSuccess) {
|
||||||
DynamicItemModel data = res['data'];
|
final data = res.data;
|
||||||
if (data.basic?.commentType == 12) {
|
if (data.basic?.commentType == 12) {
|
||||||
toDupNamed(
|
toDupNamed(
|
||||||
'/articlePage',
|
'/articlePage',
|
||||||
@@ -313,13 +313,13 @@ class PageUtils {
|
|||||||
toDupNamed(
|
toDupNamed(
|
||||||
'/dynamicDetail',
|
'/dynamicDetail',
|
||||||
arguments: {
|
arguments: {
|
||||||
'item': res['data'],
|
'item': data,
|
||||||
},
|
},
|
||||||
off: off,
|
off: off,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(res['msg']);
|
res.toast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
|
||||||
import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart';
|
import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart';
|
||||||
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
||||||
show ReplyInfo;
|
show ReplyInfo;
|
||||||
@@ -288,17 +287,17 @@ class RequestUtils {
|
|||||||
if (id != null) {
|
if (id != null) {
|
||||||
await Future.delayed(const Duration(milliseconds: 200));
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
var res = await DynamicsHttp.dynamicDetail(id: id);
|
var res = await DynamicsHttp.dynamicDetail(id: id);
|
||||||
if (res['status']) {
|
if (res.isSuccess) {
|
||||||
final ctr = Get.find<DynamicsTabController>(tag: 'all');
|
final ctr = Get.find<DynamicsTabController>(tag: 'all');
|
||||||
if (ctr.loadingState.value.isSuccess) {
|
if (ctr.loadingState.value.isSuccess) {
|
||||||
List<DynamicItemModel>? list = ctr.loadingState.value.data;
|
List<DynamicItemModel>? list = ctr.loadingState.value.data;
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
list.insert(0, res['data']);
|
list.insert(0, res.data);
|
||||||
ctr.loadingState.refresh();
|
ctr.loadingState.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctr.loadingState.value = Success([res['data']]);
|
ctr.loadingState.value = Success([res.data]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -318,7 +317,7 @@ class RequestUtils {
|
|||||||
await Future.delayed(const Duration(seconds: 5));
|
await Future.delayed(const Duration(seconds: 5));
|
||||||
}
|
}
|
||||||
var res = await DynamicsHttp.dynamicDetail(id: id, clearCookie: true);
|
var res = await DynamicsHttp.dynamicDetail(id: id, clearCookie: true);
|
||||||
bool isBan = !res['status'];
|
bool isBan = !res.isSuccess;
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title: const Text('动态检查结果'),
|
title: const Text('动态检查结果'),
|
||||||
@@ -400,23 +399,20 @@ class RequestUtils {
|
|||||||
title: Text('${isCopy ? '复制' : '移动'}到'),
|
title: Text('${isCopy ? '复制' : '移动'}到'),
|
||||||
contentPadding: const EdgeInsets.only(top: 5),
|
contentPadding: const EdgeInsets.only(top: 5),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Builder(
|
child: RadioGroup(
|
||||||
builder: (context) => Column(
|
onChanged: (value) {
|
||||||
children: List.generate(list.length, (index) {
|
checkedId = value;
|
||||||
final item = list[index];
|
(context as Element).markNeedsBuild();
|
||||||
return RadioWidget<int>(
|
},
|
||||||
padding: const EdgeInsets.only(left: 14),
|
groupValue: checkedId,
|
||||||
title: item.title,
|
child: Column(
|
||||||
groupValue: checkedId,
|
children: list.map((item) {
|
||||||
|
return RadioListTile<int>(
|
||||||
|
dense: true,
|
||||||
|
title: Text(item.title),
|
||||||
value: item.id,
|
value: item.id,
|
||||||
onChanged: (value) {
|
|
||||||
checkedId = value;
|
|
||||||
if (context.mounted) {
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}),
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import 'package:PiliPlus/utils/set_int_adapter.dart';
|
|||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
class GStorage {
|
abstract class GStorage {
|
||||||
static late final Box<UserInfoData> userInfo;
|
static late final Box<UserInfoData> userInfo;
|
||||||
static late final Box<dynamic> historyWord;
|
static late final Box<dynamic> historyWord;
|
||||||
static late final Box<dynamic> localCache;
|
static late final Box<dynamic> localCache;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import 'package:get/get.dart' hide ContextExtensionss;
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class Pref {
|
abstract class Pref {
|
||||||
static final Box _setting = GStorage.setting;
|
static final Box _setting = GStorage.setting;
|
||||||
static final Box _video = GStorage.video;
|
static final Box _video = GStorage.video;
|
||||||
static final Box _localCache = GStorage.localCache;
|
static final Box _localCache = GStorage.localCache;
|
||||||
|
|||||||
Reference in New Issue
Block a user