mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-19 00:40:08 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81713a6bc4 | ||
|
|
959bcfaa30 | ||
|
|
fa465f792d | ||
|
|
74bf78b9cd | ||
|
|
8c408e59f6 | ||
|
|
25d27e42ed | ||
|
|
0f2b0cc5f2 | ||
|
|
00ea34f45d | ||
|
|
ec936c1821 | ||
|
|
2ff84857e7 | ||
|
|
84ed34f3a7 | ||
|
|
f0508e1bc2 | ||
|
|
8ea7bf36d7 | ||
|
|
8819461eed | ||
|
|
7c30668c87 |
@@ -275,145 +275,144 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
return ColoredBox(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: SizedBox(
|
||||
height: Utils.getSheetHeight(context),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: widget.showTitle != false ? 14 : 6),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.showTitle != false)
|
||||
Text(
|
||||
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: _favStream?.stream,
|
||||
builder: (context, snapshot) => snapshot.hasData
|
||||
? mediumButton(
|
||||
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
|
||||
icon: _seasonFav == 1
|
||||
? Icons.notifications_off_outlined
|
||||
: Icons.notifications_active_outlined,
|
||||
onPressed: () async {
|
||||
dynamic result = await VideoHttp.seasonFav(
|
||||
isFav: _seasonFav == 1,
|
||||
seasonId: widget.season.id,
|
||||
);
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast(
|
||||
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
|
||||
_seasonFav = _seasonFav == 1 ? 0 : 1;
|
||||
_favStream?.add(_seasonFav);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: widget.showTitle != false ? 14 : 6),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.showTitle != false)
|
||||
Text(
|
||||
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至顶部',
|
||||
icon: Icons.vertical_align_top,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? 0
|
||||
: _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至底部',
|
||||
icon: Icons.vertical_align_bottom,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至当前',
|
||||
icon: Icons.my_location,
|
||||
onPressed: () async {
|
||||
if (_ctr != null && _ctr?.index != (_index)) {
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
if (widget.isSupportReverse == true)
|
||||
if (!_isList)
|
||||
_reverseButton
|
||||
else
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.data == _index
|
||||
? _reverseButton
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) => mediumButton(
|
||||
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
|
||||
icon: !reverse[snapshot.data]
|
||||
? MdiIcons.sortNumericAscending
|
||||
: MdiIcons.sortNumericDescending,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
reverse[_ctr?.index ?? 0] =
|
||||
!reverse[_ctr?.index ?? 0];
|
||||
});
|
||||
StreamBuilder(
|
||||
stream: _favStream?.stream,
|
||||
builder: (context, snapshot) => snapshot.hasData
|
||||
? mediumButton(
|
||||
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
|
||||
icon: _seasonFav == 1
|
||||
? Icons.notifications_off_outlined
|
||||
: Icons.notifications_active_outlined,
|
||||
onPressed: () async {
|
||||
dynamic result = await VideoHttp.seasonFav(
|
||||
isFav: _seasonFav == 1,
|
||||
seasonId: widget.season.id,
|
||||
);
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast(
|
||||
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
|
||||
_seasonFav = _seasonFav == 1 ? 0 : 1;
|
||||
_favStream?.add(_seasonFav);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至顶部',
|
||||
icon: Icons.vertical_align_top,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? 0
|
||||
: _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至底部',
|
||||
icon: Icons.vertical_align_bottom,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至当前',
|
||||
icon: Icons.my_location,
|
||||
onPressed: () async {
|
||||
if (_ctr != null && _ctr?.index != (_index)) {
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
if (widget.isSupportReverse == true)
|
||||
if (!_isList)
|
||||
_reverseButton
|
||||
else
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.data == _index
|
||||
? _reverseButton
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) => mediumButton(
|
||||
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
|
||||
icon: !reverse[snapshot.data]
|
||||
? MdiIcons.sortNumericAscending
|
||||
: MdiIcons.sortNumericDescending,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
|
||||
});
|
||||
},
|
||||
),
|
||||
if (widget.onClose != null)
|
||||
mediumButton(
|
||||
tooltip: '关闭',
|
||||
icon: Icons.close,
|
||||
onPressed: widget.onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.onClose != null)
|
||||
mediumButton(
|
||||
tooltip: '关闭',
|
||||
icon: Icons.close,
|
||||
onPressed: widget.onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
if (_isList)
|
||||
TabBar(
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
if (_isList)
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: TabBar(
|
||||
controller: _ctr,
|
||||
padding: const EdgeInsets.only(right: 60),
|
||||
isScrollable: true,
|
||||
@@ -423,20 +422,26 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
dividerHeight: 1,
|
||||
dividerColor: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
Expanded(
|
||||
child: _isList
|
||||
? TabBarView(
|
||||
),
|
||||
Expanded(
|
||||
child: _isList
|
||||
? Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: TabBarView(
|
||||
controller: _ctr,
|
||||
children: List.generate(
|
||||
widget.season.sections.length,
|
||||
(index) => _buildBody(
|
||||
index, widget.season.sections[index].episodes),
|
||||
),
|
||||
)
|
||||
: _buildBody(null, episodes),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: _buildBody(null, episodes),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,13 @@ class _SelfSizedHorizontalListState extends State<SelfSizedHorizontalList> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((v) => setState(() {}));
|
||||
}
|
||||
if (widget.itemCount == 0) return const SizedBox();
|
||||
if (isInit) return Container(key: infoKey, child: widget.childBuilder(0));
|
||||
if (isInit) {
|
||||
return Container(
|
||||
key: infoKey,
|
||||
padding: widget.padding,
|
||||
child: widget.childBuilder(0),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: height,
|
||||
|
||||
@@ -718,4 +718,8 @@ class Api {
|
||||
/// 我的关注 - 正在直播
|
||||
static const String getFollowingLive =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
|
||||
|
||||
static const String pgcIndexCondition = '/pgc/season/index/condition';
|
||||
|
||||
static const String pgcIndexResult = '/pgc/season/index/result';
|
||||
}
|
||||
|
||||
@@ -1,9 +1,55 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/bangumi/list.dart';
|
||||
import '../models/bangumi/pgc_index/condition.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class BangumiHttp {
|
||||
static Future<LoadingState> pgcIndexResult({
|
||||
required int page,
|
||||
required Map<String, dynamic> params,
|
||||
seasonType,
|
||||
type,
|
||||
indexType,
|
||||
}) async {
|
||||
dynamic res = await Request().get(
|
||||
Api.pgcIndexResult,
|
||||
queryParameters: {
|
||||
...params,
|
||||
if (seasonType != null) 'season_type': seasonType,
|
||||
if (type != null) 'type': type,
|
||||
if (indexType != null) 'index_type': indexType,
|
||||
'page': page,
|
||||
'pagesize': 21,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> pgcIndexCondition({
|
||||
seasonType,
|
||||
type,
|
||||
indexType,
|
||||
}) async {
|
||||
dynamic res = await Request().get(
|
||||
Api.pgcIndexCondition,
|
||||
queryParameters: {
|
||||
if (seasonType != null) 'season_type': seasonType,
|
||||
if (type != null) 'type': type,
|
||||
if (indexType != null) 'index_type': indexType,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(Condition.fromJson(res.data['data']));
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> bangumiList({
|
||||
int? page,
|
||||
int? indexType,
|
||||
|
||||
70
lib/models/bangumi/pgc_index/condition.dart
Normal file
70
lib/models/bangumi/pgc_index/condition.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
class Condition {
|
||||
List<Filter>? filter;
|
||||
List<Order>? order;
|
||||
|
||||
Condition({
|
||||
this.filter,
|
||||
this.order,
|
||||
});
|
||||
|
||||
Condition.fromJson(Map json) {
|
||||
filter = (json['filter'] as List?)
|
||||
?.map((item) => Filter.fromJson(item))
|
||||
.toList();
|
||||
order =
|
||||
(json['order'] as List?)?.map((item) => Order.fromJson(item)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class Order {
|
||||
String? field;
|
||||
String? name;
|
||||
String? sort;
|
||||
|
||||
Order({
|
||||
this.field,
|
||||
this.name,
|
||||
this.sort,
|
||||
});
|
||||
|
||||
Order.fromJson(Map json) {
|
||||
field = json['field'];
|
||||
name = json['name'];
|
||||
sort = json['sort'];
|
||||
}
|
||||
}
|
||||
|
||||
class Filter {
|
||||
String? field;
|
||||
String? name;
|
||||
List<Values>? values;
|
||||
|
||||
Filter({
|
||||
this.field,
|
||||
this.name,
|
||||
this.values,
|
||||
});
|
||||
|
||||
Filter.fromJson(Map json) {
|
||||
field = json['field'];
|
||||
name = json['name'];
|
||||
values = (json['values'] as List?)
|
||||
?.map((item) => Values.fromJson(item))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class Values {
|
||||
String? keyword;
|
||||
String? name;
|
||||
|
||||
Values({
|
||||
this.keyword,
|
||||
this.name,
|
||||
});
|
||||
|
||||
Values.fromJson(Map json) {
|
||||
keyword = json['keyword'];
|
||||
name = json['name'];
|
||||
}
|
||||
}
|
||||
69
lib/pages/bangumi/pgc_index/pgc_index_controller.dart
Normal file
69
lib/pages/bangumi/pgc_index/pgc_index_controller.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'package:PiliPlus/http/bangumi.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/bangumi/pgc_index/condition.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:get/get.dart' hide Condition;
|
||||
|
||||
class PgcIndexController extends CommonController {
|
||||
PgcIndexController(this.indexType);
|
||||
int? indexType;
|
||||
Rx<LoadingState> conditionState = LoadingState.loading().obs;
|
||||
|
||||
late final RxBool isExpand = false.obs;
|
||||
|
||||
RxMap<String, dynamic> indexParams = <String, dynamic>{}.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
getPgcIndexCondition();
|
||||
}
|
||||
|
||||
Future getPgcIndexCondition() async {
|
||||
dynamic res = await BangumiHttp.pgcIndexCondition(
|
||||
seasonType: indexType == null ? 1 : null,
|
||||
type: 0,
|
||||
indexType: indexType,
|
||||
);
|
||||
if (res is Success) {
|
||||
Condition data = res.response;
|
||||
if (data.order?.isNotEmpty == true) {
|
||||
indexParams['order'] = data.order!.first.field;
|
||||
}
|
||||
if (data.filter?.isNotEmpty == true) {
|
||||
for (Filter item in data.filter!) {
|
||||
indexParams['${item.field}'] = item.values?.firstOrNull?.keyword;
|
||||
}
|
||||
}
|
||||
queryData();
|
||||
}
|
||||
conditionState.value = res;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() => BangumiHttp.pgcIndexResult(
|
||||
page: currentPage,
|
||||
params: indexParams,
|
||||
seasonType: indexType == null ? 1 : null,
|
||||
type: 0,
|
||||
indexType: indexType,
|
||||
);
|
||||
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
if (response.response['has_next'] == null ||
|
||||
response.response['has_next'] == 0) {
|
||||
isEnd = true;
|
||||
}
|
||||
if (response.response['list'] == null ||
|
||||
(response.response['list'] as List?)?.isEmpty == true) {
|
||||
isEnd = true;
|
||||
}
|
||||
if (currentPage != 1 && loadingState.value is Success) {
|
||||
response.response['list']
|
||||
?.insertAll(0, (loadingState.value as Success).response);
|
||||
}
|
||||
loadingState.value = LoadingState.success(response.response['list']);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
242
lib/pages/bangumi/pgc_index/pgc_index_page.dart
Normal file
242
lib/pages/bangumi/pgc_index/pgc_index_page.dart
Normal file
@@ -0,0 +1,242 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_controller.dart';
|
||||
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_pgc_index.dart';
|
||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart' hide Condition;
|
||||
|
||||
import '../../../models/bangumi/pgc_index/condition.dart';
|
||||
|
||||
class PgcIndexPage extends StatefulWidget {
|
||||
const PgcIndexPage({super.key, this.indexType});
|
||||
|
||||
final int? indexType;
|
||||
|
||||
@override
|
||||
State<PgcIndexPage> createState() => _PgcIndexPageState();
|
||||
}
|
||||
|
||||
class _PgcIndexPageState extends State<PgcIndexPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late final _ctr = Get.put(
|
||||
PgcIndexController(widget.indexType),
|
||||
tag: '${widget.indexType}',
|
||||
);
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => widget.indexType != null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return widget.indexType == null
|
||||
? Scaffold(
|
||||
appBar: AppBar(title: const Text('索引')),
|
||||
body: Obx(() => _buildBody(_ctr.conditionState.value)),
|
||||
)
|
||||
: Obx(() => _buildBody(_ctr.conditionState.value));
|
||||
}
|
||||
|
||||
Widget _buildBody(LoadingState loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => loadingWidget,
|
||||
Success() => Builder(builder: (context) {
|
||||
Condition data = loadingState.response;
|
||||
int count = (data.order?.isNotEmpty == true ? 1 : 0) +
|
||||
(data.filter?.length ?? 0);
|
||||
if (count == 0) return const SizedBox.shrink();
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
if (widget.indexType != null)
|
||||
SliverToBoxAdapter(child: const SizedBox(height: 12)),
|
||||
SliverToBoxAdapter(
|
||||
child: AnimatedSize(
|
||||
curve: Curves.easeInOut,
|
||||
alignment: Alignment.topCenter,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: count > 5
|
||||
? Obx(() => _buildSortWidget(count, data))
|
||||
: _buildSortWidget(count, data),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
top: 12,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: Obx(() => _buildList(_ctr.loadingState.value)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Error() => scrollErrorWidget(
|
||||
errMsg: loadingState.errMsg,
|
||||
callback: () {
|
||||
_ctr.conditionState.value = LoadingState.loading();
|
||||
_ctr.getPgcIndexCondition();
|
||||
},
|
||||
),
|
||||
LoadingState() => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
|
||||
Widget _buildSortWidget(count, data) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...List.generate(
|
||||
count > 5
|
||||
? _ctr.isExpand.value
|
||||
? count
|
||||
: count ~/ 2
|
||||
: count,
|
||||
(index) {
|
||||
List? item = data.order?.isNotEmpty == true
|
||||
? index == 0
|
||||
? data.order
|
||||
: data.filter![index - 1].values
|
||||
: data.filter![index].values;
|
||||
return item?.isNotEmpty == true
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: index == 0 ? 0 : 10,
|
||||
),
|
||||
child: SelfSizedHorizontalList(
|
||||
gapSize: 12,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
childBuilder: (childIndex) => Obx(
|
||||
() => SearchText(
|
||||
bgColor: (item[childIndex] is Order
|
||||
? _ctr.indexParams['order']
|
||||
: _ctr.indexParams[data
|
||||
.filter![
|
||||
data.order?.isNotEmpty == true
|
||||
? index - 1
|
||||
: index]
|
||||
.field]) ==
|
||||
(item[childIndex] is Order
|
||||
? item[childIndex].field
|
||||
: item[childIndex].keyword)
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: (item[childIndex] is Order
|
||||
? _ctr.indexParams['order']
|
||||
: _ctr.indexParams[data
|
||||
.filter![
|
||||
data.order?.isNotEmpty == true
|
||||
? index - 1
|
||||
: index]
|
||||
.field]) ==
|
||||
(item[childIndex] is Order
|
||||
? item[childIndex].field
|
||||
: item[childIndex].keyword)
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
text: item[childIndex].name,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 3,
|
||||
),
|
||||
onTap: (_) {
|
||||
String name = item[childIndex] is Order
|
||||
? 'order'
|
||||
: data
|
||||
.filter![data.order?.isNotEmpty == true
|
||||
? index - 1
|
||||
: index]
|
||||
.field!;
|
||||
_ctr.indexParams[name] =
|
||||
(item[childIndex] is Order
|
||||
? item[childIndex].field
|
||||
: item[childIndex].keyword);
|
||||
_ctr.onReload();
|
||||
},
|
||||
),
|
||||
),
|
||||
itemCount: item!.length,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
if (count > 5) ...[
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
_ctr.isExpand.value = _ctr.isExpand.value.not;
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
_ctr.isExpand.value ? '收起' : '展开',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
_ctr.isExpand.value
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildList(LoadingState loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => HttpError(errMsg: '加载中'),
|
||||
Success() => (loadingState.response as List?)?.isNotEmpty == true
|
||||
? SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
maxCrossAxisExtent: Grid.smallCardWidth / 3 * 2,
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisExtent: MediaQuery.textScalerOf(context).scale(50),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
if (index == loadingState.response.length - 1) {
|
||||
_ctr.onLoadMore();
|
||||
}
|
||||
return BangumiCardVPgcIndex(
|
||||
bangumiItem: loadingState.response[index]);
|
||||
},
|
||||
childCount: loadingState.response.length,
|
||||
),
|
||||
)
|
||||
: HttpError(callback: _ctr.onReload),
|
||||
Error() => HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
callback: _ctr.onReload,
|
||||
),
|
||||
LoadingState() => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/tab_type.dart';
|
||||
import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -128,7 +129,12 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 10, left: 16),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 16,
|
||||
right: 10,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -136,6 +142,56 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
'推荐',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.tabType == TabType.bangumi) {
|
||||
Get.to(PgcIndexPage());
|
||||
} else {
|
||||
List titles = const ['全部', '电影', '电视剧', '纪录片', '综艺'];
|
||||
List types = const [102, 2, 5, 3, 7];
|
||||
Get.to(
|
||||
Scaffold(
|
||||
appBar: AppBar(title: const Text('索引')),
|
||||
body: DefaultTabController(
|
||||
length: types.length,
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
tabs: titles
|
||||
.map((title) => Tab(text: title))
|
||||
.toList()),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: types
|
||||
.map((type) =>
|
||||
PgcIndexPage(indexType: type))
|
||||
.toList()),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'查看更多',
|
||||
strutStyle: StrutStyle(leading: 0, height: 1),
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
127
lib/pages/bangumi/widgets/bangumi_card_v_pgc_index.dart
Normal file
127
lib/pages/bangumi/widgets/bangumi_card_v_pgc_index.dart
Normal file
@@ -0,0 +1,127 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
|
||||
// 视频卡片 - 垂直布局
|
||||
class BangumiCardVPgcIndex extends StatelessWidget {
|
||||
const BangumiCardVPgcIndex({
|
||||
super.key,
|
||||
required this.bangumiItem,
|
||||
});
|
||||
|
||||
final dynamic bangumiItem;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: bangumiItem['title'],
|
||||
cover: bangumiItem['cover'],
|
||||
),
|
||||
onTap: () {
|
||||
Utils.viewBangumi(seasonId: bangumiItem['season_id']);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: StyleString.mdRadius,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 0.75,
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: bangumiItem['cover'],
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (bangumiItem['badge'] != null &&
|
||||
bangumiItem['badge'] != '')
|
||||
PBadge(
|
||||
text: bangumiItem['badge'],
|
||||
top: 6,
|
||||
right: 6,
|
||||
bottom: null,
|
||||
left: null,
|
||||
),
|
||||
if (bangumiItem['order'] != null &&
|
||||
bangumiItem['order'] != '')
|
||||
PBadge(
|
||||
text: bangumiItem['order'],
|
||||
top: null,
|
||||
right: null,
|
||||
bottom: 6,
|
||||
left: 6,
|
||||
type: 'gray',
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
bagumiContent(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget bagumiContent(context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
// 多列
|
||||
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
|
||||
// 单列
|
||||
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
bangumiItem['title'],
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
if (bangumiItem['index_show'] != null)
|
||||
Text(
|
||||
bangumiItem['index_show'],
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
// if (bangumiItem.progress != null)
|
||||
// Text(
|
||||
// bangumiItem.progress,
|
||||
// maxLines: 1,
|
||||
// style: TextStyle(
|
||||
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
// color: Theme.of(context).colorScheme.outline,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ class DynamicDetailController extends ReplyController {
|
||||
item = Get.arguments['item'];
|
||||
floor = Get.arguments['floor'];
|
||||
if (floor == 1) {
|
||||
count.value = int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
|
||||
count.value = int.parse(item.modules!.moduleStat!.comment!.count ?? '0');
|
||||
}
|
||||
|
||||
if (oid != 0) {
|
||||
|
||||
@@ -4,8 +4,10 @@ import 'dart:math';
|
||||
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/reply_sort_type.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
@@ -15,6 +17,7 @@ import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_reply.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
@@ -25,6 +28,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply_reply/index.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../../utils/grid.dart';
|
||||
import '../widgets/dynamic_panel.dart';
|
||||
@@ -354,7 +358,10 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(left: padding / 4),
|
||||
padding: EdgeInsets.only(
|
||||
left: padding / 4,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: DynamicPanel(
|
||||
item: _dynamicDetailController.item,
|
||||
@@ -402,30 +409,204 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
),
|
||||
if (_fabAnimationCtr != null)
|
||||
Positioned(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 14,
|
||||
right: 14,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 2),
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0),
|
||||
).animate(CurvedAnimation(
|
||||
parent: _fabAnimationCtr!,
|
||||
curve: Curves.easeInOut,
|
||||
)),
|
||||
child: FloatingActionButton(
|
||||
heroTag: null,
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
dynamic oid = _dynamicDetailController.oid ??
|
||||
IdUtils.bv2av(Get.parameters['bvid']!);
|
||||
_dynamicDetailController.onReply(
|
||||
context,
|
||||
oid: oid,
|
||||
replyType: ReplyType.values[replyType],
|
||||
);
|
||||
},
|
||||
tooltip: '评论动态',
|
||||
child: const Icon(Icons.reply),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 14, bottom: 14),
|
||||
child: FloatingActionButton(
|
||||
heroTag: null,
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
dynamic oid = _dynamicDetailController.oid ??
|
||||
IdUtils.bv2av(Get.parameters['bvid']!);
|
||||
_dynamicDetailController.onReply(
|
||||
context,
|
||||
oid: oid,
|
||||
replyType: ReplyType.values[replyType],
|
||||
);
|
||||
},
|
||||
tooltip: '评论动态',
|
||||
child: const Icon(Icons.reply),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withOpacity(0.08),
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.paddingOf(context).bottom),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (btnContext) => TextButton.icon(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
builder: (context) => RepostPanel(
|
||||
item: _dynamicDetailController.item,
|
||||
callback: () {
|
||||
int count = int.tryParse(
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
.forward
|
||||
?.count ??
|
||||
'0') ??
|
||||
0;
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
.forward!
|
||||
.count = (count + 1).toString();
|
||||
if (btnContext.mounted) {
|
||||
(btnContext as Element?)
|
||||
?.markNeedsBuild();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.shareFromSquare,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
semanticLabel: "转发",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
label: Text(
|
||||
_dynamicDetailController.item.modules
|
||||
.moduleStat.forward!.count !=
|
||||
null
|
||||
? Utils.numFormat(_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
.forward!
|
||||
.count)
|
||||
: '转发',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
Share.share(
|
||||
'${HttpString.dynamicShareBaseUrl}/${_dynamicDetailController.item.idStr}');
|
||||
},
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.shareNodes,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
semanticLabel: "分享",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
label: const Text('分享'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (context) => TextButton.icon(
|
||||
onPressed: () => Utils.onLikeDynamic(
|
||||
_dynamicDetailController.item,
|
||||
() {
|
||||
if (context.mounted) {
|
||||
(context as Element?)?.markNeedsBuild();
|
||||
}
|
||||
},
|
||||
),
|
||||
icon: Icon(
|
||||
_dynamicDetailController
|
||||
.item.modules.moduleStat.like!.status!
|
||||
? FontAwesomeIcons.solidThumbsUp
|
||||
: FontAwesomeIcons.thumbsUp,
|
||||
size: 16,
|
||||
color: _dynamicDetailController
|
||||
.item.modules.moduleStat.like!.status!
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
semanticLabel: _dynamicDetailController
|
||||
.item.modules.moduleStat.like!.status!
|
||||
? "已赞"
|
||||
: "点赞",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
label: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
transitionBuilder: (Widget child,
|
||||
Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
_dynamicDetailController.item.modules
|
||||
.moduleStat.like!.count !=
|
||||
null
|
||||
? Utils.numFormat(
|
||||
_dynamicDetailController.item
|
||||
.modules.moduleStat.like!.count)
|
||||
: '点赞',
|
||||
style: TextStyle(
|
||||
color: _dynamicDetailController.item
|
||||
.modules.moduleStat.like!.status!
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:PiliPlus/http/user.dart';
|
||||
import 'package:PiliPlus/models/user/fav_detail.dart';
|
||||
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -35,23 +36,24 @@ class FavDetailController extends MultiSelectController {
|
||||
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
FavDetailData data = response.response;
|
||||
if (currentPage == 1) {
|
||||
item.value = response.response.info;
|
||||
isOwner.value = response.response.info.mid == mid;
|
||||
item.value = data.info ?? FavFolderItemData();
|
||||
isOwner.value = data.info?.mid == mid;
|
||||
}
|
||||
if (response.response.medias.isEmpty) {
|
||||
if (data.medias.isNullOrEmpty) {
|
||||
isEnd = true;
|
||||
}
|
||||
if (currentPage != 1 && loadingState.value is Success) {
|
||||
response.response.medias?.insertAll(
|
||||
data.medias?.insertAll(
|
||||
0,
|
||||
List<FavDetailItemData>.from((loadingState.value as Success).response),
|
||||
);
|
||||
}
|
||||
if (response.response.medias.length >= response.response.info.mediaCount) {
|
||||
if ((data.medias?.length ?? 0) >= (data.info?.mediaCount ?? 0)) {
|
||||
isEnd = true;
|
||||
}
|
||||
loadingState.value = LoadingState.success(response.response.medias);
|
||||
loadingState.value = LoadingState.success(data.medias);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/dynamics.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/url_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/http/html.dart';
|
||||
import 'package:PiliPlus/http/reply.dart';
|
||||
@@ -16,6 +20,8 @@ class HtmlRenderController extends ReplyController {
|
||||
late Map response;
|
||||
int? floor;
|
||||
|
||||
Rx<DynamicItemModel> item = DynamicItemModel().obs;
|
||||
|
||||
RxBool loaded = false.obs;
|
||||
|
||||
late final horizontalPreview = GStorage.horizontalPreview;
|
||||
@@ -26,10 +32,29 @@ class HtmlRenderController extends ReplyController {
|
||||
id = Get.parameters['id']!;
|
||||
dynamicType = Get.parameters['dynamicType']!;
|
||||
type = dynamicType == 'picture' ? 11 : 12;
|
||||
|
||||
if (RegExp(r'^cv', caseSensitive: false).hasMatch(id)) {
|
||||
UrlUtils.parseRedirectUrl('https://www.bilibili.com/read/$id/')
|
||||
.then((url) {
|
||||
if (url != null) {
|
||||
_queryDyn(url.split('/').last);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_queryDyn(id);
|
||||
}
|
||||
reqHtml();
|
||||
}
|
||||
|
||||
_queryDyn(id) {
|
||||
DynamicsHttp.dynamicDetail(id: id).then((res) {
|
||||
if (res['status']) {
|
||||
item.value = res['data'];
|
||||
} else {
|
||||
debugPrint('${res['msg']}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 请求动态内容
|
||||
Future reqHtml() async {
|
||||
late dynamic res;
|
||||
|
||||
@@ -3,8 +3,10 @@ import 'dart:math';
|
||||
import 'package:PiliPlus/common/widgets/article_content.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/reply_sort_type.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
@@ -14,6 +16,7 @@ import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_reply.dart';
|
||||
import 'package:PiliPlus/common/widgets/html_render.dart';
|
||||
@@ -323,7 +326,11 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
SliverPadding(
|
||||
padding: orientation == Orientation.portrait
|
||||
? EdgeInsets.symmetric(horizontal: padding)
|
||||
: EdgeInsets.only(left: padding / 4),
|
||||
: EdgeInsets.only(
|
||||
left: padding / 4,
|
||||
bottom:
|
||||
MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: _buildContent,
|
||||
),
|
||||
if (orientation == Orientation.portrait) ...[
|
||||
@@ -388,28 +395,265 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 14,
|
||||
right: 14,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 2),
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0),
|
||||
).animate(CurvedAnimation(
|
||||
parent: fabAnimationCtr,
|
||||
curve: Curves.easeInOut,
|
||||
)),
|
||||
child: FloatingActionButton(
|
||||
heroTag: null,
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
_htmlRenderCtr.onReply(
|
||||
context,
|
||||
oid: _htmlRenderCtr.oid.value,
|
||||
replyType: ReplyType.values[type],
|
||||
);
|
||||
},
|
||||
tooltip: '评论动态',
|
||||
child: const Icon(Icons.reply),
|
||||
child: Obx(
|
||||
() => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: 14,
|
||||
bottom: 14 +
|
||||
(_htmlRenderCtr.item.value.idStr != null
|
||||
? 0
|
||||
: MediaQuery.of(context).padding.bottom),
|
||||
),
|
||||
child: FloatingActionButton(
|
||||
heroTag: null,
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
_htmlRenderCtr.onReply(
|
||||
context,
|
||||
oid: _htmlRenderCtr.oid.value,
|
||||
replyType: ReplyType.values[type],
|
||||
);
|
||||
},
|
||||
tooltip: '评论动态',
|
||||
child: const Icon(Icons.reply),
|
||||
),
|
||||
),
|
||||
_htmlRenderCtr.item.value.idStr != null
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withOpacity(0.08),
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.paddingOf(context).bottom),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (btnContext) => TextButton.icon(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
builder: (context) => RepostPanel(
|
||||
item: _htmlRenderCtr.item.value,
|
||||
callback: () {
|
||||
int count = int.tryParse(
|
||||
_htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.forward
|
||||
?.count ??
|
||||
'0') ??
|
||||
0;
|
||||
_htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.forward!
|
||||
.count =
|
||||
(count + 1).toString();
|
||||
if (btnContext.mounted) {
|
||||
(btnContext as Element?)
|
||||
?.markNeedsBuild();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.shareFromSquare,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
semanticLabel: "转发",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
15, 0, 15, 0),
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
label: Text(
|
||||
_htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.forward!
|
||||
.count !=
|
||||
null
|
||||
? Utils.numFormat(_htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.forward!
|
||||
.count)
|
||||
: '转发',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
Share.share(
|
||||
'${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}');
|
||||
},
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.shareNodes,
|
||||
size: 16,
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
semanticLabel: "分享",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
15, 0, 15, 0),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
label: const Text('分享'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (context) => TextButton.icon(
|
||||
onPressed: () => Utils.onLikeDynamic(
|
||||
_htmlRenderCtr.item.value,
|
||||
() {
|
||||
if (context.mounted) {
|
||||
(context as Element?)
|
||||
?.markNeedsBuild();
|
||||
}
|
||||
},
|
||||
),
|
||||
icon: Icon(
|
||||
_htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.like
|
||||
?.status ==
|
||||
true
|
||||
? FontAwesomeIcons.solidThumbsUp
|
||||
: FontAwesomeIcons.thumbsUp,
|
||||
size: 16,
|
||||
color: _htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.like
|
||||
?.status ==
|
||||
true
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
semanticLabel: _htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.like
|
||||
?.status ==
|
||||
true
|
||||
? "已赞"
|
||||
: "点赞",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
15, 0, 15, 0),
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
label: AnimatedSwitcher(
|
||||
duration:
|
||||
const Duration(milliseconds: 400),
|
||||
transitionBuilder: (Widget child,
|
||||
Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
_htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.like
|
||||
?.count !=
|
||||
null
|
||||
? Utils.numFormat(_htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules!
|
||||
.moduleStat!
|
||||
.like!
|
||||
.count)
|
||||
: '点赞',
|
||||
style: TextStyle(
|
||||
color: _htmlRenderCtr
|
||||
.item
|
||||
.value
|
||||
.modules
|
||||
?.moduleStat
|
||||
?.like
|
||||
?.status ==
|
||||
true
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -126,8 +126,6 @@ class VideoDetailController extends GetxController
|
||||
PlayerStatus? playerStatus;
|
||||
StreamSubscription<Duration>? positionSubscription;
|
||||
|
||||
PersistentBottomSheetController? bsController;
|
||||
|
||||
bool imageStatus = false;
|
||||
|
||||
void onViewImage() {
|
||||
@@ -1229,9 +1227,9 @@ class VideoDetailController extends GetxController
|
||||
);
|
||||
}
|
||||
if (plPlayerController.isFullScreen.value) {
|
||||
bsController = scaffoldKey.currentState?.showBottomSheet(
|
||||
enableDrag: false,
|
||||
(context) => _postPanel(false),
|
||||
Utils.showFSSheet(
|
||||
child: _postPanel(),
|
||||
isFullScreen: plPlayerController.isFullScreen.value,
|
||||
);
|
||||
} else {
|
||||
childKey.currentState?.showBottomSheet(
|
||||
@@ -1241,7 +1239,7 @@ class VideoDetailController extends GetxController
|
||||
}
|
||||
}
|
||||
|
||||
Widget _postPanel([bool isChild = true]) => StatefulBuilder(
|
||||
Widget _postPanel() => StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
void updateSegment({
|
||||
required bool isFirst,
|
||||
@@ -1361,378 +1359,389 @@ class VideoDetailController extends GetxController
|
||||
];
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: isChild ? null : Utils.getSheetHeight(context),
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 16,
|
||||
title: const Text('提交片段'),
|
||||
actions: [
|
||||
iconButton(
|
||||
context: context,
|
||||
tooltip: '添加片段',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
list?.insert(
|
||||
0,
|
||||
PostSegmentModel(
|
||||
segment: Pair(
|
||||
first: 0,
|
||||
second: plPlayerController.positionSeconds.value,
|
||||
),
|
||||
category: SegmentType.sponsor,
|
||||
actionType: ActionType.skip,
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 16,
|
||||
title: const Text('提交片段'),
|
||||
actions: [
|
||||
iconButton(
|
||||
context: context,
|
||||
tooltip: '添加片段',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
list?.insert(
|
||||
0,
|
||||
PostSegmentModel(
|
||||
segment: Pair(
|
||||
first: 0,
|
||||
second: plPlayerController.positionSeconds.value,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: Icons.add,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
iconButton(
|
||||
context: context,
|
||||
tooltip: '关闭',
|
||||
onPressed: () {
|
||||
if (bsController != null) {
|
||||
bsController!.close();
|
||||
bsController = null;
|
||||
} else {
|
||||
Get.back();
|
||||
}
|
||||
},
|
||||
icon: Icons.close,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
body: list?.isNotEmpty == true
|
||||
? Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
...List.generate(
|
||||
list!.length,
|
||||
(index) => Stack(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (list![index].actionType !=
|
||||
ActionType.full) ...[
|
||||
Row(
|
||||
children: [
|
||||
...segmentWidget(
|
||||
category: SegmentType.sponsor,
|
||||
actionType: ActionType.skip,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: Icons.add,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
iconButton(
|
||||
context: context,
|
||||
tooltip: '关闭',
|
||||
onPressed: Get.back,
|
||||
icon: Icons.close,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
body: list?.isNotEmpty == true
|
||||
? Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
...List.generate(
|
||||
list!.length,
|
||||
(index) => Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (list![index].actionType !=
|
||||
ActionType.full) ...[
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
),
|
||||
if (list![index].category !=
|
||||
SegmentType
|
||||
.poi_highlight) ...[
|
||||
const SizedBox(width: 16),
|
||||
...segmentWidget(
|
||||
),
|
||||
if (list![index].category !=
|
||||
SegmentType.poi_highlight)
|
||||
Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
isFirst: false,
|
||||
index: index,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('分类: '),
|
||||
PopupMenuButton(
|
||||
initialValue:
|
||||
list![index].category,
|
||||
onSelected: (item) async {
|
||||
list![index].category =
|
||||
item;
|
||||
List<ActionType>
|
||||
constraintList =
|
||||
_segmentType2ActionType(
|
||||
item);
|
||||
if (constraintList
|
||||
.contains(list![index]
|
||||
.actionType)
|
||||
.not) {
|
||||
list![index].actionType =
|
||||
constraintList.first;
|
||||
}
|
||||
switch (item) {
|
||||
case SegmentType
|
||||
.poi_highlight:
|
||||
updateSegment(
|
||||
isFirst: false,
|
||||
index: index,
|
||||
value: list![index]
|
||||
.segment
|
||||
.first,
|
||||
);
|
||||
break;
|
||||
case SegmentType
|
||||
.exclusive_access:
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
break;
|
||||
case _:
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (context) =>
|
||||
SegmentType.values
|
||||
.map((item) =>
|
||||
PopupMenuItem<
|
||||
SegmentType>(
|
||||
value: item,
|
||||
child: Text(
|
||||
item.title),
|
||||
))
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index]
|
||||
.category
|
||||
.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color:
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
strutStyle: StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons
|
||||
.unfoldMoreHorizontal,
|
||||
size: MediaQuery
|
||||
.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
const Text('分类: '),
|
||||
PopupMenuButton(
|
||||
initialValue:
|
||||
list![index].category,
|
||||
onSelected: (item) async {
|
||||
list![index].category = item;
|
||||
List<ActionType>
|
||||
constraintList =
|
||||
_segmentType2ActionType(
|
||||
item);
|
||||
if (constraintList
|
||||
.contains(list![index]
|
||||
.actionType)
|
||||
.not) {
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('行为类别: '),
|
||||
PopupMenuButton(
|
||||
initialValue:
|
||||
list![index].actionType,
|
||||
onSelected: (item) async {
|
||||
list![index].actionType =
|
||||
constraintList.first;
|
||||
}
|
||||
switch (item) {
|
||||
case SegmentType
|
||||
.poi_highlight:
|
||||
updateSegment(
|
||||
isFirst: false,
|
||||
index: index,
|
||||
value: list![index]
|
||||
.segment
|
||||
.first,
|
||||
);
|
||||
break;
|
||||
case SegmentType
|
||||
.exclusive_access:
|
||||
item;
|
||||
if (item ==
|
||||
ActionType.full) {
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
break;
|
||||
case _:
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (context) =>
|
||||
SegmentType.values
|
||||
.map((item) =>
|
||||
PopupMenuItem<
|
||||
SegmentType>(
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (context) =>
|
||||
ActionType.values
|
||||
.map(
|
||||
(item) =>
|
||||
PopupMenuItem<
|
||||
ActionType>(
|
||||
enabled: _segmentType2ActionType(
|
||||
list![index]
|
||||
.category)
|
||||
.contains(
|
||||
item),
|
||||
value: item,
|
||||
child: Text(
|
||||
item.title),
|
||||
))
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index]
|
||||
.category
|
||||
.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index]
|
||||
.actionType
|
||||
.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color:
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
strutStyle: StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons
|
||||
.unfoldMoreHorizontal,
|
||||
size: MediaQuery
|
||||
.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
strutStyle: StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons
|
||||
.unfoldMoreHorizontal,
|
||||
size: MediaQuery
|
||||
.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const Text('行为类别: '),
|
||||
PopupMenuButton(
|
||||
initialValue:
|
||||
list![index].actionType,
|
||||
onSelected: (item) async {
|
||||
list![index].actionType =
|
||||
item;
|
||||
if (item == ActionType.full) {
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (context) =>
|
||||
ActionType.values
|
||||
.map(
|
||||
(item) =>
|
||||
PopupMenuItem<
|
||||
ActionType>(
|
||||
enabled: _segmentType2ActionType(
|
||||
list![index]
|
||||
.category)
|
||||
.contains(item),
|
||||
value: item,
|
||||
child: Text(
|
||||
item.title),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index]
|
||||
.actionType
|
||||
.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
strutStyle: StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons
|
||||
.unfoldMoreHorizontal,
|
||||
size: MediaQuery
|
||||
.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 21,
|
||||
child: iconButton(
|
||||
context: context,
|
||||
size: 26,
|
||||
tooltip: '移除',
|
||||
icon: Icons.clear,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
list!.removeAt(index);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
88 + MediaQuery.paddingOf(context).bottom,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 16,
|
||||
bottom: 16 + MediaQuery.paddingOf(context).bottom,
|
||||
child: FloatingActionButton(
|
||||
tooltip: '提交',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('确定无误再提交'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Request()
|
||||
.post(
|
||||
'${GStorage.blockServer}/api/skipSegments',
|
||||
queryParameters: {
|
||||
'videoID': bvid,
|
||||
'cid': cid.value,
|
||||
'userID': GStorage.blockUserID,
|
||||
'userAgent': Constants.userAgent,
|
||||
'videoDuration': plPlayerController
|
||||
.durationSeconds
|
||||
.value
|
||||
.inSeconds,
|
||||
},
|
||||
data: {
|
||||
'segments': list!
|
||||
.map(
|
||||
(item) => {
|
||||
'segment': [
|
||||
item.segment.first,
|
||||
item.segment.second,
|
||||
],
|
||||
'category':
|
||||
item.category.name,
|
||||
'actionType':
|
||||
item.actionType.name,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
},
|
||||
options: _options,
|
||||
)
|
||||
.then(
|
||||
(res) {
|
||||
if (res.statusCode == 200) {
|
||||
Get.back();
|
||||
SmartDialog.showToast('提交成功');
|
||||
list?.clear();
|
||||
_handleSBData(res);
|
||||
plPlayerController
|
||||
.segmentList.value =
|
||||
_segmentProgressList ??
|
||||
<Segment>[];
|
||||
if (positionSubscription ==
|
||||
null) {
|
||||
_initSkip();
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(
|
||||
'提交失败: ${{
|
||||
400: '参数错误',
|
||||
403: '被自动审核机制拒绝',
|
||||
429: '重复提交太快',
|
||||
409: '重复提交'
|
||||
}[res.statusCode]}',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('确定提交'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Icon(Icons.check),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: errorWidget(),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 21,
|
||||
child: iconButton(
|
||||
context: context,
|
||||
size: 26,
|
||||
tooltip: '移除',
|
||||
icon: Icons.clear,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
list!.removeAt(index);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 88 + MediaQuery.paddingOf(context).bottom,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 16,
|
||||
bottom: 16 + MediaQuery.paddingOf(context).bottom,
|
||||
child: FloatingActionButton(
|
||||
tooltip: '提交',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('确定无误再提交'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Request()
|
||||
.post(
|
||||
'${GStorage.blockServer}/api/skipSegments',
|
||||
queryParameters: {
|
||||
'videoID': bvid,
|
||||
'cid': cid.value,
|
||||
'userID': GStorage.blockUserID,
|
||||
'userAgent': Constants.userAgent,
|
||||
'videoDuration': plPlayerController
|
||||
.durationSeconds.value.inSeconds,
|
||||
},
|
||||
data: {
|
||||
'segments': list!
|
||||
.map(
|
||||
(item) => {
|
||||
'segment': [
|
||||
item.segment.first,
|
||||
item.segment.second,
|
||||
],
|
||||
'category':
|
||||
item.category.name,
|
||||
'actionType':
|
||||
item.actionType.name,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
},
|
||||
options: _options,
|
||||
)
|
||||
.then(
|
||||
(res) {
|
||||
if (res.statusCode == 200) {
|
||||
Get.back();
|
||||
SmartDialog.showToast('提交成功');
|
||||
list?.clear();
|
||||
_handleSBData(res);
|
||||
plPlayerController
|
||||
.segmentList.value =
|
||||
_segmentProgressList ??
|
||||
<Segment>[];
|
||||
if (positionSubscription == null) {
|
||||
_initSkip();
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(
|
||||
'提交失败: ${{
|
||||
400: '参数错误',
|
||||
403: '被自动审核机制拒绝',
|
||||
429: '重复提交太快',
|
||||
409: '重复提交'
|
||||
}[res.statusCode]}',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('确定提交'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Icon(Icons.check),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: errorWidget(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -438,6 +438,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
gapSize: 25,
|
||||
itemCount: videoItem['staff'].length,
|
||||
childBuilder: (index) => GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
int? ownerMid = !widget.loadingStatus
|
||||
? videoDetail.owner?.mid
|
||||
|
||||
@@ -26,7 +26,7 @@ import 'package:html/parser.dart' show parse;
|
||||
class ReplyItem extends StatelessWidget {
|
||||
const ReplyItem({
|
||||
super.key,
|
||||
this.replyItem,
|
||||
required this.replyItem,
|
||||
this.replyLevel,
|
||||
this.showReplyRow = true,
|
||||
this.replyReply,
|
||||
@@ -39,7 +39,7 @@ class ReplyItem extends StatelessWidget {
|
||||
this.getTag,
|
||||
this.callback,
|
||||
});
|
||||
final ReplyItemModel? replyItem;
|
||||
final ReplyItemModel replyItem;
|
||||
final String? replyLevel;
|
||||
final bool? showReplyRow;
|
||||
final Function? replyReply;
|
||||
@@ -69,7 +69,7 @@ class ReplyItem extends StatelessWidget {
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
return MorePanel(
|
||||
item: replyItem!,
|
||||
item: replyItem,
|
||||
onDelete: (rpid) {
|
||||
onDelete?.call(rpid, null);
|
||||
},
|
||||
@@ -80,7 +80,7 @@ class ReplyItem extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
if (ModuleAuthorModel.showDynDecorate &&
|
||||
(replyItem?.member?.userSailing?.cardbg?['image'] as String?)
|
||||
(replyItem.member?.userSailing?.cardbg?['image'] as String?)
|
||||
?.isNotEmpty ==
|
||||
true)
|
||||
Stack(
|
||||
@@ -95,16 +95,16 @@ class ReplyItem extends StatelessWidget {
|
||||
CachedNetworkImage(
|
||||
height: 38,
|
||||
imageUrl:
|
||||
replyItem?.member?.userSailing?.cardbg?['image'],
|
||||
replyItem.member?.userSailing?.cardbg?['image'],
|
||||
),
|
||||
if ((replyItem?.member?.userSailing?.cardbg?['fan']
|
||||
if ((replyItem.member?.userSailing?.cardbg?['fan']
|
||||
?['num_desc'] as String?)
|
||||
?.isNotEmpty ==
|
||||
true)
|
||||
Text(
|
||||
'NO.\n${replyItem?.member?.userSailing?.cardbg?['fan']?['num_desc']}',
|
||||
'NO.\n${replyItem.member?.userSailing?.cardbg?['fan']?['num_desc']}',
|
||||
style:
|
||||
(replyItem?.member?.userSailing?.cardbg?['fan']
|
||||
(replyItem.member?.userSailing?.cardbg?['fan']
|
||||
?['color'] as String?)
|
||||
?.startsWith('#') ==
|
||||
true
|
||||
@@ -113,7 +113,7 @@ class ReplyItem extends StatelessWidget {
|
||||
fontFamily: 'digital_id_num',
|
||||
color: Color(
|
||||
int.parse(
|
||||
replyItem?.member?.userSailing
|
||||
replyItem.member?.userSailing
|
||||
?.cardbg?['fan']?['color']
|
||||
.replaceFirst('#', '0xFF'),
|
||||
),
|
||||
@@ -152,11 +152,11 @@ class ReplyItem extends StatelessWidget {
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
if (ModuleAuthorModel.showDynDecorate &&
|
||||
replyItem?.member?.pendant?.image?.isNotEmpty == true) ...[
|
||||
replyItem.member?.pendant?.image?.isNotEmpty == true) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: NetworkImgLayer(
|
||||
src: replyItem!.member!.avatar,
|
||||
src: replyItem.member?.avatar,
|
||||
width: 30,
|
||||
height: 30,
|
||||
type: 'avatar',
|
||||
@@ -169,18 +169,18 @@ class ReplyItem extends StatelessWidget {
|
||||
child: CachedNetworkImage(
|
||||
width: 52,
|
||||
height: 52,
|
||||
imageUrl: replyItem!.member!.pendant!.image!,
|
||||
imageUrl: replyItem.member!.pendant!.image!,
|
||||
),
|
||||
),
|
||||
),
|
||||
] else
|
||||
NetworkImgLayer(
|
||||
src: replyItem!.member!.avatar,
|
||||
src: replyItem.member?.avatar,
|
||||
width: 34,
|
||||
height: 34,
|
||||
type: 'avatar',
|
||||
),
|
||||
if (replyItem!.member!.vip!['vipStatus'] > 0)
|
||||
if ((replyItem.member?.vip?['vipStatus'] ?? -1) > 0)
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
@@ -198,8 +198,7 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
//https://www.bilibili.com/blackboard/activity-whPrHsYJ2.html
|
||||
if (replyItem!.member!.officialVerify != null &&
|
||||
replyItem!.member!.officialVerify!['type'] == 0)
|
||||
if (replyItem.member?.officialVerify?['type'] == 0)
|
||||
Positioned(
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
@@ -217,8 +216,7 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (replyItem!.member!.officialVerify != null &&
|
||||
replyItem!.member!.officialVerify!['type'] == 1)
|
||||
if (replyItem.member?.officialVerify?['type'] == 1)
|
||||
Positioned(
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
@@ -241,7 +239,7 @@ class ReplyItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget content(BuildContext context) {
|
||||
if (replyItem?.member == null) return const SizedBox();
|
||||
if (replyItem.member == null) return const SizedBox();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -251,7 +249,7 @@ class ReplyItem extends StatelessWidget {
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
feedBack();
|
||||
Get.toNamed('/member?mid=${replyItem!.mid}');
|
||||
Get.toNamed('/member?mid=${replyItem.mid}');
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@@ -266,10 +264,9 @@ class ReplyItem extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
replyItem!.member!.uname!,
|
||||
'${replyItem.member?.uname}',
|
||||
style: TextStyle(
|
||||
color: (replyItem!.member!.vip!['vipStatus'] > 0 &&
|
||||
replyItem!.member!.vip!['vipType'] == 2)
|
||||
color: (replyItem.member?.vip?['vipType'] == 2)
|
||||
? context.vipColor
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
fontSize: 13,
|
||||
@@ -277,12 +274,11 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Image.asset(
|
||||
'assets/images/lv/lv${replyItem!.member!.level}.png',
|
||||
'assets/images/lv/lv${replyItem.member?.level}.png',
|
||||
height: 11,
|
||||
semanticLabel: "等级:${replyItem!.member!.level}",
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
if (replyItem!.isUp!)
|
||||
if (replyItem.isUp == true)
|
||||
const PBadge(
|
||||
text: 'UP',
|
||||
size: 'small',
|
||||
@@ -295,17 +291,16 @@ class ReplyItem extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
Utils.dateFormat(replyItem!.ctime),
|
||||
Utils.dateFormat(replyItem.ctime),
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
if (replyItem!.replyControl != null &&
|
||||
replyItem!.replyControl!.location != '')
|
||||
if (replyItem.replyControl?.location?.isNotEmpty == true)
|
||||
Text(
|
||||
' • ${replyItem!.replyControl!.location!}',
|
||||
' • ${replyItem.replyControl!.location!}',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
@@ -326,7 +321,7 @@ class ReplyItem extends StatelessWidget {
|
||||
const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4),
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
String text = replyItem?.content?.message ?? '';
|
||||
String text = replyItem.content?.message ?? '';
|
||||
TextStyle style = TextStyle(
|
||||
height: 1.75,
|
||||
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
|
||||
@@ -347,7 +342,7 @@ class ReplyItem extends StatelessWidget {
|
||||
style: style,
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replyItem!.isTop!) ...[
|
||||
if (replyItem.isTop == true) ...[
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
child: PBadge(
|
||||
@@ -364,7 +359,7 @@ class ReplyItem extends StatelessWidget {
|
||||
],
|
||||
buildContent(
|
||||
context,
|
||||
replyItem!,
|
||||
replyItem,
|
||||
replyReply,
|
||||
null,
|
||||
textPainter,
|
||||
@@ -378,27 +373,26 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
// 操作区域
|
||||
buttonAction(context, replyItem!.replyControl),
|
||||
buttonAction(context, replyItem.replyControl),
|
||||
// 一楼的评论
|
||||
if ((replyItem!.replyControl!.isShow! ||
|
||||
replyItem!.replies!.isNotEmpty ||
|
||||
replyItem!.replyControl!.entryText != null) &&
|
||||
showReplyRow!) ...[
|
||||
if (showReplyRow == true &&
|
||||
(replyItem.replyControl?.isShow == true ||
|
||||
replyItem.replies?.isNotEmpty == true ||
|
||||
replyItem.replyControl?.entryText?.isNotEmpty == true))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5, bottom: 12),
|
||||
child: replyItemRow(
|
||||
context: context,
|
||||
replies: replyItem!.replies,
|
||||
replyControl: replyItem!.replyControl,
|
||||
// f_rpid: replyItem!.rpid,
|
||||
replies: replyItem.replies,
|
||||
replyControl: replyItem.replyControl,
|
||||
// f_rpid: replyItem.rpid,
|
||||
replyItem: replyItem,
|
||||
replyReply: replyReply,
|
||||
onDelete: (rpid) {
|
||||
onDelete?.call(rpid, replyItem!.rpid);
|
||||
onDelete?.call(rpid, replyItem.rpid);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -433,7 +427,7 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
if (replyItem!.upAction!.like!) ...[
|
||||
if (replyItem.upAction?.like == true) ...[
|
||||
SizedBox(
|
||||
height: 32,
|
||||
child: TextButton(
|
||||
@@ -450,8 +444,7 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
],
|
||||
if (replyItem!.cardLabel!.isNotEmpty &&
|
||||
replyItem!.cardLabel!.contains('热评'))
|
||||
if (replyItem.cardLabel?.contains('热评') == true)
|
||||
Text(
|
||||
'热评',
|
||||
style: TextStyle(
|
||||
@@ -467,16 +460,15 @@ class ReplyItem extends StatelessWidget {
|
||||
|
||||
Widget replyItemRow({
|
||||
context,
|
||||
replies,
|
||||
replyControl,
|
||||
replyItem,
|
||||
List<ReplyItemModel>? replies,
|
||||
ReplyControl? replyControl,
|
||||
required ReplyItemModel replyItem,
|
||||
replyReply,
|
||||
onDelete,
|
||||
}) {
|
||||
final int extraRow = replyControl?.isShow == true ||
|
||||
(replyControl?.entryText != null && replies!.isEmpty)
|
||||
? 1
|
||||
: 0;
|
||||
final bool hasExtraRow = replyControl?.isShow == true ||
|
||||
(replyControl?.entryText?.isNotEmpty == true &&
|
||||
replies?.isEmpty == true);
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 42, right: 4, top: 0),
|
||||
child: Material(
|
||||
@@ -487,7 +479,7 @@ class ReplyItem extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (replies!.isNotEmpty)
|
||||
if (replies?.isNotEmpty == true)
|
||||
for (int i = 0; i < replies!.length; i++) ...[
|
||||
InkWell(
|
||||
// 一楼点击评论展开评论详情
|
||||
@@ -500,7 +492,7 @@ class ReplyItem extends StatelessWidget {
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
return MorePanel(
|
||||
item: replies![i],
|
||||
item: replies[i],
|
||||
onDelete: onDelete,
|
||||
);
|
||||
},
|
||||
@@ -510,13 +502,13 @@ class ReplyItem extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
8,
|
||||
i == 0 && (extraRow == 1 || replies!.length > 1) ? 8 : 4,
|
||||
i == 0 && (hasExtraRow || replies.length > 1) ? 8 : 4,
|
||||
8,
|
||||
i == 0 && (extraRow == 1 || replies!.length > 1) ? 4 : 6,
|
||||
i == 0 && (hasExtraRow || replies.length > 1) ? 4 : 6,
|
||||
),
|
||||
child: Semantics(
|
||||
label:
|
||||
'${replies![i].member!.uname} ${replies![i].content!.message}',
|
||||
'${replies[i].member?.uname} ${replies[i].content?.message}',
|
||||
excludeSemantics: true,
|
||||
child: Text.rich(
|
||||
style: TextStyle(
|
||||
@@ -534,7 +526,7 @@ class ReplyItem extends StatelessWidget {
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${replies![i].member!.uname}',
|
||||
text: '${replies[i].member?.uname}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
@@ -542,10 +534,10 @@ class ReplyItem extends StatelessWidget {
|
||||
..onTap = () {
|
||||
feedBack();
|
||||
Get.toNamed(
|
||||
'/member?mid=${replies![i].member!.mid}');
|
||||
'/member?mid=${replies[i].member?.mid}');
|
||||
},
|
||||
),
|
||||
if (replies![i].isUp!) ...[
|
||||
if (replies[i].isUp == true) ...[
|
||||
const TextSpan(text: ' '),
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
@@ -560,14 +552,14 @@ class ReplyItem extends StatelessWidget {
|
||||
const TextSpan(text: ' '),
|
||||
],
|
||||
TextSpan(
|
||||
text: replies![i].root == replies![i].parent
|
||||
text: replies[i].root == replies[i].parent
|
||||
? ': '
|
||||
: replies![i].isUp!
|
||||
: replies[i].isUp == true
|
||||
? ''
|
||||
: ' '),
|
||||
buildContent(
|
||||
context,
|
||||
replies![i],
|
||||
replies[i],
|
||||
replyReply,
|
||||
replyItem,
|
||||
null,
|
||||
@@ -580,7 +572,7 @@ class ReplyItem extends StatelessWidget {
|
||||
),
|
||||
)
|
||||
],
|
||||
if (extraRow == 1)
|
||||
if (hasExtraRow)
|
||||
InkWell(
|
||||
// 一楼点击【共xx条回复】展开评论详情
|
||||
onTap: () => replyReply?.call(replyItem, null, null),
|
||||
@@ -594,7 +586,7 @@ class ReplyItem extends StatelessWidget {
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
),
|
||||
children: [
|
||||
if (replyControl!.upReply!)
|
||||
if (replyControl?.upReply == true)
|
||||
TextSpan(
|
||||
text: 'UP主等人 ',
|
||||
style: TextStyle(
|
||||
@@ -604,7 +596,7 @@ class ReplyItem extends StatelessWidget {
|
||||
.withOpacity(0.85),
|
||||
)),
|
||||
TextSpan(
|
||||
text: replyControl!.entryText!,
|
||||
text: replyControl?.entryText,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
|
||||
@@ -822,8 +822,11 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
],
|
||||
);
|
||||
}
|
||||
final double videoWidth =
|
||||
double videoWidth =
|
||||
max(context.height / context.width * 1.04, 1 / 2) * context.width;
|
||||
if (context.width >= 560) {
|
||||
videoWidth = min(videoWidth, context.width - 280);
|
||||
}
|
||||
final double videoHeight = videoWidth * 9 / 16;
|
||||
return Row(
|
||||
children: [
|
||||
@@ -1799,14 +1802,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
videoDetailController.videoType == SearchType.media_bangumi
|
||||
? bangumiIntroController.changeSeasonOrbangu
|
||||
: videoIntroController.changeSeasonOrbangu,
|
||||
onClose: () {
|
||||
if (videoDetailController.bsController != null) {
|
||||
videoDetailController.bsController!.close();
|
||||
videoDetailController.bsController = null;
|
||||
} else {
|
||||
Get.back();
|
||||
}
|
||||
},
|
||||
onClose: Get.back,
|
||||
onReverse: () {
|
||||
Get.back();
|
||||
onReversePlay(
|
||||
@@ -1817,10 +1813,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
},
|
||||
);
|
||||
if (isFullScreen) {
|
||||
videoDetailController.bsController =
|
||||
videoDetailController.scaffoldKey.currentState?.showBottomSheet(
|
||||
(context) => listSheetContent(),
|
||||
);
|
||||
Utils.showFSSheet(child: listSheetContent(), isFullScreen: isFullScreen);
|
||||
} else {
|
||||
videoDetailController.childKey.currentState?.showBottomSheet(
|
||||
(context) => listSheetContent(),
|
||||
@@ -1899,157 +1892,138 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
}
|
||||
|
||||
void showViewPoints() {
|
||||
Widget listSheetContent(context, [bool isFS = false]) {
|
||||
Widget listSheetContent() {
|
||||
int currentIndex = -1;
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) => SizedBox(
|
||||
height: isFS ? Utils.getSheetHeight(context) : null,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 16,
|
||||
title: const Text('分段信息'),
|
||||
actions: [
|
||||
Text(
|
||||
'分段进度条',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
Obx(
|
||||
() => Transform.scale(
|
||||
alignment: Alignment.centerLeft,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon:
|
||||
WidgetStateProperty.resolveWith<Icon?>((states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value:
|
||||
videoDetailController.plPlayerController.showVP.value,
|
||||
onChanged: (value) {
|
||||
videoDetailController.plPlayerController.showVP.value =
|
||||
value;
|
||||
},
|
||||
),
|
||||
builder: (context, setState) => Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 16,
|
||||
title: const Text('分段信息'),
|
||||
actions: [
|
||||
Text(
|
||||
'分段进度条',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
Obx(
|
||||
() => Transform.scale(
|
||||
alignment: Alignment.centerLeft,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value:
|
||||
videoDetailController.plPlayerController.showVP.value,
|
||||
onChanged: (value) {
|
||||
videoDetailController.plPlayerController.showVP.value =
|
||||
value;
|
||||
},
|
||||
),
|
||||
),
|
||||
iconButton(
|
||||
context: context,
|
||||
size: 30,
|
||||
icon: Icons.clear,
|
||||
tooltip: '关闭',
|
||||
onPressed: () {
|
||||
if (videoDetailController.bsController != null) {
|
||||
videoDetailController.bsController!.close();
|
||||
videoDetailController.bsController = null;
|
||||
} else {
|
||||
Get.back();
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
...List.generate(
|
||||
videoDetailController.viewPointList.length * 2 - 1,
|
||||
(rawIndex) {
|
||||
if (rawIndex % 2 == 1) {
|
||||
return Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
);
|
||||
}
|
||||
int index = rawIndex ~/ 2;
|
||||
Segment segment =
|
||||
videoDetailController.viewPointList[index];
|
||||
if (currentIndex == -1 &&
|
||||
segment.from != null &&
|
||||
segment.to != null) {
|
||||
if (videoDetailController
|
||||
.plPlayerController.positionSeconds.value >=
|
||||
segment.from! &&
|
||||
videoDetailController
|
||||
.plPlayerController.positionSeconds.value <
|
||||
segment.to!) {
|
||||
currentIndex = index;
|
||||
}
|
||||
}
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: segment.from != null
|
||||
? () {
|
||||
currentIndex = index;
|
||||
plPlayerController?.danmakuController?.clear();
|
||||
plPlayerController?.videoPlayerController
|
||||
?.seek(Duration(seconds: segment.from!));
|
||||
if (videoDetailController.bsController != null) {
|
||||
videoDetailController.bsController!.close();
|
||||
videoDetailController.bsController = null;
|
||||
} else {
|
||||
Get.back();
|
||||
// setState(() {});
|
||||
}
|
||||
}
|
||||
: null,
|
||||
leading: segment.url?.isNotEmpty == true
|
||||
? Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: currentIndex == index
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
width: 1.8,
|
||||
strokeAlign:
|
||||
BorderSide.strokeAlignOutside,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) =>
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
src: segment.url,
|
||||
width: constraints.maxHeight *
|
||||
StyleString.aspectRatio,
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
segment.title ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
currentIndex == index ? FontWeight.bold : null,
|
||||
color: currentIndex == index
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${segment.from != null ? Utils.timeFormat(segment.from) : ''} - ${segment.to != null ? Utils.timeFormat(segment.to) : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: currentIndex == index
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom),
|
||||
],
|
||||
),
|
||||
iconButton(
|
||||
context: context,
|
||||
size: 30,
|
||||
icon: Icons.clear,
|
||||
tooltip: '关闭',
|
||||
onPressed: Get.back,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
...List.generate(
|
||||
videoDetailController.viewPointList.length * 2 - 1,
|
||||
(rawIndex) {
|
||||
if (rawIndex % 2 == 1) {
|
||||
return Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
);
|
||||
}
|
||||
int index = rawIndex ~/ 2;
|
||||
Segment segment = videoDetailController.viewPointList[index];
|
||||
if (currentIndex == -1 &&
|
||||
segment.from != null &&
|
||||
segment.to != null) {
|
||||
if (videoDetailController
|
||||
.plPlayerController.positionSeconds.value >=
|
||||
segment.from! &&
|
||||
videoDetailController
|
||||
.plPlayerController.positionSeconds.value <
|
||||
segment.to!) {
|
||||
currentIndex = index;
|
||||
}
|
||||
}
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: segment.from != null
|
||||
? () {
|
||||
currentIndex = index;
|
||||
plPlayerController?.danmakuController?.clear();
|
||||
plPlayerController?.videoPlayerController
|
||||
?.seek(Duration(seconds: segment.from!));
|
||||
Get.back();
|
||||
}
|
||||
: null,
|
||||
leading: segment.url?.isNotEmpty == true
|
||||
? Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: currentIndex == index
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
width: 1.8,
|
||||
strokeAlign:
|
||||
BorderSide.strokeAlignOutside,
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) =>
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
src: segment.url,
|
||||
width: constraints.maxHeight *
|
||||
StyleString.aspectRatio,
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
segment.title ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
currentIndex == index ? FontWeight.bold : null,
|
||||
color: currentIndex == index
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${segment.from != null ? Utils.timeFormat(segment.from) : ''} - ${segment.to != null ? Utils.timeFormat(segment.to) : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: currentIndex == index
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -2057,23 +2031,15 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
}
|
||||
|
||||
if (isFullScreen) {
|
||||
videoDetailController.bsController =
|
||||
videoDetailController.scaffoldKey.currentState?.showBottomSheet(
|
||||
(context) => listSheetContent(context, true),
|
||||
);
|
||||
Utils.showFSSheet(child: listSheetContent(), isFullScreen: isFullScreen);
|
||||
} else {
|
||||
videoDetailController.childKey.currentState?.showBottomSheet(
|
||||
(context) => listSheetContent(context),
|
||||
(context) => listSheetContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onPopInvokedWithResult(didPop, result) {
|
||||
if (videoDetailController.bsController != null) {
|
||||
videoDetailController.bsController!.close();
|
||||
videoDetailController.bsController = null;
|
||||
return;
|
||||
}
|
||||
if (plPlayerController?.controlsLock.value == true) {
|
||||
plPlayerController?.onLockControl(false);
|
||||
return;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1110,12 +1110,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
// 头部、底部控制条
|
||||
Obx(
|
||||
() => Positioned.fill(
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.headerControl != null ||
|
||||
plPlayerController.headerControl != null)
|
||||
ClipRect(
|
||||
child: AppBarAni(
|
||||
child: ClipRect(
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.headerControl != null ||
|
||||
plPlayerController.headerControl != null)
|
||||
AppBarAni(
|
||||
controller: animationController,
|
||||
visible: !plPlayerController.controlsLock.value &&
|
||||
plPlayerController.showControls.value,
|
||||
@@ -1123,21 +1123,21 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
child: widget.headerControl ??
|
||||
plPlayerController.headerControl!,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (plPlayerController.showControls.value)
|
||||
AppBarAni(
|
||||
controller: animationController,
|
||||
visible: !plPlayerController.controlsLock.value &&
|
||||
plPlayerController.showControls.value,
|
||||
position: 'bottom',
|
||||
child: widget.bottomControl ??
|
||||
BottomControl(
|
||||
controller: plPlayerController,
|
||||
buildBottomControl: buildBottomControl,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
if (plPlayerController.showControls.value)
|
||||
AppBarAni(
|
||||
controller: animationController,
|
||||
visible: !plPlayerController.controlsLock.value &&
|
||||
plPlayerController.showControls.value,
|
||||
position: 'bottom',
|
||||
child: widget.bottomControl ??
|
||||
BottomControl(
|
||||
controller: plPlayerController,
|
||||
buildBottomControl: buildBottomControl,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1750,7 +1750,9 @@ Widget buildViewPointWidget(
|
||||
return item.start >= seg;
|
||||
}).reduce((a, b) => a.start < b.start ? a : b);
|
||||
if (item.from != null) {
|
||||
plPlayerController.seekTo(Duration(seconds: item.from!));
|
||||
plPlayerController.danmakuController?.clear();
|
||||
plPlayerController.videoPlayerController
|
||||
?.seek(Duration(seconds: item.from!));
|
||||
}
|
||||
// debugPrint('${item.title},,${item.from}');
|
||||
} catch (e) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/dynamics.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/member.dart';
|
||||
@@ -33,6 +34,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:html/dom.dart' as dom;
|
||||
@@ -44,6 +46,76 @@ class Utils {
|
||||
|
||||
static const channel = MethodChannel("PiliPlus");
|
||||
|
||||
// 动态点赞
|
||||
static Future onLikeDynamic(item, VoidCallback callback) async {
|
||||
feedBack();
|
||||
String dynamicId = item.idStr!;
|
||||
// 1 已点赞 2 不喜欢 0 未操作
|
||||
Like like = item.modules.moduleStat.like;
|
||||
int count = like.count == '点赞' ? 0 : int.parse(like.count ?? '0');
|
||||
bool status = like.status!;
|
||||
int up = status ? 2 : 1;
|
||||
var res = await DynamicsHttp.likeDynamic(dynamicId: dynamicId, up: up);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast(!status ? '点赞成功' : '取消赞');
|
||||
if (up == 1) {
|
||||
item.modules.moduleStat.like.count = (count + 1).toString();
|
||||
item.modules.moduleStat.like.status = true;
|
||||
} else {
|
||||
if (count == 1) {
|
||||
item.modules.moduleStat.like.count = '点赞';
|
||||
} else {
|
||||
item.modules.moduleStat.like.count = (count - 1).toString();
|
||||
}
|
||||
item.modules.moduleStat.like.status = false;
|
||||
}
|
||||
callback();
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
static void showFSSheet({
|
||||
required Widget child,
|
||||
required bool isFullScreen,
|
||||
double? padding,
|
||||
}) {
|
||||
Navigator.of(Get.context!).push(
|
||||
GetDialogRoute(
|
||||
pageBuilder: (buildContext, animation, secondaryAnimation) {
|
||||
return MediaQuery.orientationOf(Get.context!) == Orientation.portrait
|
||||
? Column(
|
||||
children: [
|
||||
const Spacer(flex: 3),
|
||||
Expanded(flex: 7, child: child),
|
||||
if (isFullScreen && padding != null)
|
||||
SizedBox(height: padding),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(child: child),
|
||||
],
|
||||
);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 350),
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
Offset begin =
|
||||
MediaQuery.orientationOf(Get.context!) == Orientation.portrait
|
||||
? Offset(0.0, 1.0)
|
||||
: Offset(1.0, 0.0);
|
||||
var tween = Tween(begin: begin, end: Offset.zero)
|
||||
.chain(CurveTween(curve: Curves.easeInOut));
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static darkenTheme(ThemeData themeData) {
|
||||
// return themeData;
|
||||
Color color = themeData.colorScheme.surfaceContainerHighest.darken(0.7);
|
||||
@@ -1170,6 +1242,7 @@ class Utils {
|
||||
|
||||
// 检查更新
|
||||
static Future checkUpdate([bool isAuto = true]) async {
|
||||
if (BuildConfig.isDebug) return;
|
||||
SmartDialog.dismiss();
|
||||
try {
|
||||
dynamic res = await Request().get(Api.latestApp, extra: {'ua': 'mob'});
|
||||
|
||||
Reference in New Issue
Block a user