mod: 动态页面视频&专栏跳转
This commit is contained in:
122
lib/common/skeleton/dynamic_card.dart
Normal file
122
lib/common/skeleton/dynamic_card.dart
Normal file
@ -0,0 +1,122 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'skeleton.dart';
|
||||
|
||||
class DynamicCardSkeleton extends StatelessWidget {
|
||||
const DynamicCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Skeleton(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 8,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 100,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 50,
|
||||
height: 11,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: double.infinity,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 7),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: double.infinity,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 7),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 300,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 7),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 250,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 7),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 100,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 7),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
for (var i = 0; i < 3; i++)
|
||||
TextButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.radio_button_unchecked_outlined,
|
||||
size: 20,
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withOpacity(0.2),
|
||||
),
|
||||
label: Text(
|
||||
i == 0
|
||||
? '转发'
|
||||
: i == 1
|
||||
? '评论'
|
||||
: '点赞',
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ class DynamicsHttp {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': '请求错误 🙅',
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/dynamics.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class DynamicsController extends GetxController {
|
||||
int page = 1;
|
||||
@ -8,6 +12,7 @@ class DynamicsController extends GetxController {
|
||||
RxList<DynamicItemModel>? dynamicsList = [DynamicItemModel()].obs;
|
||||
RxString dynamicsType = 'all'.obs;
|
||||
RxString dynamicsTypeLabel = '全部'.obs;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
Future queryFollowDynamic({type = 'init'}) async {
|
||||
var res = await DynamicsHttp.followDynamic(
|
||||
@ -27,16 +32,22 @@ class DynamicsController extends GetxController {
|
||||
return res;
|
||||
}
|
||||
|
||||
onSelectType(value, label) {
|
||||
onSelectType(value, label) async {
|
||||
dynamicsType.value = value;
|
||||
dynamicsTypeLabel.value = label;
|
||||
queryFollowDynamic();
|
||||
await queryFollowDynamic();
|
||||
scrollController.animateTo(0,
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
}
|
||||
|
||||
pushDetail(item, floor) {
|
||||
pushDetail(item, floor, {action = 'all'}) async {
|
||||
if (action == 'comment') {
|
||||
Get.toNamed('/dynamicDetail',
|
||||
arguments: {'item': item, 'floor': floor, 'action': action});
|
||||
return false;
|
||||
}
|
||||
switch (item!.type) {
|
||||
case 'DYNAMIC_TYPE_FORWARD':
|
||||
print('转发的动态');
|
||||
Get.toNamed('/dynamicDetail',
|
||||
arguments: {'item': item, 'floor': floor});
|
||||
break;
|
||||
@ -45,10 +56,25 @@ class DynamicsController extends GetxController {
|
||||
arguments: {'item': item, 'floor': floor});
|
||||
break;
|
||||
case 'DYNAMIC_TYPE_AV':
|
||||
print('视频');
|
||||
String bvid = item.modules.moduleDynamic.major.archive.bvid;
|
||||
int aid = item.modules.moduleDynamic.major.archive.aid;
|
||||
String cover = item.modules.moduleDynamic.major.archive.cover;
|
||||
String heroTag = Utils.makeHeroTag(aid);
|
||||
try {
|
||||
int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {'pic': cover, 'heroTag': heroTag});
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
break;
|
||||
case 'DYNAMIC_TYPE_ARTICLE':
|
||||
print('文章/专栏');
|
||||
String title = item.modules.moduleDynamic.major.opus.title;
|
||||
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {'url': 'https:$url', 'type': 'note', 'pageTitle': title},
|
||||
);
|
||||
break;
|
||||
case 'DYNAMIC_TYPE_PGC':
|
||||
print('番剧');
|
||||
|
@ -24,6 +24,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||
late StreamController<bool> titleStreamC; // appBar title
|
||||
final ScrollController scrollController = ScrollController();
|
||||
bool _visibleTitle = false;
|
||||
String? action;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -37,11 +38,16 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
||||
type = 11;
|
||||
}
|
||||
|
||||
action =
|
||||
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
||||
_dynamicDetailController = Get.put(DynamicDetailController(oid, type));
|
||||
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
||||
titleStreamC = StreamController<bool>();
|
||||
scrollController.addListener(_listen);
|
||||
if (action == 'comment') {
|
||||
_visibleTitle = true;
|
||||
titleStreamC.add(true);
|
||||
}
|
||||
}
|
||||
|
||||
void _listen() async {
|
||||
@ -90,12 +96,13 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: DynamicPanel(
|
||||
item: _dynamicDetailController!.item,
|
||||
source: 'detail',
|
||||
if (action != 'comment')
|
||||
SliverToBoxAdapter(
|
||||
child: DynamicPanel(
|
||||
item: _dynamicDetailController!.item,
|
||||
source: 'detail',
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPersistentHeader(
|
||||
delegate: _MySliverPersistentHeaderDelegate(
|
||||
child: Container(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/models/common/dynamics_type.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
@ -16,9 +17,9 @@ class DynamicsPage extends StatefulWidget {
|
||||
|
||||
class _DynamicsPageState extends State<DynamicsPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
Future? _futureBuilderFuture;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
// final ScrollController scrollController = ScrollController();
|
||||
bool _isLoadingMore = false;
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@ -28,10 +29,11 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
super.initState();
|
||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||
|
||||
scrollController.addListener(
|
||||
_dynamicsController.scrollController.addListener(
|
||||
() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
if (_dynamicsController.scrollController.position.pixels >=
|
||||
_dynamicsController.scrollController.position.maxScrollExtent -
|
||||
200) {
|
||||
if (!_isLoadingMore) {
|
||||
_isLoadingMore = true;
|
||||
await _dynamicsController.queryFollowDynamic(type: 'onLoad');
|
||||
@ -92,7 +94,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
List<DynamicItemModel> list = _dynamicsController.dynamicsList!;
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
controller: scrollController,
|
||||
controller: _dynamicsController.scrollController,
|
||||
shrinkWrap: true,
|
||||
itemCount: list.length,
|
||||
itemBuilder: (BuildContext context, index) {
|
||||
@ -105,19 +107,18 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
fn: () => _dynamicsController.queryFollowDynamic(),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
// return SliverList(
|
||||
// delegate: SliverChildBuilderDelegate((context, index) {
|
||||
// return const VideoCardHSkeleton();
|
||||
// }, childCount: 10),
|
||||
// );
|
||||
return Text('加载中');
|
||||
return ListView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 5,
|
||||
itemBuilder: ((context, index) => const DynamicCardSkeleton()),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -26,7 +26,8 @@ Widget action(item, context) {
|
||||
label: Text(stat.forward!.count ?? '转发'),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () => _dynamicsController.pushDetail(item, 1),
|
||||
onPressed: () =>
|
||||
_dynamicsController.pushDetail(item, 1, action: 'comment'),
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.comment,
|
||||
size: 16,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
|
||||
Widget picWidget(item, context) {
|
||||
@ -14,7 +15,6 @@ Widget picWidget(item, context) {
|
||||
}
|
||||
int len = pictures.length;
|
||||
List picList = [];
|
||||
|
||||
List<Widget> list = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
picList.add(pictures[i].src ?? pictures[i].url);
|
||||
@ -42,7 +42,8 @@ Widget picWidget(item, context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
double maxWidth = box.maxWidth;
|
||||
double aspectRatio = 1.1;
|
||||
double aspectRatio = 1.0;
|
||||
double origAspectRatio = 0.0;
|
||||
double crossCount = len == 1
|
||||
? 1
|
||||
: len < 3
|
||||
@ -51,20 +52,19 @@ Widget picWidget(item, context) {
|
||||
|
||||
double height = 0.0;
|
||||
if (len == 1) {
|
||||
aspectRatio = pictures.first.width / pictures.first.height;
|
||||
origAspectRatio =
|
||||
aspectRatio = pictures.first.width / pictures.first.height;
|
||||
if (aspectRatio < 0.4) {
|
||||
aspectRatio = 0.4;
|
||||
}
|
||||
height = pictures.first.height * maxWidth / pictures.first.width;
|
||||
if (pictures.first.width != 1920) {
|
||||
if (origAspectRatio < 0.5 || pictures.first.width < 1920) {
|
||||
crossCount = 2;
|
||||
height = maxWidth / 2 / aspectRatio;
|
||||
}
|
||||
} else {
|
||||
aspectRatio = 1;
|
||||
height = maxWidth /
|
||||
crossCount *
|
||||
(len % crossCount == 0
|
||||
? len ~/ crossCount
|
||||
: len ~/ crossCount + 1) +
|
||||
6;
|
||||
height = maxWidth / crossCount * ((len / crossCount).ceil()) + 6;
|
||||
}
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
@ -73,14 +73,20 @@ Widget picWidget(item, context) {
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
height: height,
|
||||
child: GridView.count(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: crossCount.toInt(),
|
||||
mainAxisSpacing: 4.0,
|
||||
crossAxisSpacing: 4.0,
|
||||
childAspectRatio: aspectRatio,
|
||||
children: list,
|
||||
child: Stack(
|
||||
children: [
|
||||
GridView.count(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: crossCount.toInt(),
|
||||
mainAxisSpacing: 4.0,
|
||||
crossAxisSpacing: 4.0,
|
||||
childAspectRatio: aspectRatio,
|
||||
children: list,
|
||||
),
|
||||
if (len == 1 && origAspectRatio < 0.4)
|
||||
pBadge('长图', context, null, null, 6.0, 6.0, type: 'gray')
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
|
Reference in New Issue
Block a user